| 1 | package main |
| 2 | |
| 3 | import ( |
| 4 | "os" |
| 5 | "path/filepath" |
| 6 | "strings" |
| 7 | "testing" |
| 8 | ) |
| 9 | |
| 10 | // A failed task is only actionable if the record says what the agent actually |
| 11 | // produced. For an exploration task the grader's "want X, got Y" line is the |
| 12 | // entire finding — a bare false would leave every failure indistinguishable. |
| 13 | func TestGradeVerboseKeepsWhatTheGraderSaid(t *testing.T) { |
| 14 | taskDir := t.TempDir() |
| 15 | verify := "#!/usr/bin/env bash\necho \"answer.txt normalized to 'askrigg', want 'gorsefen'\" >&2\nexit 1\n" |
| 16 | if err := os.WriteFile(filepath.Join(taskDir, "verify.sh"), []byte(verify), 0o755); err != nil { |
| 17 | t.Fatal(err) |
| 18 | } |
| 19 | |
| 20 | passed, said := gradeVerbose(t.TempDir(), taskDir) |
| 21 | if passed { |
| 22 | t.Fatal("a grader exiting non-zero must not pass") |
| 23 | } |
| 24 | if !strings.Contains(said, "want 'gorsefen'") { |
| 25 | t.Fatalf("grader output lost: %q", said) |
| 26 | } |
| 27 | } |
| 28 | |
| 29 | func TestGradeVerboseStaysQuietWhenTheTaskPasses(t *testing.T) { |
| 30 | taskDir := t.TempDir() |
| 31 | if err := os.WriteFile(filepath.Join(taskDir, "verify.sh"), []byte("#!/usr/bin/env bash\nexit 0\n"), 0o755); err != nil { |
| 32 | t.Fatal(err) |
| 33 | } |
| 34 | if passed, said := gradeVerbose(t.TempDir(), taskDir); !passed || said != "" { |
| 35 | t.Fatalf("clean grade = %v, %q", passed, said) |
| 36 | } |
| 37 | } |
| 38 | |
| 39 | // The excerpt is bounded so a chatty grader cannot crowd out the report, and |
| 40 | // bounded on runes so a multi-byte log cannot be cut mid-character. |
| 41 | func TestUtf8PrefixBoundsOnRunes(t *testing.T) { |
| 42 | if got := utf8Prefix("short", 10); got != "short" { |
| 43 | t.Errorf("under the limit was altered: %q", got) |
| 44 | } |
| 45 | got := utf8Prefix("这是一个很长的输出", 4) |
| 46 | if []rune(got)[4] != '…' || len([]rune(got)) != 5 { |
| 47 | t.Errorf("utf8Prefix = %q, want 4 runes plus an ellipsis", got) |
| 48 | } |
| 49 | } |
| 50 | |
| 51 | func TestAppendNoteKeepsBothCauses(t *testing.T) { |
| 52 | if got := appendNote("", "grader: x"); got != "grader: x" { |
| 53 | t.Errorf("first note = %q", got) |
| 54 | } |
| 55 | // A run that both errored and failed grading must report each: the run |
| 56 | // error explains the grade, and dropping it hides why. |
| 57 | if got := appendNote("run: exit 1", "grader: want y"); !strings.Contains(got, "run: exit 1") || !strings.Contains(got, "grader: want y") { |
| 58 | t.Errorf("combined note lost a cause: %q", got) |
| 59 | } |
| 60 | } |
| 61 |