diff --git a/runner/normalize_test.go b/runner/normalize_test.go new file mode 100644 index 0000000..bdef334 --- /dev/null +++ b/runner/normalize_test.go @@ -0,0 +1,96 @@ +package runner + +import ( + "os" + "path/filepath" + "testing" + + "github.com/Mond1c/judge/dsl" +) + +// Under a wrapper, only args that name a staged file become absolute; numeric +// and flag args must be passed through verbatim (regression: a numeric arg "1" +// was being turned into "/1", breaking programs that take non-path +// arguments). +func TestAbsoluteArgsOnlyRewritesStagedFiles(t *testing.T) { + dir := t.TempDir() + if err := os.WriteFile(filepath.Join(dir, "01.in"), []byte("x"), 0644); err != nil { + t.Fatal(err) + } + + got := absoluteArgs(dir, []string{"$.txt", "1", "1", "3", "01.in", "--flag"}) + want := []string{"$.txt", "1", "1", "3", filepath.Join(dir, "01.in"), "--flag"} + for i := range want { + if got[i] != want[i] { + t.Errorf("arg[%d] = %q, want %q", i, got[i], want[i]) + } + } +} + +// A staged/expected value loaded from disk on Windows can carry CRLF. With +// normalize_crlf the runner must normalize the EXPECTED side too, otherwise it +// can never match the LF-normalized actual output. +func TestNormalizeExpectationsCRLF(t *testing.T) { + r := &Runner{file: &dsl.File{NormalizeCRLF: true}} + + test := &dsl.Test{ + Stdout: dsl.ExactMatcher{Value: "1\r\n2\r\n"}, + Stderr: dsl.NoMatcher{}, + OutFiles: map[string]string{"out.txt": "a\r\nb\r\n"}, + } + r.normalizeExpectations(test) + + m, ok := test.Stdout.(dsl.ExactMatcher) + if !ok { + t.Fatalf("stdout matcher type changed: %T", test.Stdout) + } + if m.Value != "1\n2\n" { + t.Errorf("expected stdout normalized to %q, got %q", "1\n2\n", m.Value) + } + if got := test.OutFiles["out.txt"]; got != "a\nb\n" { + t.Errorf("expected outfile normalized to %q, got %q", "a\nb\n", got) + } + + // And the normalized expected must match normalized actual end-to-end. + if fails := test.Stdout.Match("stdout", normalizeOutput("1\r\n2\r\n", r.file)); len(fails) != 0 { + t.Errorf("normalized expected should match normalized actual, got %v", fails) + } +} + +func TestNormalizeExpectationsTrimTrailingWS(t *testing.T) { + r := &Runner{file: &dsl.File{TrimTrailingWS: true}} + test := &dsl.Test{ + Stdout: dsl.AnyOrderMatcher{Lines: []string{"foo ", "bar\t"}}, + Stderr: dsl.NoMatcher{}, + } + r.normalizeExpectations(test) + + m := test.Stdout.(dsl.AnyOrderMatcher) + if m.Lines[0] != "foo" || m.Lines[1] != "bar" { + t.Errorf("anyOrder lines not trimmed: %q", m.Lines) + } +} + +// Regex matchers must never be rewritten: a literal \r in the pattern is +// meaningful and normalization would corrupt it. +func TestNormalizeExpectationsLeavesRegexAlone(t *testing.T) { + r := &Runner{file: &dsl.File{NormalizeCRLF: true}} + test := &dsl.Test{ + Stdout: dsl.RegexMatcher{Pattern: "a\r\nb"}, + Stderr: dsl.NoMatcher{}, + } + r.normalizeExpectations(test) + if m := test.Stdout.(dsl.RegexMatcher); m.Pattern != "a\r\nb" { + t.Errorf("regex pattern was modified: %q", m.Pattern) + } +} + +func TestNormalizeExpectationsNoopWhenDisabled(t *testing.T) { + r := &Runner{file: &dsl.File{}} + want := "1\r\n2\r\n" + test := &dsl.Test{Stdout: dsl.ExactMatcher{Value: want}, Stderr: dsl.NoMatcher{}} + r.normalizeExpectations(test) + if m := test.Stdout.(dsl.ExactMatcher); m.Value != want { + t.Errorf("value changed with normalization disabled: %q", m.Value) + } +} diff --git a/runner/runner.go b/runner/runner.go index 6a35abb..b20f7b8 100644 --- a/runner/runner.go +++ b/runner/runner.go @@ -589,14 +589,22 @@ func (r *Runner) runTest(t *dsl.Test) *TestResult { return tr } +// absoluteArgs rewrites only those arguments that name a file staged in the +// test's working directory to an absolute path. This helps wrappers (gdb, +// valgrind) that may resolve paths from a different directory, without +// corrupting non-path arguments — a numeric arg like "1" must stay "1", not +// become "/1". The process runs with Dir=dir anyway, so relative paths +// already resolve there; this is purely a safety net for genuine file args. func absoluteArgs(dir string, args []string) []string { out := make([]string, len(args)) for i, a := range args { - if !filepath.IsAbs(a) { - out[i] = filepath.Join(dir, a) - } else { - out[i] = a + if a != "" && !filepath.IsAbs(a) { + if _, err := os.Stat(filepath.Join(dir, a)); err == nil { + out[i] = filepath.Join(dir, a) + continue + } } + out[i] = a } return out }