github.com/tetratelabs/wazero@v1.7.3-0.20240513003603-48f702e154b5/internal/wasmdebug/debug_test.go (about)

     1  package wasmdebug
     2  
     3  import (
     4  	"errors"
     5  	"runtime"
     6  	"testing"
     7  
     8  	"github.com/tetratelabs/wazero/api"
     9  	"github.com/tetratelabs/wazero/internal/testing/require"
    10  	"github.com/tetratelabs/wazero/internal/wasmruntime"
    11  )
    12  
    13  func TestFuncName(t *testing.T) {
    14  	tests := []struct {
    15  		name, moduleName, funcName string
    16  		funcIdx                    uint32
    17  		expected                   string
    18  	}{ // Only tests a few edge cases to show what it might end up as.
    19  		{name: "empty", expected: ".$0"},
    20  		{name: "empty module", funcName: "y", expected: ".y"},
    21  		{name: "empty function", moduleName: "x", funcIdx: 255, expected: "x.$255"},
    22  		{name: "looks like index in function", moduleName: "x", funcName: "[255]", expected: "x.[255]"},
    23  		{name: "no special characters", moduleName: "x", funcName: "y", expected: "x.y"},
    24  		{name: "dots in module", moduleName: "w.x", funcName: "y", expected: "w.x.y"},
    25  		{name: "dots in function", moduleName: "x", funcName: "y.z", expected: "x.y.z"},
    26  		{name: "spaces in module", moduleName: "w x", funcName: "y", expected: "w x.y"},
    27  		{name: "spaces in function", moduleName: "x", funcName: "y z", expected: "x.y z"},
    28  	}
    29  
    30  	for _, tt := range tests {
    31  		tc := tt
    32  		t.Run(tc.name, func(t *testing.T) {
    33  			funcName := FuncName(tc.moduleName, tc.funcName, tc.funcIdx)
    34  			require.Equal(t, tc.expected, funcName)
    35  		})
    36  	}
    37  }
    38  
    39  func TestAddSignature(t *testing.T) {
    40  	i32, i64, f32, f64 := api.ValueTypeI32, api.ValueTypeI64, api.ValueTypeF32, api.ValueTypeF64
    41  	tests := []struct {
    42  		name                    string
    43  		paramTypes, resultTypes []api.ValueType
    44  		expected                string
    45  	}{
    46  		{name: "v_v", expected: "x.y()"},
    47  		{name: "i32_v", paramTypes: []api.ValueType{i32}, expected: "x.y(i32)"},
    48  		{name: "i32f64_v", paramTypes: []api.ValueType{i32, f64}, expected: "x.y(i32,f64)"},
    49  		{name: "f32i32f64_v", paramTypes: []api.ValueType{f32, i32, f64}, expected: "x.y(f32,i32,f64)"},
    50  		{name: "v_i64", resultTypes: []api.ValueType{i64}, expected: "x.y() i64"},
    51  		{name: "v_i64f32", resultTypes: []api.ValueType{i64, f32}, expected: "x.y() (i64,f32)"},
    52  		{name: "v_f32i32f64", resultTypes: []api.ValueType{f32, i32, f64}, expected: "x.y() (f32,i32,f64)"},
    53  		{name: "i32_i64", paramTypes: []api.ValueType{i32}, resultTypes: []api.ValueType{i64}, expected: "x.y(i32) i64"},
    54  		{name: "i64f32_i64f32", paramTypes: []api.ValueType{i64, f32}, resultTypes: []api.ValueType{i64, f32}, expected: "x.y(i64,f32) (i64,f32)"},
    55  		{name: "i64f32f64_f32i32f64", paramTypes: []api.ValueType{i64, f32, f64}, resultTypes: []api.ValueType{f32, i32, f64}, expected: "x.y(i64,f32,f64) (f32,i32,f64)"},
    56  	}
    57  
    58  	for _, tt := range tests {
    59  		tc := tt
    60  		t.Run(tc.name, func(t *testing.T) {
    61  			withSignature := signature("x.y", tc.paramTypes, tc.resultTypes)
    62  			require.Equal(t, tc.expected, withSignature)
    63  		})
    64  	}
    65  }
    66  
    67  var (
    68  	argErr       = errors.New("invalid argument")
    69  	rteErr       = testRuntimeErr("index out of bounds")
    70  	i32          = api.ValueTypeI32
    71  	i32i32i32i32 = []api.ValueType{i32, i32, i32, i32}
    72  )
    73  
    74  func TestErrorBuilder(t *testing.T) {
    75  	tests := []struct {
    76  		name         string
    77  		build        func(ErrorBuilder) error
    78  		expectedErr  string
    79  		expectUnwrap error
    80  	}{
    81  		{
    82  			name: "one",
    83  			build: func(builder ErrorBuilder) error {
    84  				builder.AddFrame("x.y", nil, nil, nil)
    85  				return builder.FromRecovered(argErr)
    86  			},
    87  			expectedErr: `invalid argument (recovered by wazero)
    88  wasm stack trace:
    89  	x.y()`,
    90  			expectUnwrap: argErr,
    91  		},
    92  		{
    93  			name: "two",
    94  			build: func(builder ErrorBuilder) error {
    95  				builder.AddFrame("wasi_snapshot_preview1.fd_write", i32i32i32i32, []api.ValueType{i32}, nil)
    96  				builder.AddFrame("x.y", nil, nil, nil)
    97  				return builder.FromRecovered(argErr)
    98  			},
    99  			expectedErr: `invalid argument (recovered by wazero)
   100  wasm stack trace:
   101  	wasi_snapshot_preview1.fd_write(i32,i32,i32,i32) i32
   102  	x.y()`,
   103  			expectUnwrap: argErr,
   104  		},
   105  		{
   106  			name: "wasmruntime.Error",
   107  			build: func(builder ErrorBuilder) error {
   108  				builder.AddFrame("wasi_snapshot_preview1.fd_write", i32i32i32i32, []api.ValueType{i32},
   109  					[]string{"/opt/homebrew/Cellar/tinygo/0.26.0/src/runtime/runtime_tinygowasm.go:73:6"})
   110  				builder.AddFrame("x.y", nil, nil, nil)
   111  				return builder.FromRecovered(wasmruntime.ErrRuntimeStackOverflow)
   112  			},
   113  			expectedErr: `wasm error: stack overflow
   114  wasm stack trace:
   115  	wasi_snapshot_preview1.fd_write(i32,i32,i32,i32) i32
   116  		/opt/homebrew/Cellar/tinygo/0.26.0/src/runtime/runtime_tinygowasm.go:73:6
   117  	x.y()`,
   118  			expectUnwrap: wasmruntime.ErrRuntimeStackOverflow,
   119  		},
   120  	}
   121  
   122  	for _, tt := range tests {
   123  		tc := tt
   124  		t.Run(tc.name, func(t *testing.T) {
   125  			withStackTrace := tc.build(NewErrorBuilder())
   126  			require.Equal(t, tc.expectUnwrap, errors.Unwrap(withStackTrace))
   127  			require.EqualError(t, withStackTrace, tc.expectedErr)
   128  		})
   129  	}
   130  }
   131  
   132  func TestErrorBuilderGoRuntimeError(t *testing.T) {
   133  	builder := NewErrorBuilder()
   134  	builder.AddFrame("wasi_snapshot_preview1.fd_write", i32i32i32i32, []api.ValueType{i32}, nil)
   135  	builder.AddFrame("x.y", nil, nil, nil)
   136  	withStackTrace := builder.FromRecovered(rteErr)
   137  
   138  	require.Equal(t, rteErr, errors.Unwrap(withStackTrace))
   139  
   140  	errStr := withStackTrace.Error()
   141  	require.Contains(t, errStr, `index out of bounds (recovered by wazero)
   142  wasm stack trace:
   143  	wasi_snapshot_preview1.fd_write(i32,i32,i32,i32) i32
   144  	x.y()`)
   145  	require.Contains(t, errStr, "Go runtime stack trace:")
   146  	require.Contains(t, errStr, "goroutine ")
   147  	require.Contains(t, errStr, "/internal/wasmdebug/debug_test.go")
   148  }
   149  
   150  // compile-time check to ensure testRuntimeErr implements runtime.Error.
   151  var _ runtime.Error = testRuntimeErr("")
   152  
   153  type testRuntimeErr string
   154  
   155  func (e testRuntimeErr) RuntimeError() {}
   156  
   157  func (e testRuntimeErr) Error() string {
   158  	return string(e)
   159  }
   160  
   161  func Test_AddFrame_MaxFrame(t *testing.T) {
   162  	builder := NewErrorBuilder().(*stackTrace)
   163  	for i := 0; i < MaxFrames+10; i++ {
   164  		builder.AddFrame("x.y", nil, nil, []string{"a.go:1:2", "b.go:3:4"})
   165  	}
   166  	require.Equal(t, MaxFrames, builder.frameCount)
   167  	require.Equal(t, MaxFrames*3 /* frame + two inlined sources */ +1, len(builder.lines))
   168  	require.Equal(t, "... maybe followed by omitted frames", builder.lines[len(builder.lines)-1])
   169  }