224 lines
		
	
	
		
			5.8 KiB
		
	
	
	
		
			Go
		
	
	
	
			
		
		
	
	
			224 lines
		
	
	
		
			5.8 KiB
		
	
	
	
		
			Go
		
	
	
	
| /*
 | |
| Copyright 2023 The Dapr Authors
 | |
| Licensed under the Apache License, Version 2.0 (the "License");
 | |
| you may not use this file except in compliance with the License.
 | |
| You may obtain a copy of the License at
 | |
| 
 | |
| 	http://www.apache.org/licenses/LICENSE-2.0
 | |
| 
 | |
| Unless required by applicable law or agreed to in writing, software
 | |
| distributed under the License is distributed on an "AS IS" BASIS,
 | |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implieout.
 | |
| See the License for the specific language governing permissions and
 | |
| limitations under the License.
 | |
| */
 | |
| 
 | |
| package wasm
 | |
| 
 | |
| import (
 | |
| 	"bytes"
 | |
| 	"context"
 | |
| 	_ "embed"
 | |
| 	"testing"
 | |
| 	"time"
 | |
| 
 | |
| 	"github.com/stretchr/testify/require"
 | |
| 	"github.com/tetratelabs/wazero"
 | |
| 	"github.com/tetratelabs/wazero/imports/wasi_snapshot_preview1"
 | |
| 
 | |
| 	"github.com/dapr/components-contrib/metadata"
 | |
| )
 | |
| 
 | |
| const (
 | |
| 	urlArgsFile  = "file://testdata/args/main.wasm"
 | |
| 	urlPythonOCI = "oci://ghcr.io/vmware-labs/python-wasm:3.11.3"
 | |
| )
 | |
| 
 | |
| //go:embed testdata/args/main.wasm
 | |
| var binArgs []byte
 | |
| 
 | |
| func TestGetInitMetadata(t *testing.T) {
 | |
| 	testCtx, cancel := context.WithCancel(context.Background())
 | |
| 	t.Cleanup(cancel)
 | |
| 
 | |
| 	type testCase struct {
 | |
| 		name        string
 | |
| 		metadata    metadata.Base
 | |
| 		expected    *InitMetadata
 | |
| 		expectedErr string
 | |
| 	}
 | |
| 
 | |
| 	tests := []testCase{
 | |
| 		{
 | |
| 			name: "file valid",
 | |
| 			metadata: metadata.Base{Properties: map[string]string{
 | |
| 				"url": urlArgsFile,
 | |
| 			}},
 | |
| 			expected: &InitMetadata{
 | |
| 				URL:       urlArgsFile,
 | |
| 				Guest:     binArgs,
 | |
| 				GuestName: "main",
 | |
| 			},
 | |
| 		},
 | |
| 		{
 | |
| 			name: "file valid - strictSandbox",
 | |
| 			metadata: metadata.Base{Properties: map[string]string{
 | |
| 				"url":           urlArgsFile,
 | |
| 				"strictSandbox": "true",
 | |
| 			}},
 | |
| 			expected: &InitMetadata{
 | |
| 				URL:           urlArgsFile,
 | |
| 				Guest:         binArgs,
 | |
| 				GuestName:     "main",
 | |
| 				StrictSandbox: true,
 | |
| 			},
 | |
| 		},
 | |
| 		{
 | |
| 			name:        "empty url",
 | |
| 			metadata:    metadata.Base{Properties: map[string]string{}},
 | |
| 			expectedErr: "missing url",
 | |
| 		},
 | |
| 		{
 | |
| 			name: "http invalid",
 | |
| 			metadata: metadata.Base{Properties: map[string]string{
 | |
| 				"url": "http:// ",
 | |
| 			}},
 | |
| 			expectedErr: "parse \"http:// \": invalid character \" \" in host name",
 | |
| 		},
 | |
| 		{
 | |
| 			name: "https invalid",
 | |
| 			metadata: metadata.Base{Properties: map[string]string{
 | |
| 				"url": "https:// ",
 | |
| 			}},
 | |
| 			expectedErr: "parse \"https:// \": invalid character \" \" in host name",
 | |
| 		},
 | |
| 		{
 | |
| 			name: "TODO oci",
 | |
| 			metadata: metadata.Base{Properties: map[string]string{
 | |
| 				"url": urlPythonOCI,
 | |
| 			}},
 | |
| 			expectedErr: "TODO oci",
 | |
| 		},
 | |
| 		{
 | |
| 			name: "TODO http",
 | |
| 			metadata: metadata.Base{Properties: map[string]string{
 | |
| 				"url": "http://foo.invalid/bar.wasm",
 | |
| 			}},
 | |
| 			expectedErr: "no such host",
 | |
| 		},
 | |
| 		{
 | |
| 			name: "TODO https",
 | |
| 			metadata: metadata.Base{Properties: map[string]string{
 | |
| 				"url": "https://foo.invalid/bar.wasm",
 | |
| 			}},
 | |
| 			expectedErr: "no such host",
 | |
| 		},
 | |
| 		{
 | |
| 			name: "unsupported scheme",
 | |
| 			metadata: metadata.Base{Properties: map[string]string{
 | |
| 				"url": "ldap://foo/bar.wasm",
 | |
| 			}},
 | |
| 			expectedErr: "unsupported URL scheme: ldap",
 | |
| 		},
 | |
| 		{
 | |
| 			name: "file not found",
 | |
| 			metadata: metadata.Base{Properties: map[string]string{
 | |
| 				"url": "file://testduta",
 | |
| 			}},
 | |
| 			expectedErr: "open testduta: ",
 | |
| 		},
 | |
| 		{
 | |
| 			name: "file dir not file",
 | |
| 			metadata: metadata.Base{Properties: map[string]string{
 | |
| 				"url": "file://testdata",
 | |
| 			}},
 | |
| 			// Below ends in "is a directory" in unix, and "The handle is invalid." in windows.
 | |
| 			expectedErr: "read testdata: ",
 | |
| 		},
 | |
| 	}
 | |
| 
 | |
| 	for _, tt := range tests {
 | |
| 		tc := tt
 | |
| 		t.Run(tc.name, func(t *testing.T) {
 | |
| 			md, err := GetInitMetadata(testCtx, tc.metadata)
 | |
| 			if tc.expectedErr == "" {
 | |
| 				require.NoError(t, err)
 | |
| 				require.Equal(t, tc.expected, md)
 | |
| 			} else {
 | |
| 				// Use substring match as the error can be different in Windows.
 | |
| 				require.Contains(t, err.Error(), tc.expectedErr)
 | |
| 			}
 | |
| 		})
 | |
| 	}
 | |
| }
 | |
| 
 | |
| //go:embed testdata/strict/main.wasm
 | |
| var binStrict []byte
 | |
| 
 | |
| func TestNewModuleConfig(t *testing.T) {
 | |
| 	type testCase struct {
 | |
| 		name                     string
 | |
| 		metadata                 *InitMetadata
 | |
| 		minDuration, maxDuration time.Duration
 | |
| 	}
 | |
| 
 | |
| 	tests := []testCase{
 | |
| 		{
 | |
| 			name:     "strictSandbox = false",
 | |
| 			metadata: &InitMetadata{Guest: binStrict},
 | |
| 			// In CI, Nanosleep(50ms) returned after 197ms.
 | |
| 			// As we can't control the platform clock, we have to be lenient
 | |
| 			minDuration: 50 * time.Millisecond,
 | |
| 			maxDuration: 50 * time.Millisecond * 5,
 | |
| 		},
 | |
| 		{
 | |
| 			name:     "strictSandbox = true",
 | |
| 			metadata: &InitMetadata{StrictSandbox: true, Guest: binStrict},
 | |
| 			// In strict mode, nanosleep is implemented by an incrementing
 | |
| 			// number. The resolution of the real clock timing the wasm
 | |
| 			// invocation is lower resolution in Windows, so we can't verify a
 | |
| 			// lower bound. In any case, the important part is that we aren't
 | |
| 			// actually sleeping 50ms, which is what wasm thinks is happening.
 | |
| 			minDuration: 0,
 | |
| 			maxDuration: 1 * time.Millisecond,
 | |
| 		},
 | |
| 	}
 | |
| 
 | |
| 	ctx := context.Background()
 | |
| 	rt := wazero.NewRuntime(ctx)
 | |
| 	defer rt.Close(ctx)
 | |
| 	wasi_snapshot_preview1.MustInstantiate(ctx, rt)
 | |
| 
 | |
| 	for _, tt := range tests {
 | |
| 		tc := tt
 | |
| 		t.Run(tc.name, func(t *testing.T) {
 | |
| 			var out bytes.Buffer
 | |
| 
 | |
| 			cfg := NewModuleConfig(tc.metadata).
 | |
| 				WithStdout(&out).WithStderr(&out).
 | |
| 				WithStartFunctions() // don't include instantiation in duration
 | |
| 			mod, err := rt.InstantiateWithConfig(ctx, tc.metadata.Guest, cfg)
 | |
| 			require.NoError(t, err)
 | |
| 
 | |
| 			start := time.Now()
 | |
| 			_, err = mod.ExportedFunction("_start").Call(ctx)
 | |
| 			require.NoError(t, err)
 | |
| 			duration := time.Since(start)
 | |
| 
 | |
| 			// TODO: TinyGo doesn't seem to use monotonic time. Track below:
 | |
| 			// https://github.com/tinygo-org/tinygo/issues/3776
 | |
| 			deterministicOut := `2000000
 | |
| 1000000
 | |
| 6393cff83a
 | |
| `
 | |
| 			if tc.metadata.StrictSandbox {
 | |
| 				require.Equal(t, deterministicOut, out.String())
 | |
| 			} else {
 | |
| 				require.NotEqual(t, deterministicOut, out.String())
 | |
| 			}
 | |
| 			require.GreaterOrEqual(t, duration, tc.minDuration)
 | |
| 			require.LessOrEqual(t, duration, tc.maxDuration)
 | |
| 		})
 | |
| 	}
 | |
| }
 |