Files
nfs/internal/nfsfs/local_cache_linux_test.go
T

74 lines
1.9 KiB
Go
Raw Normal View History

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