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