Files
judge/runner/expander_test.go
Mikhail Kornilovich 1ef3973a15
All checks were successful
go-test / go test (push) Successful in 26s
Release / Build & publish (push) Successful in 59s
some fixes
2026-06-10 09:55:25 +03:00

335 lines
8.6 KiB
Go

package runner
import (
"os"
"path/filepath"
"strings"
"testing"
"github.com/Mond1c/judge/dsl"
)
func writeFile(t *testing.T, dir, name, content string) {
t.Helper()
path := filepath.Join(dir, name)
if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(path, []byte(content), 0644); err != nil {
t.Fatal(err)
}
}
func TestExpandGlobPairedStdioMode(t *testing.T) {
dir := t.TempDir()
writeFile(t, dir, "tests/01.in", "1 2 3\n")
writeFile(t, dir, "tests/01.ans", "6\n")
writeFile(t, dir, "tests/02.in", "10 20\n")
writeFile(t, dir, "tests/02.ans", "30\n")
cwd, _ := os.Getwd()
defer os.Chdir(cwd)
os.Chdir(dir)
tests, err := expandPattern(&dsl.Pattern{
InputGlob: "tests/*.in",
OutputGlob: "tests/*.ans",
})
if err != nil {
t.Fatal(err)
}
if len(tests) != 2 {
t.Fatalf("expected 2 tests, got %d", len(tests))
}
for _, tc := range tests {
if tc.Stdin == nil {
t.Errorf("%s: stdin should be set in stdio mode", tc.Name)
}
if len(tc.Args) != 0 {
t.Errorf("%s: args should be empty without template", tc.Name)
}
if _, ok := tc.Stdout.(dsl.ExactMatcher); !ok {
t.Errorf("%s: stdout should be ExactMatcher, got %T", tc.Name, tc.Stdout)
}
}
}
func TestExpandGlobWithSharedOutput(t *testing.T) {
dir := t.TempDir()
writeFile(t, dir, "tests/01.in", "1\n")
writeFile(t, dir, "tests/02.in", "2\n")
writeFile(t, dir, "tests/03.in", "3\n")
writeFile(t, dir, "expected.ans", "ok\n")
cwd, _ := os.Getwd()
defer os.Chdir(cwd)
os.Chdir(dir)
tests, err := expandPattern(&dsl.Pattern{
InputGlob: "tests/*.in",
OutputGlob: "expected.ans",
})
if err != nil {
t.Fatalf("expand: %v", err)
}
if len(tests) != 3 {
t.Fatalf("expected 3 tests, got %d", len(tests))
}
for _, tc := range tests {
m, ok := tc.Stdout.(dsl.ExactMatcher)
if !ok {
t.Fatalf("%s: stdout should be ExactMatcher, got %T", tc.Name, tc.Stdout)
}
if m.Value != "ok\n" {
t.Errorf("%s: shared output = %q, want %q", tc.Name, m.Value, "ok\n")
}
}
}
func TestExpandGlobFileModeInputOnly(t *testing.T) {
dir := t.TempDir()
writeFile(t, dir, "tests/01.in", "hello\n")
writeFile(t, dir, "tests/01.ans", "HELLO\n")
cwd, _ := os.Getwd()
defer os.Chdir(cwd)
os.Chdir(dir)
tests, err := expandPattern(&dsl.Pattern{
InputGlob: "tests/*.in",
OutputGlob: "tests/*.ans",
Args: []string{"{input_path}"},
})
if err != nil {
t.Fatal(err)
}
if len(tests) != 1 {
t.Fatalf("expected 1 test, got %d", len(tests))
}
tc := tests[0]
if tc.Stdin != nil {
t.Errorf("stdin should be nil in file mode for input")
}
if content, ok := tc.InFiles["01.in"]; !ok || content != "hello\n" {
t.Errorf("InFiles[01.in] = %q, want %q", content, "hello\n")
}
if _, ok := tc.Stdout.(dsl.ExactMatcher); !ok {
t.Errorf("stdout should still be ExactMatcher when output not in file mode, got %T", tc.Stdout)
}
if len(tc.Args) != 1 || tc.Args[0] != "01.in" {
t.Errorf("args = %v, want [01.in]", tc.Args)
}
}
func TestExpandGlobFileModeBoth(t *testing.T) {
dir := t.TempDir()
writeFile(t, dir, "tests/01.in", "1 2 3\n")
writeFile(t, dir, "tests/01.ans", "6\n")
cwd, _ := os.Getwd()
defer os.Chdir(cwd)
os.Chdir(dir)
tests, err := expandPattern(&dsl.Pattern{
InputGlob: "tests/*.in",
OutputGlob: "tests/*.ans",
Args: []string{"{input_path}", "{output_path}"},
})
if err != nil {
t.Fatal(err)
}
tc := tests[0]
if tc.Stdin != nil {
t.Error("stdin should be nil")
}
if _, ok := tc.Stdout.(dsl.NoMatcher); !ok {
t.Errorf("stdout should be NoMatcher when output is file, got %T", tc.Stdout)
}
if content := tc.OutFiles["01.ans"]; content != "6\n" {
t.Errorf("OutFiles[01.ans] = %q, want %q", content, "6\n")
}
if len(tc.Args) != 2 || tc.Args[0] != "01.in" || tc.Args[1] != "01.ans" {
t.Errorf("args = %v, want [01.in 01.ans]", tc.Args)
}
}
func TestExpandGlobArgsWithStaticAndPlaceholders(t *testing.T) {
dir := t.TempDir()
writeFile(t, dir, "tests/01.in", "x\n")
writeFile(t, dir, "tests/01.ans", "x\n")
cwd, _ := os.Getwd()
defer os.Chdir(cwd)
os.Chdir(dir)
tests, err := expandPattern(&dsl.Pattern{
InputGlob: "tests/*.in",
OutputGlob: "tests/*.ans",
Args: []string{"--mode", "strict", "{input_path}"},
})
if err != nil {
t.Fatal(err)
}
if len(tests[0].Args) != 3 || tests[0].Args[0] != "--mode" || tests[0].Args[2] != "01.in" {
t.Errorf("args = %v", tests[0].Args)
}
}
func TestExpandDirModeWithArgs(t *testing.T) {
dir := t.TempDir()
writeFile(t, dir, "cases/a/input.txt", "one\n")
writeFile(t, dir, "cases/a/expected.txt", "ONE\n")
writeFile(t, dir, "cases/b/input.txt", "two\n")
writeFile(t, dir, "cases/b/expected.txt", "TWO\n")
cwd, _ := os.Getwd()
defer os.Chdir(cwd)
os.Chdir(dir)
tests, err := expandPattern(&dsl.Pattern{
DirsGlob: "cases/*",
InputFile: "input.txt",
OutputFile: "expected.txt",
Args: []string{"{input_path}", "{output_path}"},
})
if err != nil {
t.Fatal(err)
}
if len(tests) != 2 {
t.Fatalf("expected 2 tests, got %d", len(tests))
}
for _, tc := range tests {
if _, ok := tc.InFiles["input.txt"]; !ok {
t.Errorf("%s: missing InFiles[input.txt]", tc.Name)
}
if _, ok := tc.OutFiles["expected.txt"]; !ok {
t.Errorf("%s: missing OutFiles[expected.txt]", tc.Name)
}
if len(tc.Args) != 2 {
t.Errorf("%s: args = %v", tc.Name, tc.Args)
}
}
}
func TestExpandDirModeStageAll(t *testing.T) {
dir := t.TempDir()
// Two test directories, each with several input files the program discovers
// itself, plus an args file and an expected-stdout file.
writeFile(t, dir, "cases/test_1/1.txt", "10\n")
writeFile(t, dir, "cases/test_1/2.txt", "20\n")
writeFile(t, dir, "cases/test_1/decoy.log", "ignore me\n")
writeFile(t, dir, "cases/test_1/args.txt", "$.txt 1 1 2\n")
writeFile(t, dir, "cases/test_1/answer.txt", "10\n20\n")
writeFile(t, dir, "cases/test_2/a.txt", "x\n")
writeFile(t, dir, "cases/test_2/args.txt", "a.txt\n")
writeFile(t, dir, "cases/test_2/answer.txt", "x\n")
cwd, _ := os.Getwd()
defer os.Chdir(cwd)
os.Chdir(dir)
tests, err := expandPattern(&dsl.Pattern{
DirsGlob: "cases/*",
StageAll: true,
ArgsFile: "args.txt",
OutputFile: "answer.txt",
})
if err != nil {
t.Fatal(err)
}
if len(tests) != 2 {
t.Fatalf("expected 2 tests, got %d", len(tests))
}
var tc1 *dsl.Test
for _, tc := range tests {
if tc.Name == "pattern:test_1" {
tc1 = tc
}
}
if tc1 == nil {
t.Fatalf("test_1 not found among %v", tests)
}
// All input files staged, including the decoy; answer/args excluded.
for _, name := range []string{"1.txt", "2.txt", "decoy.log"} {
if _, ok := tc1.InFiles[name]; !ok {
t.Errorf("staged InFiles missing %q (have %v)", name, tc1.InFiles)
}
}
if _, ok := tc1.InFiles["answer.txt"]; ok {
t.Error("answer.txt must not be staged")
}
if _, ok := tc1.InFiles["args.txt"]; ok {
t.Error("args.txt must not be staged")
}
// Args read from args.txt, whitespace-split.
want := []string{"$.txt", "1", "1", "2"}
if strings.Join(tc1.Args, " ") != strings.Join(want, " ") {
t.Errorf("args = %v, want %v", tc1.Args, want)
}
// Expected stdout is the answer file content.
m, ok := tc1.Stdout.(dsl.ExactMatcher)
if !ok {
t.Fatalf("stdout should be ExactMatcher, got %T", tc1.Stdout)
}
if m.Value != "10\n20\n" {
t.Errorf("stdout = %q, want %q", m.Value, "10\n20\n")
}
// A successful functional test must exit zero.
if tc1.ExitCode == nil || *tc1.ExitCode != 0 {
t.Errorf("ExitCode = %v, want 0", tc1.ExitCode)
}
}
func TestExpandStageAllRejectedInGlobMode(t *testing.T) {
_, err := expandPattern(&dsl.Pattern{
InputGlob: "tests/*.in",
StageAll: true,
})
if err == nil || !strings.Contains(err.Error(), "stage") {
t.Fatalf("expected stage-only-with-dirs error, got %v", err)
}
}
func TestExpandArgsFileRejectedInGlobMode(t *testing.T) {
_, err := expandPattern(&dsl.Pattern{
InputGlob: "tests/*.in",
ArgsFile: "args.txt",
})
if err == nil || !strings.Contains(err.Error(), "args_file") {
t.Fatalf("expected args_file-only-with-dirs error, got %v", err)
}
}
func TestExpandPatternRejectsAllLiterals(t *testing.T) {
_, err := expandPattern(&dsl.Pattern{
InputGlob: "tests/a.in",
OutputGlob: "tests/a.ans",
})
if err == nil {
t.Fatal("expected error when no glob fields")
}
if !strings.Contains(err.Error(), "glob") {
t.Errorf("error %q does not mention glob", err.Error())
}
}
func TestExpandPatternNoMatches(t *testing.T) {
dir := t.TempDir()
cwd, _ := os.Getwd()
defer os.Chdir(cwd)
os.Chdir(dir)
_, err := expandPattern(&dsl.Pattern{
InputGlob: "missing/*.in",
OutputGlob: "missing/*.ans",
})
if err == nil {
t.Fatal("expected error on zero matches")
}
}