Files
petrbalvin a9b8039ef7
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
feat: full NFSv4.2 server and client in pure Go
Assisted-by: GLM 5.3 Flash
2026-09-21 18:51:17 +02:00

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")
}