github.com/tetratelabs/wazero@v1.2.1/internal/gojs/compiler_test.go (about) 1 package gojs_test 2 3 import ( 4 "bytes" 5 "context" 6 _ "embed" 7 "fmt" 8 "log" 9 "os" 10 "os/exec" 11 "path" 12 "path/filepath" 13 "reflect" 14 "runtime" 15 "testing" 16 "time" 17 18 "github.com/tetratelabs/wazero" 19 "github.com/tetratelabs/wazero/experimental/gojs" 20 "github.com/tetratelabs/wazero/internal/fstest" 21 internalgojs "github.com/tetratelabs/wazero/internal/gojs" 22 "github.com/tetratelabs/wazero/internal/gojs/config" 23 "github.com/tetratelabs/wazero/internal/gojs/run" 24 ) 25 26 type newConfig func(moduleConfig wazero.ModuleConfig) (wazero.ModuleConfig, *config.Config) 27 28 func defaultConfig(moduleConfig wazero.ModuleConfig) (wazero.ModuleConfig, *config.Config) { 29 return moduleConfig, config.NewConfig() 30 } 31 32 func compileAndRun(ctx context.Context, arg string, config newConfig) (stdout, stderr string, err error) { 33 rt := wazero.NewRuntimeWithConfig(ctx, wazero.NewRuntimeConfig(). 34 // In order to avoid race condition on scheduleTimeoutEvent, we need to set the memory max 35 // and WithMemoryCapacityFromMax(true) above. See #992. 36 WithMemoryCapacityFromMax(true). 37 // Set max to a high value, e.g. so that Test_stdio_large can pass. 38 WithMemoryLimitPages(1024). // 64MB 39 WithCompilationCache(cache)) 40 return compileAndRunWithRuntime(ctx, rt, arg, config) // use global runtime 41 } 42 43 func compileAndRunWithRuntime(ctx context.Context, r wazero.Runtime, arg string, config newConfig) (stdout, stderr string, err error) { 44 var stdoutBuf, stderrBuf bytes.Buffer 45 46 builder := r.NewHostModuleBuilder("go") 47 gojs.NewFunctionExporter().ExportFunctions(builder) 48 if _, err = builder.Instantiate(ctx); err != nil { 49 return 50 } 51 52 // Note: this hits the file cache. 53 compiled, err := r.CompileModule(testCtx, testBin) 54 if err != nil { 55 log.Panicln(err) 56 } 57 58 mc, c := config(wazero.NewModuleConfig(). 59 WithStdout(&stdoutBuf). 60 WithStderr(&stderrBuf). 61 WithArgs("test", arg)) 62 63 var s *internalgojs.State 64 s, err = run.RunAndReturnState(ctx, r, compiled, mc, c) 65 if err == nil { 66 if !reflect.DeepEqual(s, internalgojs.NewState(c)) { 67 log.Panicf("unexpected state: %v\n", s) 68 } 69 } 70 71 stdout = stdoutBuf.String() 72 stderr = stderrBuf.String() 73 return 74 } 75 76 // testBin is not checked in as it is >7.5MB 77 var testBin []byte 78 79 // testCtx is configured in TestMain to re-use wazero's compilation cache. 80 var ( 81 testCtx = context.Background() 82 testFS = fstest.FS 83 cache = wazero.NewCompilationCache() 84 ) 85 86 func TestMain(m *testing.M) { 87 // For some reason, windows and freebsd fail to compile with exit status 1. 88 if o := runtime.GOOS; o != "darwin" && o != "linux" { 89 log.Println("gojs: skipping due to not yet supported OS:", o) 90 os.Exit(0) 91 } 92 93 // Find the go binary (if present), and compile the Wasm binary. 94 goBin, err := findGoBin() 95 if err != nil { 96 log.Println("gojs: skipping due missing Go binary:", err) 97 os.Exit(0) 98 } 99 if err = compileJsWasm(goBin); err != nil { 100 log.Panicln(err) 101 } 102 103 // Define a compilation cache so that tests run faster. This works because 104 // all tests use the same binary. 105 compilationCacheDir, err := os.MkdirTemp("", "gojs") 106 if err != nil { 107 log.Panicln(err) 108 } 109 defer os.RemoveAll(compilationCacheDir) 110 cache, err := wazero.NewCompilationCacheWithDir(compilationCacheDir) 111 if err != nil { 112 log.Panicln(err) 113 } 114 115 // Seed wazero's compilation cache to see any error up-front and to prevent 116 // one test from a cache-miss performance penalty. 117 r := wazero.NewRuntimeWithConfig(testCtx, wazero.NewRuntimeConfig().WithCompilationCache(cache)) 118 _, err = r.CompileModule(testCtx, testBin) 119 if err != nil { 120 log.Panicln(err) 121 } 122 123 var exit int 124 defer func() { 125 cache.Close(testCtx) 126 r.Close(testCtx) 127 os.Exit(exit) 128 }() 129 exit = m.Run() 130 } 131 132 // compileJsWasm allows us to generate a binary with runtime.GOOS=js and 133 // runtime.GOARCH=wasm. This intentionally does so on-demand, as it allows us 134 // to test the user's current version of Go, as opposed to a specific one. 135 // For example, this allows testing both Go 1.18 and 1.19 in CI. 136 func compileJsWasm(goBin string) error { 137 // Prepare the working directory. 138 workdir, err := os.MkdirTemp("", "example") 139 if err != nil { 140 return err 141 } 142 defer os.RemoveAll(workdir) 143 144 ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) 145 defer cancel() 146 147 bin := path.Join(workdir, "out.wasm") 148 cmd := exec.CommandContext(ctx, goBin, "build", "-o", bin, ".") //nolint:gosec 149 cmd.Env = append(os.Environ(), "GOOS=js", "GOARCH=wasm", "GOWASM=satconv,signext") 150 cmd.Dir = "testdata" 151 out, err := cmd.CombinedOutput() 152 if err != nil { 153 return fmt.Errorf("couldn't compile %s: %w", string(out), err) 154 } 155 156 testBin, err = os.ReadFile(bin) //nolint:gosec 157 return err 158 } 159 160 func findGoBin() (string, error) { 161 binName := "go" 162 if runtime.GOOS == "windows" { 163 binName += ".exe" 164 } 165 goBin := filepath.Join(runtime.GOROOT(), "bin", binName) 166 if _, err := os.Stat(goBin); err == nil { 167 return goBin, nil 168 } 169 // Now, search the path 170 return exec.LookPath(binName) 171 }