// Copyright (c) 2026 Petr BalvĂ­n (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) } }