github.com/tetratelabs/wazero@v1.7.3-0.20240513003603-48f702e154b5/internal/integration_test/filecache/filecache_test.go (about) 1 package filecache 2 3 import ( 4 "bytes" 5 "context" 6 "fmt" 7 "os" 8 "os/exec" 9 "strings" 10 "testing" 11 "time" 12 13 "github.com/tetratelabs/wazero" 14 "github.com/tetratelabs/wazero/api" 15 "github.com/tetratelabs/wazero/experimental" 16 "github.com/tetratelabs/wazero/experimental/logging" 17 "github.com/tetratelabs/wazero/internal/integration_test/spectest" 18 v1 "github.com/tetratelabs/wazero/internal/integration_test/spectest/v1" 19 "github.com/tetratelabs/wazero/internal/platform" 20 "github.com/tetratelabs/wazero/internal/testing/binaryencoding" 21 "github.com/tetratelabs/wazero/internal/testing/require" 22 "github.com/tetratelabs/wazero/internal/wasm" 23 ) 24 25 func TestFileCache_compiler(t *testing.T) { 26 if !platform.CompilerSupported() { 27 return 28 } 29 runAllFileCacheTests(t, wazero.NewRuntimeConfigCompiler()) 30 } 31 32 func runAllFileCacheTests(t *testing.T, config wazero.RuntimeConfig) { 33 t.Run("spectest", func(t *testing.T) { 34 testSpecTestCompilerCache(t, config) 35 }) 36 t.Run("listeners", func(t *testing.T) { 37 testListeners(t, config) 38 }) 39 t.Run("close on context done", func(t *testing.T) { 40 testWithCloseOnContextDone(t, config) 41 }) 42 } 43 44 func testSpecTestCompilerCache(t *testing.T, config wazero.RuntimeConfig) { 45 const cachePathKey = "FILE_CACHE_DIR" 46 cacheDir := os.Getenv(cachePathKey) 47 if len(cacheDir) == 0 { 48 // This case, this is the parent of the test. 49 cacheDir = t.TempDir() 50 51 // Before running test, no file should exist in the directory. 52 files, err := os.ReadDir(cacheDir) 53 require.NoError(t, err) 54 require.True(t, len(files) == 0) 55 56 // Get the executable path of this test. 57 testExecutable, err := os.Executable() 58 require.NoError(t, err) 59 60 // Execute this test multiple times with the env $cachePathKey=cacheDir, so that 61 // the subsequent execution of this test will enter the following "else" block. 62 var exp []string 63 buf := bytes.NewBuffer(nil) 64 for i := 0; i < 2; i++ { 65 cmd := exec.Command(testExecutable) 66 cmd.Args = append(cmd.Args, fmt.Sprintf("-test.run=%s", t.Name())) 67 cmd.Env = append(cmd.Env, fmt.Sprintf("%s=%s", cachePathKey, cacheDir)) 68 cmd.Stdout = buf 69 cmd.Stderr = buf 70 err = cmd.Run() 71 require.NoError(t, err, buf.String()) 72 exp = append(exp, "PASS\n") 73 } 74 75 // Ensures that the tests actually run 2 times. 76 require.Equal(t, strings.Join(exp, ""), buf.String()) 77 78 // Check the number of cache files is greater than zero. 79 files, err = os.ReadDir(cacheDir) 80 require.NoError(t, err) 81 require.True(t, len(files) > 0) 82 } else { 83 // Run the spectest with the file cache. 84 cc, err := wazero.NewCompilationCacheWithDir(cacheDir) 85 require.NoError(t, err) 86 spectest.Run(t, v1.Testcases, context.Background(), 87 config.WithCompilationCache(cc).WithCoreFeatures(api.CoreFeaturesV1)) 88 } 89 } 90 91 // TestListeners ensures that compilation cache works as expected on and off with respect to listeners. 92 func testListeners(t *testing.T, config wazero.RuntimeConfig) { 93 if !platform.CompilerSupported() { 94 t.Skip() 95 } 96 97 var ( 98 zero uint32 = 0 99 wasmBin = binaryencoding.EncodeModule(&wasm.Module{ 100 TypeSection: []wasm.FunctionType{{}}, 101 FunctionSection: []wasm.Index{0, 0, 0, 0}, 102 CodeSection: []wasm.Code{ 103 {Body: []byte{wasm.OpcodeCall, 1, wasm.OpcodeEnd}}, 104 {Body: []byte{wasm.OpcodeCall, 2, wasm.OpcodeEnd}}, 105 {Body: []byte{wasm.OpcodeCall, 3, wasm.OpcodeEnd}}, 106 {Body: []byte{wasm.OpcodeEnd}}, 107 }, 108 StartSection: &zero, 109 NameSection: &wasm.NameSection{ 110 FunctionNames: wasm.NameMap{{Index: 0, Name: "1"}, {Index: 1, Name: "2"}, {Index: 2, Name: "3"}, {Index: 3, Name: "4"}}, 111 ModuleName: "test", 112 }, 113 }) 114 ) 115 116 t.Run("always on", func(t *testing.T) { 117 dir := t.TempDir() 118 119 out := bytes.NewBuffer(nil) 120 ctxWithListener := experimental.WithFunctionListenerFactory( 121 context.Background(), logging.NewLoggingListenerFactory(out)) 122 123 { 124 cc, err := wazero.NewCompilationCacheWithDir(dir) 125 require.NoError(t, err) 126 rc := config.WithCompilationCache(cc) 127 128 r := wazero.NewRuntimeWithConfig(ctxWithListener, rc) 129 _, err = r.CompileModule(ctxWithListener, wasmBin) 130 require.NoError(t, err) 131 err = r.Close(ctxWithListener) 132 require.NoError(t, err) 133 } 134 135 cc, err := wazero.NewCompilationCacheWithDir(dir) 136 require.NoError(t, err) 137 rc := config.WithCompilationCache(cc) 138 r := wazero.NewRuntimeWithConfig(ctxWithListener, rc) 139 _, err = r.Instantiate(ctxWithListener, wasmBin) 140 require.NoError(t, err) 141 err = r.Close(ctxWithListener) 142 require.NoError(t, err) 143 144 // Ensures that compilation cache works with listeners. 145 require.Equal(t, `--> test.1() 146 --> test.2() 147 --> test.3() 148 --> test.4() 149 <-- 150 <-- 151 <-- 152 <-- 153 `, out.String()) 154 }) 155 156 t.Run("with->without", func(t *testing.T) { 157 dir := t.TempDir() 158 159 // Compile with listeners. 160 { 161 cc, err := wazero.NewCompilationCacheWithDir(dir) 162 require.NoError(t, err) 163 rc := config.WithCompilationCache(cc) 164 165 out := bytes.NewBuffer(nil) 166 ctxWithListener := experimental.WithFunctionListenerFactory( 167 context.Background(), logging.NewLoggingListenerFactory(out)) 168 r := wazero.NewRuntimeWithConfig(ctxWithListener, rc) 169 _, err = r.CompileModule(ctxWithListener, wasmBin) 170 require.NoError(t, err) 171 err = r.Close(ctxWithListener) 172 require.NoError(t, err) 173 } 174 175 // Then compile without listeners -> run it. 176 cc, err := wazero.NewCompilationCacheWithDir(dir) 177 require.NoError(t, err) 178 rc := config.WithCompilationCache(cc) 179 r := wazero.NewRuntimeWithConfig(context.Background(), rc) 180 _, err = r.Instantiate(context.Background(), wasmBin) 181 require.NoError(t, err) 182 err = r.Close(context.Background()) 183 require.NoError(t, err) 184 }) 185 186 t.Run("without->with", func(t *testing.T) { 187 dir := t.TempDir() 188 189 // Compile without listeners. 190 { 191 cc, err := wazero.NewCompilationCacheWithDir(dir) 192 require.NoError(t, err) 193 rc := config.WithCompilationCache(cc) 194 r := wazero.NewRuntimeWithConfig(context.Background(), rc) 195 _, err = r.CompileModule(context.Background(), wasmBin) 196 require.NoError(t, err) 197 err = r.Close(context.Background()) 198 require.NoError(t, err) 199 } 200 201 // Then compile with listeners -> run it. 202 out := bytes.NewBuffer(nil) 203 ctxWithListener := experimental.WithFunctionListenerFactory( 204 context.Background(), logging.NewLoggingListenerFactory(out)) 205 206 cc, err := wazero.NewCompilationCacheWithDir(dir) 207 require.NoError(t, err) 208 rc := config.WithCompilationCache(cc) 209 r := wazero.NewRuntimeWithConfig(ctxWithListener, rc) 210 _, err = r.Instantiate(ctxWithListener, wasmBin) 211 require.NoError(t, err) 212 err = r.Close(ctxWithListener) 213 require.NoError(t, err) 214 215 // Ensures that compilation cache works with listeners. 216 require.Equal(t, `--> test.1() 217 --> test.2() 218 --> test.3() 219 --> test.4() 220 <-- 221 <-- 222 <-- 223 <-- 224 `, out.String()) 225 }) 226 } 227 228 // TestWithCloseOnContextDone ensures that compilation cache works as expected on and off with respect to WithCloseOnContextDone config. 229 func testWithCloseOnContextDone(t *testing.T, config wazero.RuntimeConfig) { 230 var ( 231 zero uint32 = 0 232 wasmBin = binaryencoding.EncodeModule(&wasm.Module{ 233 TypeSection: []wasm.FunctionType{{}}, 234 FunctionSection: []wasm.Index{0}, 235 CodeSection: []wasm.Code{ 236 {Body: []byte{ 237 wasm.OpcodeLoop, 0, 238 wasm.OpcodeBr, 0, 239 wasm.OpcodeEnd, 240 wasm.OpcodeEnd, 241 }}, 242 }, 243 StartSection: &zero, 244 }) 245 ) 246 247 t.Run("always on", func(t *testing.T) { 248 dir := t.TempDir() 249 ctx := context.Background() 250 { 251 cc, err := wazero.NewCompilationCacheWithDir(dir) 252 require.NoError(t, err) 253 rc := config.WithCompilationCache(cc).WithCloseOnContextDone(true) 254 255 r := wazero.NewRuntimeWithConfig(ctx, rc) 256 _, err = r.CompileModule(ctx, wasmBin) 257 require.NoError(t, err) 258 err = r.Close(ctx) 259 require.NoError(t, err) 260 } 261 262 cc, err := wazero.NewCompilationCacheWithDir(dir) 263 require.NoError(t, err) 264 rc := config.WithCompilationCache(cc).WithCloseOnContextDone(true) 265 r := wazero.NewRuntimeWithConfig(ctx, rc) 266 267 timeoutCtx, done := context.WithTimeout(ctx, time.Second) 268 defer done() 269 _, err = r.Instantiate(timeoutCtx, wasmBin) 270 require.EqualError(t, err, "module closed with context deadline exceeded") 271 err = r.Close(ctx) 272 require.NoError(t, err) 273 }) 274 275 t.Run("off->on", func(t *testing.T) { 276 dir := t.TempDir() 277 ctx := context.Background() 278 { 279 cc, err := wazero.NewCompilationCacheWithDir(dir) 280 require.NoError(t, err) 281 rc := config.WithCompilationCache(cc).WithCloseOnContextDone(false) 282 283 r := wazero.NewRuntimeWithConfig(ctx, rc) 284 _, err = r.CompileModule(ctx, wasmBin) 285 require.NoError(t, err) 286 err = r.Close(ctx) 287 require.NoError(t, err) 288 } 289 290 cc, err := wazero.NewCompilationCacheWithDir(dir) 291 require.NoError(t, err) 292 rc := config.WithCompilationCache(cc).WithCloseOnContextDone(true) 293 r := wazero.NewRuntimeWithConfig(ctx, rc) 294 295 timeoutCtx, done := context.WithTimeout(ctx, time.Second) 296 defer done() 297 _, err = r.Instantiate(timeoutCtx, wasmBin) 298 require.EqualError(t, err, "module closed with context deadline exceeded") 299 err = r.Close(ctx) 300 require.NoError(t, err) 301 }) 302 }