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