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  }