Skip to content

ci(quality): wire Stryker mutation testing as advisory nightly (Fase 7 Task 11) - #3898

Merged
diegosouzapw merged 1 commit into
release/v3.8.26from
feat/gate-stryker-nightly
Jun 15, 2026
Merged

diegosouzapw merged 1 commit into
release/v3.8.26from
feat/gate-stryker-nightly

Conversation

@diegosouzapw

Copy link
Copy Markdown
Collaborator

O quê

Liga o Stryker (mutation testing) como job nightly advisory sobre os 8 módulos críticos — fecha o gap #2 da auditoria de completude (config existia mas estava órfã, sem script/job).

Decisão técnica: runner = tap-runner (não vitest)

Os 8 módulos críticos (chatCore/combo/accountFallback/auth/routeGuard/error/publicCreds/circuitBreaker) são cobertos por node:test, não vitest (vitest cobre só MCP/autoCombo/cache). Com vitest-runner o Stryker acharia zero testes cobrindo. Config ajustada para @stryker-mutator/tap-runner rodando os testes node:test via tsx.

Mudanças

  • stryker.conf.json: runner tap + comando node:test (tsx + polyfill); mutate = 8 módulos; ignorePatterns corrigido (não excluir tests/ — o Stryker sandboxa); tap.testFiles = união explícita das 129 specs que cobrem os 8 módulos (11 podadas: timing/heap/streaming-sensitive + introspecção-de-source incompatível com mutação por design).
  • package.json: devDeps @stryker-mutator/core + @stryker-mutator/tap-runner + script test:mutation.
  • .github/workflows/nightly-mutation.yml: schedule 17 3 * * * (cron livre) + workflow_dispatch; advisory (continue-on-error); timeout-minutes: 120; upload do report. Não roda em PR.
  • config/quality/dependency-allowlist.json: + 2 entradas stryker (o gate check-deps exige).
  • .gitignore: artefatos .stryker-tmp/, reports/mutation/.

Validação

  • Dry-run end-to-end verde (npx stryker --dryRunOnly): Initial test run succeeded. Ran 129 tests → instrumentou 8 source file(s) with 15261 mutant(s). Run estreito (error.ts) matou/sobreviveu mutantes reais (61%, 35 survived).
  • check:deps OK (entradas aceitas) · check:lockfile OK · YAML válido · sem secrets em if: de job.

Status honesto

O nightly produz scores reais para os 8 módulos. Como a fase de mutação completa (15k+ mutantes) excede o dry-run de 20min, o job é advisory com cap de 120min → produz report parcial, não falha. A catraca de mutationScore.<módulo> fica para a fase INT (depois do 1º nightly calibrar). Não gera versão.

…7 Task 11)

Wire Stryker mutation testing over the 8 critical modules as an ADVISORY,
NIGHTLY-ONLY job. It never runs on PRs and the mutation score is not yet
enforced as a ratchet (that lands in a later INT phase) — for now the nightly
just runs and uploads the HTML/JSON report as an artifact.

Test runner — @stryker-mutator/tap-runner (NOT vitest)
------------------------------------------------------
The 8 modules (chatCore, combo, accountFallback, src/sse/services/auth,
routeGuard, error, publicCreds, circuitBreaker) are covered by node:test files
in tests/unit/ (run via `node --import tsx --test`), NOT by vitest. The vitest
config only includes a small set of .test.tsx + open-sse/**/__tests__ files, so
@stryker-mutator/vitest-runner would find ZERO covering tests for them. Switched
testRunner to "tap" and configured tap.nodeArgs to load tsx + the project
polyfill exactly as `npm run test:unit` does, so each node:test file runs under
the runner and its TAP output is parsed.

Key calibration findings (each fixed):
  - ignorePatterns must NOT exclude tests/ — Stryker copies the project to a
    sandbox and runs the runner there; excluding tests means the tap glob finds
    nothing ("No tests were found"). Scoped ignorePatterns to heavy, irrelevant
    trees only (build output, coverage, docs/i18n, other worktrees).
  - tap.testFiles enumerates the explicit covering union (files importing any of
    the 8 modules), not a broad glob, so the dry-run stays tractable. A glob over
    the full ~1300-file unit suite would make the per-test dry-run take hours.
  - Pruned 11 files from the union: 7 timing/heap/streaming-sensitive integration
    tests (flake under the sandbox) and 5 source-introspection tests that
    readFileSync the source of a mutated module and assert on its content (those
    are incompatible with mutation by design — the sandbox content is mutated).
  - concurrency=1 + dryRunTimeoutMinutes=30 for a deterministic baseline.

Validated (NOT a faked run)
---------------------------
  - `npx stryker run --dryRunOnly` exits 0: all 129 covering test files run
    GREEN in the Stryker sandbox and the perTest coverage map builds for all 8
    instrumented modules (15,261 mutants). Baseline dry-run ~20 min at
    concurrency=1; the mutation phase runs on top. So the nightly produces REAL
    mutation scores for the 8 modules.
  - A narrowed end-to-end run (error.ts + 2 covering files) reached 61% with
    mutants killed/surviving, confirming scoring works.
  - `npm run check:deps` and `check-lockfile` accept the two new devDeps.
  - nightly-mutation.yml parses as valid YAML.

Changes
-------
  - devDeps @stryker-mutator/core + @stryker-mutator/tap-runner (real npm install)
  - dependency-allowlist.json: both packages added
  - package.json: "test:mutation": "stryker run"
  - stryker.conf.json: tap runner + 129-file covering set + 8-module mutate
  - .github/workflows/nightly-mutation.yml: schedule "17 3 * * *" + workflow_dispatch,
    advisory (continue-on-error), npm ci + npx stryker run, upload reports/mutation/
  - .gitignore: ignore .stryker-tmp/, reports/mutation/, stryker-output-*.json
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces Stryker mutation testing to the project, adding the core and TAP runner packages, updating the dependency allowlist, and configuring stryker.conf.json to run tests sequentially with the TAP runner. Feedback points out a style guide violation because stryker.conf.json is placed in the project root instead of config/quality/. Additionally, there is a concern that running with a concurrency of one for over 15,000 mutants will cause the nightly CI workflow to time out, suggesting that database state should be isolated to allow parallel execution.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread package.json
"test:protocols:e2e": "node scripts/dev/run-protocol-clients-tests.mjs",
"test:vitest": "vitest run --config vitest.mcp.config.ts",
"test:vitest:ui": "vitest run --config vitest.config.ts tests/unit/ui",
"test:mutation": "stryker run",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

De acordo com o Guia de Estilo do Repositório (Regra 1 - File Placement & Organization), o diretório raiz do projeto deve conter apenas os arquivos de configuração explicitamente listados. Como o stryker.conf.json não está nessa lista, ele deve ser movido para config/quality/stryker.conf.json para manter a raiz limpa.

Podemos ajustar o script test:mutation para apontar para o novo caminho do arquivo de configuração.

Suggested change
"test:mutation": "stryker run",
"test:mutation": "stryker run config/quality/stryker.conf.json",
References
  1. O diretório raiz do projeto deve conter apenas arquivos de configuração específicos listados no guia de estilo. Arquivos de configuração adicionais, como o do Stryker, devem ser colocados em subdiretórios apropriados (ex: config/quality/). (link)

Comment thread stryker.conf.json
"timeoutMS": 60000,
"timeoutFactor": 2.5,
"concurrency": 2,
"concurrency": 1,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Definir "concurrency": 1 garante um baseline determinístico ao evitar concorrência em testes que compartilham estado do SQLite ou do módulo. No entanto, com 15.261 mutantes, a execução sequencial será extremamente lenta e excederá facilmente o limite de 120 minutos do workflow do GitHub Actions, resultando em relatórios de mutação muito parciais e incompletos.

Para viabilizar a execução paralela e obter relatórios completos no orçamento de tempo do nightly, considere isolar o estado do banco de dados em cada arquivo de teste (por exemplo, utilizando bancos de dados SQLite em memória ou arquivos temporários exclusivos por execução de teste).

@diegosouzapw
diegosouzapw merged commit 9f5e651 into release/v3.8.26 Jun 15, 2026
2 checks passed
@diegosouzapw diegosouzapw mentioned this pull request Jun 16, 2026
@diegosouzapw
diegosouzapw deleted the feat/gate-stryker-nightly branch June 17, 2026 00:54
tkgo11 pushed a commit to tkgo11/OmniRoute that referenced this pull request Sep 23, 2026
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.

1 participant