package cartography import ( "go/ast" "go/parser" "go/token" "os" "path/filepath" "strings" ) // ExtractTests walks test files and associates test functions with the // declarations they test. Updates HasTest, TestFuncs, TestFile, TestPatterns, // Validation, and bumps Confidence from 0.3 → 0.5 for entries with tests. func ExtractTests(atlas *Atlas, projectRoot string) error { return filepath.Walk(projectRoot, func(path string, info os.FileInfo, err error) error { if err != nil { return nil } if info.IsDir() { if skipDirs[info.Name()] { return filepath.SkipDir } return nil } if !strings.HasSuffix(path, "_test.go") { return nil } rel, _ := filepath.Rel(projectRoot, path) extractTestFile(atlas, path, rel) return nil }) } // extractTestFile parses a single test file and associates its test functions // with atlas entries. func extractTestFile(atlas *Atlas, absPath, relPath string) { fset := token.NewFileSet() file, err := parser.ParseFile(fset, absPath, nil, parser.ParseComments) if err != nil { return } pkgName := "" if file.Name != nil { pkgName = file.Name.Name } for _, decl := range file.Decls { fn, ok := decl.(*ast.FuncDecl) if !ok { continue } name := fn.Name.Name if !strings.HasPrefix(name, "Test") { continue } // Match test to declaration. target := matchTestToDecl(name, pkgName, atlas) if target == nil { continue } target.HasTest = true target.TestFuncs = appendUnique(target.TestFuncs, name) if target.TestFile == "" { target.TestFile = relPath } // Extract test table case names. patterns := extractTestTableNames(fn.Body) for _, p := range patterns { target.TestPatterns = appendUnique(target.TestPatterns, p) } // Update validation text. target.Validation = buildValidation(target) // Bump confidence. if target.Source == "mechanical" && target.Confidence < 0.5 { target.Confidence = 0.5 } } } // matchTestToDecl tries to find the atlas entry that a test function tests. // Uses naming conventions: // // TestFoo → matches func Foo or type Foo // TestFoo_Bar → matches func Foo (sub-test "Bar") // TestType_Foo → matches method Type.Foo func matchTestToDecl(testName, pkg string, atlas *Atlas) *Entry { // Strip "Test" prefix. rest := strings.TrimPrefix(testName, "Test") if rest == "" { return nil } // Strip leading underscore (Test_foo convention for unexported). rest = strings.TrimPrefix(rest, "_") // Split on underscore — first part is the target, rest are sub-test names. parts := strings.SplitN(rest, "_", 2) target := parts[0] // Try direct function match: pkg.Target if e, ok := atlas.Entries[pkg+"."+target]; ok { return e } // Try method match: pkg.Target.SubPart (e.g., TestNode_Bond → lattice.Node.Bond) if len(parts) > 1 { methodName := parts[1] if e, ok := atlas.Entries[pkg+"."+target+"."+methodName]; ok { return e } } // Search by bare name. for _, e := range atlas.Entries { if e.Package == pkg && e.Name == target { return e } } return nil } // extractTestTableNames looks for table-driven test patterns inside a test // function body. Scans for composite literals with a "name" or "desc" field // that has string literal values. func extractTestTableNames(body *ast.BlockStmt) []string { if body == nil { return nil } var names []string ast.Inspect(body, func(n ast.Node) bool { cl, ok := n.(*ast.CompositeLit) if !ok { return true } // Look for struct literals with a "name" or "desc" field. for _, elt := range cl.Elts { kv, ok := elt.(*ast.KeyValueExpr) if !ok { continue } ident, ok := kv.Key.(*ast.Ident) if !ok { continue } keyName := strings.ToLower(ident.Name) if keyName != "name" && keyName != "desc" && keyName != "description" && keyName != "label" && keyName != "in" { continue } bl, ok := kv.Value.(*ast.BasicLit) if !ok || bl.Kind != token.STRING { continue } // Strip quotes. val := strings.Trim(bl.Value, `"` + "`") if val != "" { names = append(names, val) } } return true }) return names } // buildValidation generates a human-readable validation description. func buildValidation(e *Entry) string { if len(e.TestFuncs) == 0 { return "" } var b strings.Builder b.WriteString("Tested by ") b.WriteString(strings.Join(e.TestFuncs, ", ")) b.WriteString(".") if len(e.TestPatterns) > 0 { b.WriteString(" Cases: ") // Show at most 5 patterns. n := len(e.TestPatterns) if n > 5 { n = 5 } b.WriteString(strings.Join(e.TestPatterns[:n], ", ")) if len(e.TestPatterns) > 5 { b.WriteString(" ...") } b.WriteString(".") } return b.String() } // appendUnique appends a string to a slice only if not already present. func appendUnique(slice []string, s string) []string { for _, existing := range slice { if existing == s { return slice } } return append(slice, s) }