You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
120 lines
3.9 KiB
120 lines
3.9 KiB
package memory
|
|
|
|
import (
|
|
"testing"
|
|
"time"
|
|
|
|
"yunyan/comm"
|
|
"yunyan/pb"
|
|
)
|
|
|
|
// 补偿要找的是「最近一个**已结束**的周期」,口径必须与 cron 的 tick 完全一致:
|
|
// 差一周就会去补一个 cron 已经建过的周期(重复生成)或者永远差一格(永远补不上)。
|
|
func TestLastFinishedPeriods(t *testing.T) {
|
|
loc := time.FixedZone("CST", 8*3600)
|
|
cases := []struct {
|
|
name string
|
|
now time.Time
|
|
wantWeek string // 周一~周日
|
|
wantMonth string
|
|
}{
|
|
{
|
|
name: "周中",
|
|
now: time.Date(2026, 9, 18, 10, 0, 0, 0, loc), // 周五
|
|
wantWeek: "2026-09-07~2026-09-13",
|
|
wantMonth: "2026-08-01~2026-08-31",
|
|
},
|
|
{
|
|
name: "周一 cron 触发那一刻",
|
|
now: time.Date(2026, 9, 14, 0, 30, 0, 0, loc),
|
|
wantWeek: "2026-09-07~2026-09-13",
|
|
wantMonth: "2026-08-01~2026-08-31",
|
|
},
|
|
{
|
|
name: "周日(本周还没结束,仍看上一周)",
|
|
now: time.Date(2026, 9, 20, 23, 59, 0, 0, loc),
|
|
wantWeek: "2026-09-07~2026-09-13",
|
|
wantMonth: "2026-08-01~2026-08-31",
|
|
},
|
|
{
|
|
name: "跨年:1 月 1 日的上个月是去年 12 月",
|
|
now: time.Date(2026, 1, 1, 0, 30, 0, 0, loc),
|
|
wantMonth: "2025-12-01~2025-12-31",
|
|
},
|
|
}
|
|
for _, c := range cases {
|
|
t.Run(c.name, func(t *testing.T) {
|
|
if c.wantWeek != "" {
|
|
w := lastFinishedWeek(c.now)
|
|
if got := w.start + "~" + w.end; got != c.wantWeek {
|
|
t.Errorf("上周范围应为 %s,得到 %s", c.wantWeek, got)
|
|
}
|
|
if w.ptype != comm.MemoryPeriodWeek {
|
|
t.Errorf("周期类型应为 week,得到 %s", w.ptype)
|
|
}
|
|
}
|
|
m := lastFinishedMonth(c.now)
|
|
if got := m.start + "~" + m.end; got != c.wantMonth {
|
|
t.Errorf("上月范围应为 %s,得到 %s", c.wantMonth, got)
|
|
}
|
|
if m.ptype != comm.MemoryPeriodMonth {
|
|
t.Errorf("周期类型应为 month,得到 %s", m.ptype)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// 周一 00:30 的 cron 与 ensureRecent 必须算出同一个周期键,
|
|
// 否则一个建单、另一个再建一次,同一周会有两份报告在跑。
|
|
func TestLastFinishedWeekMatchesCronKey(t *testing.T) {
|
|
loc := time.FixedZone("CST", 8*3600)
|
|
now := time.Date(2026, 9, 14, 0, 30, 0, 0, loc) // 周一,cron 触发时刻
|
|
cronKey := comm.MemoryWeekKey(now.AddDate(0, 0, -1))
|
|
if got := lastFinishedWeek(now).key; got != cronKey {
|
|
t.Errorf("ensureRecent 算出 %s,cron 算出 %s,两者必须一致", got, cronKey)
|
|
}
|
|
}
|
|
|
|
// 重试次数寄存在 error_msg 的 "retry=N;" 前缀里(不加列)。
|
|
// 计数丢了的后果是一份永远失败的报告可以无限重试,每次都烧一次 LLM。
|
|
func TestRetryCountRoundTrip(t *testing.T) {
|
|
cases := []struct {
|
|
msg string
|
|
want int
|
|
}{
|
|
{"", 0},
|
|
{"统计失败: connection refused", 0},
|
|
{"retry=1;统计失败", 1},
|
|
{"retry=3;统计失败", 3},
|
|
{"retry=;坏格式当没重试过", 0},
|
|
{"retry=abc;坏格式当没重试过", 0},
|
|
{"retry=2没有分号也当没重试过", 0},
|
|
}
|
|
for _, c := range cases {
|
|
if got := retryCount(c.msg); got != c.want {
|
|
t.Errorf("retryCount(%q) = %d,期望 %d", c.msg, got, c.want)
|
|
}
|
|
}
|
|
|
|
// 加计数要保住原来的错误正文,换计数不能越叠越长
|
|
got := withRetryCount("统计失败: x", 1)
|
|
if got != "retry=1;统计失败: x" {
|
|
t.Errorf("首次加计数得到 %q", got)
|
|
}
|
|
if got = withRetryCount(got, 2); got != "retry=2;统计失败: x" {
|
|
t.Errorf("再次加计数得到 %q(正文应原样保留、前缀不叠加)", got)
|
|
}
|
|
}
|
|
|
|
// fail 覆盖 error_msg 时必须保住已有计数——不然重试上限形同虚设。
|
|
func TestFailKeepsRetryCount(t *testing.T) {
|
|
rec := &pb.DBMemoryReport{ErrorMsg: "retry=2;上一次的原因"}
|
|
// 只验计数的保留逻辑本身,不走 fail(它要写库)
|
|
msg := "这次的原因"
|
|
if n := retryCount(rec.ErrorMsg); n > 0 {
|
|
msg = withRetryCount(msg, n)
|
|
}
|
|
if retryCount(msg) != 2 {
|
|
t.Errorf("覆盖错误正文后计数应仍为 2,得到 %q", msg)
|
|
}
|
|
}
|
|
|