| 1 | package main |
| 2 | |
| 3 | import ( |
| 4 | "bytes" |
| 5 | "io" |
| 6 | "os" |
| 7 | "os/exec" |
| 8 | "path/filepath" |
| 9 | "strings" |
| 10 | ) |
| 11 | |
| 12 | // graderNoteLimit bounds the grader excerpt kept on a result: enough for the |
| 13 | // "want X, got Y" line a grader leads with, never a whole test log. |
| 14 | const graderNoteLimit = 300 |
| 15 | |
| 16 | func appendNote(existing, add string) string { |
| 17 | if existing == "" { |
| 18 | return add |
| 19 | } |
| 20 | return existing + "; " + add |
| 21 | } |
| 22 | |
| 23 | func utf8Prefix(s string, limit int) string { |
| 24 | r := []rune(s) |
| 25 | if len(r) <= limit { |
| 26 | return s |
| 27 | } |
| 28 | return string(r[:limit]) + "…" |
| 29 | } |
| 30 | |
| 31 | func grade(work, taskDir string) bool { |
| 32 | passed, _ := gradeVerbose(work, taskDir) |
| 33 | return passed |
| 34 | } |
| 35 | |
| 36 | // gradeVerbose also returns what the grader printed. A failed task is only |
| 37 | // diagnosable if the record says what the agent actually produced, not merely |
| 38 | // that it was wrong — "answer.txt normalized to 'x', want 'y'" is the whole |
| 39 | // finding for an exploration task. |
| 40 | func gradeVerbose(work, taskDir string) (bool, string) { |
| 41 | verify := filepath.Join(taskDir, "verify.sh") |
| 42 | if !fileExists(verify) { |
| 43 | return false, "" |
| 44 | } |
| 45 | dst := filepath.Join(work, "verify.sh") |
| 46 | if err := copyFile(verify, dst); err != nil { |
| 47 | return false, "" |
| 48 | } |
| 49 | cmd := exec.Command("bash", "verify.sh") |
| 50 | cmd.Dir = work |
| 51 | var buf bytes.Buffer |
| 52 | cmd.Stdout = io.MultiWriter(os.Stderr, &buf) |
| 53 | cmd.Stderr = io.MultiWriter(os.Stderr, &buf) |
| 54 | return cmd.Run() == nil, strings.TrimSpace(buf.String()) |
| 55 | } |
| 56 |