All checks were successful
Test / Create distribution (push) Successful in 47s
Test / Sandbox (push) Successful in 2m48s
Test / ShareFS (push) Successful in 4m51s
Test / Sandbox (race detector) (push) Successful in 5m11s
Test / Hpkg (push) Successful in 5m24s
Test / Hakurei (push) Successful in 5m43s
Test / Hakurei (race detector) (push) Successful in 7m36s
Test / Flake checks (push) Successful in 1m57s
The struct turned out not necessary during initial implementation but was not unwrapped into its single string field. This change replaces it with the underlying string and removes the indirection. Signed-off-by: Ophestra <cat@gensokyo.uk>
180 lines
4.5 KiB
Go
180 lines
4.5 KiB
Go
package container
|
|
|
|
import (
|
|
"errors"
|
|
"os"
|
|
"reflect"
|
|
"strconv"
|
|
"syscall"
|
|
"testing"
|
|
|
|
"hakurei.app/container/check"
|
|
"hakurei.app/container/stub"
|
|
"hakurei.app/container/vfs"
|
|
)
|
|
|
|
func TestMessageFromError(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
testCases := []struct {
|
|
name string
|
|
err error
|
|
want string
|
|
wantOk bool
|
|
}{
|
|
{"mount", &MountError{
|
|
Source: SourceTmpfsEphemeral,
|
|
Target: "/sysroot/tmp",
|
|
Fstype: FstypeTmpfs,
|
|
Flags: syscall.MS_NOSUID | syscall.MS_NODEV,
|
|
Data: zeroString,
|
|
Errno: syscall.EINVAL,
|
|
}, "cannot mount tmpfs on /sysroot/tmp: invalid argument", true},
|
|
|
|
{"path", &os.PathError{
|
|
Op: "mount",
|
|
Path: "/sysroot",
|
|
Err: stub.UniqueError(0xdeadbeef),
|
|
}, "cannot mount /sysroot: unique error 3735928559 injected by the test suite", true},
|
|
|
|
{"absolute", check.AbsoluteError("etc/mtab"),
|
|
`path "etc/mtab" is not absolute`, true},
|
|
|
|
{"repeat", OpRepeatError("autoetc"),
|
|
"autoetc is not repeatable", true},
|
|
|
|
{"state", OpStateError("overlay"),
|
|
"impossible overlay state reached", true},
|
|
|
|
{"vfs parse", &vfs.DecoderError{Op: "parse", Line: 0xdead, Err: &strconv.NumError{Func: "Atoi", Num: "meow", Err: strconv.ErrSyntax}},
|
|
`cannot parse mountinfo at line 57005: numeric field "meow" invalid syntax`, true},
|
|
|
|
{"tmpfs", TmpfsSizeError(-1),
|
|
"tmpfs size -1 out of bounds", true},
|
|
|
|
{"unsupported", stub.UniqueError(0xdeadbeef), zeroString, false},
|
|
}
|
|
for _, tc := range testCases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
got, ok := messageFromError(tc.err)
|
|
if got != tc.want {
|
|
t.Errorf("messageFromError: %q, want %q", got, tc.want)
|
|
}
|
|
if ok != tc.wantOk {
|
|
t.Errorf("messageFromError: ok = %v, want %v", ok, tc.wantOk)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestMountError(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
testCases := []struct {
|
|
name string
|
|
err error
|
|
errno syscall.Errno
|
|
want string
|
|
}{
|
|
{"bind", &MountError{
|
|
Source: "/host/nix/store",
|
|
Target: "/sysroot/nix/store",
|
|
Fstype: FstypeNULL,
|
|
Flags: syscall.MS_SILENT | syscall.MS_BIND | syscall.MS_REC,
|
|
Data: zeroString,
|
|
Errno: syscall.ENOSYS,
|
|
}, syscall.ENOSYS,
|
|
"bind /host/nix/store on /sysroot/nix/store: function not implemented"},
|
|
|
|
{"remount", &MountError{
|
|
Source: SourceNone,
|
|
Target: "/sysroot/nix/store",
|
|
Fstype: FstypeNULL,
|
|
Flags: syscall.MS_SILENT | syscall.MS_BIND | syscall.MS_REMOUNT,
|
|
Data: zeroString,
|
|
Errno: syscall.EPERM,
|
|
}, syscall.EPERM,
|
|
"remount /sysroot/nix/store: operation not permitted"},
|
|
|
|
{"overlay", &MountError{
|
|
Source: SourceOverlay,
|
|
Target: sysrootPath,
|
|
Fstype: FstypeOverlay,
|
|
Data: `lowerdir=/host/var/lib/planterette/base/debian\:f92c9052`,
|
|
Errno: syscall.EINVAL,
|
|
}, syscall.EINVAL,
|
|
"mount overlay on /sysroot: invalid argument"},
|
|
|
|
{"fallback", &MountError{
|
|
Source: SourceNone,
|
|
Target: sysrootPath,
|
|
Fstype: FstypeNULL,
|
|
Errno: syscall.ENOTRECOVERABLE,
|
|
}, syscall.ENOTRECOVERABLE,
|
|
"mount /sysroot: state not recoverable"},
|
|
}
|
|
for _, tc := range testCases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
t.Run("is", func(t *testing.T) {
|
|
if !errors.Is(tc.err, tc.errno) {
|
|
t.Errorf("Is: %#v is not %v", tc.err, tc.errno)
|
|
}
|
|
})
|
|
t.Run("error", func(t *testing.T) {
|
|
if got := tc.err.Error(); got != tc.want {
|
|
t.Errorf("Error: %q, want %q", got, tc.want)
|
|
}
|
|
})
|
|
})
|
|
}
|
|
|
|
t.Run("zero", func(t *testing.T) {
|
|
t.Parallel()
|
|
if errors.Is(new(MountError), syscall.Errno(0)) {
|
|
t.Errorf("Is: zero MountError unexpected true")
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestErrnoFallback(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
testCases := []struct {
|
|
name string
|
|
err error
|
|
wantErrno syscall.Errno
|
|
wantPath *os.PathError
|
|
}{
|
|
{"mount", &MountError{
|
|
Errno: syscall.ENOTRECOVERABLE,
|
|
}, syscall.ENOTRECOVERABLE, nil},
|
|
|
|
{"path errno", &os.PathError{
|
|
Err: syscall.ETIMEDOUT,
|
|
}, syscall.ETIMEDOUT, nil},
|
|
|
|
{"fallback", stub.UniqueError(0xcafebabe), 0, &os.PathError{
|
|
Op: "fallback",
|
|
Path: "/proc/nonexistent",
|
|
Err: stub.UniqueError(0xcafebabe),
|
|
}},
|
|
}
|
|
for _, tc := range testCases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
errno, err := errnoFallback(tc.name, Nonexistent, tc.err)
|
|
if errno != tc.wantErrno {
|
|
t.Errorf("errnoFallback: errno = %v, want %v", errno, tc.wantErrno)
|
|
}
|
|
if !reflect.DeepEqual(err, tc.wantPath) {
|
|
t.Errorf("errnoFallback: pathError = %#v, want %#v", err, tc.wantPath)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// InternalMessageFromError exports messageFromError for other tests.
|
|
func InternalMessageFromError(err error) (string, bool) { return messageFromError(err) }
|