This commit is contained in:
2026-04-05 18:20:42 +03:00
commit 32737ee6d6
18 changed files with 2719 additions and 0 deletions

View File

@@ -0,0 +1,148 @@
name: judge
run-name: "Sum tests (${{ inputs.student_url || github.repository }})"
on:
push:
pull_request:
workflow_dispatch:
inputs:
student_url:
description: "Student repo (owner/repo), leave empty to use current repo"
required: false
type: string
default: ""
student_ref:
description: "Ref (branch / tag / SHA)"
required: false
type: string
default: "main"
env:
SUITE_FILE: sum.jdg
SOURCES_DIR: __sources__
jobs:
test:
name: "${{ matrix.toolchain.system }} / ${{ matrix.toolchain.use_compiler }} / ${{ matrix.toolchain.build_type }}${{ matrix.toolchain.wrapper != 'no' && format(' ({0})', matrix.toolchain.wrapper) || '' }}"
strategy:
fail-fast: false
matrix:
toolchain:
- { system: Linux, use_compiler: gcc, build_type: Release, cflags: "-O2", wrapper: no, timeout_factor: 1.0 }
- { system: Linux, use_compiler: gcc, build_type: Debug, cflags: "-O0 -g", wrapper: no, timeout_factor: 2.5 }
- { system: Linux, use_compiler: gcc, build_type: Sanitized, cflags: "-O1 -g -fsanitize=address,undefined", wrapper: no, timeout_factor: 2.5 }
- { system: Linux, use_compiler: gcc, build_type: Debug, cflags: "-O0 -g", wrapper: valgrind, timeout_factor: 5.0 }
- { system: Linux, use_compiler: clang, build_type: Release, cflags: "-O2", wrapper: no, timeout_factor: 1.0 }
- { system: Linux, use_compiler: clang, build_type: Sanitized, cflags: "-O1 -g -fsanitize=address,undefined", wrapper: no, timeout_factor: 2.5 }
- { system: Windows, use_compiler: clang, build_type: Release, cflags: "-O2", wrapper: no, timeout_factor: 2.0 }
- { system: Windows, use_compiler: clang, build_type: Debug, cflags: "-O0 -g", wrapper: no, timeout_factor: 3.0 }
- { system: Windows, use_compiler: msvc, build_type: Release, cflags: "/O2", wrapper: no, timeout_factor: 2.0 }
- { system: Windows, use_compiler: msvc, build_type: Debug, cflags: "/Od /Zi", wrapper: no, timeout_factor: 5.0 }
runs-on: ${{ matrix.toolchain.system }}-Runner
timeout-minutes: 10
env:
REPORT_NAME: "report_${{ matrix.toolchain.system }}_${{ matrix.toolchain.use_compiler }}_${{ matrix.toolchain.build_type }}_${{ matrix.toolchain.wrapper }}"
CC: ${{ matrix.toolchain.use_compiler }}
CFLAGS: ${{ matrix.toolchain.cflags }}
steps:
- name: Checkout judge harness
uses: actions/checkout@v4
- name: Checkout student sources
if: ${{ inputs.student_url != '' }}
uses: actions/checkout@v4
with:
repository: ${{ inputs.student_url }}
ref: ${{ inputs.student_ref }}
path: ${{ env.SOURCES_DIR }}
token: ${{ secrets.VAR_TOKEN }}
- name: Stage sources (self)
if: ${{ inputs.student_url == '' }}
shell: bash
run: |
mkdir -p "${SOURCES_DIR}"
cp solution.c "${SOURCES_DIR}/"
- name: Set up MSVC environment
if: matrix.toolchain.use_compiler == 'msvc'
uses: ilammy/msvc-dev-cmd@v1
- name: Install judge
shell: bash
run: |
go install github.com/Mond1c/judge/cmd/cli@latest
echo "$HOME/go/bin" >> "$GITHUB_PATH"
- name: Install valgrind
if: matrix.toolchain.wrapper == 'valgrind'
run: sudo apt-get update && sudo apt-get install -y valgrind
- name: Run judge
shell: bash
working-directory: ${{ env.SOURCES_DIR }}
env:
WRAPPER: ${{ matrix.toolchain.wrapper }}
run: |
cp ../${{ env.SUITE_FILE }} .
WRAPPER_ARG=""
case "$WRAPPER" in
valgrind) WRAPPER_ARG='--wrapper=valgrind --error-exitcode=99 --leak-check=full -q' ;;
no) WRAPPER_ARG="" ;;
*) WRAPPER_ARG="--wrapper=$WRAPPER" ;;
esac
# For MSVC the suffixed .exe is produced; runner auto-detects it.
judge ${{ env.SUITE_FILE }} . --json $WRAPPER_ARG > "$GITHUB_WORKSPACE/${REPORT_NAME}.json" \
|| echo "judge exited non-zero (expected when tests fail)"
judge ${{ env.SUITE_FILE }} . $WRAPPER_ARG || true
- name: Upload report
if: ${{ always() }}
uses: https://github.com/christopherHX/gitea-upload-artifact@v4
with:
name: ${{ env.REPORT_NAME }}
path: ${{ env.REPORT_NAME }}.json
retention-days: 7
compression-level: 9
summary:
needs: [test]
if: ${{ always() }}
name: SUMMARY
runs-on: Linux-Runner
timeout-minutes: 5
steps:
- name: Download all reports
uses: https://github.com/christopherHX/gitea-download-artifact@v4
with:
path: reports
pattern: report_*
- name: Aggregate
shell: bash
run: |
echo "# Judge results" > SUMMARY.md
echo "" >> SUMMARY.md
echo "| Configuration | Score |" >> SUMMARY.md
echo "|---|---|" >> SUMMARY.md
for f in reports/*/*.json; do
cfg=$(basename "$(dirname "$f")" | sed 's/^report_//')
score=$(grep -o '"TotalScore":[^,}]*' "$f" | head -1 | cut -d: -f2)
echo "| $cfg | $score |" >> SUMMARY.md
done
cat SUMMARY.md
- name: Upload summary
uses: https://github.com/christopherHX/gitea-upload-artifact@v4
with:
name: SUMMARY
path: SUMMARY.md
retention-days: 7

49
example/c-sum/README.md Normal file
View File

@@ -0,0 +1,49 @@
# c-sum — cross-platform C example
Minimal example: a C program that reads `N` then `N` integers and prints their
sum. Tested with `judge` across **gcc / clang / MSVC** on **Linux / Windows**,
with optional **valgrind** and **sanitizer** runs.
## Files
- `solution.c` — student-facing solution (could be what the student submits)
- `sum.jdg` — judge test suite
- `.gitea/workflows/judge.yml` — Gitea CI matrix
## Run locally
```sh
# Linux / macOS
CC=gcc judge sum.jdg .
CC=clang judge sum.jdg .
# With valgrind
judge sum.jdg . --wrapper="valgrind --error-exitcode=99 --leak-check=full -q"
# With ASan+UBSan build
CC=clang CFLAGS="-O1 -g -fsanitize=address,undefined" judge sum.jdg .
```
On Windows (inside an MSVC dev cmd shell), `build_windows` kicks in and
produces `solution.exe`, which the runner auto-detects.
## Notes about the `.jdg`
- `normalize_crlf = true` — Windows `printf` emits `\r\n`; we strip `\r` before
matching so the same expected outputs work on both platforms.
- `trim_trailing_ws = true` — forgives trailing spaces a student's output might
pick up (rare but annoying to debug).
- `binary = "solution"` — the runner tries `solution` first, then
`solution.exe` on Windows automatically.
- Per-group `scoring = all_or_none` on `stress` gives weight only if every
stress test passes.
## Adapting the Gitea workflow
- `runs-on: ${{ matrix.toolchain.system }}-Runner` assumes you have
self-hosted Gitea runners labelled `Linux-Runner` / `Windows-Runner` (same
naming as your existing `fixed_floating` pipeline).
- `secrets.VAR_TOKEN` is only needed when pulling a student repo from a
private org.
- The summary job shells `grep` over the JSON; swap to `jq` if available on
your runners.

16
example/c-sum/solution.c Normal file
View File

@@ -0,0 +1,16 @@
#include <stdio.h>
int main(void) {
int n;
if (scanf("%d", &n) != 1) return 1;
long long sum = 0;
for (int i = 0; i < n; i++) {
int x;
if (scanf("%d", &x) != 1) return 1;
sum += x;
}
printf("%lld\n", sum);
return 0;
}

75
example/c-sum/sum.jdg Normal file
View File

@@ -0,0 +1,75 @@
// Cross-platform C solution test suite.
// $CC is supplied by CI matrix (gcc / clang / cl).
//
// Run locally:
// CC=gcc judge sum.jdg .
// CC=clang judge sum.jdg .
//
// Under MSVC on CI we use build_windows (cl's CLI is different).
build "$CC -O2 -std=c11 -Wall -Wextra solution.c -o solution"
build_windows "cl /nologo /O2 /W3 solution.c /Fe:solution.exe"
binary = "solution"
timeout 5s
// Windows printf emits \r\n; normalize so tests are portable.
normalize_crlf = true
trim_trailing_ws = true
group("basic") {
weight = 0.4
timeout = 2s
test("one number") {
stdin = "1\n42\n"
stdout = "42\n"
}
test("three numbers") {
stdin = "3\n1 2 3\n"
stdout = "6\n"
}
test("negatives") {
stdin = "4\n-1 -2 3 5\n"
stdout = "5\n"
}
test("zero count") {
stdin = "0\n"
stdout = "0\n"
}
}
group("edge") {
weight = 0.3
test("large sum fits in int64") {
stdin = "3\n2000000000 2000000000 2000000000\n"
stdout = "6000000000\n"
}
test("multiline input") {
stdin = """
5
10
20
30
40
50
"""
stdout = "150\n"
}
}
group("stress") {
weight = 0.3
timeout = 3s
scoring = all_or_none
test("sum of 1..100") {
stdin = "100\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100\n"
stdout = "5050\n"
}
}

60
example/lab1.jdg Normal file
View File

@@ -0,0 +1,60 @@
build "go build -o solution ."
timeout 10s
group("basic") {
weight = 0.2
timeout = 2s
test("empty one element") {
stdin = "1\n42\n"
stdout = "42\n"
}
test("already sorted") {
stdin = "3\n1 2 3\n"
stdout = "1 2 3\n"
}
test("reverse order") {
stdin = "4\n4 3 2 1\n"
stdout = "1 2 3 4\n"
}
}
group("main") {
weight = 0.5
test("basic") {
stdin = "5\n1 3 2 5 4\n"
stdout = "1 2 3 4 5\n"
}
test("negative numbers") {
stdin = "5\n-3 1 -1 0 2\n"
stdout = "-3 -1 0 1 2\n"
}
test("same numbers") {
stdin = "4\n5 5 5 5\n"
stdout = "5 5 5 5\n"
}
test("multiline stdin") {
stdin = """
6
100 -50 0 75 -25 50
"""
stdout = "-50 -25 0 50 75 100\n"
}
}
group("file-pattern") {
weight = 0.3
timeout = 5s
pattern {
input = "testdata/*/input.txt"
output = "testdata/*/output.txt"
}
}

34
example/solution/main.go Normal file
View File

@@ -0,0 +1,34 @@
package main
import (
"bufio"
"fmt"
"os"
"sort"
"strconv"
"strings"
)
func main() {
scanner := bufio.NewScanner(os.Stdin)
scanner.Scan()
n, _ := strconv.Atoi(strings.TrimSpace(scanner.Text()))
scanner.Scan()
parts := strings.Fields(scanner.Text())
nums := make([]int, 0, n)
for _, p := range parts {
x, _ := strconv.Atoi(p)
nums = append(nums, x)
}
sort.Ints(nums)
out := make([]string, len(nums))
for i, v := range nums {
out[i] = strconv.Itoa(v)
}
fmt.Println(strings.Join(out, " "))
}