Repository: tetratelabs/wazero
Pull request #332 (2022-03-04 08:56:51)

uses less code testing by not repeating the read-side in error tests


Patch:
diff --git a/config_supported.go b/config_supported.go
index 69e3553d..13ae5d2b 100644
--- a/config_supported.go
+++ b/config_supported.go
@@ -2,6 +2,8 @@
 
 package wazero
 
+const JITSupported = true
+
 // NewRuntimeConfig returns NewRuntimeConfigJIT
 func NewRuntimeConfig() *RuntimeConfig {
 	return NewRuntimeConfigJIT()
diff --git a/config_unsupported.go b/config_unsupported.go
index 435fb383..c247b7d2 100644
--- a/config_unsupported.go
+++ b/config_unsupported.go
@@ -2,6 +2,8 @@
 
 package wazero
 
+const JITSupported = false
+
 // NewRuntimeConfig returns NewRuntimeConfigInterpreter
 func NewRuntimeConfig() *RuntimeConfig {
 	return NewRuntimeConfigInterpreter()


Test patch:
diff --git a/internal/wasi/wasi_test.go b/internal/wasi/wasi_test.go
index a90e4d33..5b3816b8 100644
--- a/internal/wasi/wasi_test.go
+++ b/internal/wasi/wasi_test.go
@@ -1147,11 +1147,19 @@ func TestSnapshotPreview1_FdWrite_Errors(t *testing.T) {
 		}
 	})
 
+	// Setup valid test memory
+	iovs, iovsCount := uint64(0), uint64(1)
+	memory := []byte{
+		8, 0, 0, 0, // = iovs[0].offset (where the data "hi" begins)
+		2, 0, 0, 0, // = iovs[0].length (how many bytes are in "hi")
+		'h', 'i', // iovs[0].length bytes
+	}
+
 	tests := []struct {
-		name                            string
-		fd, iovs, iovsCount, resultSize uint64
-		memory                          []byte
-		expectedErrno                   wasi.Errno
+		name           string
+		fd, resultSize uint64
+		memory         []byte
+		expectedErrno  wasi.Errno
 	}{
 		{
 			name:          "invalid fd",
@@ -1161,54 +1169,32 @@ func TestSnapshotPreview1_FdWrite_Errors(t *testing.T) {
 		{
 			name:          "out-of-memory reading iovs[0].offset",
 			fd:            validFD,
-			iovs:          1,
-			memory:        []byte{'?'},
+			memory:        []byte{},
 			expectedErrno: wasi.ErrnoFault,
 		},
 		{
-			name: "out-of-memory reading iovs[0].length",
-			fd:   validFD,
-			iovs: 1, iovsCount: 1,
-			memory: []byte{
-				'?',        // `iovs` is after this
-				9, 0, 0, 0, // = iovs[0].offset
-			},
+			name:          "out-of-memory reading iovs[0].length",
+			fd:            validFD,
+			memory:        memory[0:4], // iovs[0].offset was 4 bytes and iovs[0].length next, but not enough mem!
 			expectedErrno: wasi.ErrnoFault,
 		},
 		{
-			name: "iovs[0].offset is outside memory",
-			fd:   validFD,
-			iovs: 1, iovsCount: 1,
-			memory: []byte{
-				'?',        // `iovs` is after this
-				9, 0, 0, 0, // = iovs[0].offset = one past the size of this memory
-				1, 0, 0, 0, // = iovs[0].length
-			},
+			name:          "iovs[0].offset is outside memory",
+			fd:            validFD,
+			memory:        memory[0:8], // iovs[0].offset (where to read "hi") is outside memory.
 			expectedErrno: wasi.ErrnoFault,
 		},
 		{
-			name: "length to write exceeds memory by 1",
-			fd:   validFD,
-			iovs: 1, iovsCount: 1,
-			memory: []byte{
-				'?',        // `iovs` is after this
-				9, 0, 0, 0, // = iovs[0].offset
-				2, 0, 0, 0, // = iovs[0].length = one past the size of this memory
-				'?',
-			},
+			name:          "length to read exceeds memory by 1",
+			fd:            validFD,
+			memory:        memory[0:9], // iovs[0].offset (where to read "hi") is in memory, but truncated.
 			expectedErrno: wasi.ErrnoFault,
 		},
 		{
-			name: "resultSize offset is outside memory",
-			fd:   validFD,
-			iovs: 1, iovsCount: 1,
-			resultSize: 10, // 1 past memory
-			memory: []byte{
-				'?',        // `iovs` is after this
-				9, 0, 0, 0, // = iovs[0].offset
-				1, 0, 0, 0, // = iovs[0].length
-				'?',
-			},
+			name:          "resultSize offset is outside memory",
+			fd:            validFD,
+			memory:        memory,
+			resultSize:    uint64(len(memory)), // read was ok, but there wasn't enough memory to write the result.
 			expectedErrno: wasi.ErrnoFault,
 		},
 	}
@@ -1216,12 +1202,9 @@ func TestSnapshotPreview1_FdWrite_Errors(t *testing.T) {
 	for _, tt := range tests {
 		tc := tt
 		t.Run(tc.name, func(t *testing.T) {
-			offset := uint64(wasm.MemoryPagesToBytesNum(testMemoryPageSize)) - uint64(len(tc.memory))
-
-			memoryWriteOK := mem.Write(uint32(offset), tc.memory)
-			require.True(t, memoryWriteOK)
+			mem.(*wasm.MemoryInstance).Buffer = tc.memory
 
-			results, err := fn.Call(context.Background(), tc.fd, tc.iovs+offset, tc.iovsCount+offset, tc.resultSize+offset)
+			results, err := fn.Call(context.Background(), tc.fd, iovs, iovsCount, tc.resultSize)
 			require.NoError(t, err)
 			require.Equal(t, tc.expectedErrno, wasi.Errno(results[0])) // results[0] is the errno
 		})
diff --git a/tests/engine/adhoc_test.go b/tests/engine/adhoc_test.go
index 839668c9..f7c30566 100644
--- a/tests/engine/adhoc_test.go
+++ b/tests/engine/adhoc_test.go
@@ -5,8 +5,6 @@ import (
 	_ "embed"
 	"fmt"
 	"math"
-	"runtime"
-	"sync"
 	"testing"
 
 	"github.com/stretchr/testify/require"
@@ -19,15 +17,15 @@ import (
 // ctx is a default context used to avoid lint warnings even though these tests don't use any context data.
 var ctx = context.Background()
 
-func TestJIT(t *testing.T) {
-	if runtime.GOARCH != "amd64" && runtime.GOARCH != "arm64" {
+func TestJITAdhoc(t *testing.T) {
+	if !wazero.JITSupported {
 		t.Skip()
 	}
-	runTests(t, wazero.NewRuntimeConfigJIT)
+	runAdhocTests(t, wazero.NewRuntimeConfigJIT)
 }
 
-func TestInterpreter(t *testing.T) {
-	runTests(t, wazero.NewRuntimeConfigInterpreter)
+func TestInterpreterAdhoc(t *testing.T) {
+	runAdhocTests(t, wazero.NewRuntimeConfigInterpreter)
 }
 
 var (
@@ -45,7 +43,7 @@ var (
 	hugestackWasm []byte
 )
 
-func runTests(t *testing.T, newRuntimeConfig func() *wazero.RuntimeConfig) {
+func runAdhocTests(t *testing.T, newRuntimeConfig func() *wazero.RuntimeConfig) {
 	t.Run("huge stack", func(t *testing.T) {
 		testHugeStack(t, newRuntimeConfig)
 	})
@@ -73,35 +71,18 @@ func runTests(t *testing.T, newRuntimeConfig func() *wazero.RuntimeConfig) {
 }
 
 func testHugeStack(t *testing.T, newRuntimeConfig func() *wazero.RuntimeConfig) {
-	// We execute 1000 times in order to ensure the JIT engine is stable under high concurrency
-	// and we have no conflict with Go's runtime.
-	const goroutines = 1000
-
 	r := wazero.NewRuntimeWithConfig(newRuntimeConfig())
-
 	module, err := r.NewModuleFromSource(hugestackWasm)
 	require.NoError(t, err)
 
 	fn := module.Function("main")
 	require.NotNil(t, fn)
 
-	var wg sync.WaitGroup
-	wg.Add(goroutines)
-	for i := 0; i < goroutines; i++ {
-		go func() {
-			defer wg.Done()
-			_, err = fn.Call(ctx)
-			require.NoError(t, err)
-		}()
-	}
-	wg.Wait()
+	_, err = fn.Call(ctx)
+	require.NoError(t, err)
 }
 
 func testFibonacci(t *testing.T, newRuntimeConfig func() *wazero.RuntimeConfig) {
-	// We execute 1000 times in order to ensure the JIT engine is stable under high concurrency
-	// and we have no conflict with Go's runtime.
-	const goroutines = 1000
-
 	r := wazero.NewRuntimeWithConfig(newRuntimeConfig())
 	module, err := r.NewModuleFromSource(fibWasm)
 	require.NoError(t, err)
@@ -109,17 +90,9 @@ func testFibonacci(t *testing.T, newRuntimeConfig func() *wazero.RuntimeConfig)
 	fib := module.Function("fib")
 	require.NotNil(t, fib)
 
-	var wg sync.WaitGroup
-	wg.Add(goroutines)
-	for i := 0; i < goroutines; i++ {
-		go func() {
-			defer wg.Done()
-			results, err := fib.Call(ctx, 20)
-			require.NoError(t, err)
-			require.Equal(t, uint64(10946), results[0])
-		}()
-	}
-	wg.Wait()
+	results, err := fib.Call(ctx, 20)
+	require.NoError(t, err)
+	require.Equal(t, uint64(10946), results[0])
 }
 
 func testFac(t *testing.T, newRuntimeConfig func() *wazero.RuntimeConfig) {
diff --git a/tests/engine/concurrency_test.go b/tests/engine/concurrency_test.go
new file mode 100644
index 00000000..868f166c
--- /dev/null
+++ b/tests/engine/concurrency_test.go
@@ -0,0 +1,68 @@
+package adhoc
+
+import (
+	"sync"
+	"testing"
+
+	"github.com/tetratelabs/wazero"
+)
+
+func TestJITConcurrency(t *testing.T) {
+	if !wazero.JITSupported {
+		t.Skip()
+	}
+	runAdhocTestsUnderHighConcurrency(t, wazero.NewRuntimeConfigJIT)
+	// TODO: Add conccurent instantiation, invocation and release on a single store test case in https://github.com/tetratelabs/wazero/issues/293
+
+}
+
+func TestInterpreterConcurrency(t *testing.T) {
+	runAdhocTestsUnderHighConcurrency(t, wazero.NewRuntimeConfigInterpreter)
+	// TODO: Add conccurent instantiation, invocation and release on a single store test case in https://github.com/tetratelabs/wazero/issues/293
+}
+
+func runAdhocTestsUnderHighConcurrency(t *testing.T, newRuntimeConfig func() *wazero.RuntimeConfig) {
+	t.Run("huge stack", func(t *testing.T) {
+		t.Parallel()
+		runAdhocTestUnderHighConcurrency(t, newRuntimeConfig, testHugeStack)
+	})
+	t.Run("fibonacci", func(t *testing.T) {
+		t.Parallel()
+		runAdhocTestUnderHighConcurrency(t, newRuntimeConfig, testFibonacci)
+	})
+	t.Run("fac", func(t *testing.T) {
+		t.Parallel()
+		runAdhocTestUnderHighConcurrency(t, newRuntimeConfig, testFac)
+	})
+	t.Run("unreachable", func(t *testing.T) {
+		t.Parallel()
+		runAdhocTestUnderHighConcurrency(t, newRuntimeConfig, testUnreachable)
+	})
+	t.Run("memory", func(t *testing.T) {
+		t.Parallel()
+		runAdhocTestUnderHighConcurrency(t, newRuntimeConfig, testMemory)
+	})
+	t.Run("recursive entry", func(t *testing.T) {
+		t.Parallel()
+		runAdhocTestUnderHighConcurrency(t, newRuntimeConfig, testRecursiveEntry)
+	})
+	t.Run("imported-and-exported func", func(t *testing.T) {
+		t.Parallel()
+		runAdhocTestUnderHighConcurrency(t, newRuntimeConfig, testImportedAndExportedFunc)
+	})
+}
+
+// runAdhocTestUnderHighConcurrency runs a test case in adhoc_test.go with multiple goroutines.
+func runAdhocTestUnderHighConcurrency(t *testing.T, newRuntimeConfig func() *wazero.RuntimeConfig,
+	adhocTest func(t *testing.T, newRuntimeConfig func() *wazero.RuntimeConfig)) {
+	const goroutinesPerCase = 1000
+	var wg sync.WaitGroup
+	wg.Add(goroutinesPerCase)
+	for i := 0; i < goroutinesPerCase; i++ {
+		go func() {
+			defer wg.Done()
+			adhocTest(t, newRuntimeConfig)
+		}()
+	}
+	wg.Wait()
+}