package rond import ( "os" "path/filepath" "regexp" "runtime" "strings" "testing" ) // D3 回归:parse.go 里每个 rows 循环的收尾都必须查 rows.Err()。 // // 只 Close 不查 Err 是「不报错也不算错」的那一类:驱动或连接中途出错时 // Next() 只返回 false、从不报错,读到一半的表会被当成完整的交出去—— // 表现成导入后到访/位移莫名变少,日志里一个字都没有。 // // 两种合格写法:循环后 `if err = drainRows(x, "…")`,或自己 `x.Err()`。 func TestParseDrainsEveryRowsLoop(t *testing.T) { _, self, _, ok := runtime.Caller(0) if !ok { t.Fatal("定位测试文件失败") } src, err := os.ReadFile(filepath.Join(filepath.Dir(self), "parse.go")) if err != nil { t.Fatalf("读 parse.go 失败: %v", err) } text := string(src) lits := regexp.MustCompile(`for (\w+)\.Next\(\)`).FindAllStringSubmatch(text, -1) seen := map[string]bool{} for _, m := range lits { name := m[1] if seen[name] { continue } seen[name] = true if !strings.Contains(text, name+".Err()") && !strings.Contains(text, "drainRows("+name+",") { t.Errorf("parse.go 里 %s 的循环收尾没查 %s.Err()(只 Close 会把读一半的表当完整的交出去)", name, name) } } if len(seen) < 10 { t.Fatalf("只认出 %d 个 rows 循环变量(期望 ≥10),判据可能被改写架空,请同步更新本用例", len(seen)) } // 收尾助手自己必须真的读 Err,否则上面那条判据能被一个空壳 drainRows 骗过去 body := between(text, "func drainRows(", "\nfunc ") if body == "" { t.Fatal("找不到 drainRows 的实现") } if !strings.Contains(body, "Err()") || !strings.Contains(body, "Close()") { t.Errorf("drainRows 必须既查 Err() 又 Close(),实际:\n%s", body) } } func between(s, start, end string) string { i := strings.Index(s, start) if i < 0 { return "" } rest := s[i+len(start):] if j := strings.Index(rest, end); j >= 0 { return rest[:j] } return rest }