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 }