github.com/tetratelabs/wazero@v1.7.3-0.20240513003603-48f702e154b5/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  	"strings"
    10  	"testing"
    11  	"time"
    12  
    13  	"github.com/tetratelabs/wazero"
    14  	"github.com/tetratelabs/wazero/api"
    15  	"github.com/tetratelabs/wazero/experimental"
    16  	"github.com/tetratelabs/wazero/experimental/logging"
    17  	"github.com/tetratelabs/wazero/internal/integration_test/spectest"
    18  	v1 "github.com/tetratelabs/wazero/internal/integration_test/spectest/v1"
    19  	"github.com/tetratelabs/wazero/internal/platform"
    20  	"github.com/tetratelabs/wazero/internal/testing/binaryencoding"
    21  	"github.com/tetratelabs/wazero/internal/testing/require"
    22  	"github.com/tetratelabs/wazero/internal/wasm"
    23  )
    24  
    25  func TestFileCache_compiler(t *testing.T) {
    26  	if !platform.CompilerSupported() {
    27  		return
    28  	}
    29  	runAllFileCacheTests(t, wazero.NewRuntimeConfigCompiler())
    30  }
    31  
    32  func runAllFileCacheTests(t *testing.T, config wazero.RuntimeConfig) {
    33  	t.Run("spectest", func(t *testing.T) {
    34  		testSpecTestCompilerCache(t, config)
    35  	})
    36  	t.Run("listeners", func(t *testing.T) {
    37  		testListeners(t, config)
    38  	})
    39  	t.Run("close on context done", func(t *testing.T) {
    40  		testWithCloseOnContextDone(t, config)
    41  	})
    42  }
    43  
    44  func testSpecTestCompilerCache(t *testing.T, config wazero.RuntimeConfig) {
    45  	const cachePathKey = "FILE_CACHE_DIR"
    46  	cacheDir := os.Getenv(cachePathKey)
    47  	if len(cacheDir) == 0 {
    48  		// This case, this is the parent of the test.
    49  		cacheDir = t.TempDir()
    50  
    51  		// Before running test, no file should exist in the directory.
    52  		files, err := os.ReadDir(cacheDir)
    53  		require.NoError(t, err)
    54  		require.True(t, len(files) == 0)
    55  
    56  		// Get the executable path of this test.
    57  		testExecutable, err := os.Executable()
    58  		require.NoError(t, err)
    59  
    60  		// Execute this test multiple times with the env $cachePathKey=cacheDir, so that
    61  		// the subsequent execution of this test will enter the following "else" block.
    62  		var exp []string
    63  		buf := bytes.NewBuffer(nil)
    64  		for i := 0; i < 2; i++ {
    65  			cmd := exec.Command(testExecutable)
    66  			cmd.Args = append(cmd.Args, fmt.Sprintf("-test.run=%s", t.Name()))
    67  			cmd.Env = append(cmd.Env, fmt.Sprintf("%s=%s", cachePathKey, cacheDir))
    68  			cmd.Stdout = buf
    69  			cmd.Stderr = buf
    70  			err = cmd.Run()
    71  			require.NoError(t, err, buf.String())
    72  			exp = append(exp, "PASS\n")
    73  		}
    74  
    75  		// Ensures that the tests actually run 2 times.
    76  		require.Equal(t, strings.Join(exp, ""), buf.String())
    77  
    78  		// Check the number of cache files is greater than zero.
    79  		files, err = os.ReadDir(cacheDir)
    80  		require.NoError(t, err)
    81  		require.True(t, len(files) > 0)
    82  	} else {
    83  		// Run the spectest with the file cache.
    84  		cc, err := wazero.NewCompilationCacheWithDir(cacheDir)
    85  		require.NoError(t, err)
    86  		spectest.Run(t, v1.Testcases, context.Background(),
    87  			config.WithCompilationCache(cc).WithCoreFeatures(api.CoreFeaturesV1))
    88  	}
    89  }
    90  
    91  // TestListeners ensures that compilation cache works as expected on and off with respect to listeners.
    92  func testListeners(t *testing.T, config wazero.RuntimeConfig) {
    93  	if !platform.CompilerSupported() {
    94  		t.Skip()
    95  	}
    96  
    97  	var (
    98  		zero    uint32 = 0
    99  		wasmBin        = binaryencoding.EncodeModule(&wasm.Module{
   100  			TypeSection:     []wasm.FunctionType{{}},
   101  			FunctionSection: []wasm.Index{0, 0, 0, 0},
   102  			CodeSection: []wasm.Code{
   103  				{Body: []byte{wasm.OpcodeCall, 1, wasm.OpcodeEnd}},
   104  				{Body: []byte{wasm.OpcodeCall, 2, wasm.OpcodeEnd}},
   105  				{Body: []byte{wasm.OpcodeCall, 3, wasm.OpcodeEnd}},
   106  				{Body: []byte{wasm.OpcodeEnd}},
   107  			},
   108  			StartSection: &zero,
   109  			NameSection: &wasm.NameSection{
   110  				FunctionNames: wasm.NameMap{{Index: 0, Name: "1"}, {Index: 1, Name: "2"}, {Index: 2, Name: "3"}, {Index: 3, Name: "4"}},
   111  				ModuleName:    "test",
   112  			},
   113  		})
   114  	)
   115  
   116  	t.Run("always on", func(t *testing.T) {
   117  		dir := t.TempDir()
   118  
   119  		out := bytes.NewBuffer(nil)
   120  		ctxWithListener := experimental.WithFunctionListenerFactory(
   121  			context.Background(), logging.NewLoggingListenerFactory(out))
   122  
   123  		{
   124  			cc, err := wazero.NewCompilationCacheWithDir(dir)
   125  			require.NoError(t, err)
   126  			rc := config.WithCompilationCache(cc)
   127  
   128  			r := wazero.NewRuntimeWithConfig(ctxWithListener, rc)
   129  			_, err = r.CompileModule(ctxWithListener, wasmBin)
   130  			require.NoError(t, err)
   131  			err = r.Close(ctxWithListener)
   132  			require.NoError(t, err)
   133  		}
   134  
   135  		cc, err := wazero.NewCompilationCacheWithDir(dir)
   136  		require.NoError(t, err)
   137  		rc := config.WithCompilationCache(cc)
   138  		r := wazero.NewRuntimeWithConfig(ctxWithListener, rc)
   139  		_, err = r.Instantiate(ctxWithListener, wasmBin)
   140  		require.NoError(t, err)
   141  		err = r.Close(ctxWithListener)
   142  		require.NoError(t, err)
   143  
   144  		// Ensures that compilation cache works with listeners.
   145  		require.Equal(t, `--> test.1()
   146  	--> test.2()
   147  		--> test.3()
   148  			--> test.4()
   149  			<--
   150  		<--
   151  	<--
   152  <--
   153  `, out.String())
   154  	})
   155  
   156  	t.Run("with->without", func(t *testing.T) {
   157  		dir := t.TempDir()
   158  
   159  		// Compile with listeners.
   160  		{
   161  			cc, err := wazero.NewCompilationCacheWithDir(dir)
   162  			require.NoError(t, err)
   163  			rc := config.WithCompilationCache(cc)
   164  
   165  			out := bytes.NewBuffer(nil)
   166  			ctxWithListener := experimental.WithFunctionListenerFactory(
   167  				context.Background(), logging.NewLoggingListenerFactory(out))
   168  			r := wazero.NewRuntimeWithConfig(ctxWithListener, rc)
   169  			_, err = r.CompileModule(ctxWithListener, wasmBin)
   170  			require.NoError(t, err)
   171  			err = r.Close(ctxWithListener)
   172  			require.NoError(t, err)
   173  		}
   174  
   175  		// Then compile without listeners -> run it.
   176  		cc, err := wazero.NewCompilationCacheWithDir(dir)
   177  		require.NoError(t, err)
   178  		rc := config.WithCompilationCache(cc)
   179  		r := wazero.NewRuntimeWithConfig(context.Background(), rc)
   180  		_, err = r.Instantiate(context.Background(), wasmBin)
   181  		require.NoError(t, err)
   182  		err = r.Close(context.Background())
   183  		require.NoError(t, err)
   184  	})
   185  
   186  	t.Run("without->with", func(t *testing.T) {
   187  		dir := t.TempDir()
   188  
   189  		// Compile without listeners.
   190  		{
   191  			cc, err := wazero.NewCompilationCacheWithDir(dir)
   192  			require.NoError(t, err)
   193  			rc := config.WithCompilationCache(cc)
   194  			r := wazero.NewRuntimeWithConfig(context.Background(), rc)
   195  			_, err = r.CompileModule(context.Background(), wasmBin)
   196  			require.NoError(t, err)
   197  			err = r.Close(context.Background())
   198  			require.NoError(t, err)
   199  		}
   200  
   201  		// Then compile with listeners -> run it.
   202  		out := bytes.NewBuffer(nil)
   203  		ctxWithListener := experimental.WithFunctionListenerFactory(
   204  			context.Background(), logging.NewLoggingListenerFactory(out))
   205  
   206  		cc, err := wazero.NewCompilationCacheWithDir(dir)
   207  		require.NoError(t, err)
   208  		rc := config.WithCompilationCache(cc)
   209  		r := wazero.NewRuntimeWithConfig(ctxWithListener, rc)
   210  		_, err = r.Instantiate(ctxWithListener, wasmBin)
   211  		require.NoError(t, err)
   212  		err = r.Close(ctxWithListener)
   213  		require.NoError(t, err)
   214  
   215  		// Ensures that compilation cache works with listeners.
   216  		require.Equal(t, `--> test.1()
   217  	--> test.2()
   218  		--> test.3()
   219  			--> test.4()
   220  			<--
   221  		<--
   222  	<--
   223  <--
   224  `, out.String())
   225  	})
   226  }
   227  
   228  // TestWithCloseOnContextDone ensures that compilation cache works as expected on and off with respect to WithCloseOnContextDone config.
   229  func testWithCloseOnContextDone(t *testing.T, config wazero.RuntimeConfig) {
   230  	var (
   231  		zero    uint32 = 0
   232  		wasmBin        = binaryencoding.EncodeModule(&wasm.Module{
   233  			TypeSection:     []wasm.FunctionType{{}},
   234  			FunctionSection: []wasm.Index{0},
   235  			CodeSection: []wasm.Code{
   236  				{Body: []byte{
   237  					wasm.OpcodeLoop, 0,
   238  					wasm.OpcodeBr, 0,
   239  					wasm.OpcodeEnd,
   240  					wasm.OpcodeEnd,
   241  				}},
   242  			},
   243  			StartSection: &zero,
   244  		})
   245  	)
   246  
   247  	t.Run("always on", func(t *testing.T) {
   248  		dir := t.TempDir()
   249  		ctx := context.Background()
   250  		{
   251  			cc, err := wazero.NewCompilationCacheWithDir(dir)
   252  			require.NoError(t, err)
   253  			rc := config.WithCompilationCache(cc).WithCloseOnContextDone(true)
   254  
   255  			r := wazero.NewRuntimeWithConfig(ctx, rc)
   256  			_, err = r.CompileModule(ctx, wasmBin)
   257  			require.NoError(t, err)
   258  			err = r.Close(ctx)
   259  			require.NoError(t, err)
   260  		}
   261  
   262  		cc, err := wazero.NewCompilationCacheWithDir(dir)
   263  		require.NoError(t, err)
   264  		rc := config.WithCompilationCache(cc).WithCloseOnContextDone(true)
   265  		r := wazero.NewRuntimeWithConfig(ctx, rc)
   266  
   267  		timeoutCtx, done := context.WithTimeout(ctx, time.Second)
   268  		defer done()
   269  		_, err = r.Instantiate(timeoutCtx, wasmBin)
   270  		require.EqualError(t, err, "module closed with context deadline exceeded")
   271  		err = r.Close(ctx)
   272  		require.NoError(t, err)
   273  	})
   274  
   275  	t.Run("off->on", func(t *testing.T) {
   276  		dir := t.TempDir()
   277  		ctx := context.Background()
   278  		{
   279  			cc, err := wazero.NewCompilationCacheWithDir(dir)
   280  			require.NoError(t, err)
   281  			rc := config.WithCompilationCache(cc).WithCloseOnContextDone(false)
   282  
   283  			r := wazero.NewRuntimeWithConfig(ctx, rc)
   284  			_, err = r.CompileModule(ctx, wasmBin)
   285  			require.NoError(t, err)
   286  			err = r.Close(ctx)
   287  			require.NoError(t, err)
   288  		}
   289  
   290  		cc, err := wazero.NewCompilationCacheWithDir(dir)
   291  		require.NoError(t, err)
   292  		rc := config.WithCompilationCache(cc).WithCloseOnContextDone(true)
   293  		r := wazero.NewRuntimeWithConfig(ctx, rc)
   294  
   295  		timeoutCtx, done := context.WithTimeout(ctx, time.Second)
   296  		defer done()
   297  		_, err = r.Instantiate(timeoutCtx, wasmBin)
   298  		require.EqualError(t, err, "module closed with context deadline exceeded")
   299  		err = r.Close(ctx)
   300  		require.NoError(t, err)
   301  	})
   302  }