Add comprehensive test system for Go-to-Zig compiler
- Implement end-to-end test runner for compilation and behavior tests - Add test cases for basic print functionality - Refactor translator to use proper AST generation - Remove redundant programs directory in favor of tests
This commit is contained in:
228
cmd/testrunner/main.go
Normal file
228
cmd/testrunner/main.go
Normal file
@@ -0,0 +1,228 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type TestResult struct {
|
||||
Name string
|
||||
Passed bool
|
||||
Output string
|
||||
Error string
|
||||
Elapsed time.Duration
|
||||
}
|
||||
|
||||
type TestCase struct {
|
||||
GoFile string
|
||||
ExpectedFile string
|
||||
ErrorFile string
|
||||
StdinFile string
|
||||
ArgsFile string
|
||||
}
|
||||
|
||||
func main() {
|
||||
// Find all test cases
|
||||
testCases, err := findTestCases("tests")
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error finding tests: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if len(testCases) == 0 {
|
||||
fmt.Println("No tests found")
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
fmt.Printf("Running %d tests...\n\n", len(testCases))
|
||||
|
||||
// Run tests in parallel
|
||||
results := runTests(testCases)
|
||||
|
||||
// Print results
|
||||
passed := 0
|
||||
failed := 0
|
||||
for _, result := range results {
|
||||
if result.Passed {
|
||||
fmt.Printf("✓ %s (%v)\n", result.Name, result.Elapsed)
|
||||
passed++
|
||||
} else {
|
||||
fmt.Printf("✗ %s (%v)\n", result.Name, result.Elapsed)
|
||||
fmt.Printf(" Error: %s\n", result.Error)
|
||||
if result.Output != "" {
|
||||
fmt.Printf(" Output:\n%s\n", indent(result.Output, " "))
|
||||
}
|
||||
failed++
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Printf("\n%d passed, %d failed\n", passed, failed)
|
||||
|
||||
if failed > 0 {
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func findTestCases(dir string) ([]TestCase, error) {
|
||||
var testCases []TestCase
|
||||
|
||||
err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if strings.HasSuffix(path, ".go") {
|
||||
base := strings.TrimSuffix(path, ".go")
|
||||
testCase := TestCase{
|
||||
GoFile: path,
|
||||
ExpectedFile: base + ".expected",
|
||||
ErrorFile: base + ".error",
|
||||
StdinFile: base + ".stdin",
|
||||
ArgsFile: base + ".args",
|
||||
}
|
||||
|
||||
// Check if either .expected or .error exists
|
||||
if fileExists(testCase.ExpectedFile) || fileExists(testCase.ErrorFile) {
|
||||
testCases = append(testCases, testCase)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
return testCases, err
|
||||
}
|
||||
|
||||
func runTests(testCases []TestCase) []TestResult {
|
||||
results := make([]TestResult, len(testCases))
|
||||
var wg sync.WaitGroup
|
||||
|
||||
for i, tc := range testCases {
|
||||
wg.Add(1)
|
||||
go func(idx int, testCase TestCase) {
|
||||
defer wg.Done()
|
||||
results[idx] = runTest(testCase)
|
||||
}(i, tc)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
return results
|
||||
}
|
||||
|
||||
func runTest(tc TestCase) TestResult {
|
||||
start := time.Now()
|
||||
testName := filepath.Base(strings.TrimSuffix(tc.GoFile, ".go"))
|
||||
|
||||
result := TestResult{
|
||||
Name: testName,
|
||||
Elapsed: time.Since(start),
|
||||
}
|
||||
|
||||
// Create temporary directory for test outputs
|
||||
tempDir, err := os.MkdirTemp("", "go-zig-test-*")
|
||||
if err != nil {
|
||||
result.Error = fmt.Sprintf("Failed to create temp dir: %v", err)
|
||||
return result
|
||||
}
|
||||
defer os.RemoveAll(tempDir)
|
||||
|
||||
zigFile := filepath.Join(tempDir, testName+".zig")
|
||||
|
||||
// Compile Go to Zig
|
||||
compileCmd := exec.Command("go", "run", "./internal/main.go", "-o", zigFile, tc.GoFile)
|
||||
var compileOut bytes.Buffer
|
||||
compileCmd.Stderr = &compileOut
|
||||
compileCmd.Stdout = &compileOut
|
||||
|
||||
if err := compileCmd.Run(); err != nil {
|
||||
// Check if this is an expected compilation error
|
||||
if fileExists(tc.ErrorFile) {
|
||||
expectedError, _ := os.ReadFile(tc.ErrorFile)
|
||||
actualError := strings.TrimSpace(compileOut.String())
|
||||
expectedErrorStr := strings.TrimSpace(string(expectedError))
|
||||
|
||||
if actualError == expectedErrorStr {
|
||||
result.Passed = true
|
||||
} else {
|
||||
result.Error = fmt.Sprintf("Expected error:\n%s\n\nActual error:\n%s", expectedErrorStr, actualError)
|
||||
}
|
||||
} else {
|
||||
result.Error = fmt.Sprintf("Compilation failed: %v\nOutput: %s", err, compileOut.String())
|
||||
}
|
||||
result.Elapsed = time.Since(start)
|
||||
return result
|
||||
}
|
||||
|
||||
// If we expected a compilation error but didn't get one
|
||||
if fileExists(tc.ErrorFile) {
|
||||
result.Error = "Expected compilation to fail, but it succeeded"
|
||||
result.Elapsed = time.Since(start)
|
||||
return result
|
||||
}
|
||||
|
||||
// Run the Zig program
|
||||
runCmd := exec.Command("zig", "run", zigFile)
|
||||
|
||||
// Set up stdin if provided
|
||||
if fileExists(tc.StdinFile) {
|
||||
stdinData, _ := os.ReadFile(tc.StdinFile)
|
||||
runCmd.Stdin = bytes.NewReader(stdinData)
|
||||
}
|
||||
|
||||
// Set up args if provided
|
||||
if fileExists(tc.ArgsFile) {
|
||||
argsData, _ := os.ReadFile(tc.ArgsFile)
|
||||
args := strings.Fields(string(argsData))
|
||||
runCmd.Args = append(runCmd.Args, args...)
|
||||
}
|
||||
|
||||
// Capture both stdout and stderr (Zig's debug.print goes to stderr)
|
||||
output, err := runCmd.CombinedOutput()
|
||||
if err != nil {
|
||||
result.Error = fmt.Sprintf("Execution failed: %v\nOutput: %s", err, string(output))
|
||||
result.Output = string(output)
|
||||
result.Elapsed = time.Since(start)
|
||||
return result
|
||||
}
|
||||
|
||||
// Compare output with expected
|
||||
if fileExists(tc.ExpectedFile) {
|
||||
expectedOutput, _ := os.ReadFile(tc.ExpectedFile)
|
||||
actualOutput := string(output)
|
||||
|
||||
if actualOutput == string(expectedOutput) {
|
||||
result.Passed = true
|
||||
} else {
|
||||
result.Error = fmt.Sprintf("Output mismatch.\nExpected:\n%s\nActual:\n%s",
|
||||
string(expectedOutput), actualOutput)
|
||||
result.Output = actualOutput
|
||||
}
|
||||
} else {
|
||||
// No expected file, just check it runs without error
|
||||
result.Passed = true
|
||||
}
|
||||
|
||||
result.Elapsed = time.Since(start)
|
||||
return result
|
||||
}
|
||||
|
||||
func fileExists(path string) bool {
|
||||
_, err := os.Stat(path)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
func indent(s string, prefix string) string {
|
||||
lines := strings.Split(s, "\n")
|
||||
for i, line := range lines {
|
||||
if line != "" {
|
||||
lines[i] = prefix + line
|
||||
}
|
||||
}
|
||||
return strings.Join(lines, "\n")
|
||||
}
|
Reference in New Issue
Block a user