335 lines
8.6 KiB
Go
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")
|
|
}
|
|
}
|