Repository: tetratelabs/wazero
Pull request #767 (2022-08-26 01:32:11)

exp: create the compilation cache dir when not exist
Signed-off-by: Takeshi Yoneda <takeshi@tetrate.io>


Patch:
diff --git a/experimental/compilation_cache.go b/experimental/compilation_cache.go
index 9a2bc4a9..2c12f88a 100644
--- a/experimental/compilation_cache.go
+++ b/experimental/compilation_cache.go
@@ -2,6 +2,9 @@ package experimental
 
 import (
 	"context"
+	"errors"
+	"fmt"
+	"os"
 
 	"github.com/tetratelabs/wazero/internal/compilationcache"
 )
@@ -10,6 +13,8 @@ import (
 // Regardless of the usage of this, the compiled functions are cached in memory, but its lifetime is
 // bound to the lifetime of wazero.Runtime or wazero.CompiledModule.
 //
+// If the dirname doesn't exist, this creates the directory.
+//
 // With the given non-empty directory, wazero persists the cache into the directory and that cache
 // will be used as long as the running wazero version match the version of compilation wazero.
 //
@@ -21,11 +26,20 @@ import (
 //
 // Usage:
 //
-//	ctx := experimental.WithCompilationCacheDirName(context.Background(), "/home/me/.cache/wazero")
+//	ctx, _ := experimental.WithCompilationCacheDirName(context.Background(), "/home/me/.cache/wazero")
 //	r := wazero.NewRuntimeWithConfig(ctx, wazero.NewRuntimeConfigCompiler())
-func WithCompilationCacheDirName(ctx context.Context, dirname string) context.Context {
-	if len(dirname) != 0 {
-		ctx = context.WithValue(ctx, compilationcache.FileCachePathKey{}, dirname)
+func WithCompilationCacheDirName(ctx context.Context, dirname string) (context.Context, error) {
+	if st, err := os.Stat(dirname); errors.Is(err, os.ErrNotExist) {
+		// If the directory not found, create the cache dir.
+		if err = os.MkdirAll(dirname, 0o700); err != nil {
+			return nil, fmt.Errorf("create diretory %s: %v", dirname, err)
+		}
+	} else if err != nil {
+		return nil, err
+	} else if !st.IsDir() {
+		return nil, fmt.Errorf("%s is not dir", dirname)
 	}
-	return ctx
+
+	ctx = context.WithValue(ctx, compilationcache.FileCachePathKey{}, dirname)
+	return ctx, nil
 }


Test patch:
diff --git a/experimental/compilation_cache_example_test.go b/experimental/compilation_cache_example_test.go
index 5b923618..0737c3ab 100644
--- a/experimental/compilation_cache_example_test.go
+++ b/experimental/compilation_cache_example_test.go
@@ -20,7 +20,10 @@ func Example_withCompilationCacheDirName() {
 	defer os.RemoveAll(cacheDir)
 
 	// Append the directory into the context for configuration.
-	ctx := experimental.WithCompilationCacheDirName(context.Background(), cacheDir)
+	ctx, err := experimental.WithCompilationCacheDirName(context.Background(), cacheDir)
+	if err != nil {
+		log.Panicln(err)
+	}
 
 	// Repeat newRuntimeCompileClose with the same cache directory.
 	newRuntimeCompileClose(ctx)
diff --git a/experimental/compilation_cache_test.go b/experimental/compilation_cache_test.go
new file mode 100644
index 00000000..952ca602
--- /dev/null
+++ b/experimental/compilation_cache_test.go
@@ -0,0 +1,45 @@
+package experimental
+
+import (
+	"context"
+	"fmt"
+	"os"
+	"path"
+	"testing"
+
+	"github.com/tetratelabs/wazero/internal/compilationcache"
+	"github.com/tetratelabs/wazero/internal/testing/require"
+)
+
+func TestWithCompilationCacheDirName(t *testing.T) {
+	t.Run("ok", func(t *testing.T) {
+		dir := t.TempDir()
+		ctx, err := WithCompilationCacheDirName(context.Background(), dir)
+		require.NoError(t, err)
+		actual, ok := ctx.Value(compilationcache.FileCachePathKey{}).(string)
+		require.True(t, ok)
+		require.Equal(t, dir, actual)
+
+		// Ensure that the sanity check file has been removed.
+		entries, err := os.ReadDir(dir)
+		require.NoError(t, err)
+		require.Equal(t, 0, len(entries))
+	})
+	t.Run("create dir", func(t *testing.T) {
+		dir := path.Join(t.TempDir(), "1", "2", "3", t.Name()) // Non-existent directory.
+		fmt.Println(dir)
+		ctx, err := WithCompilationCacheDirName(context.Background(), dir)
+		require.NoError(t, err)
+		actual, ok := ctx.Value(compilationcache.FileCachePathKey{}).(string)
+		require.True(t, ok)
+		require.Equal(t, dir, actual)
+	})
+	t.Run("non dir", func(t *testing.T) {
+		f, err := os.CreateTemp(t.TempDir(), "nondir")
+		require.NoError(t, err)
+		defer f.Close()
+
+		_, err = WithCompilationCacheDirName(context.Background(), f.Name())
+		require.Contains(t, err.Error(), "is not dir")
+	})
+}
diff --git a/internal/integration_test/bench/bench_test.go b/internal/integration_test/bench/bench_test.go
index 6c375725..9a7c29e1 100644
--- a/internal/integration_test/bench/bench_test.go
+++ b/internal/integration_test/bench/bench_test.go
@@ -60,7 +60,10 @@ func BenchmarkCompilation(b *testing.B) {
 	// Note: recreate runtime each time in the loop to ensure that
 	// recompilation happens if the extern cache is not used.
 	b.Run("with extern cache", func(b *testing.B) {
-		ctx := experimental.WithCompilationCacheDirName(context.Background(), b.TempDir())
+		ctx, err := experimental.WithCompilationCacheDirName(context.Background(), b.TempDir())
+		if err != nil {
+			b.Fatal(err)
+		}
 		for i := 0; i < b.N; i++ {
 			r := wazero.NewRuntimeWithConfig(ctx, wazero.NewRuntimeConfigCompiler())
 			runCompilation(b, r)
diff --git a/internal/integration_test/filecache/filecache_test.go b/internal/integration_test/filecache/filecache_test.go
index 8f87493f..976123af 100644
--- a/internal/integration_test/filecache/filecache_test.go
+++ b/internal/integration_test/filecache/filecache_test.go
@@ -60,7 +60,8 @@ func TestSpecTestCompilerCache(t *testing.T) {
 		require.True(t, len(files) > 0)
 	} else {
 		// Run the spectest with the file cache.
-		ctx := experimental.WithCompilationCacheDirName(context.Background(), cacheDir)
+		ctx, err := experimental.WithCompilationCacheDirName(context.Background(), cacheDir)
+		require.NoError(t, err)
 		spectest.Run(t, v1.Testcases, ctx, compiler.NewEngine, v1.EnabledFeatures)
 	}
 }