Skip to content

ci: GitHub Actions workflow for tests, style and judging (#80) - #81

Merged
matbrgz merged 2 commits into
masterfrom
feat/80-github-actions-ci
Sep 13, 2026
Merged

matbrgz merged 2 commits into
masterfrom
feat/80-github-actions-ci

Conversation

@matbrgz

@matbrgz matbrgz commented Sep 13, 2026

Copy link
Copy Markdown
Member

Closes #80.

Três jobs, separados pelo que cada um consegue de fato provar.

backend — PHP 8.3, composer cache, PHPUnit, Pint

--no-coverage não é otimização, é obrigatório. O phpunit.xml configura relatórios de <coverage>; sem driver instalado o PHPUnit aborta com No tests executed! e exit code 0. Sem a flag este job ficaria permanentemente verde sem ter rodado nada — descobri isso na marra enquanto testava a #77.

E2E fica de fora aqui porque essas suítes compilam e executam submissões reais e precisam das 14 toolchains + bubblewrap, que um runner pelado não tem. O job judge-image roda a suíte inteira onde isso existe. Sobram 658 testes.

Pint roda só nos arquivos que o PR adiciona. 278 dos 363 arquivos PHP do repo são anteriores ao Pint, então um --test no repo inteiro falha hoje — e mesmo um check em "todos os arquivos alterados" deixaria o PR vermelho por formatação que ele herdou em vez de escrever. Arquivo novo não tem essa desculpa. O comentário no workflow registra o caminho de saída: um ./vendor/bin/pint único na árvore, depois --diff-filter=ACMR, depois o check repo-wide simples.

judge-image — a suíte inteira dentro da imagem real do juiz

Builda o Dockerfile.judge contra o layer cache do GHA e roda tudo lá dentro, --privileged, do jeito que o docker-compose.yml roda o serviço autojudge.

É o job que responde a pergunta que mais importa num PR deste projeto: o julgamento ainda funciona? O --fail-on-skipped é o que torna a resposta confiável — as suítes de julgamento se auto-skipam quando não há sandbox utilizável, então sem ele um sandbox quebrado apareceria como build verde que silenciosamente não testou nada.

frontend — Node 22, npm cache, testes, build de produção

Node 22 não é arbitrário: tests/Frontend/vue-loader.js chama module.registerHooks(), que só existe a partir do Node 22.15. No Node 20 — que é o que o serviço node do docker-compose.yml ainda fixa — o features.test.js morre no import e 11 dos 26 testes nunca rodam, com a suíte ainda reportando sucesso. Vale reconciliar isso separadamente.

Verificado, não presumido

Rodei cada comando do workflow contra este commit antes de escrevê-lo:

resultado
phpunit --no-coverage --exclude-testsuite E2E (runner pelado) OK, 658 testes
phpunit --no-coverage --fail-on-skipped na imagem do juiz, --privileged OK, 739 testes, 3818 asserções
npm ci && npm run build no Linux OK
npm run test:frontend 26/26 no Node 22 vs 14/15 no Node 20
lógica do Pint por diff pula quando não há PHP novo; passa nos arquivos recentes

O build da imagem do juiz leva ~2 min localmente com cache quente; na primeira execução do CI paga os downloads do Kotlin (GitHub releases) e do Free Pascal (SourceForge). Se o SourceForge se mostrar instável, esse job é o candidato natural a virar schedule noturno em vez de rodar em todo PR.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Pvf2R8xtgbWQm1wRvjBqQY

Closes #80. Three jobs, split by what they can actually prove.

**backend** — PHP 8.3, composer cache, PHPUnit, Pint.

`--no-coverage` is load-bearing rather than an optimisation: phpunit.xml
configures <coverage> reports, and with no driver installed PHPUnit aborts
with "No tests executed!" and exit code 0. Without the flag this job would
be permanently, silently green having run nothing.

E2E is excluded here because those suites compile and run real
submissions and need the fourteen language toolchains plus bubblewrap; the
judge-image job runs them where that exists. 658 tests remain.

Pint is scoped to files a change *adds*. 278 of the repo's 363 PHP files
predate Pint, so a repo-wide --test fails today, and even an
all-changed-files check would redden a PR over formatting it inherited
rather than wrote. The comment records the path out: a one-off repo-wide
`pint`, then --diff-filter=ACMR, then the plain whole-repo check.

**judge-image** — builds Dockerfile.judge (the fourteen toolchains plus
bubblewrap) against the GHA layer cache and runs the whole suite inside
it, --privileged, the way docker-compose.yml runs the autojudge service.

This is the job that answers the question this project most needs
answered on a PR: does judging still work? `--fail-on-skipped` is what
makes the answer trustworthy -- the judging suites skip themselves when no
usable sandbox exists, so without it a broken sandbox reads as a green
build that quietly tested nothing.

**frontend** — Node 22, npm cache, frontend tests, production asset build.

Node 22 is not arbitrary: tests/Frontend/vue-loader.js calls
module.registerHooks(), which only exists from Node 22.15. On Node 20 --
which is what docker-compose.yml's node service still pins --
features.test.js dies at import time and eleven of the twenty-six tests
never run while the suite still reports success. Worth reconciling
separately.

Verified against this commit rather than assumed: the backend job's
command is green (658 tests), the judge-image command is green with
--fail-on-skipped (739 tests, 3818 assertions) in an image built from
Dockerfile.judge and run --privileged, `npm ci && npm run build` succeeds
on Linux, and the frontend suite is 26/26 on Node 22 against 14/15 on
Node 20.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pvf2R8xtgbWQm1wRvjBqQY
matbrgz added a commit that referenced this pull request Sep 13, 2026
The CI workflow proposed in #81 gates Laravel Pint on files a change
adds, which is everything new here: the exception, the test trait, the
confinement suite and the isolation suite. The pre-existing files this
branch also touches are left alone -- 278 of the repo's 363 PHP files
predate Pint, and reformatting them from here would bury the actual
change.

No behaviour change: 135 tests / 535 assertions still green in an image
built from Dockerfile.judge.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pvf2R8xtgbWQm1wRvjBqQY
The judge-image job failed its first real run with 241 errors, every one
of them a storage write: "Unable to create a directory at
/var/www/html/storage/...", "could not be opened", "Permission denied".

Dockerfile.judge runs as its own `judge` user (uid 1000) and the runner
owns the checkout as a different uid, so the mounted workspace is
effectively read-only to the test process. It does not reproduce on a
developer machine because Docker Desktop's bind mounts ignore uid
ownership.

Widening the directories the suite writes to is the targeted fix. The
alternative -- running the container as the runner's uid -- would stop
matching how docker-compose.yml runs the autojudge service, which is the
thing this job exists to verify.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pvf2R8xtgbWQm1wRvjBqQY
@matbrgz
matbrgz merged commit 6bc386f into master Sep 13, 2026
3 checks passed
@matbrgz
matbrgz deleted the feat/80-github-actions-ci branch September 13, 2026 18:01
matbrgz added a commit that referenced this pull request Sep 13, 2026
…) (#103)

Closes #101.

docker-compose pinned node:20-alpine and the frontend suite needs 22.15.
On Node 20, tests/Frontend/vue-loader.js dies at import --
module.registerHooks() does not exist there -- and features.test.js goes
with it. Measured:

    node:20   # tests 15   # pass 14   # fail 1
    node:22   # tests 26   # pass 26   # fail 0

Eleven tests missing, and `node --test` reports one failed file rather
than eleven absent tests. The total changes too, so nothing looks wrong
unless you already know the number.

CI has been on 22 since #81 and is green at 26/26, which is exactly the
split worth closing: a developer running `docker compose --profile dev up`
saw fourteen passing and had no reason to doubt it.

engines.node makes npm say so at install time, rather than leaving a
vanished test to be the signal.

Verified on node:22-alpine, the image the compose file now uses: 26/26.


Claude-Session: https://claude.ai/code/session_01Pvf2R8xtgbWQm1wRvjBqQY

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ci: Add GitHub Actions CI workflow for automated testing and code quality

1 participant