From 856d7935b5ef0ab2d64f13de199f8f81f5ea1cd2 Mon Sep 17 00:00:00 2001 From: Mikhail Kornilovich Date: Mon, 6 Apr 2026 14:28:51 +0300 Subject: [PATCH] fix aggregate --- .gitea/workflows/judge.yml | 29 ++++++++++--------- cmd/cli/main.go | 18 +++++++++++- reporter/reporter.go | 57 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 90 insertions(+), 14 deletions(-) diff --git a/.gitea/workflows/judge.yml b/.gitea/workflows/judge.yml index cbbc94e..7d378d6 100644 --- a/.gitea/workflows/judge.yml +++ b/.gitea/workflows/judge.yml @@ -156,13 +156,27 @@ jobs: compression-level: 9 summary: - needs: [test] + needs: [build_judge, test] if: ${{ always() }} name: SUMMARY runs-on: Linux-Runner timeout-minutes: 5 steps: + - name: Download judge binary + uses: https://github.com/christopherHX/gitea-download-artifact@v4 + with: + name: judge-bin + path: judge-bin + + - name: Install judge on PATH + shell: bash + run: | + mkdir -p bin + cp judge-bin/judge-linux-amd64 bin/judge + chmod +x bin/judge + echo "$PWD/bin" >> "$GITHUB_PATH" + - name: Download all reports uses: https://github.com/christopherHX/gitea-download-artifact@v4 with: @@ -171,18 +185,7 @@ jobs: - name: Aggregate shell: bash - run: | - echo "# Judge results" > SUMMARY.md - echo "" >> SUMMARY.md - echo "| Configuration | Score |" >> SUMMARY.md - echo "|---|---|" >> SUMMARY.md - shopt -s nullglob - for f in reports/*/*.json; do - cfg=$(basename "$(dirname "$f")" | sed 's/^report_//') - score=$(grep -o '"total_score":[^,}]*' "$f" | head -1 | cut -d: -f2) - echo "| $cfg | ${score:- -} |" >> SUMMARY.md - done - cat SUMMARY.md + run: judge aggregate reports > SUMMARY.md - name: Upload summary uses: https://github.com/christopherHX/gitea-upload-artifact@v4 diff --git a/cmd/cli/main.go b/cmd/cli/main.go index c332957..6c38d72 100644 --- a/cmd/cli/main.go +++ b/cmd/cli/main.go @@ -14,6 +14,7 @@ const usage = `judge — CI/CD testing system for student solutions Usage: judge [flags] + judge aggregate Flags: --json output as JSON instead of text @@ -24,10 +25,15 @@ Flags: Example: judge lab1.jdg ./student-solution judge lab1.jdg ./student-solution --json - judge lab1.jdg ./student-solution --wrapper "valgrind --error-exitcode=99" + judge aggregate reports/ ` func main() { + if len(os.Args) >= 2 && os.Args[1] == "aggregate" { + runAggregate() + return + } + fs := flag.NewFlagSet("judge", flag.ContinueOnError) fs.SetOutput(os.Stderr) fs.Usage = func() { fmt.Fprint(os.Stderr, usage) } @@ -85,6 +91,16 @@ func main() { } } +func runAggregate() { + if len(os.Args) < 3 { + fatalf("usage: judge aggregate ") + } + dir := os.Args[2] + if err := reporter.Aggregate(os.Stdout, dir); err != nil { + fatalf("%v", err) + } +} + func fatalf(msg string, args ...any) { fmt.Fprintf(os.Stderr, "error: "+msg+"\n", args...) os.Exit(1) diff --git a/reporter/reporter.go b/reporter/reporter.go index a8eb517..271a775 100644 --- a/reporter/reporter.go +++ b/reporter/reporter.go @@ -4,6 +4,9 @@ import ( "encoding/json" "fmt" "io" + "os" + "path/filepath" + "sort" "strings" "github.com/Mond1c/judge/runner" @@ -74,6 +77,60 @@ type jsonTestResult struct { Failures []string `json:"failures,omitempty"` } +func Aggregate(w io.Writer, dir string) error { + files, err := filepath.Glob(filepath.Join(dir, "*", "*.json")) + if err != nil { + return fmt.Errorf("glob reports: %w", err) + } + if len(files) == 0 { + files, _ = filepath.Glob(filepath.Join(dir, "*.json")) + } + if len(files) == 0 { + return fmt.Errorf("no JSON reports found in %s", dir) + } + sort.Strings(files) + + type entry struct { + Config string + Score float64 + } + + var entries []entry + allPassed := true + + for _, f := range files { + data, err := os.ReadFile(f) + if err != nil { + return fmt.Errorf("read %s: %w", f, err) + } + var report jsonSuiteResult + if err := json.Unmarshal(data, &report); err != nil { + return fmt.Errorf("parse %s: %w", f, err) + } + + cfg := filepath.Base(filepath.Dir(f)) + cfg = strings.TrimPrefix(cfg, "report_") + + entries = append(entries, entry{Config: cfg, Score: report.TotalScore}) + if report.TotalScore < 0.9999 { + allPassed = false + } + } + + fmt.Fprintln(w, "# Judge results") + fmt.Fprintln(w) + fmt.Fprintln(w, "| Configuration | Score |") + fmt.Fprintln(w, "|---|---|") + for _, e := range entries { + fmt.Fprintf(w, "| %s | %.4f |\n", e.Config, e.Score) + } + + if !allPassed { + return fmt.Errorf("one or more configurations scored below 1.0") + } + return nil +} + func jsonResult(r *runner.SuiteResult) jsonSuiteResult { res := jsonSuiteResult{ TotalScore: r.TotalScore,