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