| 1 | //go:build darwin && cgo |
| 2 | |
| 3 | package main |
| 4 | |
| 5 | import ( |
| 6 | "fmt" |
| 7 | "os" |
| 8 | "os/exec" |
| 9 | "path/filepath" |
| 10 | "testing" |
| 11 | |
| 12 | "golang.org/x/sys/unix" |
| 13 | "reasonix/internal/sessioncatalog" |
| 14 | ) |
| 15 | |
| 16 | func TestCatalogWatchLargeRootUsesBoundedDescriptors(t *testing.T) { |
| 17 | const child = "REASONIX_CATALOG_NOFILE_CHILD" |
| 18 | if os.Getenv(child) != "1" { |
| 19 | cmd := exec.Command(os.Args[0], "-test.run=^TestCatalogWatchLargeRootUsesBoundedDescriptors$", "-test.count=1", "-test.v") |
| 20 | cmd.Env = append(os.Environ(), child+"=1") |
| 21 | if output, err := cmd.CombinedOutput(); err != nil { |
| 22 | t.Fatalf("catalog watcher exceeded descriptor budget: %v\n%s", err, output) |
| 23 | } |
| 24 | return |
| 25 | } |
| 26 | root := t.TempDir() |
| 27 | for i := range 512 { |
| 28 | if err := os.WriteFile(filepath.Join(root, fmt.Sprintf("%04d.jsonl", i)), []byte("unread body"), 0600); err != nil { |
| 29 | t.Fatal(err) |
| 30 | } |
| 31 | } |
| 32 | var old unix.Rlimit |
| 33 | if err := unix.Getrlimit(unix.RLIMIT_NOFILE, &old); err != nil { |
| 34 | t.Fatal(err) |
| 35 | } |
| 36 | limit := old |
| 37 | limit.Cur = min(256, old.Max) |
| 38 | if err := unix.Setrlimit(unix.RLIMIT_NOFILE, &limit); err != nil { |
| 39 | t.Fatal(err) |
| 40 | } |
| 41 | t.Cleanup(func() { _ = unix.Setrlimit(unix.RLIMIT_NOFILE, &old) }) |
| 42 | watcher, err := newWorkspaceWatcher() |
| 43 | if err != nil { |
| 44 | t.Fatal(err) |
| 45 | } |
| 46 | t.Cleanup(func() { _ = watcher.Close() }) |
| 47 | if _, ok := watcher.(*darwinWorkspaceWatcher); !ok { |
| 48 | t.Fatalf("catalog backend enumerates source files: %T", watcher) |
| 49 | } |
| 50 | watched, dirty := map[string]bool{}, map[string]bool{} |
| 51 | targets := []sessioncatalog.DirectoryTarget{{Path: root, Scope: "global"}} |
| 52 | current := refreshCatalogWatchTargets(watcher, nil, targets, watched, dirty, nil) |
| 53 | key := canonicalWorkspaceRoot(root) |
| 54 | if !watched[key] || !dirty[key] { |
| 55 | t.Fatalf("initial watch/discovery missing: watched=%v dirty=%v", watched, dirty) |
| 56 | } |
| 57 | if count := darwinOpenFDCount(t); count >= 128 { |
| 58 | t.Fatalf("catalog watch opened %d descriptors for 512 sessions", count) |
| 59 | } |
| 60 | clear(dirty) |
| 61 | refreshCatalogWatchTargets(watcher, current, targets, watched, dirty, nil) |
| 62 | if len(dirty) != 0 { |
| 63 | t.Fatalf("idle root scheduled another scan: %v", dirty) |
| 64 | } |
| 65 | } |
| 66 |