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

140 lines
4.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
name: Fix caller detection
overview: 修复三个日志/测试相关 bug:(1) 调用者智能识别漏掉 log/ 包自身 (2) 测试模式 SQL 出错不会导致用例失败 (3) 测试模式 WebConnectLog 写入已关闭的 NUL 文件
todos:
- id: fix-framework-detection
content: 在 isHoTimeFrameworkFile 中 runtime/ 检查之后,添加 log/ 前缀检查
status: completed
- id: fix-sql-error-test-fail
content: 测试模式 SQL 出错时自动让当前用例失败(db 增加错误计数 + execute 中检查)
status: completed
- id: fix-zerolog-nul-closed
content: NewTestApp 中 Init 完成后将 WebConnectLog 置为 nil,避免写入已关闭的 NUL 文件
status: completed
isProject: false
---
# 修复日志/测试三个 Bug
---
## Bug 1:调用者智能识别始终显示 log/logger.go:318
### 根因
[log/logger.go](log/logger.go) 中的 `isHoTimeFrameworkFile` 函数对 `log/` 包自身存在识别盲区。
```mermaid
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["显示在日志输出中"]
```
`findCaller``i=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](log/logger.go) 第 387-389 行 `runtime/` 检查之后加入 `log/` 前缀检查:
```go
if strings.HasPrefix(file, "runtime/") {
return true
}
if strings.HasPrefix(file, "log/") {
return true
}
```
---
## Bug 2:测试模式 SQL 执行失败不会导致用例失败
### 根因
[db/query.go](db/query.go) `logSQL` 在测试模式下只是把 SQL 错误写入 `testLogBuf`,但 [testing_api.go](testing_api.go) 的 `execute` 中只在断言失败时才打印这些日志,**从不检查是否有 SQL 错误**。即使请求处理过程中数据库出错,只要 HTTP 响应的 status/msg 符合预期,测试就通过了。
当前流程:
```mermaid
flowchart LR
A["ServeHTTP"] --> B["检查 status/msg/result"]
B -->|"通过"| C["FlushTestLog 丢弃"]
B -->|"失败"| D["FlushTestLog 打印"]
```
### 修复
在 [db/db.go](db/db.go) 增加一个测试 SQL 错误计数器:
```go
testSQLErrCount int32 // 测试模式下 SQL 错误计数(atomic)
```
在 [db/query.go](db/query.go) `logSQL` 中,当 `err != nil``testLogBuf != nil` 时,`atomic.AddInt32(&that.testSQLErrCount, 1)`
在 [db/db.go](db/db.go) 增加两个方法:
```go
func (that *HoTimeDB) TestSQLErrorCount() int32 {
return atomic.LoadInt32(&that.testSQLErrCount)
}
func (that *HoTimeDB) ResetTestSQLErrorCount() {
atomic.StoreInt32(&that.testSQLErrCount, 0)
}
```
在 [testing_api.go](testing_api.go) `execute` 中,`ServeHTTP` 之后、断言之前,检查计数:
```go
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)
}
```
修复后流程:
```mermaid
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](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](testing_helper.go) `devNull.Close()` 之后、`app.Log.SetOutput(io.Discard)` 之前,加一行:
```go
app.WebConnectLog = nil
```
测试环境不需要访问日志。`application.go` 第 350 行 `if that.WebConnectLog != nil` 已有 nil 保护,置 nil 即可安全跳过。