131f860a94
Extrae test cases individuales con su código desde archivos _test. Go detecta func TestXxx, Python detecta def test_xxx, Bash soporta tres convenciones: test_xxx(){}, secciones === nombre ===, y comentarios # Test:.
134 lines
3.6 KiB
Go
134 lines
3.6 KiB
Go
package registry
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"regexp"
|
|
"strings"
|
|
)
|
|
|
|
// testCase represents a single test extracted from a test file.
|
|
type testCase struct {
|
|
Name string
|
|
Code string
|
|
}
|
|
|
|
// testPos marks the start of a test within a file.
|
|
type testPos struct {
|
|
name string
|
|
startLine int
|
|
}
|
|
|
|
// parseTestFile reads a test file and extracts individual test cases.
|
|
// Supports Go, Python, and Bash test formats.
|
|
func parseTestFile(path, lang string) ([]testCase, error) {
|
|
data, err := os.ReadFile(path)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("reading test file %s: %w", path, err)
|
|
}
|
|
content := string(data)
|
|
|
|
switch lang {
|
|
case "go":
|
|
return parseGoTests(content), nil
|
|
case "py":
|
|
return parsePythonTests(content), nil
|
|
case "bash":
|
|
return parseBashTests(content), nil
|
|
default:
|
|
return nil, nil
|
|
}
|
|
}
|
|
|
|
// parseGoTests extracts Go test functions (func TestXxx).
|
|
var goTestFuncRe = regexp.MustCompile(`(?m)^func\s+(Test\w+)\s*\(`)
|
|
|
|
func parseGoTests(content string) []testCase {
|
|
lines := strings.Split(content, "\n")
|
|
var positions []testPos
|
|
|
|
for i, line := range lines {
|
|
if m := goTestFuncRe.FindStringSubmatch(line); m != nil {
|
|
positions = append(positions, testPos{name: m[1], startLine: i})
|
|
}
|
|
}
|
|
|
|
return extractBlocks(lines, positions)
|
|
}
|
|
|
|
// parsePythonTests extracts Python test functions (def test_xxx).
|
|
var pyTestFuncRe = regexp.MustCompile(`(?m)^def\s+(test_\w+)\s*\(`)
|
|
|
|
func parsePythonTests(content string) []testCase {
|
|
lines := strings.Split(content, "\n")
|
|
var positions []testPos
|
|
|
|
for i, line := range lines {
|
|
if m := pyTestFuncRe.FindStringSubmatch(line); m != nil {
|
|
positions = append(positions, testPos{name: m[1], startLine: i})
|
|
}
|
|
}
|
|
|
|
return extractBlocks(lines, positions)
|
|
}
|
|
|
|
// parseBashTests extracts Bash test blocks.
|
|
// Tries three conventions in order:
|
|
// 1. test_xxx() { ... } — function-based tests
|
|
// 2. === section === — section headers (echo "=== name ===")
|
|
// 3. # Test: ... — comment-based test blocks
|
|
var bashTestFuncRe = regexp.MustCompile(`(?m)^(test_\w+)\s*\(\)\s*\{`)
|
|
var bashTestCommentRe = regexp.MustCompile(`(?m)^#\s*[Tt]est:\s*(.+)`)
|
|
var bashSectionRe = regexp.MustCompile(`(?i)^(?:echo\s+["'])?===\s*(\w[\w\s]*\w)\s*===["']?\s*$`)
|
|
|
|
func parseBashTests(content string) []testCase {
|
|
lines := strings.Split(content, "\n")
|
|
|
|
// Strategy 1: test_xxx() { ... }
|
|
var positions []testPos
|
|
for i, line := range lines {
|
|
if m := bashTestFuncRe.FindStringSubmatch(line); m != nil {
|
|
positions = append(positions, testPos{name: m[1], startLine: i})
|
|
}
|
|
}
|
|
if len(positions) > 0 {
|
|
return extractBlocks(lines, positions)
|
|
}
|
|
|
|
// Strategy 2: === section === headers
|
|
for i, line := range lines {
|
|
trimmed := strings.TrimSpace(line)
|
|
if m := bashSectionRe.FindStringSubmatch(trimmed); m != nil {
|
|
positions = append(positions, testPos{name: m[1], startLine: i})
|
|
}
|
|
}
|
|
if len(positions) > 0 {
|
|
return extractBlocks(lines, positions)
|
|
}
|
|
|
|
// Strategy 3: # Test: ... comments
|
|
for i, line := range lines {
|
|
if m := bashTestCommentRe.FindStringSubmatch(line); m != nil {
|
|
positions = append(positions, testPos{name: strings.TrimSpace(m[1]), startLine: i})
|
|
}
|
|
}
|
|
return extractBlocks(lines, positions)
|
|
}
|
|
|
|
// extractBlocks splits lines into code blocks based on test positions.
|
|
func extractBlocks(lines []string, positions []testPos) []testCase {
|
|
var tests []testCase
|
|
for i, pos := range positions {
|
|
endLine := len(lines)
|
|
if i+1 < len(positions) {
|
|
endLine = positions[i+1].startLine
|
|
}
|
|
for endLine > pos.startLine && strings.TrimSpace(lines[endLine-1]) == "" {
|
|
endLine--
|
|
}
|
|
code := strings.Join(lines[pos.startLine:endLine], "\n")
|
|
tests = append(tests, testCase{Name: pos.name, Code: code})
|
|
}
|
|
return tests
|
|
}
|