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
74 lines
1.9 KiB
Go
74 lines
1.9 KiB
Go
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package nfsfs
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
)
|
|
|
|
// openFDs counts the descriptors the process holds through /proc. The
|
|
// count is a lower bound of live descriptors and good enough to prove a
|
|
// cache does not leak: a thousand operations over one file must not add a
|
|
// thousand descriptors.
|
|
func openFDs(t *testing.T) int {
|
|
t.Helper()
|
|
entries, err := os.ReadDir("/proc/self/fd")
|
|
if err != nil {
|
|
t.Skipf("/proc/self/fd is unavailable: %v", err)
|
|
}
|
|
return len(entries)
|
|
}
|
|
|
|
// TestCacheNoDescriptorLeak drives more operations over one file than the
|
|
// cache can hold and requires the process descriptor count to stay flat.
|
|
func TestCacheNoDescriptorLeak(t *testing.T) {
|
|
l, h, _ := cacheTestRoot(t, "leak")
|
|
before := openFDs(t)
|
|
for range 1000 {
|
|
if _, err := l.Read(h, 0, 4); err != nil {
|
|
t.Fatalf("Read: %v", err)
|
|
}
|
|
}
|
|
after := openFDs(t)
|
|
if after-before > 8 {
|
|
t.Fatalf("descriptors grew from %d to %d over 1000 reads", before, after)
|
|
}
|
|
}
|
|
|
|
// TestCacheBound holds when many distinct files flow through: after
|
|
// touching twice the bound, at most the bound of cache entries may remain.
|
|
func TestCacheBound(t *testing.T) {
|
|
root := t.TempDir()
|
|
l, err := NewLocal(root)
|
|
if err != nil {
|
|
t.Fatalf("NewLocal: %v", err)
|
|
}
|
|
rh, err := l.Root()
|
|
if err != nil {
|
|
t.Fatalf("Root: %v", err)
|
|
}
|
|
for i := range 2 * fdCacheLimit {
|
|
name := fmt.Sprintf("f%d", i)
|
|
if err := os.WriteFile(filepath.Join(root, name), []byte("x"), 0o644); err != nil {
|
|
t.Fatalf("WriteFile: %v", err)
|
|
}
|
|
h, _, err := l.Lookup(rh, name)
|
|
if err != nil {
|
|
t.Fatalf("Lookup: %v", err)
|
|
}
|
|
if _, err := l.Read(h, 0, 1); err != nil {
|
|
t.Fatalf("Read %s: %v", name, err)
|
|
}
|
|
}
|
|
l.fdMu.Lock()
|
|
n := len(l.fds)
|
|
l.fdMu.Unlock()
|
|
if n > fdCacheLimit {
|
|
t.Fatalf("cache holds %d entries, bound is %d", n, fdCacheLimit)
|
|
}
|
|
}
|