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") }