Test / test (push) Successful in 2m4s
Release / gates (push) Successful in 2m5s
Release / build (amd64, freebsd) (push) Successful in 1m27s
Release / build (amd64, linux) (push) Successful in 1m22s
Release / build (amd64, netbsd) (push) Successful in 1m19s
Release / build (amd64, openbsd) (push) Successful in 1m20s
Release / build (arm64, darwin) (push) Successful in 1m21s
Release / build (arm64, freebsd) (push) Successful in 1m26s
Release / build (arm64, linux) (push) Successful in 1m25s
Release / build (arm64, netbsd) (push) Successful in 1m31s
Release / build (arm64, openbsd) (push) Successful in 1m27s
Release / build (loong64, linux) (push) Successful in 1m37s
Release / build (riscv64, linux) (push) Successful in 1m21s
Release / release (push) Successful in 40s
Assisted-by: GLM 5.3 Flash
80 lines
2.5 KiB
Go
80 lines
2.5 KiB
Go
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package nfsfs
|
|
|
|
import (
|
|
"errors"
|
|
"os"
|
|
"path/filepath"
|
|
"syscall"
|
|
"testing"
|
|
)
|
|
|
|
// The create and write error wrappers map the errno families the
|
|
// protocol knows: a missing parent is NoEnt, a permission problem is
|
|
// Permission, a collision is Exist, and the rest stay raw.
|
|
func TestWrapErrFamilies(t *testing.T) {
|
|
// The wrapper level: each mapped errno.
|
|
cases := []struct {
|
|
err error
|
|
want error
|
|
}{
|
|
{nil, nil},
|
|
{&os.PathError{Op: "open", Err: syscall.EACCES}, ErrPermission},
|
|
{&os.PathError{Op: "open", Err: syscall.EPERM}, ErrPermission},
|
|
{&os.PathError{Op: "open", Err: syscall.EEXIST}, ErrExist},
|
|
{&os.PathError{Op: "open", Err: syscall.EISDIR}, ErrIsDir},
|
|
{&os.PathError{Op: "open", Err: syscall.ENOTDIR}, ErrNotDir},
|
|
{&os.PathError{Op: "write", Err: syscall.ENOSPC}, ErrNoSpace},
|
|
{&os.PathError{Op: "write", Err: syscall.EIO}, ErrIO},
|
|
}
|
|
for _, c := range cases {
|
|
if got := wrapCreateErr(c.err); !errors.Is(got, c.want) {
|
|
t.Fatalf("create wrap of %v: %v, want %v", c.err, got, c.want)
|
|
}
|
|
}
|
|
// The write wrapper answers a vanished path with stale, not noent.
|
|
if got := wrapWriteErr(&os.PathError{Op: "open", Err: syscall.ENOENT}); !errors.Is(got, ErrStale) {
|
|
t.Fatalf("write wrap of a vanished path: %v, want stale", got)
|
|
}
|
|
// An errno outside the families lands in the io error family with
|
|
// its cause kept in the text.
|
|
if got := wrapCreateErr(&os.PathError{Op: "open", Err: syscall.EDQUOT}); !errors.Is(got, ErrIO) {
|
|
t.Fatalf("unmapped errno: %v, want the io error family", got)
|
|
}
|
|
}
|
|
|
|
// A hard link to a missing target and one under a file both answer
|
|
// their own sentinels.
|
|
func TestLinkErrors(t *testing.T) {
|
|
root := t.TempDir()
|
|
l, err := NewLocal(root)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
rh, err := l.Root()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, _, err := l.Link(nfsfsMissingHandle(), rh, "x"); err == nil {
|
|
t.Fatal("a link to a missing target succeeded")
|
|
}
|
|
target := filepath.Join(root, "t.txt")
|
|
if err := os.WriteFile(target, []byte("t"), 0o644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
th, _, err := l.Lookup(rh, "t.txt")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, _, err := l.Link(th, nfsfsMissingHandle(), "x"); err == nil {
|
|
t.Fatal("a link under a missing directory succeeded")
|
|
}
|
|
}
|
|
|
|
// nfsfsMissingHandle builds a handle that resolves to nothing.
|
|
func nfsfsMissingHandle() Handle {
|
|
return Handle("nfs\x02\x00\x00\x00stale-handle-bytes")
|
|
}
|