github.com/tetratelabs/wazero@v1.7.1/internal/integration_test/stdlibs/bench_test.go (about)

     1  package wazevo_test
     2  
     3  import (
     4  	"context"
     5  	"crypto/rand"
     6  	"io"
     7  	"os"
     8  	"path/filepath"
     9  	"runtime"
    10  	"strings"
    11  	"testing"
    12  
    13  	"github.com/tetratelabs/wazero"
    14  	"github.com/tetratelabs/wazero/imports/wasi_snapshot_preview1"
    15  	"github.com/tetratelabs/wazero/internal/testing/require"
    16  	"github.com/tetratelabs/wazero/sys"
    17  )
    18  
    19  func BenchmarkZig(b *testing.B) {
    20  	c := wazero.NewRuntimeConfigCompiler()
    21  	runtBenches(b, context.Background(), c, zigTestCase)
    22  }
    23  
    24  func BenchmarkTinyGo(b *testing.B) {
    25  	c := wazero.NewRuntimeConfigCompiler()
    26  	runtBenches(b, context.Background(), c, tinyGoTestCase)
    27  }
    28  
    29  func BenchmarkWasip1(b *testing.B) {
    30  	c := wazero.NewRuntimeConfigCompiler()
    31  	runtBenches(b, context.Background(), c, wasip1TestCase)
    32  }
    33  
    34  type testCase struct {
    35  	name, dir    string
    36  	readTestCase func(fpath string, fname string) (_ []byte, c wazero.ModuleConfig, stdout, stderr *os.File, err error)
    37  }
    38  
    39  var (
    40  	zigTestCase = testCase{
    41  		name: "zig",
    42  		dir:  "testdata/zig/",
    43  		readTestCase: func(fpath string, fname string) (_ []byte, c wazero.ModuleConfig, stdout, stderr *os.File, err error) {
    44  			bin, err := os.ReadFile(fpath)
    45  			c, stdout, stderr = defaultModuleConfig()
    46  			c = c.WithFSConfig(wazero.NewFSConfig().WithDirMount(".", "/")).
    47  				WithArgs("test.wasm")
    48  			return bin, c, stdout, stderr, err
    49  		},
    50  	}
    51  	tinyGoTestCase = testCase{
    52  		name: "tinygo",
    53  		dir:  "testdata/tinygo/",
    54  		readTestCase: func(fpath string, fname string) (_ []byte, c wazero.ModuleConfig, stdout, stderr *os.File, err error) {
    55  			if !strings.HasSuffix(fname, ".test") {
    56  				return nil, nil, nil, nil, nil
    57  			}
    58  			bin, err := os.ReadFile(fpath)
    59  
    60  			fsconfig := wazero.NewFSConfig().
    61  				WithDirMount(".", "/").
    62  				WithDirMount(os.TempDir(), "/tmp")
    63  
    64  			c, stdout, stderr = defaultModuleConfig()
    65  			c = c.WithFSConfig(fsconfig).
    66  				WithArgs(fname, "-test.v")
    67  
    68  			return bin, c, stdout, stderr, err
    69  		},
    70  	}
    71  	wasip1TestCase = testCase{
    72  		name: "wasip1",
    73  		dir:  "testdata/go/",
    74  		readTestCase: func(fpath string, fname string) (_ []byte, c wazero.ModuleConfig, stdout, stderr *os.File, err error) {
    75  			if !strings.HasSuffix(fname, ".test") {
    76  				return nil, nil, nil, nil, nil
    77  			}
    78  			bin, err := os.ReadFile(fpath)
    79  			if err != nil {
    80  				return nil, nil, nil, nil, err
    81  			}
    82  			fsuffixstripped := strings.ReplaceAll(fname, ".test", "")
    83  			inferredpath := strings.ReplaceAll(fsuffixstripped, "_", "/")
    84  			testdir := filepath.Join(runtime.GOROOT(), inferredpath)
    85  			err = os.Chdir(testdir)
    86  
    87  			sysroot := filepath.VolumeName(testdir) + string(os.PathSeparator)
    88  			normalizedTestdir := normalizeOsPath(testdir)
    89  
    90  			c, stdout, stderr = defaultModuleConfig()
    91  			c = c.WithFSConfig(
    92  				wazero.NewFSConfig().
    93  					WithDirMount(sysroot, "/")).
    94  				WithEnv("PWD", normalizedTestdir)
    95  
    96  			args := []string{fname, "-test.short", "-test.v"}
    97  
    98  			// Skip tests that are fragile on Windows.
    99  			if runtime.GOOS == "windows" {
   100  				c = c.
   101  					WithEnv("GOROOT", normalizeOsPath(runtime.GOROOT()))
   102  
   103  				args = append(args,
   104  					"-test.skip=TestRenameCaseDifference/dir|"+
   105  						"TestDirFSPathsValid|TestDirFS|TestDevNullFile|"+
   106  						"TestOpenError|TestSymlinkWithTrailingSlash")
   107  			}
   108  			c = c.WithArgs(args...)
   109  
   110  			return bin, c, stdout, stderr, err
   111  		},
   112  	}
   113  )
   114  
   115  func runtBenches(b *testing.B, ctx context.Context, rc wazero.RuntimeConfig, tc testCase) {
   116  	cwd, _ := os.Getwd()
   117  	files, err := os.ReadDir(tc.dir)
   118  	require.NoError(b, err)
   119  	for _, f := range files {
   120  		fname := f.Name()
   121  		// Ensure we are on root dir.
   122  		err = os.Chdir(cwd)
   123  		require.NoError(b, err)
   124  
   125  		fpath := filepath.Join(cwd, tc.dir, fname)
   126  		bin, modCfg, stdout, stderr, err := tc.readTestCase(fpath, fname)
   127  		require.NoError(b, err)
   128  		if bin == nil {
   129  			continue
   130  		}
   131  
   132  		b.Run("Compile/"+fname, func(b *testing.B) {
   133  			b.ResetTimer()
   134  			for i := 0; i < b.N; i++ {
   135  				r := wazero.NewRuntimeWithConfig(ctx, rc)
   136  				_, err := r.CompileModule(ctx, bin)
   137  				require.NoError(b, err)
   138  				require.NoError(b, r.Close(ctx))
   139  			}
   140  		})
   141  		b.Run("Run/"+fname, func(b *testing.B) {
   142  			r := wazero.NewRuntimeWithConfig(ctx, rc)
   143  			wasi_snapshot_preview1.MustInstantiate(ctx, r)
   144  			b.Cleanup(func() { r.Close(ctx) })
   145  
   146  			cm, err := r.CompileModule(ctx, bin)
   147  			require.NoError(b, err)
   148  
   149  			b.ResetTimer()
   150  			for i := 0; i < b.N; i++ {
   151  				// Instantiate in the loop as _start cannot be called multiple times.
   152  				m, err := r.InstantiateModule(ctx, cm, modCfg)
   153  				requireZeroExitCode(b, err, stdout, stderr)
   154  				require.NoError(b, m.Close(ctx))
   155  			}
   156  		})
   157  	}
   158  }
   159  
   160  // Normalize an absolute path to a Unix-style path, regardless if it is a Windows path.
   161  func normalizeOsPath(path string) string {
   162  	// Remove volume name. This is '/' on *Nix and 'C:' (with C being any letter identifier).
   163  	root := filepath.VolumeName(path)
   164  	testdirnoprefix := path[len(root):]
   165  	// Normalizes all the path separators to a Unix separator.
   166  	testdirnormalized := strings.ReplaceAll(testdirnoprefix, string(os.PathSeparator), "/")
   167  	return testdirnormalized
   168  }
   169  
   170  func defaultModuleConfig() (c wazero.ModuleConfig, stdout, stderr *os.File) {
   171  	var err error
   172  	// Note: do not use os.Stdout or os.Stderr as they will mess up the `-bench` output to be fed to the benchstat tool.
   173  	stdout, err = os.CreateTemp("", "")
   174  	if err != nil {
   175  		panic(err)
   176  	}
   177  	stderr, err = os.CreateTemp("", "")
   178  	if err != nil {
   179  		panic(err)
   180  	}
   181  	c = wazero.NewModuleConfig().
   182  		WithSysNanosleep().
   183  		WithSysNanotime().
   184  		WithSysWalltime().
   185  		WithRandSource(rand.Reader).
   186  		// Some tests require Stdout and Stderr to be present.
   187  		WithStdout(stdout).
   188  		WithStderr(stderr)
   189  	return
   190  }
   191  
   192  func requireZeroExitCode(b *testing.B, err error, stdout, stderr *os.File) {
   193  	b.Helper()
   194  	if se, ok := err.(*sys.ExitError); ok {
   195  		if se.ExitCode() != 0 { // Don't err on success.
   196  			stdoutBytes, _ := io.ReadAll(stdout)
   197  			stderrBytes, _ := io.ReadAll(stderr)
   198  			require.NoError(b, err, "stdout: %s\nstderr: %s", string(stdoutBytes), string(stderrBytes))
   199  		}
   200  	} else if err != nil {
   201  		stdoutBytes, _ := io.ReadAll(stdout)
   202  		stderrBytes, _ := io.ReadAll(stderr)
   203  		require.NoError(b, err, "stdout: %s\nstderr: %s", string(stdoutBytes), string(stderrBytes))
   204  	}
   205  }