Repository: tetratelabs/wazero
Pull request #1109 (2023-02-08 16:01:47)

wasi: backfills errno conversion tests
This also re-organizes the error conversion logic so it is easier to compare what's covered.


Patch:
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):
+	case errors.Is(err, fs.ErrInvalid):
+		return ErrnoInval
+	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 errors.Is(err, syscall.EINVAL), errors.Is(err, fs.ErrInvalid):
+	case syscall.EEXIST:
+		return ErrnoExist
+	case syscall.EINVAL:
 		return ErrnoInval
-	case errors.Is(err, syscall.EISDIR):
+	case syscall.EIO:
+		return ErrnoIo
+	case 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):
+	case syscall.ELOOP:
+		return ErrnoLoop
+	case syscall.ENAMETOOLONG:
+		return ErrnoNametoolong
+	case syscall.ENOENT:
 		return ErrnoNoent
-	case errors.Is(err, syscall.ENOSYS):
+	case syscall.ENOSYS:
 		return ErrnoNosys
-	case errors.Is(err, syscall.ENOTSUP):
-		return ErrnoNotsup
-	case errors.Is(err, syscall.ENOTDIR):
+	case syscall.ENOTDIR:
 		return ErrnoNotdir
-	case errors.Is(err, syscall.EPERM), errors.Is(err, fs.ErrPermission):
+	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
 	}


Test patch:
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)
+		})
+	}
+}