From f89292b57de7314df643870359d2ba2d1672884b Mon Sep 17 00:00:00 2001 From: ruinivist Date: Sat, 21 Mar 2026 13:29:12 +0000 Subject: [PATCH] feat: show actual vs expected on fail --- internal/mire/test.go | 30 ++++++++++++++++++++++++++++- internal/mire/test_test.go | 39 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 68 insertions(+), 1 deletion(-) diff --git a/internal/mire/test.go b/internal/mire/test.go index aef9414..b55bfc0 100644 --- a/internal/mire/test.go +++ b/internal/mire/test.go @@ -2,6 +2,7 @@ package mire import ( "bytes" + "errors" "fmt" "io" "io/fs" @@ -38,6 +39,15 @@ type testFixtureFiles struct { outPath string } +type testMismatchError struct { + expected []byte + actual []byte +} + +func (e *testMismatchError) Error() string { + return "output differed" +} + // RunTests replays all scenarios under the configured test directory. func RunTests(path string) error { return runTests(path, testIO{ @@ -89,6 +99,10 @@ func runTests(path string, tio testIO) error { elapsed := time.Since(start) summary.failed++ output.Fprintf(tio.out, "%s %s (%s): %v\n", output.Label("FAIL", output.Fail), scenario.relPath, formatElapsed(elapsed), err) + var mismatchErr *testMismatchError + if errors.As(err, &mismatchErr) { + writeScenarioMismatch(tio.out, mismatchErr) + } continue } @@ -256,12 +270,26 @@ func replayScenario(scenario testScenario, shellPath string, _ testIO, sandboxCo } if !bytes.Equal(got, want) { - return fmt.Errorf("output differed") + return &testMismatchError{ + expected: want, + actual: got, + } } return nil } +func writeScenarioMismatch(w io.Writer, mismatch *testMismatchError) { + output.Fprintf(w, "Expected:\n%s", string(mismatch.expected)) + if len(mismatch.expected) == 0 || mismatch.expected[len(mismatch.expected)-1] != '\n' { + output.Fprintf(w, "\n") + } + output.Fprintf(w, "Actual:\n%s", string(mismatch.actual)) + if len(mismatch.actual) == 0 || mismatch.actual[len(mismatch.actual)-1] != '\n' { + output.Fprintf(w, "\n") + } +} + func trimReplayOutputToMarker(data []byte, shellPath string) ([]byte, error) { idx := bytes.Index(data, []byte(compareOutputMarker)) if idx == -1 { diff --git a/internal/mire/test_test.go b/internal/mire/test_test.go index ed98d7b..1398bf1 100644 --- a/internal/mire/test_test.go +++ b/internal/mire/test_test.go @@ -157,6 +157,45 @@ func TestReplayScenarioFailsWhenCompareMarkerMissing(t *testing.T) { } } +func TestRunTestsShowsExpectedAndActualForMismatchedScenario(t *testing.T) { + root := t.TempDir() + testDir := filepath.Join(root, "e2e") + testutil.AddFakeRecordDependencies(t, "script") + t.Setenv("FAKE_SCRIPT_ECHO_STDIN", "1") + + testutil.WriteFile(t, filepath.Join(root, "mire.toml"), testutil.ValidConfigContent("e2e")) + mustWriteRecordShell(t, testDir) + testutil.WriteScenarioFixtures(t, filepath.Join(testDir, "suite", "spec"), "echo two\n", "echo one\n") + + var stdout bytes.Buffer + var stderr bytes.Buffer + err := testutil.WithWorkingDir(t, root, func() error { + return runTests("", testIO{ + out: &stdout, + err: &stderr, + }) + }) + if err != nil { + t.Fatalf("runTests() error = %v", err) + } + if stderr.String() != "" { + t.Fatalf("stderr = %q, want empty", stderr.String()) + } + + for _, want := range []string{ + "RUN suite/spec", + "FAIL suite/spec (", + "output differed", + "Expected:\necho one\n", + "Actual:\necho two\n", + "Summary: total=1 passed=0 failed=1", + } { + if !strings.Contains(stdout.String(), want) { + t.Fatalf("stdout = %q, want substring %q", stdout.String(), want) + } + } +} + func TestDiscoverTestScenariosUsesDisplayRootForRelativePaths(t *testing.T) { testDir := filepath.Join(t.TempDir(), "e2e") scopedDir := filepath.Join(testDir, "nested")