Documentation
¶
Index ¶
- func Run[T any](t *testing.T, c Contract[T], ...)
- func RunHitWithClock[T any](t *testing.T, c Contract[T], factory ClockFactory[T])
- func RunNoBackgroundGoroutine[T any](t *testing.T, ...)
- func RunReadIsolation[T any](t *testing.T, c Contract[T], ...)
- func RunReadPromotion[T any](t *testing.T, c Contract[T], capacity int, ...)
- func RunSourceInjectionWithClock[T any](t *testing.T, c Contract[T], factory ClockFactory[T])
- func RunStaleIfErrorRepeatedWithClock[T any](t *testing.T, c Contract[T], factory ClockFactory[T])
- func RunStaleIfErrorWithClock[T any](t *testing.T, c Contract[T], factory ClockFactory[T])
- func RunStatsWithClock[T any](t *testing.T, c Contract[T], factory ClockFactory[T])
- func RunSweepWithClock[T any](t *testing.T, c Contract[T], factory ClockFactory[T])
- func RunWithClock[T any](t *testing.T, c Contract[T], factory ClockFactory[T])
- type ClockFactory
- type Contract
- type FakeClock
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func Run ¶
func Run[T any](t *testing.T, c Contract[T], factory func(t *testing.T, source cache.SourceFunc[T]) cache.CacheAside[T])
Run 跑完整契约场景(8 个子测试,任何子测试失败即实现违反契约)。
factory 每个场景调用一次,用套件给定的 source 构造全新被测缓存 (source 的行为——返回值/计数——由各场景闭包控制,factory 原样注入即可); 被测缓存的后台资源由 factory 经 t.Cleanup 清理。
每个子测试覆盖一条必须跨实现一致的语义:
- miss_returns_nil_nil 未命中返回 (nil, nil) 且非 error,Exists=false
- set_get_roundtrip Set/Get 往返相等;重复 Set 覆盖旧值
- set_with_ttl_expiry SetWithTTL 短 TTL 过期后 miss(有界等待)
- del_and_delbatch Del/DelBatch 后 miss;对不存在的 key 幂等
- get_or_load_backfill 未命中回源回填,source 计数恰 1
- null_marker Source (nil,nil) → null 标记防穿透,Del 后重新回源
- refresh Refresh 取 source 新值并更新缓存
- concurrent_mixed_ops 并发混合读写删无 panic(配合 -race 检测数据竞争)
func RunHitWithClock ¶ added in v0.16.0
func RunHitWithClock[T any](t *testing.T, c Contract[T], factory ClockFactory[T])
RunHitWithClock 跑「命中语义」契约(pkg#25)。
实现须提供 cache.HitGetter(GetEx(ctx) (*T, bool, error))——未提供 即判失败。
覆盖五种情形:
- key 不存在 → (nil, false, nil)
- 命中 null marker → (nil, false, nil)(null marker 表示数据不存在)
- 命中显式 nil 值 → (nil, true, nil)(有值但值为 nil——Get 无法区分)
- 命中真值 → (value, true, nil)
- 条目已过期 → (nil, false, nil)
前提:实现须能存储显式 nil 值(本地实现天然支持;序列化型实现须定义 nil 值的编码——本场景对未声明该能力的实现不适用)。
func RunNoBackgroundGoroutine ¶ added in v0.16.0
func RunNoBackgroundGoroutine[T any](t *testing.T, factory func(t *testing.T, source cache.SourceFunc[T]) cache.CacheAside[T])
RunNoBackgroundGoroutine 跑「不起后台 goroutine」契约(pkg#25)。
构造缓存不得在进程内新增 goroutine——短命命令行/宿主内嵌场景不该 为缓存常驻后台协程(LocalCache:LocalConfig.NoBackgroundSweep)。
断言:构造前后 runtime.NumGoroutine() 不得增加。
func RunReadIsolation ¶ added in v0.16.0
func RunReadIsolation[T any](t *testing.T, c Contract[T], factory func(t *testing.T, source cache.SourceFunc[T]) cache.CacheAside[T])
RunReadIsolation 跑「读命中返回副本」契约(pkg#25)。
factory 须构造「读命中返回副本」模式的缓存(LocalCache: LocalConfig.CloneOnRead 开启;序列化型后端天然满足)。
断言:调用方改写 Get 返回值后,再次 Get 必须拿到原始值——缓存内部 条目未被污染。期望值用**独立值副本**保存(不与缓存内条目共享指针), 避免「改写返回值时把期望值一起改掉」造成的假绿。
func RunReadPromotion ¶ added in v0.16.0
func RunReadPromotion[T any](t *testing.T, c Contract[T], capacity int, factory func(t *testing.T, source cache.SourceFunc[T]) cache.CacheAside[T])
RunReadPromotion 跑「读命中提升顺序」契约(pkg#25)。
factory 须构造「读命中提升顺序(LRU)」且容量为 capacity 的缓存 (LocalCache:LocalConfig.EvictPolicy=EvictLRU + MaxSize=capacity)。
断言:写满容量 → 反复读最旧的 key(提升顺序)→ 再写一条触发淘汰 → 热点 key 仍存活;且确有条目被淘汰(证明场景真的触发了淘汰, 不是因为压根没写满而"通过")。
同一场景在 FIFO 实现下热点 key 必被淘汰——即该场景能区分两种策略。
func RunSourceInjectionWithClock ¶ added in v0.24.0
func RunSourceInjectionWithClock[T any](t *testing.T, c Contract[T], factory ClockFactory[T])
RunSourceInjectionWithClock 跑「按调用注入回源」契约(pkg#77)。
实现须提供 cache.SourceInjector——未提供即判失败。
覆盖五点:
- 按调用注入的 source 被用于回源(构造期 source 不被调用);
- 命中缓存时不回源(注入的 source 也不被调用);
- 注入的 source 为 nil 时退回构造期 source;
- 注入的动态 TTL 生效(回源给出的 TTL 决定回填窗口);
- 显式传入的 ttl 覆盖回源给出的动态 TTL。
场景 4/5 需要推进时钟才能观察 TTL 窗口,故用 ClockFactory。
func RunStaleIfErrorRepeatedWithClock ¶ added in v0.23.0
func RunStaleIfErrorRepeatedWithClock[T any](t *testing.T, c Contract[T], factory ClockFactory[T])
RunStaleIfErrorRepeatedWithClock 跑「stale 兜底可反复生效」契约(pkg#72)。
stale-if-error 的兜底值必须**留在表内**——否则第一次兜底后过期真值被 回收,第二次回源失败时就没有可退的值了(fail-open 只在第一次生效, 形同虚设)。
场景:写入 30s TTL 真值 → 推进时钟越过 TTL → 连续三次回源失败 → 三次都应回退同一份历史真值。
func RunStaleIfErrorWithClock ¶ added in v0.23.0
func RunStaleIfErrorWithClock[T any](t *testing.T, c Contract[T], factory ClockFactory[T])
RunStaleIfErrorWithClock 跑「stale-if-error」契约(pkg#72)。
factory 须构造**开启 stale-if-error** 的缓存(LocalCache: LocalConfig.StaleIfError=true),并把传入的 source 作为回源路径。
场景:写入一条 30s TTL 的真值 → 推进时钟越过 TTL(真值过期但仍在表内) → 回源失败(source 恒返回错误)→ GetOrLoad 必须回退过期真值返回 (fail-open,err=nil),而不是把回源错误原样抛出。
前提:实现须能在回源失败时保留并读到过期真值(LocalCache 的 GetOrLoad 刻意绕开惰性删除,把回收交给后台清扫与其他读路径)。对未声明该能力的 实现本场景不适用——显式调用即视为声明该能力,能力缺失即判失败。
func RunStatsWithClock ¶ added in v0.24.0
func RunStatsWithClock[T any](t *testing.T, c Contract[T], factory ClockFactory[T])
RunStatsWithClock 跑「观测计数」契约(pkg#76)。
实现须提供 cache.StatsProvider(Stats() cache.Stats)——未提供即判失败。
覆盖五点(计数口径自洽 + 单调不减):
- 初始命中/未命中计数全 0;
- 读不存在的 key → Misses+1、Hits 不变;
- Set 后读 → Hits+1、Misses 不变;
- null marker 读 → Misses+1(null marker 无值可返,按未命中口径);
- TTL 过期后读 → Misses+1。
关键断言:**每次读恰好计入命中或未命中之一**(互斥),且两个计数各自 单调不减。Size 只断言非负(条目数口径由实现定义)。
刻意不断言 Evictions/Expirations:两者的口径与实现的容量策略、回收 路径相关(契约层不绑死),由实现自带的单元测试钉住。
func RunSweepWithClock ¶ added in v0.16.0
func RunSweepWithClock[T any](t *testing.T, c Contract[T], factory ClockFactory[T])
RunSweepWithClock 跑「手动清扫」契约(pkg#25)。
实现须提供 cache.Sweeper(Sweep(ctx) int)——未提供即判失败。
场景:两条 TTL 条目 + 一条 TTL=0(永不过期)条目 → 推进时钟越过 TTL → Sweep 恰好清除 2 条、未过期条目保留 → 再扫一次返回 0(幂等)。
前提:场景窗口(毫秒级)内实现不得自行回收过期条目——LocalCache 的 后台清扫节奏为 TTL/2(真实时间 15s),远大于场景耗时。
func RunWithClock ¶ added in v0.13.0
func RunWithClock[T any](t *testing.T, c Contract[T], factory ClockFactory[T])
RunWithClock 跑时钟驱动的 TTL 边界场景(4 个子测试,#9——供支持时钟注入的 实现接入,补 Run 真实时间套件覆盖不了的确定性边界):
- ttl_expiry_boundary 过期边界语义:SetWithTTL 后推进到过期时刻**仍命中** (expiresAt 为闭边界,After 判定),越过 1ns 即 miss
- ttl_zero_never_expires TTL=0(自定义 TTL 传 0)永不过期——推进 100 年仍命中
- null_marker_out_of_window Source (nil,nil) 写 null 标记;推进远超空值窗口 后重新回源(source 计数 1→2)
- overwrite_resets_expiry 重写刷新过期时刻:原 TTL 已越过、新 TTL 仍在窗内 → 命中新值
Types ¶
type ClockFactory ¶ added in v0.13.0
type ClockFactory[T any] func(t *testing.T, source cache.SourceFunc[T], clock *FakeClock) cache.CacheAside[T]
ClockFactory 是支持时钟注入的缓存工厂形态:实现把 clock.Now 路由到 全部 TTL 判定(如 LocalConfig.Now),后台资源由 factory 经 t.Cleanup 清理。 支持空值缓存的实现请配置正值 NullTTL(套件以「远超缺省空值窗口」的推进 验证过窗重源)。
type Contract ¶
type Contract[T any] struct { // NewValue 返回第 i 个测试值(i 从 0 递增)。 // 实现必须保证任意 i != j 时两值两两不同(套件依赖此性质断言覆盖与刷新)。 NewValue func(i int) *T // Equal 判断两个值是否相等。 // 实现必须容忍 nil 入参(miss 场景会拿 nil 与期望值比较)。 Equal func(a, b *T) bool }
Contract 定义被测缓存的价值工厂与相等性。
通过 Contract 抽象,套件对任意值类型 T 复用同一组场景; 可比较类型可直接用 Comparable 获得现成实现。
func Comparable ¶
func Comparable[T comparable]() Contract[T]
Comparable 返回可比较类型的现成 Contract。
NewValue 经反射按类型枚举生成第 i 个值:
- 有符号/无符号整数系:取 i 本身;
- 字符串系:strconv.Itoa(i)。
其他 comparable 类型(bool、struct、指针、channel 等)无法从整数可靠 枚举出两两不同的值,请自定义 Contract。
type FakeClock ¶ added in v0.13.0
type FakeClock struct {
// contains filtered or unexported fields
}
FakeClock 是套件提供的假时钟(并发安全;测试内手动推进)。 实现把 clock.Now 注入配置后,场景经 Advance 确定性驱动 TTL 过期—— 无 sleep 无 flake(#9)。
func NewFakeClock ¶ added in v0.13.0
func NewFakeClock() *FakeClock
NewFakeClock 创建起点为 2026-01-01T00:00:00Z 的假时钟 (取非零真实量级时刻,避开零值 time.Time 的「无过期」歧义)。