wa-lang.org/wazero@v1.0.2/internal/gojs/compiler_test.go (about)

     1  package gojs_test
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	_ "embed"
     7  	"fmt"
     8  	"io/fs"
     9  	"log"
    10  	"os"
    11  	"os/exec"
    12  	"path"
    13  	"path/filepath"
    14  	"runtime"
    15  	"testing"
    16  	"testing/fstest"
    17  	"time"
    18  
    19  	"wa-lang.org/wazero"
    20  	"wa-lang.org/wazero/experimental"
    21  	gojs "wa-lang.org/wazero/imports/go"
    22  )
    23  
    24  func compileAndRun(ctx context.Context, arg string, config wazero.ModuleConfig) (stdout, stderr string, err error) {
    25  	var stdoutBuf, stderrBuf bytes.Buffer
    26  
    27  	r := wazero.NewRuntimeWithConfig(testCtx, wazero.NewRuntimeConfig())
    28  	defer r.Close(ctx)
    29  
    30  	compiled, compileErr := r.CompileModule(ctx, testBin)
    31  	if compileErr != nil {
    32  		err = compileErr
    33  		return
    34  	}
    35  
    36  	err = gojs.Run(ctx, r, compiled, config.WithStdout(&stdoutBuf).WithStderr(&stderrBuf).
    37  		WithArgs("test", arg))
    38  	stdout = stdoutBuf.String()
    39  	stderr = stderrBuf.String()
    40  	return
    41  }
    42  
    43  // testBin is not checked in as it is >7.5MB
    44  var testBin []byte
    45  
    46  // testCtx is configured in TestMain to re-use wazero's compilation cache.
    47  var (
    48  	testCtx context.Context
    49  	testFS  = fstest.MapFS{
    50  		"empty.txt":    {},
    51  		"test.txt":     {Data: []byte("animals")},
    52  		"sub":          {Mode: fs.ModeDir},
    53  		"sub/test.txt": {Data: []byte("greet sub dir\n")},
    54  	}
    55  )
    56  
    57  func TestMain(m *testing.M) {
    58  	// For some reason, windows and freebsd fail to compile with exit status 1.
    59  	if o := runtime.GOOS; o != "darwin" && o != "linux" {
    60  		log.Println("gojs: skipping due to not yet supported OS:", o)
    61  		os.Exit(0)
    62  	}
    63  
    64  	// Find the go binary (if present), and compile the Wasm binary.
    65  	goBin, err := findGoBin()
    66  	if err != nil {
    67  		log.Println("gojs: skipping due missing Go binary:", err)
    68  		os.Exit(0)
    69  	}
    70  	if err = compileJsWasm(goBin); err != nil {
    71  		log.Panicln(err)
    72  	}
    73  
    74  	// Define a compilation cache so that tests run faster. This works because
    75  	// all tests use the same binary.
    76  	compilationCacheDir, err := os.MkdirTemp("", "gojs")
    77  	if err != nil {
    78  		log.Panicln(err)
    79  	}
    80  	defer os.RemoveAll(compilationCacheDir)
    81  	testCtx, err = experimental.WithCompilationCacheDirName(context.Background(), compilationCacheDir)
    82  	if err != nil {
    83  		log.Panicln(err)
    84  	}
    85  
    86  	// Seed wazero's compilation cache to see any error up-front and to prevent
    87  	// one test from a cache-miss performance penalty.
    88  	rt := wazero.NewRuntimeWithConfig(testCtx, wazero.NewRuntimeConfig())
    89  	defer rt.Close(testCtx)
    90  	_, err = rt.CompileModule(testCtx, testBin)
    91  	if err != nil {
    92  		log.Panicln(err)
    93  	}
    94  	rt.Close(testCtx)
    95  
    96  	// Configure fs test data
    97  	if d, err := fs.Sub(testFS, "sub"); err != nil {
    98  		log.Panicln(err)
    99  	} else if err = fstest.TestFS(d, "test.txt"); err != nil {
   100  		log.Panicln(err)
   101  	}
   102  	os.Exit(m.Run())
   103  }
   104  
   105  // compileJsWasm allows us to generate a binary with runtime.GOOS=js and
   106  // runtime.GOARCH=wasm. This intentionally does so on-demand, as it allows us
   107  // to test the user's current version of Go, as opposed to a specific one.
   108  // For example, this allows testing both Go 1.18 and 1.19 in CI.
   109  func compileJsWasm(goBin string) error {
   110  	// Prepare the working directory.
   111  	workDir, err := os.MkdirTemp("", "example")
   112  	if err != nil {
   113  		return err
   114  	}
   115  	defer os.RemoveAll(workDir)
   116  
   117  	ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
   118  	defer cancel()
   119  
   120  	bin := path.Join(workDir, "out.wasm")
   121  	cmd := exec.CommandContext(ctx, goBin, "build", "-o", bin, ".") //nolint:gosec
   122  	cmd.Env = append(os.Environ(), "GOOS=js", "GOARCH=wasm", "GOWASM=satconv,signext")
   123  	cmd.Dir = "testdata"
   124  	out, err := cmd.CombinedOutput()
   125  	if err != nil {
   126  		return fmt.Errorf("couldn't compile %s: %w", string(out), err)
   127  	}
   128  
   129  	testBin, err = os.ReadFile(bin) //nolint:gosec
   130  	return err
   131  }
   132  
   133  func findGoBin() (string, error) {
   134  	binName := "go"
   135  	if runtime.GOOS == "windows" {
   136  		binName += ".exe"
   137  	}
   138  	goBin := filepath.Join(runtime.GOROOT(), "bin", binName)
   139  	if _, err := os.Stat(goBin); err == nil {
   140  		return goBin, nil
   141  	}
   142  	// Now, search the path
   143  	return exec.LookPath(binName)
   144  }