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  }