Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 21 additions & 9 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1058,6 +1058,16 @@ jobs:
npm run test:coverage -- --maxWorkers=4 --shard=${{ matrix.shard }}/3 --reporter=default --reporter=blob --reporter=junit --outputFile.blob=blob-report/report-${{ matrix.shard }}.blob --outputFile.junit=reports/junit/vitest.xml "${EXCLUDE_ARGS[@]}" "${SCOPE_ARGS[@]}" 2>&1 | tee vitest-scoped-output.log
if grep -q "No test files found" vitest-scoped-output.log; then
echo "no_tests_matched=true" >> "$GITHUB_OUTPUT"
elif [ ! -s coverage/lcov.info ]; then
# #8194 (the week's third scoped-selection edge case, PR #8192's shards): the selection
# matched and PASSED real tests -- set -o pipefail above means this line is only reachable
# on vitest exit 0 -- but none of them exercise anything under coverage.include (src/**):
# a test-only/docs-drift diff. --coverage.all=false then writes an empty or absent lcov,
# which is NOT a failed run; the junit report written above is the proof the suite ran.
# Without this output, "Verify coverage report exists" fails the green run. The success
# signal here is the test run itself, never the coverage file's size.
echo "scoped run passed but exercised no src/** files -- skipping coverage artifacts for this shard"
echo "no_src_coverage=true" >> "$GITHUB_OUTPUT"
fi
else
# Duration-aware sharding (#ci-duration-aware-sharding), full-suite case only: vitest's own
Expand Down Expand Up @@ -1092,10 +1102,12 @@ jobs:
- name: Verify coverage report exists
# Skipped when the scoped-selection branch above matched zero test files (steps.coverage.outputs.
# no_tests_matched) -- a legitimately test-free diff (e.g. docs-only under a scoped path) writes no
# coverage/lcov.info at all, and that is not a failure. A real failure (tests ran and either failed
# or the coverage step crashed before writing output) still fails this job via `success()` below,
# same as before.
if: ${{ success() && steps.coverage.outputs.no_tests_matched != 'true' }}
# coverage/lcov.info at all, and that is not a failure. Also skipped when the selection matched
# tests that PASSED but exercised nothing under src/** (no_src_coverage, #8194) -- same legitimacy,
# different detector: tests matched, so no_tests_matched can't see it. A real failure (tests ran
# and either failed or the coverage step crashed before writing output) still fails this job via
# `success()` below, same as before.
if: ${{ success() && steps.coverage.outputs.no_tests_matched != 'true' && steps.coverage.outputs.no_src_coverage != 'true' }}
run: |
if [ ! -s coverage/lcov.info ]; then
echo "::error title=Coverage::coverage/lcov.info is missing or empty"
Expand All @@ -1104,7 +1116,7 @@ jobs:
# Consumed by validate-tests-merge to re-check the global coverage threshold against all shards
# combined -- see this job's own header comment.
- name: Upload coverage blob report
if: ${{ success() && steps.coverage.outputs.no_tests_matched != 'true' }}
if: ${{ success() && steps.coverage.outputs.no_tests_matched != 'true' && steps.coverage.outputs.no_src_coverage != 'true' }}
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: coverage-blob-shard-${{ matrix.shard }}
Expand All @@ -1115,7 +1127,7 @@ jobs:
- name: Upload coverage to Codecov
# Same no_tests_matched skip as the verify step above (#8167 follow-up): with zero matched tests
# there is no lcov to upload, and fail_ci_if_error would otherwise turn that non-event red.
if: ${{ success() && steps.coverage.outputs.no_tests_matched != 'true' && github.event.pull_request.head.repo.fork != true }}
if: ${{ success() && steps.coverage.outputs.no_tests_matched != 'true' && steps.coverage.outputs.no_src_coverage != 'true' && github.event.pull_request.head.repo.fork != true }}
uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0
with:
token: ${{ secrets.CODECOV_TOKEN }}
Expand All @@ -1133,7 +1145,7 @@ jobs:
# override, etc). Condition dropped the push-or-backend check since this job's own `if:` already
# covers it.
- name: Upload coverage to Codecov (fork PR tokenless)
if: ${{ success() && steps.coverage.outputs.no_tests_matched != 'true' && github.event.pull_request.head.repo.fork == true }}
if: ${{ success() && steps.coverage.outputs.no_tests_matched != 'true' && steps.coverage.outputs.no_src_coverage != 'true' && github.event.pull_request.head.repo.fork == true }}
uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0
with:
files: ./coverage/lcov.info
Expand All @@ -1147,7 +1159,7 @@ jobs:
# their upload non-blocking so a JUnit ingestion hiccup does not fail CI
# after the tests and hard coverage upload have already passed.
- name: Upload Vitest results to Codecov
if: ${{ !cancelled() && steps.coverage.outputs.no_tests_matched != 'true' && github.event.pull_request.head.repo.fork != true }}
if: ${{ !cancelled() && steps.coverage.outputs.no_tests_matched != 'true' && steps.coverage.outputs.no_src_coverage != 'true' && github.event.pull_request.head.repo.fork != true }}
uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0
with:
token: ${{ secrets.CODECOV_TOKEN }}
Expand All @@ -1160,7 +1172,7 @@ jobs:
override_pr: ${{ github.event_name == 'pull_request' && github.event.pull_request.number || '' }}
fail_ci_if_error: false
- name: Upload Vitest results to Codecov (fork PR tokenless)
if: ${{ !cancelled() && steps.coverage.outputs.no_tests_matched != 'true' && github.event.pull_request.head.repo.fork == true }}
if: ${{ !cancelled() && steps.coverage.outputs.no_tests_matched != 'true' && steps.coverage.outputs.no_src_coverage != 'true' && github.event.pull_request.head.repo.fork == true }}
uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0
with:
files: ./reports/junit/vitest.xml
Expand Down
17 changes: 14 additions & 3 deletions test/unit/codecov-policy.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,12 +61,23 @@ describe("Codecov policy", () => {
// every upload below it — must skip on that output rather than fail closed on a missing lcov. The
// policy stays: absent that explicit escape hatch, a missing report still fails the build, and the
// uploads can never run without the verify guard's own condition.
expect(String(verifyStep.if)).toBe("${{ success() && steps.coverage.outputs.no_tests_matched != 'true' }}");
// EVERY upload below the verify step must carry the same escape: with zero matched tests there is no
// lcov/junit at all, and the coverage uploads' fail_ci_if_error would turn that non-event red.
expect(String(verifyStep.if)).toBe(
"${{ success() && steps.coverage.outputs.no_tests_matched != 'true' && steps.coverage.outputs.no_src_coverage != 'true' }}",
);
// EVERY upload below the verify step must carry the same escapes: with zero matched tests there is no
// lcov/junit at all, and the coverage uploads' fail_ci_if_error would turn that non-event red. The
// second escape (#8194) is the scoped run that matched tests which PASSED but exercised nothing under
// src/** -- an empty lcov there is a legitimate outcome, never a failed suite.
expect(String(coverageUpload.if)).toContain("success()");
expect(String(coverageUpload.if)).toContain("no_tests_matched != 'true'");
expect(String(coverageUpload.if)).toContain("no_src_coverage != 'true'");
expect(String(testResultsUpload.if)).toContain("no_tests_matched != 'true'");
expect(String(testResultsUpload.if)).toContain("no_src_coverage != 'true'");
// The scoped branch must derive BOTH outputs from the run itself (vitest's own stdout + exit status),
// never from re-deriving selection logic -- pin the detector lines so they can't silently vanish.
const coverageStep = steps[stepNames.indexOf("Test with coverage (shard ${{ matrix.shard }}/3)")]!;
expect(String(coverageStep.run)).toContain('grep -q "No test files found" vitest-scoped-output.log');
expect(String(coverageStep.run)).toContain('echo "no_src_coverage=true"');
expect(String(verifyStep.run)).toContain("coverage/lcov.info is missing or empty");
expect(String(verifyStep.run)).toContain("exit 1");

Expand Down
Loading