package builtin import ( "sync" "testing" "time" ) // TestDefaultFileStateCache_BasicRecordAndGet verifies the round trip: // RecordState writes are visible to GetState, and missing paths return // (zero, false). // // TestDefaultFileStateCache_BasicRecordAndGet 验证读写回环: RecordState // 写入对 GetState 可见, 不存在路径返回 (zero, false). func TestDefaultFileStateCache_BasicRecordAndGet(t *testing.T) { cache := NewDefaultFileStateCache() now := time.Now() entry := FileStateCacheEntry{ ContentHash: "deadbeef", Size: 42, LineCount: 7, ModTime: now, IsPartialView: false, } cache.RecordState("/tmp/foo", entry) got, ok := cache.GetState("/tmp/foo") if !ok { t.Fatal("expected entry to be present") } if got.ContentHash != "deadbeef" || got.Size != 42 || got.LineCount != 7 { t.Errorf("entry mismatch: got %+v", got) } if _, ok := cache.GetState("/tmp/missing"); ok { t.Error("expected (zero, false) for missing path") } } // TestDefaultFileStateCache_OverwriteSamePath verifies that recording // the same path twice overwrites with the new entry (idempotent). // // TestDefaultFileStateCache_OverwriteSamePath 验证同路径多次 record // 用新 entry 覆盖旧 (幂等). func TestDefaultFileStateCache_OverwriteSamePath(t *testing.T) { cache := NewDefaultFileStateCache() cache.RecordState("/tmp/x", FileStateCacheEntry{ContentHash: "old"}) cache.RecordState("/tmp/x", FileStateCacheEntry{ContentHash: "new"}) got, _ := cache.GetState("/tmp/x") if got.ContentHash != "new" { t.Errorf("got hash %q, want %q", got.ContentHash, "new") } } // TestDefaultFileStateCache_Reset verifies Reset clears all entries // (analog to Claude Code commands/clear/conversation.ts:130 // readFileState.clear()). // // TestDefaultFileStateCache_Reset 验证 Reset 清空全部 entries (对位 // Claude Code commands/clear/conversation.ts:130 readFileState.clear()). func TestDefaultFileStateCache_Reset(t *testing.T) { cache := NewDefaultFileStateCache() cache.RecordState("/tmp/a", FileStateCacheEntry{ContentHash: "a"}) cache.RecordState("/tmp/b", FileStateCacheEntry{ContentHash: "b"}) cache.Reset() if _, ok := cache.GetState("/tmp/a"); ok { t.Error("expected /tmp/a cleared after Reset") } if _, ok := cache.GetState("/tmp/b"); ok { t.Error("expected /tmp/b cleared after Reset") } } // TestDefaultFileStateCache_ConcurrentAccess verifies the cache is safe // for concurrent RecordState / GetState from multiple goroutines. // Detected by -race; pure functional check just asserts no entries lost. // // TestDefaultFileStateCache_ConcurrentAccess 验证 cache 在多 goroutine // 并发 RecordState / GetState 下安全. 由 -race 检测; 函数正确性仅断言 // 无 entry 丢失. func TestDefaultFileStateCache_ConcurrentAccess(t *testing.T) { cache := NewDefaultFileStateCache() var wg sync.WaitGroup const N = 100 for i := range N { wg.Add(1) go func(i int) { defer wg.Done() path := "/tmp/file" + string(rune('a'+(i%26))) cache.RecordState(path, FileStateCacheEntry{Size: int64(i)}) }(i) } for range N { wg.Add(1) go func() { defer wg.Done() _, _ = cache.GetState("/tmp/filea") }() } wg.Wait() // 26 distinct paths recorded; all must be present (last writer wins). for i := range 26 { path := "/tmp/file" + string(rune('a'+i)) if _, ok := cache.GetState(path); !ok { t.Errorf("expected %q present after concurrent record", path) } } } // TestDefaultFileStateCache_InterfaceAssertions is a compile-time // guard that DefaultFileStateCache satisfies both Recorder and Reader // interfaces. Mirrors the var _ assertion at the package level. // // TestDefaultFileStateCache_InterfaceAssertions 编译期断言 // DefaultFileStateCache 同时实现 Recorder 与 Reader 接口. 对位包级 // var _ 断言. func TestDefaultFileStateCache_InterfaceAssertions(t *testing.T) { var ( _ FileStateCacheRecorder = NewDefaultFileStateCache() _ FileStateCacheReader = NewDefaultFileStateCache() ) }