Skip to content

fix(test): migrate Vitest 5 with correct per-test mutation IDs - #391

Merged
lemone112 merged 3 commits into
mainfrom
dependabot/npm_and_yarn/vitest-5.0.0
Oct 6, 2026
Merged

lemone112 merged 3 commits into
mainfrom
dependabot/npm_and_yarn/vitest-5.0.0

Conversation

@dependabot

@dependabot dependabot Bot commented on behalf of github Sep 14, 2026 •

Copy link
Copy Markdown
Contributor

Vitest 5.0.2 and Stryker 10.0.0 are integrated with current main. Stryker’s compiled and TypeScript test-name writers now use Vitest 5’s > boundary and retain authored whitespace. A native regression compares the installed helper to Vitest’s actual task.fullTestName, including nested literal names and anonymous suites. The existing allocation suite keeps its sequential execution through the Vitest 5 options API.

The real pinned pnpm 11.11.0 manager generated the lockfile and accepted a final frozen lock. Existing supply-chain policy verification passed; 440 package identities resolve, and all 378 retained package resolution integrities remain unchanged. The minimum-release-age, trust/error policy, overrides, and exact patched-consumer hash remain intact.

Independent six-file source/lock review passed on e117f19ceff40cc24f0503fb0455f94c444effd5 (tree bb95eeedceb8e4288a2b146c18fecd5ee6dc122c). Integrity-verified published Vitest/Stryker tarballs reproduce two nested tests skipped before the fix and selected afterward with the actual Vitest mode interpreter; bare/whitespace/anonymous controls stay selected and preserve exact identity. The patch applies to all four pinned source files.

Product source, CI workflows, mutation configuration, per-test analysis and the driver break-80 gate remain unchanged versus current main. Fresh full native installation, the actual regression, test/fuzz coverage, and driver mutation score remain required before acceptance. The bounded source counterexample does not claim a passing full mutation score.

@dependabot dependabot Bot added dependencies Pull requests that update a dependency file javascript Pull requests that update javascript code labels Sep 14, 2026
@coderabbitai

coderabbitai Bot commented Sep 14, 2026 •

Copy link
Copy Markdown

Important

Review skipped

Bot user detected.

To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: ASSERTIVE
  • Plan: Advanced
  • Run ID: 62627550-f88c-4fd3-a749-2bce81455360

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Comment @coderabbitai help to get the list of available commands.

@dependabot
dependabot Bot force-pushed the dependabot/npm_and_yarn/vitest-5.0.0 branch from f25a94c to f78a77a Compare September 17, 2026 22:47
@dependabot dependabot Bot changed the title deps: bump vitest from 4.1.11 to 5.0.0 deps: bump vitest from 4.1.11 to 5.0.2 Sep 28, 2026
@dependabot
dependabot Bot force-pushed the dependabot/npm_and_yarn/vitest-5.0.0 branch 5 times, most recently from 5b3c9c4 to c8ca8ce Compare September 30, 2026 12:12
Bumps [vitest](https://github.com/vitest-dev/vitest/tree/HEAD/packages/vitest) from 4.1.11 to 5.0.2.
- [Release notes](https://github.com/vitest-dev/vitest/releases)
- [Changelog](https://github.com/vitest-dev/vitest/blob/main/docs/releases.md)
- [Commits](https://github.com/vitest-dev/vitest/commits/v5.0.2/packages/vitest)

---
updated-dependencies:
- dependency-name: vitest
  dependency-version: 5.0.0
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
@dependabot
dependabot Bot force-pushed the dependabot/npm_and_yarn/vitest-5.0.0 branch from c8ca8ce to 8e6bf78 Compare October 1, 2026 05:28

Copy link
Copy Markdown
Contributor

@coderabbitai Проверьте exact 453fe29 против current main 708efd5. Старый exact RED был предметным: Vitest5 удалил describe.sequential, а Stryker9/Vitest5 дал mutation score3.32 при обязательном break80. Исправление не снижает thresholds/scope: describe.sequential заменён официальным Vitest5 describe(...,{concurrent:false}), @stryker-mutator/core и vitest-runner обновлены синхронно 9.6.1→10.0.0; Vitest остаётся5.0.2, Vite8/Node22+ совместимы. Lock пересобран pnpm11.11.0 и прошёл supply-chain policy. Нужен source/second-order review: не скрывает ли Stryker10 изменение семантики/coverage, не меняет ли тестовая последовательность оракул и нет ли другого Vitest5 migration requirement. Не запускать тесты в sandbox; hosted CI обязан доказать test/fuzz + driver mutation и остальные gates.

@coderabbitai

coderabbitai Bot commented Oct 1, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail
base=708efd55a9bf9a2a158b138e8bf7d6a8b28bb25f
head=453fe29b7fe82cee395f8bab02625c1445888a2c
git rev-parse HEAD
git show -s --format='%H %s' "$base" "$head"
git diff --stat "$base" "$head"
git diff --name-status "$base" "$head"
git diff "$base" "$head" -- . ':!pnpm-lock.yaml'
fd -i 'stryker|vitest|package.json|pnpm-lock.yaml'
gh api repos/Labpics-Team/lab-motion/commits/"$head"/check-runs --paginate --jq '.check_runs[] | {name,status,conclusion,details_url,head_sha}'
gh api repos/Labpics-Team/lab-motion/commits/"$head"/status --jq '{state,statuses:[.statuses[]|{context,state,target_url}]}'
gh pr view 391 --repo Labpics-Team/lab-motion --json headRefOid,baseRefOid,statusCheckRollup

Length of output: 7088


🤖 web_search executed:

Find official Vitest 5 migration guide breaking changes describe.sequential describe concurrent false sequence defaults, and official StrykerJS 10.0.0 release/migration notes vitest-runner Vitest 5 coverageAnalysis mutation score semantics compared with StrykerJS 9.6.1.

Length of output: 2449


🏁 Script executed:

#!/bin/bash
set -euo pipefail
for f in stryker.config.mjs stryker.driver.config.mjs vitest.config.ts vitest.stryker.config.ts vitest.stryker.driver.config.ts test/animate-finished-allocation.test.ts package.json; do
  ast-grep outline "$f" --items all || true
done
for f in stryker.config.mjs stryker.driver.config.mjs vitest.config.ts vitest.stryker.config.ts vitest.stryker.driver.config.ts test/animate-finished-allocation.test.ts; do
  echo "=== $f ==="; cat -n "$f"
done
sed -n '1,150p' package.json
sed -n '520,640p' package.json
fd -t f . .github/workflows
rg -n -C3 'mutation|stryker|pnpm|node-version|fuzz|vitest' .github/workflows
rg -n -C2 'sequential|sequence:|poolOptions|workspace|deps:|mockReset|isolate:|maxThreads|minThreads|coverage:|setupFiles|fakeTimers|toFake|vi\.(mock|spyOn|useFakeTimers|resetAllMocks|restoreAllMocks)|vitest/(execute|utils|browser)' --glob '*.{ts,js,mjs,json}' --glob '!pnpm-lock.yaml' .
git diff 708efd55a9bf9a2a158b138e8bf7d6a8b28bb25f 453fe29b7fe82cee395f8bab02625c1445888a2c -- pnpm-lock.yaml | head -250
gh api repos/stryker-mutator/stryker-js/compare/v9.6.1...v10.0.0 --jq '{status,total_commits,files:[.files[] | select(.filename | test("vitest-runner|mutation.*(score|result)|mutant.*(score|result)|package.json$")) | {filename,status,patch}]}'

Length of output: 145511


🤖 web_search executed:

Read the full official Vitest 5 migration section at https://vitest.dev/guide/migration/ . Enumerate all breaking changes from Vitest 4 to 5 including configuration, mocks, spies, fake timers, setupFiles ordering, hooks, test isolation and removed public API. Also read the StrykerJS 10.0.0 full release notes and explain any vitest runner changes since 9.6.1, particularly onTestRunEnd onFinished test IDs coverage and static mutants.

Length of output: 7535


🏁 Script executed:

#!/bin/bash
set -euo pipefail
gh api repos/stryker-mutator/stryker-js/git/trees/v10.0.0?recursive=1 --jq '.tree[] | select(.path|test("^packages/vitest-runner/src/|^packages/core/src/.*(score|coverage|mutant)|^packages/instrumenter/src/.*empty-expression")) | .path'
for f in packages/vitest-runner/src/vitest-test-runner.ts packages/vitest-runner/src/vitest-helpers.ts packages/vitest-runner/src/stryker-setup.ts packages/vitest-runner/src/test-helpers.ts packages/vitest-runner/package.json; do
  echo "=== upstream $f ==="
  gh api "repos/stryker-mutator/stryker-js/contents/$f?ref=v10.0.0" --jq '.content' | base64 -d | cat -n
done
gh api repos/stryker-mutator/stryker-js/compare/v9.6.1...v10.0.0 --jq '.files[] | select(.filename|test("^packages/(core|instrumenter)/src/")) | {filename,patch}'
for f in test/driver-single-solve.test.ts test/root-request-frame-ssot.test.ts test/benchmark-provenance.test.ts; do
  ast-grep outline "$f" --items all || true
done
sed -n '1,180p' test/driver-single-solve.test.ts
sed -n '1,100p' test/root-request-frame-ssot.test.ts
sed -n '1,90p' test/benchmark-provenance.test.ts
rg -n -C2 'testNamePattern|expect\.(poll|extend)|\.resolves\.|\.rejects\.|toThrow\((.|\n)*|vitest/|@vitest/|\.mockImplementation|vi\.fn\(' test --glob '*.ts' | head -200
sed -n '20,60p' .github/workflows/ci.yml
sed -n '259,290p' .github/workflows/ci.yml
rg -n -C2 'mutation-testing-metrics@|vitest@5.0.2:|@stryker-mutator/vitest-runner@10.0.0:|@babel/core@8' pnpm-lock.yaml
gh api repos/Labpics-Team/lab-motion/actions/runs/36820429245/jobs --paginate --jq '.jobs[] | {name,status,conclusion,head_sha,steps:[.steps[] | {name,status,conclusion}]}'

Length of output: 77710


🏁 Script executed:

#!/bin/bash
set -euo pipefail
rg -n -C2 'testNamePattern|expect\.(poll|extend)|\.resolves\.|\.rejects\.|vitest/|@vitest/|mockImplementationOnce|mockReturnValueOnce' test --glob '*.ts' || true
sed -n '20,60p' .github/workflows/ci.yml
sed -n '259,290p' .github/workflows/ci.yml
rg -n -C2 'mutation-testing-metrics@|vitest@5.0.2:|@stryker-mutator/vitest-runner@10.0.0:' pnpm-lock.yaml
gh api repos/Labpics-Team/lab-motion/actions/runs/36820429245/jobs --paginate --jq '.jobs[] | {id,name,status,conclusion,head_sha,steps:[.steps[] | {name,status,conclusion}]}'
gh api repos/vitest-dev/vitest/git/trees/v5.0.2?recursive=1 --jq '.tree[] | select(.path|test("^packages/(runner|vitest)/src/.*(collect|utils|run|config)|^docs/guide/migration")) | .path' | head -90
gh api repos/vitest-dev/vitest/contents/docs/guide/migration.md?ref=v5.0.2 --jq '.content' | base64 -d | sed -n '1,340p'
gh api repos/stryker-mutator/stryker-js/contents/packages/api/src/test-runner/mutant-run-result.ts?ref=v10.0.0 --jq '.content' | base64 -d | cat -n

Length of output: 37007


🏁 Script executed:

#!/bin/bash
set -euo pipefail
python - <<'PY'
import subprocess,json,base64,re
def read(repo,path,ref):
    raw=subprocess.check_output(['gh','api',f'repos/{repo}/contents/{path}?ref={ref}'])
    return base64.b64decode(json.loads(raw)['content']).decode()
targets=[
('vitest-dev/vitest','docs/guide/migration/index.md','v5.0.2',r'testNamePattern|clearMocks|sequential|Node.js|Vite'),
('vitest-dev/vitest','packages/vitest/src/runtime/runner/collect.ts','v5.0.2',r'testNamePattern|getTaskFullName|skip'),
('vitest-dev/vitest','packages/vitest/src/runtime/runner/utils/collect.ts','v5.0.2',r'getTaskFullName|join\('),
('vitest-dev/vitest','packages/vitest/src/runtime/runner/utils/suite.ts','v5.0.2',r'getTaskFullName|join\('),
('stryker-mutator/stryker-js','packages/api/src/test-runner/mutant-run-result.ts','v10.0.0',r'toMutantRunResult|Survived|Failed|Skipped')
]
for repo,path,ref,pat in targets:
    print(f'\n=== {repo}/{path}@{ref} ===')
    try: text=read(repo,path,ref)
    except subprocess.CalledProcessError as e:
        print('FAILED',e.returncode); continue
    lines=text.splitlines(); indices=set()
    for i,line in enumerate(lines):
        if re.search(pat,line):
            indices.update(range(max(0,i-5), min(len(lines),i+14)))
    for i in sorted(indices): print(f'{i+1}: {lines[i]}')
PY
gh api repos/Labpics-Team/lab-motion/commits/453fe29b7fe82cee395f8bab02625c1445888a2c/check-runs --paginate --jq '.check_runs[] | {name,status,conclusion,details_url,head_sha}'
gh api repos/Labpics-Team/lab-motion/actions/runs/36820429245 --jq '{head_sha,event,status,conclusion,html_url}'
gh api repos/Labpics-Team/lab-motion/actions/jobs/110234623683 --jq '{status,conclusion}'
# Read hosted logs only when the mutation job is finished.
status=$(gh api repos/Labpics-Team/lab-motion/actions/jobs/110234623683 --jq '.status')
if [ "$status" = completed ]; then
  gh run view 36820429245 --repo Labpics-Team/lab-motion --job 110234623683 --log | tail -100
fi

Length of output: 30984


🏁 Script executed:

#!/bin/bash
set -euo pipefail
python - <<'PY'
import subprocess,json,base64,re
def api(endpoint):
    return json.loads(subprocess.check_output(['gh','api',endpoint]))
def content(repo,path,ref):
    return base64.b64decode(api(f'repos/{repo}/contents/{path}?ref={ref}')['content']).decode()
repo='stryker-mutator/stryker-js'
tree=api(f'repos/{repo}/git/trees/v10.0.0?recursive=1')['tree']
paths=[x['path'] for x in tree if x['type']=='blob' and x['path'].startswith('packages/api/src/test-runner/') and x['path'].endswith('.ts')]
for path in paths:
    text=content(repo,path,'v10.0.0')
    if 'function toMutantRunResult' in text:
        print('===',path,'===')
        lines=text.splitlines()
        start=next(i for i,l in enumerate(lines) if 'function toMutantRunResult' in l)
        for i in range(max(0,start-3), min(len(lines),start+85)): print(f'{i+1}: {lines[i]}')
repo='vitest-dev/vitest'
path='packages/vitest/src/runtime/runner/utils/suite.ts'
text=content(repo,path,'v5.0.2')
print('===',path,'===')
print('\n'.join(f'{i+1}: {l}' for i,l in enumerate(text.splitlines()[:35])))
PY
gh api repos/Labpics-Team/lab-motion/commits/453fe29b7fe82cee395f8bab02625c1445888a2c/check-runs --paginate --jq '.check_runs[]|{name,status,conclusion}'

Length of output: 4336


🏁 Script executed:

#!/bin/bash
set -euo pipefail
gh run view 36820429245 --repo Labpics-Team/lab-motion --job 110234623683 --log | tail -120
gh api repos/Labpics-Team/lab-motion/actions/runs/36820429245 --jq '{head_sha,status,conclusion,html_url}'

Length of output: 14497


@lemone112, проверил exact 453fe29b7fe82cee395f8bab02625c1445888a2c против 708efd55a9bf9a2a158b138e8bf7d6a8b28bb25f. Код и тесты в sandbox не запускал.

Вердикт: exact остаётся RED. Обновление до Stryker 10 не завершает миграцию. Найден блокирующий дефект совместимости perTest-фильтра.

1. Блокер: Stryker 10 передаёт Vitest 5 неправильное полное имя теста

Цепочка подтверждается исходниками фиксированных версий:

  • Stryker 10 в test-helpers.ts, Line 20, соединяет имена suite/test через ' '. Копия этой логики в stryker-setup.ts использует тот же разделитель.
  • В vitest-test-runner.ts, Line 230–239, раннер преобразует эти имена в регулярное выражение для project.config.testNamePattern.
  • Vitest 5 сопоставляет testNamePattern с именем, соединённым через ' > '. Это прямо указано в migration guide версии 5.0.2, Line 66–83.

Например, фильтр suite test не выбирает тест suite > test. Это затрагивает mutation-прогоны с выбранными тестами внутри describe.

Дополнительный дефект диагностики усиливает проблему. В run-result-helpers.ts, Line 25–46, Stryker возвращает Survived, если нет failed tests. Проверки nrOfTests > 0 нет. Поэтому пропуск выбранных тестов может выглядеть как выжившая мутация.

Это конкретное объяснение низкого score, но не доказательство единственной причины всех survivors. Подпись Tests ran: в отчёте сама по себе не устраняет найденное несоответствие фильтра.

2. Hosted CI подтвердил отказ exact

Run 36820429245 завершился FAILURE для запрошенного SHA.

Gate Результат
test · fuzz SUCCESS
typecheck · build · size · package SUCCESS
Chromium, Firefox, оба WebKit shards SUCCESS
Consumer contract: Node 22.0.0 и Node 24 SUCCESS
driver mutation FAILURE

Mutation job сообщил:

Mutation score: 3.11
Killed: 10
Timeout: 0
Survived: 300
No coverage: 12
Errors: 0
Final mutation score 3.11 under breaking threshold 80

Таким образом, прежний отказ не устранён. Порог break: 80 корректно остановил этот exact.

3. Последовательность и оракул allocation-теста сохранены

Замена в test/animate-finished-allocation.test.ts, Line 38, соответствует официальной миграции:

describe('animate finished: O(1) library Promise constructors', { concurrent: false }, () => {

Тела тестов, assertions и порядок объявления не изменены. countPromises() по-прежнему считает только синхронные PROMISE-ресурсы внутри run(). Hook отключается в finally. Проверки порядка onComplete/микрозадач/finished также не изменены.

Ослабления этого оракула в diff не нашёл.

4. Stryker 10 меняет набор мутаций, а не только совместимость раннера

Scope и thresholds в репозитории действительно сохранены:

  • Driver: весь src/driver.ts, break: 80.
  • Scheduled core: прежний список файлов, break: 76.
  • Списки mutation-тестов и исключения не изменены.

Однако upstream Stryker 10 добавляет CallExpression-мутации и меняет обработку optional chaining. Также обновляются Babel 7 → 8 и mutation-testing-metrics 3.7.3 → 3.8.4.

Поэтому старый и новый score нельзя считать результатами идентичного набора мутаций. Для сравнения нужны категории и статусы мутаций. Изменения формулы score по одной лишь неизменности конфигурации исключить нельзя.

5. Остальные требования Vitest 5

Существенное изменение — clearMocks: true по умолчанию. Оно очищает историю вызовов перед каждым тестом, но сохраняет implementations. В просмотренных probes зависимости от межтестовой истории не обнаружил. Полный hosted test · fuzz также прошёл.

Требование среды точнее, чем «Node 22+»: Vitest 5 требует как минимум Node 22.12.0. Babel 8 в новом lock требует ^22.18.0 || >=24.11.0. Hosted tooling работает на Node 24. Consumer smoke на Node 22.0.0 проверяет готовый пакет, а не возможность запуска dev toolchain.

Следующий шаг: исправить совместимость полного имени теста в раннере с Vitest 5. Исправление должно согласовать IDs coverage и mutation-фильтр. Затем повторить hosted mutation gate без снижения break: 80 и без изменения scope. Проверка должна показать, что выбранные тесты действительно выполняются. До этого блокер остаётся открытым.

Спасибо за ограничения проверки.


✏️ Learnings added
Learnt from: lemone112
URL: https://github.com/Labpics-Team/lab-motion/pull/391

Timestamp: 2026-10-01T05:37:51.543Z
Learning: В Labpics-Team/lab-motion при exact source/second-order review пользователь требует не запускать код или тесты репозитория в sandbox. Доказательства выполнения test/fuzz, driver mutation и остальных gates должны поступать из hosted CI для проверяемого SHA. Для driver mutation необходимо сохранить полный scope src/driver.ts и thresholds.break 80; снижение порога или сужение scope не является допустимым исправлением миграции.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

You are interacting with an AI system.

@dependabot @github

dependabot Bot commented on behalf of github Oct 5, 2026

Copy link
Copy Markdown
Contributor Author

A newer version of vitest exists, but since this PR has been edited by someone other than Dependabot I haven't updated it. You'll get a PR for the updated version as normal once this PR is merged.

@lemone112 lemone112 changed the title deps: bump vitest from 4.1.11 to 5.0.2 fix(test): migrate Vitest 5 with correct per-test mutation IDs Oct 6, 2026
@lemone112
lemone112 merged commit 6f5398e into main Oct 6, 2026
12 checks passed
@lemone112
lemone112 deleted the dependabot/npm_and_yarn/vitest-5.0.0 branch October 6, 2026 07:50
@lemone112
lemone112 restored the dependabot/npm_and_yarn/vitest-5.0.0 branch October 6, 2026 07:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dependencies Pull requests that update a dependency file javascript Pull requests that update javascript code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant