Help us shape the future of Go! Take the 2026 Go Developer Survey

cachetest

package
v0.28.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 25, 2026 License: MIT Imports: 10 Imported by: 1

Documentation

Index

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 清理。

每个子测试覆盖一条必须跨实现一致的语义:

  1. miss_returns_nil_nil 未命中返回 (nil, nil) 且非 error,Exists=false
  2. set_get_roundtrip Set/Get 往返相等;重复 Set 覆盖旧值
  3. set_with_ttl_expiry SetWithTTL 短 TTL 过期后 miss(有界等待)
  4. del_and_delbatch Del/DelBatch 后 miss;对不存在的 key 幂等
  5. get_or_load_backfill 未命中回源回填,source 计数恰 1
  6. null_marker Source (nil,nil) → null 标记防穿透,Del 后重新回源
  7. refresh Refresh 取 source 新值并更新缓存
  8. 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))——未提供 即判失败。

覆盖五种情形:

  1. key 不存在 → (nil, false, nil)
  2. 命中 null marker → (nil, false, nil)(null marker 表示数据不存在)
  3. 命中显式 nil 值 → (nil, true, nil)(有值但值为 nil——Get 无法区分)
  4. 命中真值 → (value, true, nil)
  5. 条目已过期 → (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——未提供即判失败。

覆盖五点:

  1. 按调用注入的 source 被用于回源(构造期 source 不被调用);
  2. 命中缓存时不回源(注入的 source 也不被调用);
  3. 注入的 source 为 nil 时退回构造期 source;
  4. 注入的动态 TTL 生效(回源给出的 TTL 决定回填窗口);
  5. 显式传入的 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)——未提供即判失败。

覆盖五点(计数口径自洽 + 单调不减):

  1. 初始命中/未命中计数全 0;
  2. 读不存在的 key → Misses+1、Hits 不变;
  3. Set 后读 → Hits+1、Misses 不变;
  4. null marker 读 → Misses+1(null marker 无值可返,按未命中口径);
  5. 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 真实时间套件覆盖不了的确定性边界):

  1. ttl_expiry_boundary 过期边界语义:SetWithTTL 后推进到过期时刻**仍命中** (expiresAt 为闭边界,After 判定),越过 1ns 即 miss
  2. ttl_zero_never_expires TTL=0(自定义 TTL 传 0)永不过期——推进 100 年仍命中
  3. null_marker_out_of_window Source (nil,nil) 写 null 标记;推进远超空值窗口 后重新回源(source 计数 1→2)
  4. 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 的「无过期」歧义)。

func (*FakeClock) Advance added in v0.13.0

func (c *FakeClock) Advance(d time.Duration)

Advance 把假时钟前进 d。

func (*FakeClock) Now added in v0.13.0

func (c *FakeClock) Now() time.Time

Now 返回当前假时刻(可直接作为 LocalConfig.Now 等注入点)。

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL