Files
hotime/.cursor/plans/fix_caller_detection_05e66771.plan.md
T
hoteas 3430bdec19 enhance(tests): 改进测试用例记录与覆盖率报告
- 在 ApiCase 中添加失败原因记录,提升测试结果的可读性
- 更新 CoverageReport 结构,增加总用例、通过用例和失败用例计数
- 优化 PrintCoverage 方法,支持详细输出未通过接口及其失败原因
- 更新 Swagger 生成逻辑,包含失败原因字段,增强调试信息
- 改进 SQL 错误计数逻辑,确保在测试模式下准确记录 SQL 错误
2026-04-13 06:45:16 +08:00

4.7 KiB
Raw Blame History

name, overview, todos, isProject
name overview todos isProject
Fix caller detection 修复三个日志/测试相关 bug:(1) 调用者智能识别漏掉 log/ 包自身 (2) 测试模式 SQL 出错不会导致用例失败 (3) 测试模式 WebConnectLog 写入已关闭的 NUL 文件
id content status
fix-framework-detection 在 isHoTimeFrameworkFile 中 runtime/ 检查之后,添加 log/ 前缀检查 completed
id content status
fix-sql-error-test-fail 测试模式 SQL 出错时自动让当前用例失败(db 增加错误计数 + execute 中检查) completed
id content status
fix-zerolog-nul-closed NewTestApp 中 Init 完成后将 WebConnectLog 置为 nil,避免写入已关闭的 NUL 文件 completed
false

修复日志/测试三个 Bug


Bug 1:调用者智能识别始终显示 log/logger.go:318

根因

log/logger.go 中的 isHoTimeFrameworkFile 函数对 log/ 包自身存在识别盲区。

flowchart TD
    A["应用代码 或 db/query.go 调用 Logger.Debug()"] --> B["zerolog Event.Msg()"]
    B --> C["callerMarshalFunc (log/logger.go:317)"]
    C --> D["findCaller 通过 runtime.Caller(i) 逐帧遍历调用栈"]
    D --> E{"isHoTimeFrameworkFile?"}
    E -->|"是框架文件 -> 跳过,继续向上"| D
    E -->|"非框架文件 -> 作为调用者返回"| F["显示在日志输出中"]

findCalleri=1 时拿到 log/logger.go:318。经过 shortenPath 截断后变成 log/logger.go。两条检测路径都漏掉了它:

  • 路径一(第 392 行)frameworkDirs 包含 "log/",但要求路径含 "hotime"shortenPath 只保留 2 段路径,log/logger.go 中没有 "hotime"未命中。
  • 路径二(第 410 行)frameworkCoreDirs 只有 db/common/code/cache/没有 "log/"未命中。

修复

log/logger.go 第 387-389 行 runtime/ 检查之后加入 log/ 前缀检查:

if strings.HasPrefix(file, "runtime/") {
    return true
}
if strings.HasPrefix(file, "log/") {
    return true
}

Bug 2:测试模式 SQL 执行失败不会导致用例失败

根因

db/query.go logSQL 在测试模式下只是把 SQL 错误写入 testLogBuf,但 testing_api.goexecute 中只在断言失败时才打印这些日志,从不检查是否有 SQL 错误。即使请求处理过程中数据库出错,只要 HTTP 响应的 status/msg 符合预期,测试就通过了。

当前流程:

flowchart LR
    A["ServeHTTP"] --> B["检查 status/msg/result"]
    B -->|"通过"| C["FlushTestLog 丢弃"]
    B -->|"失败"| D["FlushTestLog 打印"]

修复

db/db.go 增加一个测试 SQL 错误计数器:

testSQLErrCount int32  // 测试模式下 SQL 错误计数(atomic)

db/query.go logSQL 中,当 err != niltestLogBuf != nil 时,atomic.AddInt32(&that.testSQLErrCount, 1)

db/db.go 增加两个方法:

func (that *HoTimeDB) TestSQLErrorCount() int32 {
    return atomic.LoadInt32(&that.testSQLErrCount)
}
func (that *HoTimeDB) ResetTestSQLErrorCount() {
    atomic.StoreInt32(&that.testSQLErrCount, 0)
}

testing_api.go execute 中,ServeHTTP 之后、断言之前,检查计数:

c.api.app.Db.FlushTestLog() // 先清空请求前的残留
// ... ServeHTTP ...
sqlErrCount := c.api.app.Db.TestSQLErrorCount()
c.api.app.Db.ResetTestSQLErrorCount()
if sqlErrCount > 0 {
    sqlLog := c.api.app.Db.FlushTestLog()
    t.Fatalf("请求处理中有 %d 次 SQL 错误:\n%s", sqlErrCount, sqlLog)
}

修复后流程:

flowchart LR
    A["ServeHTTP"] --> B{"SQL 出错?"}
    B -->|"是"| C["t.Fatalf 用例直接失败"]
    B -->|"否"| D["正常断言 status/msg/result"]

Bug 3zerolog: could not write event: write NUL: file already closed

根因

testing_helper.go NewTestApp 中的初始化顺序:

1. os.Stderr = devNull           (重定向到 NUL)
2. Init(configPath)              (创建 WebConnectLog,其 ConsoleWriter.Out = devNull)
3. os.Stderr = origStderr        (恢复真实 stderr)
4. devNull.Close()               (关闭 NUL 文件)
5. app.Log.SetOutput(io.Discard) (只修复了主日志)
   -- WebConnectLog 仍然引用已关闭的 devNull --
6. 测试请求触发 WebConnectLog.Info() → 写入已关闭文件 → 报错

修复

testing_helper.go devNull.Close() 之后、app.Log.SetOutput(io.Discard) 之前,加一行:

app.WebConnectLog = nil

测试环境不需要访问日志。application.go 第 350 行 if that.WebConnectLog != nil 已有 nil 保护,置 nil 即可安全跳过。