Avoids returning ExitError on exit code zero, and optimizes for no allocations (#1284)

Fixes #1283

Signed-off-by: Adrian Cole <adrian@tetrate.io>
This commit is contained in:
Crypt Keeper
2023-03-24 16:42:30 +01:00
committed by GitHub
parent 86f3b190eb
commit 451c792ee8
22 changed files with 162 additions and 108 deletions

View File

@@ -459,7 +459,7 @@ func TestRun(t *testing.T) {
name: "timeout: a binary that exceeds the deadline should print an error",
wazeroOpts: []string{"-timeout=1ms"},
wasm: wasmInfiniteLoop,
expectedStderr: "error: module \"\" closed with context deadline exceeded (timeout 1ms)\n",
expectedStderr: "error: module closed with context deadline exceeded (timeout 1ms)\n",
expectedExitCode: int(sys.ExitCodeDeadlineExceeded),
test: func(t *testing.T) {
require.NoError(t, err)

View File

@@ -45,7 +45,7 @@ func ExampleRuntimeConfig_WithCloseOnContextDone_context_timeout() {
fmt.Println(err)
// Output:
// module "malicious_wasm" closed with context deadline exceeded
// module closed with context deadline exceeded
}
// ExampleRuntimeConfig_WithCloseOnContextDone_context_cancel demonstrates how to ensure the termination
@@ -82,7 +82,7 @@ func ExampleRuntimeConfig_WithCloseOnContextDone_context_cancel() {
fmt.Println(err)
// Output:
// module "malicious_wasm" closed with context canceled
// module closed with context canceled
}
// ExampleRuntimeConfig_WithCloseOnContextDone_moduleClose demonstrates how to ensure the termination
@@ -120,5 +120,5 @@ func ExampleRuntimeConfig_WithCloseOnContextDone_moduleClose() {
fmt.Println(err)
// Output:
// module "malicious_wasm" closed with exit_code(1)
// module closed with exit_code(1)
}

View File

@@ -177,7 +177,7 @@ func abort(ctx context.Context, mod api.Module, _ []uint64) {
_ = mod.CloseWithExitCode(ctx, exitCode)
// Prevent any code from executing after this function.
panic(sys.NewExitError(mod.Name(), exitCode))
panic(sys.NewExitError(exitCode))
}
// traceDisabled ignores the input.

View File

@@ -11,7 +11,6 @@ import (
"github.com/tetratelabs/wazero/experimental/logging"
"github.com/tetratelabs/wazero/imports/wasi_snapshot_preview1"
"github.com/tetratelabs/wazero/internal/testing/require"
"github.com/tetratelabs/wazero/sys"
)
// growWasm was compiled from testdata/grow.cc
@@ -45,10 +44,9 @@ func TestGrow(t *testing.T) {
_, err := Instantiate(ctx, r)
require.NoError(t, err)
// Emscripten exits main with zero by default
// Emscripten exits main with zero by default, which coerces to nul.
_, err = r.Instantiate(ctx, growWasm)
require.Error(t, err)
require.Zero(t, err.(*sys.ExitError).ExitCode())
require.Nil(t, err)
// We expect the memory no-op memory growth hook to be invoked as wasm.
require.Contains(t, log.String(), "==> env.emscripten_notify_memory_growth(memory_index=0)")

View File

@@ -37,7 +37,7 @@ func procExitFn(ctx context.Context, mod api.Module, params []uint64) {
// Prevent any code from executing after this function. For example, LLVM
// inserts unreachable instructions after calls to exit.
// See: https://github.com/emscripten-core/emscripten/issues/12322
panic(sys.NewExitError(mod.Name(), exitCode))
panic(sys.NewExitError(exitCode))
}
// procRaise is stubbed and will never be supported, as it was removed.

View File

@@ -72,7 +72,7 @@ func TestNewFunctionExporter(t *testing.T) {
_, err = r.Instantiate(testCtx, exitOnStartWasm)
// Ensure the modified function was used!
require.Zero(t, err.(*sys.ExitError).ExitCode())
require.Nil(t, err)
})
}

View File

@@ -15,7 +15,7 @@ func Test_argsAndEnv(t *testing.T) {
return moduleConfig.WithEnv("c", "d").WithEnv("a", "b"), config.NewConfig()
})
require.EqualError(t, err, `module "" closed with exit_code(0)`)
require.EqualError(t, err, `module closed with exit_code(0)`)
require.Zero(t, stderr)
require.Equal(t, `
args 0 = test

View File

@@ -20,7 +20,7 @@ func Test_crypto(t *testing.T) {
stdout, stderr, err := compileAndRun(loggingCtx, "crypto", defaultConfig)
require.Zero(t, stderr)
require.EqualError(t, err, `module "" closed with exit_code(0)`)
require.EqualError(t, err, `module closed with exit_code(0)`)
require.Equal(t, `7a0c9f9f0d
`, stdout)
require.Equal(t, `==> go.runtime.getRandomData(r_len=32)

View File

@@ -20,7 +20,7 @@ func Test_fs(t *testing.T) {
})
require.Zero(t, stderr)
require.EqualError(t, err, `module "" closed with exit_code(0)`)
require.EqualError(t, err, `module closed with exit_code(0)`)
require.Equal(t, `sub mode drwxr-xr-x
/animals.txt mode -rw-r--r--
animals.txt mode -rw-r--r--
@@ -50,7 +50,7 @@ func Test_testfs(t *testing.T) {
})
require.Zero(t, stderr)
require.EqualError(t, err, `module "" closed with exit_code(0)`)
require.EqualError(t, err, `module closed with exit_code(0)`)
require.Zero(t, stdout)
}
@@ -67,7 +67,7 @@ func Test_writefs(t *testing.T) {
})
require.Zero(t, stderr)
require.EqualError(t, err, `module "" closed with exit_code(0)`)
require.EqualError(t, err, `module closed with exit_code(0)`)
if platform.CompilerSupported() {
// Note: as of Go 1.19, only the Sec field is set on update in fs_js.go.

View File

@@ -46,7 +46,7 @@ func Test_http(t *testing.T) {
return moduleConfig.WithEnv("BASE_URL", "http://host"), config
})
require.EqualError(t, err, `module "" closed with exit_code(0)`)
require.EqualError(t, err, `module closed with exit_code(0)`)
require.Zero(t, stderr)
require.Equal(t, `Get "http://host/error": net/http: fetch() failed: error
1

View File

@@ -23,7 +23,7 @@ func Test_exit(t *testing.T) {
stdout, stderr, err := compileAndRun(loggingCtx, "exit", defaultConfig)
require.EqualError(t, err, `module "" closed with exit_code(255)`)
require.EqualError(t, err, `module closed with exit_code(255)`)
require.Zero(t, stderr)
require.Zero(t, stdout)
require.Equal(t, `==> go.runtime.wasmExit(code=255)
@@ -36,7 +36,7 @@ func Test_goroutine(t *testing.T) {
stdout, stderr, err := compileAndRun(testCtx, "goroutine", defaultConfig)
require.EqualError(t, err, `module "" closed with exit_code(0)`)
require.EqualError(t, err, `module closed with exit_code(0)`)
require.Zero(t, stderr)
require.Equal(t, `producer
consumer
@@ -52,7 +52,7 @@ func Test_mem(t *testing.T) {
stdout, stderr, err := compileAndRun(loggingCtx, "mem", defaultConfig)
require.EqualError(t, err, `module "" closed with exit_code(0)`)
require.EqualError(t, err, `module closed with exit_code(0)`)
require.Zero(t, stderr)
require.Zero(t, stdout)
@@ -71,7 +71,7 @@ func Test_stdio(t *testing.T) {
})
require.Equal(t, "stderr 6\n", stderr)
require.EqualError(t, err, `module "" closed with exit_code(0)`)
require.EqualError(t, err, `module closed with exit_code(0)`)
require.Equal(t, "stdout 6\n", stdout)
}
@@ -89,7 +89,7 @@ func Test_stdio_large(t *testing.T) {
return defaultConfig(moduleConfig.WithStdin(bytes.NewReader(input)))
})
require.EqualError(t, err, `module "" closed with exit_code(0)`)
require.EqualError(t, err, `module closed with exit_code(0)`)
require.Equal(t, fmt.Sprintf("stderr %d\n", size), stderr)
require.Equal(t, fmt.Sprintf("stdout %d\n", size), stdout)
@@ -107,7 +107,7 @@ func Test_gc(t *testing.T) {
stdout, stderr, err := compileAndRun(testCtx, "gc", defaultConfig)
require.EqualError(t, err, `module "" closed with exit_code(0)`)
require.EqualError(t, err, `module closed with exit_code(0)`)
require.Equal(t, "", stderr)
require.Equal(t, "before gc\nafter gc\n", stdout)
}

View File

@@ -18,7 +18,7 @@ func Test_process(t *testing.T) {
})
require.Zero(t, stderr)
require.EqualError(t, err, `module "" closed with exit_code(0)`)
require.EqualError(t, err, `module closed with exit_code(0)`)
require.Equal(t, `syscall.Getpid()=1
syscall.Getppid()=0
syscall.Getuid()=0

View File

@@ -19,7 +19,7 @@ func Test_time(t *testing.T) {
stdout, stderr, err := compileAndRun(loggingCtx, "time", defaultConfig)
require.EqualError(t, err, `module "" closed with exit_code(0)`)
require.EqualError(t, err, `module closed with exit_code(0)`)
require.Zero(t, stderr)
require.Equal(t, `Local
1ms

View File

@@ -3,7 +3,6 @@ package adhoc
import (
"context"
_ "embed"
"fmt"
"math"
"strconv"
"testing"
@@ -115,7 +114,7 @@ func testEnsureTerminationOnClose(t *testing.T, r wazero.Runtime) {
}()
_, err = infinite.Call(ctx)
require.Error(t, err)
require.Contains(t, err.Error(), fmt.Sprintf("module \"%s\" closed with context canceled", t.Name()))
require.Contains(t, err.Error(), "module closed with context canceled")
})
t.Run("context cancel in advance", func(t *testing.T) {
@@ -124,7 +123,7 @@ func testEnsureTerminationOnClose(t *testing.T, r wazero.Runtime) {
cancel()
_, err = infinite.Call(ctx)
require.Error(t, err)
require.Contains(t, err.Error(), fmt.Sprintf("module \"%s\" closed with context canceled", t.Name()))
require.Contains(t, err.Error(), "module closed with context canceled")
})
t.Run("context timeout", func(t *testing.T) {
@@ -133,7 +132,7 @@ func testEnsureTerminationOnClose(t *testing.T, r wazero.Runtime) {
defer cancel()
_, err = infinite.Call(ctx)
require.Error(t, err)
require.Contains(t, err.Error(), fmt.Sprintf("module \"%s\" closed with context deadline exceeded", t.Name()))
require.Contains(t, err.Error(), "module closed with context deadline exceeded")
})
t.Run("explicit close of module", func(t *testing.T) {
@@ -144,7 +143,7 @@ func testEnsureTerminationOnClose(t *testing.T, r wazero.Runtime) {
}()
_, err = infinite.Call(context.Background())
require.Error(t, err)
require.Contains(t, err.Error(), fmt.Sprintf("module \"%s\" closed with exit_code(2)", t.Name()))
require.Contains(t, err.Error(), "module closed with exit_code(2)")
})
}
@@ -688,11 +687,11 @@ func testCloseInFlight(t *testing.T, r wazero.Runtime) {
var expectedErr error
if tc.closeImported != 0 && tc.closeImporting != 0 {
// When both modules are closed, importing is the better one to choose in the error message.
expectedErr = sys.NewExitError(importing.Name(), tc.closeImporting)
expectedErr = sys.NewExitError(tc.closeImporting)
} else if tc.closeImported != 0 {
expectedErr = sys.NewExitError(imported.Name(), tc.closeImported)
expectedErr = sys.NewExitError(tc.closeImported)
} else if tc.closeImporting != 0 {
expectedErr = sys.NewExitError(importing.Name(), tc.closeImporting)
expectedErr = sys.NewExitError(tc.closeImporting)
} else {
t.Fatal("invalid test case")
}

View File

@@ -99,7 +99,7 @@ func closeModuleWhileInUse(t *testing.T, r wazero.Runtime, closeFn func(imported
i := importing // pin the module used inside goroutines
hammer.NewHammer(t, P, 1).Run(func(name string) {
// In all cases, the importing module is closed, so the error should have that as its module name.
requireFunctionCallExits(t, i.Name(), i.ExportedFunction("call_return_input"))
requireFunctionCallExits(t, i.ExportedFunction("call_return_input"))
}, func() { // When all functions are in-flight, re-assign the modules.
imported, importing = closeFn(imported, importing)
// Unblock all the calls
@@ -122,7 +122,7 @@ func requireFunctionCall(t *testing.T, fn api.Function) {
require.Equal(t, uint64(3), res[0])
}
func requireFunctionCallExits(t *testing.T, moduleName string, fn api.Function) {
func requireFunctionCallExits(t *testing.T, fn api.Function) {
_, err := fn.Call(testCtx, 3)
require.Equal(t, sys.NewExitError(moduleName, 0), err)
require.Equal(t, sys.NewExitError(0), err)
}

View File

@@ -42,6 +42,10 @@ func Contains(t TestingT, s, substr string, formatWithArgs ...interface{}) {
//
// - formatWithArgs are optional. When the first is a string that contains '%', it is treated like fmt.Sprintf.
func Equal(t TestingT, expected, actual interface{}, formatWithArgs ...interface{}) {
if expected == nil {
Nil(t, actual)
return
}
if equal(expected, actual) {
return
}

View File

@@ -61,7 +61,7 @@ type CallContext struct {
// FailIfClosed returns a sys.ExitError if CloseWithExitCode was called.
func (m *CallContext) FailIfClosed() (err error) {
if closed := atomic.LoadUint64(&m.Closed); closed != 0 {
return sys.NewExitError(m.module.Name, uint32(closed>>32)) // Unpack the high order bits as the exit code.
return sys.NewExitError(uint32(closed >> 32)) // Unpack the high order bits as the exit code.
}
return nil
}

View File

@@ -271,7 +271,7 @@ func TestCallContext_CloseModuleOnCanceledOrTimeout(t *testing.T) {
defer done()
err := cc.FailIfClosed()
require.EqualError(t, err, "module \"test\" closed with context deadline exceeded")
require.EqualError(t, err, "module closed with context deadline exceeded")
})
t.Run("cancel", func(t *testing.T) {
@@ -286,7 +286,7 @@ func TestCallContext_CloseModuleOnCanceledOrTimeout(t *testing.T) {
time.Sleep(time.Second)
err := cc.FailIfClosed()
require.EqualError(t, err, "module \"test\" closed with context canceled")
require.EqualError(t, err, "module closed with context canceled")
})
t.Run("timeout over cancel", func(t *testing.T) {
@@ -316,7 +316,7 @@ func TestCallContext_CloseModuleOnCanceledOrTimeout(t *testing.T) {
time.Sleep(time.Second)
err := cc.FailIfClosed()
require.EqualError(t, err, "module \"test\" closed with context canceled")
require.EqualError(t, err, "module closed with context canceled")
})
t.Run("cancel works", func(t *testing.T) {
@@ -359,7 +359,7 @@ func TestCallContext_CloseWithCtxErr(t *testing.T) {
cc.CloseWithCtxErr(ctx)
err := cc.FailIfClosed()
require.EqualError(t, err, "module \"test\" closed with context canceled")
require.EqualError(t, err, "module closed with context canceled")
})
t.Run("context timeout", func(t *testing.T) {
@@ -373,7 +373,7 @@ func TestCallContext_CloseWithCtxErr(t *testing.T) {
cc.CloseWithCtxErr(ctx)
err := cc.FailIfClosed()
require.EqualError(t, err, "module \"test\" closed with context deadline exceeded")
require.EqualError(t, err, "module closed with context deadline exceeded")
})
t.Run("no error", func(t *testing.T) {

View File

@@ -34,11 +34,14 @@ type Runtime interface {
//
// mod, _ := r.Instantiate(ctx, wasm)
//
// See InstantiateWithConfig for configuration overrides.
// # Notes
//
// - See notes on InstantiateModule for error scenarios.
// - See InstantiateWithConfig for configuration overrides.
Instantiate(ctx context.Context, source []byte) (api.Module, error)
// InstantiateWithConfig instantiates a module from the WebAssembly binary
// (%.wasm) or errs if invalid.
// (%.wasm) or errs for reasons including exit or validation.
//
// Here's an example:
// ctx := context.Background()
@@ -50,11 +53,12 @@ type Runtime interface {
//
// # Notes
//
// - See notes on InstantiateModule for error scenarios.
// - If you aren't overriding defaults, use Instantiate.
// - This is a convenience utility that chains CompileModule with
// InstantiateModule. To instantiate the same source multiple times,
// use CompileModule as InstantiateModule avoids redundant decoding
// and/or compilation.
// - If you aren't overriding defaults, use Instantiate.
InstantiateWithConfig(ctx context.Context, source []byte, config ModuleConfig) (api.Module, error)
// NewHostModuleBuilder lets you create modules out of functions defined in Go.
@@ -87,15 +91,25 @@ type Runtime interface {
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#name-section%E2%91%A0
CompileModule(ctx context.Context, binary []byte) (CompiledModule, error)
// InstantiateModule instantiates the module or errs if the configuration was invalid.
// InstantiateModule instantiates the module or errs for reasons including
// exit or validation.
//
// Here's an example:
// mod, _ := n.InstantiateModule(ctx, compiled, wazero.NewModuleConfig().WithName("prod"))
// mod, _ := n.InstantiateModule(ctx, compiled, wazero.NewModuleConfig().
// WithName("prod"))
//
// While CompiledModule is pre-validated, there are a few situations which can cause an error:
// # Errors
//
// While CompiledModule is pre-validated, there are a few situations which
// can cause an error:
// - The module name is already in use.
// - The module has a table element initializer that resolves to an index outside the Table minimum size.
// - The module has a table element initializer that resolves to an index
// outside the Table minimum size.
// - The module has a start function, and it failed to execute.
// - The module was compiled to WASI and exited with a non-zero exit
// code, you'll receive a sys.ExitError.
// - RuntimeConfig.WithCloseOnContextDone was enabled and a context
// cancellation or deadline triggered before a start function returned.
InstantiateModule(ctx context.Context, compiled CompiledModule, config ModuleConfig) (api.Module, error)
// CloseWithExitCode closes all the modules that have been initialized in this Runtime with the provided exit code.
@@ -294,6 +308,11 @@ func (r *runtime) InstantiateModule(
if code.closeWithModule {
_ = code.Close(ctx) // don't overwrite the error
}
if se, ok := err.(*sys.ExitError); ok {
if se.ExitCode() == 0 { // Don't err on success.
err = nil
}
}
return
}
@@ -310,7 +329,11 @@ func (r *runtime) InstantiateModule(
}
if _, err = start.Call(ctx); err != nil {
_ = mod.Close(ctx) // Don't leak the module on error.
if _, ok := err.(*sys.ExitError); ok {
if se, ok := err.(*sys.ExitError); ok {
if se.ExitCode() == 0 { // Don't err on success.
err = nil
}
return // Don't wrap an exit error
}
err = fmt.Errorf("module[%s] function[%s] failed: %w", name, fn, err)

View File

@@ -471,32 +471,73 @@ func TestRuntime_InstantiateModule_ExitError(t *testing.T) {
r := NewRuntime(testCtx)
defer r.Close(testCtx)
start := func(ctx context.Context, m api.Module) {
require.NoError(t, m.CloseWithExitCode(ctx, 2))
tests := []struct {
name string
exitCode uint32
export bool
expectedErr error
}{
{
name: "start: exit code 0",
exitCode: 0,
},
{
name: "start: exit code 2",
exitCode: 2,
expectedErr: sys.NewExitError(2),
},
{
name: "_start: exit code 0",
exitCode: 0,
export: true,
},
{
name: "_start: exit code 2",
exitCode: 2,
export: true,
expectedErr: sys.NewExitError(2),
},
}
_, err := r.NewHostModuleBuilder("env").
for _, tt := range tests {
tc := tt
t.Run(tc.name, func(t *testing.T) {
start := func(ctx context.Context, m api.Module) {
require.NoError(t, m.CloseWithExitCode(ctx, tc.exitCode))
}
env, err := r.NewHostModuleBuilder("env").
NewFunctionBuilder().WithFunc(start).Export("exit").
Instantiate(testCtx)
require.NoError(t, err)
defer env.Close(testCtx)
one := uint32(1)
binary := binaryencoding.EncodeModule(&wasm.Module{
mod := &wasm.Module{
TypeSection: []wasm.FunctionType{{}},
ImportSection: []wasm.Import{{Module: "env", Name: "exit", Type: wasm.ExternTypeFunc, DescFunc: 0}},
FunctionSection: []wasm.Index{0},
CodeSection: []wasm.Code{
{Body: []byte{wasm.OpcodeCall, 0, wasm.OpcodeEnd}}, // Call the imported env.start.
},
StartSection: &one,
})
}
if tc.export {
mod.ExportSection = []wasm.Export{
{Name: "_start", Type: wasm.ExternTypeFunc, Index: 1},
}
} else {
one := uint32(1)
mod.StartSection = &one
}
binary := binaryencoding.EncodeModule(mod)
// Instantiate the module, which calls the start function.
_, err = r.InstantiateWithConfig(testCtx, binary,
NewModuleConfig().WithName("call-exit"))
// Ensure the exit error propagated and didn't wrap.
require.Equal(t, err, sys.NewExitError("call-exit", 2))
require.Equal(t, tc.expectedErr, err)
})
}
}
func TestRuntime_CloseWithExitCode(t *testing.T) {
@@ -559,10 +600,10 @@ func TestRuntime_CloseWithExitCode(t *testing.T) {
// Modules closed so calls fail
_, err = func1.Call(testCtx)
require.ErrorIs(t, err, sys.NewExitError("mod1", tc.exitCode))
require.ErrorIs(t, err, sys.NewExitError(tc.exitCode))
_, err = func2.Call(testCtx)
require.ErrorIs(t, err, sys.NewExitError("mod2", tc.exitCode))
require.ErrorIs(t, err, sys.NewExitError(tc.exitCode))
})
}
}

View File

@@ -25,7 +25,7 @@ const (
// main := module.ExportedFunction("main")
// if err := main(ctx); err != nil {
// if exitErr, ok := err.(*sys.ExitError); ok {
// // If your main function expects to exit, this could be ok if Code == 0
// // This means your module exited with non-zero code!
// }
// --snip--
//
@@ -37,17 +37,18 @@ const (
//
// Note: In the case of context cancellation or timeout, the api.Module from which the api.Function created is closed.
type ExitError struct {
moduleName string
// Note: this is a struct not a uint32 type as it was originally one and
// we don't want to break call-sites that cast into it.
exitCode uint32
}
func NewExitError(moduleName string, exitCode uint32) *ExitError {
return &ExitError{moduleName: moduleName, exitCode: exitCode}
}
var exitZero = &ExitError{}
// ModuleName is the api.Module that was closed.
func (e *ExitError) ModuleName() string {
return e.moduleName
func NewExitError(exitCode uint32) *ExitError {
if exitCode == 0 {
return exitZero
}
return &ExitError{exitCode: exitCode}
}
// ExitCode returns zero on success, and an arbitrary value otherwise.
@@ -59,18 +60,18 @@ func (e *ExitError) ExitCode() uint32 {
func (e *ExitError) Error() string {
switch e.exitCode {
case ExitCodeContextCanceled:
return fmt.Sprintf("module %q closed with %s", e.moduleName, context.Canceled)
return fmt.Sprintf("module closed with %s", context.Canceled)
case ExitCodeDeadlineExceeded:
return fmt.Sprintf("module %q closed with %s", e.moduleName, context.DeadlineExceeded)
return fmt.Sprintf("module closed with %s", context.DeadlineExceeded)
default:
return fmt.Sprintf("module %q closed with exit_code(%d)", e.moduleName, e.exitCode)
return fmt.Sprintf("module closed with exit_code(%d)", e.exitCode)
}
}
// Is allows use via errors.Is
func (e *ExitError) Is(err error) bool {
if target, ok := err.(*ExitError); ok {
return e.moduleName == target.moduleName && e.exitCode == target.exitCode
return e.exitCode == target.exitCode
}
return false
}

View File

@@ -8,7 +8,6 @@ import (
)
type notExitError struct {
moduleName string
exitCode uint32
}
@@ -17,7 +16,7 @@ func (e *notExitError) Error() string {
}
func TestIs(t *testing.T) {
err := NewExitError("some module", 2)
err := NewExitError(2)
tests := []struct {
name string
target error
@@ -28,25 +27,14 @@ func TestIs(t *testing.T) {
target: err,
matches: true,
},
{
name: "same content",
target: NewExitError("some module", 2),
matches: true,
},
{
name: "different module name",
target: NewExitError("not some module", 2),
matches: false,
},
{
name: "different exit code",
target: NewExitError("some module", 0),
target: NewExitError(1),
matches: false,
},
{
name: "different type",
target: &notExitError{
moduleName: "some module",
exitCode: 2,
},
matches: false,
@@ -64,18 +52,18 @@ func TestIs(t *testing.T) {
func TestExitError_Error(t *testing.T) {
t.Run("timeout", func(t *testing.T) {
err := NewExitError("foo", ExitCodeDeadlineExceeded)
err := NewExitError(ExitCodeDeadlineExceeded)
require.Equal(t, ExitCodeDeadlineExceeded, err.ExitCode())
require.EqualError(t, err, "module \"foo\" closed with context deadline exceeded")
require.EqualError(t, err, "module closed with context deadline exceeded")
})
t.Run("cancel", func(t *testing.T) {
err := NewExitError("foo", ExitCodeContextCanceled)
err := NewExitError(ExitCodeContextCanceled)
require.Equal(t, ExitCodeContextCanceled, err.ExitCode())
require.EqualError(t, err, "module \"foo\" closed with context canceled")
require.EqualError(t, err, "module closed with context canceled")
})
t.Run("normal", func(t *testing.T) {
err := NewExitError("foo", 123)
err := NewExitError(123)
require.Equal(t, uint32(123), err.ExitCode())
require.EqualError(t, err, "module \"foo\" closed with exit_code(123)")
require.EqualError(t, err, "module closed with exit_code(123)")
})
}