From 7badba4ea83178ba9e035a5fc09a537e62795dea Mon Sep 17 00:00:00 2001 From: Crypt Keeper <64215+codefromthecrypt@users.noreply.github.com> Date: Wed, 8 Feb 2023 13:55:12 -0500 Subject: [PATCH] wasi: backfills errno conversion tests (#1109) Signed-off-by: Adrian Cole --- internal/wasi_snapshot_preview1/errno.go | 74 +++++++--- internal/wasi_snapshot_preview1/errno_test.go | 139 ++++++++++++++++++ 2 files changed, 191 insertions(+), 22 deletions(-) create mode 100644 internal/wasi_snapshot_preview1/errno_test.go diff --git a/internal/wasi_snapshot_preview1/errno.go b/internal/wasi_snapshot_preview1/errno.go index 6a3b83c1..3855c86a 100644 --- a/internal/wasi_snapshot_preview1/errno.go +++ b/internal/wasi_snapshot_preview1/errno.go @@ -4,6 +4,7 @@ import ( "errors" "fmt" "io/fs" + "os" "syscall" ) @@ -266,31 +267,60 @@ var errnoToString = [...]string{ // error codes. For example, wasi-filesystem and GOOS=js don't map to these // Errno. func ToErrno(err error) Errno { + if pe, ok := err.(*os.PathError); ok { + err = pe.Unwrap() + } + if se, ok := err.(syscall.Errno); ok { + return errnoFromSyscall(se) + } + // Below are all the fs.ErrXXX in fs.go. errors.Is is more expensive, so + // try it last. Note: Once we have our own file type, we should never see + // these. switch { - case errors.Is(err, syscall.ENOTDIR): - return ErrnoNotdir - case errors.Is(err, syscall.EBADF), errors.Is(err, fs.ErrClosed): - return ErrnoBadf - case errors.Is(err, syscall.EINVAL), errors.Is(err, fs.ErrInvalid): + case errors.Is(err, fs.ErrInvalid): return ErrnoInval - case errors.Is(err, syscall.EISDIR): - return ErrnoIsdir - case errors.Is(err, syscall.ENOTEMPTY): - return ErrnoNotempty - case errors.Is(err, syscall.EEXIST), errors.Is(err, fs.ErrExist): - return ErrnoExist - case errors.Is(err, syscall.ENOENT), errors.Is(err, fs.ErrNotExist): - return ErrnoNoent - case errors.Is(err, syscall.ENOSYS): - return ErrnoNosys - case errors.Is(err, syscall.ENOTSUP): - return ErrnoNotsup - case errors.Is(err, syscall.ENOTDIR): - return ErrnoNotdir - case errors.Is(err, syscall.EPERM), errors.Is(err, fs.ErrPermission): + case errors.Is(err, fs.ErrPermission): + return ErrnoPerm + case errors.Is(err, fs.ErrExist): + return ErrnoExist + case errors.Is(err, fs.ErrNotExist): + return ErrnoNoent + case errors.Is(err, fs.ErrClosed): + return ErrnoBadf + default: + return ErrnoIo + } +} + +func errnoFromSyscall(errno syscall.Errno) Errno { + // The below Errno have references in existing WASI code. + switch errno { + case syscall.EBADF: + return ErrnoBadf + case syscall.EEXIST: + return ErrnoExist + case syscall.EINVAL: + return ErrnoInval + case syscall.EIO: + return ErrnoIo + case syscall.EISDIR: + return ErrnoIsdir + case syscall.ELOOP: + return ErrnoLoop + case syscall.ENAMETOOLONG: + return ErrnoNametoolong + case syscall.ENOENT: + return ErrnoNoent + case syscall.ENOSYS: + return ErrnoNosys + case syscall.ENOTDIR: + return ErrnoNotdir + case syscall.ENOTEMPTY: + return ErrnoNotempty + case syscall.ENOTSUP: + return ErrnoNotsup + case syscall.EPERM: return ErrnoPerm - case errors.Is(err, syscall.ELOOP): - return ErrnoLoop default: return ErrnoIo } diff --git a/internal/wasi_snapshot_preview1/errno_test.go b/internal/wasi_snapshot_preview1/errno_test.go new file mode 100644 index 00000000..88077ea2 --- /dev/null +++ b/internal/wasi_snapshot_preview1/errno_test.go @@ -0,0 +1,139 @@ +package wasi_snapshot_preview1 + +import ( + "errors" + "fmt" + "io/fs" + "os" + "syscall" + "testing" + + "github.com/tetratelabs/wazero/internal/testing/require" +) + +func TestToErrno(t *testing.T) { + tests := []struct { + name string + input error + expected Errno + }{ + { + name: "syscall.EBADF", + input: syscall.EBADF, + expected: ErrnoBadf, + }, + { + name: "syscall.EEXIST", + input: syscall.EEXIST, + expected: ErrnoExist, + }, + { + name: "syscall.EINVAL", + input: syscall.EINVAL, + expected: ErrnoInval, + }, + { + name: "syscall.EIO", + input: syscall.EIO, + expected: ErrnoIo, + }, + { + name: "syscall.EISDIR", + input: syscall.EISDIR, + expected: ErrnoIsdir, + }, + { + name: "syscall.ELOOP", + input: syscall.ELOOP, + expected: ErrnoLoop, + }, + { + name: "syscall.ENAMETOOLONG", + input: syscall.ENAMETOOLONG, + expected: ErrnoNametoolong, + }, + { + name: "syscall.ENOENT", + input: syscall.ENOENT, + expected: ErrnoNoent, + }, + { + name: "syscall.ENOSYS", + input: syscall.ENOSYS, + expected: ErrnoNosys, + }, + { + name: "syscall.ENOTDIR", + input: syscall.ENOTDIR, + expected: ErrnoNotdir, + }, + { + name: "syscall.ENOTEMPTY", + input: syscall.ENOTEMPTY, + expected: ErrnoNotempty, + }, + { + name: "syscall.ENOTSUP", + input: syscall.ENOTSUP, + expected: ErrnoNotsup, + }, + { + name: "syscall.EPERM", + input: syscall.EPERM, + expected: ErrnoPerm, + }, + { + name: "syscall.Errno unexpected == ErrnoIo", + input: syscall.Errno(0xfe), + expected: ErrnoIo, + }, + { + name: "PathError ErrInvalid", + input: &os.PathError{Err: fs.ErrInvalid}, + expected: ErrnoInval, + }, + { + name: "PathError ErrPermission", + input: &os.PathError{Err: fs.ErrPermission}, + expected: ErrnoPerm, + }, + { + name: "PathError ErrExist", + input: &os.PathError{Err: fs.ErrExist}, + expected: ErrnoExist, + }, + { + name: "PathError ErrNotExist", + input: &os.PathError{Err: fs.ErrNotExist}, + expected: ErrnoNoent, + }, + { + name: "PathError ErrClosed", + input: &os.PathError{Err: fs.ErrClosed}, + expected: ErrnoBadf, + }, + { + name: "PathError unknown == ErrnoIo", + input: &os.PathError{Err: errors.New("ice cream")}, + expected: ErrnoIo, + }, + { + name: "unknown == ErrnoIo", + input: errors.New("ice cream"), + expected: ErrnoIo, + }, + { + name: "very wrapped unknown == ErrnoIo", + input: fmt.Errorf("%w", fmt.Errorf("%w", fmt.Errorf("%w", errors.New("ice cream")))), + expected: ErrnoIo, + }, + } + + for _, tt := range tests { + tc := tt + t.Run(tc.name, func(t *testing.T) { + errno := ToErrno(tc.input) + require.Equal(t, tc.expected, errno) + }) + } +}