package engine import ( "fmt" "sync" "testing" "time" ) // ========== NewScratchpad ========== func TestNewScratchpad_EmptyInitialState(t *testing.T) { sp := NewScratchpad() if sp == nil { t.Fatal("NewScratchpad 不应返回 nil") } if sp.Len() != 0 { t.Errorf("初始条目数应为 0,got %d", sp.Len()) } if keys := sp.Keys(); len(keys) != 0 { t.Errorf("初始 Keys() 应为空,got %v", keys) } } // ========== Set / Get ========== func TestScratchpad_SetGet_Basic(t *testing.T) { sp := NewScratchpad() sp.Set("foo", "bar", 0) val, found := sp.Get("foo") if !found { t.Error("Get 应该找到刚写入的 key") } if val != "bar" { t.Errorf("Get 应返回 'bar',got %q", val) } } func TestScratchpad_Get_MissingKey(t *testing.T) { sp := NewScratchpad() val, found := sp.Get("nonexistent") if found { t.Error("不存在的 key 不应被找到") } if val != "" { t.Errorf("不存在的 key 应返回空字符串,got %q", val) } } func TestScratchpad_Set_OverwriteExisting(t *testing.T) { sp := NewScratchpad() sp.Set("k", "v1", 0) sp.Set("k", "v2", 0) // 覆盖 val, found := sp.Get("k") if !found { t.Error("覆盖后 key 应仍然存在") } if val != "v2" { t.Errorf("覆盖后值应为 'v2',got %q", val) } if sp.Len() != 1 { t.Errorf("覆盖后条目数应为 1,got %d", sp.Len()) } } func TestScratchpad_Set_EmptyValue(t *testing.T) { sp := NewScratchpad() sp.Set("key", "", 0) val, found := sp.Get("key") if !found { t.Error("空值 key 应能被找到") } if val != "" { t.Errorf("空值应原样返回,got %q", val) } } // ========== TTL ========== func TestScratchpad_TTL_NotExpired(t *testing.T) { sp := NewScratchpad() sp.Set("key", "value", 10*time.Second) // 10s 后才过期 val, found := sp.Get("key") if !found { t.Error("未过期的 key 应能被找到") } if val != "value" { t.Errorf("未过期 key 应返回 'value',got %q", val) } } func TestScratchpad_TTL_Expired(t *testing.T) { sp := NewScratchpad() // 精妙之处(CLEVER): 用 1 纳秒 TTL(最小正 Duration)触发即时过期-- // 不依赖 time.Sleep(测试不应 sleep),而是先 Set 再立即等待极短时间. sp.Set("key", "value", time.Nanosecond) // 确保时间推进(time.Sleep 极短,比 nanosecond TTL 更长即可) time.Sleep(time.Microsecond) val, found := sp.Get("key") if found { t.Error("过期的 key 不应被找到") } if val != "" { t.Errorf("过期 key 应返回空字符串,got %q", val) } } func TestScratchpad_TTL_Zero_NeverExpires(t *testing.T) { sp := NewScratchpad() sp.Set("key", "value", 0) // TTL=0 永不过期 // Keys() 和 Get() 都应正常工作 keys := sp.Keys() if len(keys) != 1 || keys[0] != "key" { t.Errorf("TTL=0 的 key 应永远出现在 Keys() 中,got %v", keys) } val, found := sp.Get("key") if !found || val != "value" { t.Errorf("TTL=0 的 key 应永远可读,found=%v val=%q", found, val) } } func TestScratchpad_TTL_Overwrite_RefreshesTTL(t *testing.T) { sp := NewScratchpad() // 先写入极短 TTL sp.Set("key", "v1", time.Nanosecond) time.Sleep(time.Microsecond) // 让第一次写入过期 // 覆盖写入,带更长 TTL sp.Set("key", "v2", 10*time.Second) val, found := sp.Get("key") if !found { t.Error("覆盖写入后 key 应可读") } if val != "v2" { t.Errorf("覆盖后值应为 'v2',got %q", val) } } // ========== Delete ========== func TestScratchpad_Delete_ExistingKey(t *testing.T) { sp := NewScratchpad() sp.Set("k", "v", 0) sp.Delete("k") _, found := sp.Get("k") if found { t.Error("Delete 后 key 不应被找到") } if sp.Len() != 0 { t.Errorf("Delete 后条目数应为 0,got %d", sp.Len()) } } func TestScratchpad_Delete_NonExistentKey(t *testing.T) { sp := NewScratchpad() // 删除不存在的 key 不应 panic 或报错 sp.Delete("ghost") if sp.Len() != 0 { t.Errorf("删除不存在的 key 后条目数应为 0,got %d", sp.Len()) } } // ========== Clear ========== func TestScratchpad_Clear(t *testing.T) { sp := NewScratchpad() sp.Set("a", "1", 0) sp.Set("b", "2", 0) sp.Set("c", "3", 0) sp.Clear() if sp.Len() != 0 { t.Errorf("Clear 后条目数应为 0,got %d", sp.Len()) } if keys := sp.Keys(); len(keys) != 0 { t.Errorf("Clear 后 Keys() 应为空,got %v", keys) } } func TestScratchpad_Clear_ThenSet(t *testing.T) { sp := NewScratchpad() sp.Set("x", "y", 0) sp.Clear() sp.Set("new", "val", 0) val, found := sp.Get("new") if !found || val != "val" { t.Errorf("Clear 后重新写入应能正常读取,found=%v val=%q", found, val) } if sp.Len() != 1 { t.Errorf("Clear 后写入一条,条目数应为 1,got %d", sp.Len()) } } // ========== Keys ========== func TestScratchpad_Keys_Sorted(t *testing.T) { sp := NewScratchpad() sp.Set("zebra", "z", 0) sp.Set("apple", "a", 0) sp.Set("mango", "m", 0) keys := sp.Keys() if len(keys) != 3 { t.Fatalf("Keys() 应返回 3 个 key,got %d", len(keys)) } expected := []string{"apple", "mango", "zebra"} for i, k := range keys { if k != expected[i] { t.Errorf("Keys()[%d] 应为 %q,got %q", i, expected[i], k) } } } func TestScratchpad_Keys_ExcludesExpired(t *testing.T) { sp := NewScratchpad() sp.Set("live", "v", 10*time.Second) sp.Set("dead", "v", time.Nanosecond) time.Sleep(time.Microsecond) // 让 dead 过期 keys := sp.Keys() if len(keys) != 1 || keys[0] != "live" { t.Errorf("Keys() 应只返回未过期的 key,got %v", keys) } } // ========== 并发安全 ========== func TestScratchpad_Concurrent_SetGet(t *testing.T) { sp := NewScratchpad() const goroutines = 50 var wg sync.WaitGroup // 并发写 for i := 0; i < goroutines; i++ { wg.Add(1) go func(n int) { defer wg.Done() key := string(rune('a' + n%26)) sp.Set(key, "value", 0) }(i) } wg.Wait() // 并发读 for i := 0; i < goroutines; i++ { wg.Add(1) go func(n int) { defer wg.Done() key := string(rune('a' + n%26)) sp.Get(key) //nolint:errcheck }(i) } wg.Wait() } func TestScratchpad_Concurrent_MixedOps(t *testing.T) { sp := NewScratchpad() // 预写入一些数据 for i := 0; i < 10; i++ { sp.Set(fmt.Sprintf("k%d", i), "init", 0) } var wg sync.WaitGroup // 并发执行 Set/Get/Delete/Keys for i := 0; i < 100; i++ { wg.Add(1) go func(n int) { defer wg.Done() key := fmt.Sprintf("k%d", n%10) switch n % 4 { case 0: sp.Set(key, "updated", 0) case 1: sp.Get(key) case 2: sp.Delete(key) case 3: sp.Keys() } }(i) } wg.Wait() // 只要不 panic,不死锁,测试通过 } func TestScratchpad_Concurrent_TTLExpiry(t *testing.T) { sp := NewScratchpad() var wg sync.WaitGroup // 并发写入短 TTL 条目并立即读取 for i := 0; i < 20; i++ { wg.Add(1) go func(n int) { defer wg.Done() key := fmt.Sprintf("ttl_key_%d", n) sp.Set(key, "val", time.Nanosecond) time.Sleep(time.Microsecond) sp.Get(key) // 应返回 ("", false) 但不 panic }(i) } wg.Wait() } // ========== 边界条件 ========== func TestScratchpad_LargeValue(t *testing.T) { sp := NewScratchpad() bigVal := string(make([]byte, 1<<20)) // 1MB sp.Set("big", bigVal, 0) val, found := sp.Get("big") if !found || len(val) != 1<<20 { t.Errorf("大值应能正常存取,found=%v len=%d", found, len(val)) } } func TestScratchpad_ManyKeys(t *testing.T) { sp := NewScratchpad() const n = 1000 for i := 0; i < n; i++ { sp.Set(fmt.Sprintf("key_%04d", i), "v", 0) } if sp.Len() != n { t.Errorf("写入 %d 个 key 后条目数应为 %d,got %d", n, n, sp.Len()) } keys := sp.Keys() if len(keys) != n { t.Errorf("Keys() 应返回 %d 个 key,got %d", n, len(keys)) } // 验证有序 for i := 1; i < len(keys); i++ { if keys[i] < keys[i-1] { t.Errorf("Keys() 应有序,但 keys[%d]=%q < keys[%d]=%q", i, keys[i], i-1, keys[i-1]) break } } }