Skip to content

Commit 127198a

Browse files
Hannia Valerahanniavalera
authored andcommitted
enhance test coverage protocols and scripts for Copilot integration
1 parent 17780aa commit 127198a

5 files changed

Lines changed: 83 additions & 38 deletions

File tree

.github/PULL_REQUEST_TEMPLATE/coverage.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ about: PR opened by the Copilot coverage agent
1717
### Self-audit results
1818

1919
- [ ] `yarn backendTests` passes
20-
- [ ] `yarn unitTests` passes
2120
- [ ] Every file listed in the issue improved by ≥ 10 percentage points OR reached ≥ 60% line coverage
2221
- [ ] No test uses `assert.ok(true)` or is an empty stub
2322
- [ ] Test names describe behavior, not implementation
@@ -26,6 +25,7 @@ about: PR opened by the Copilot coverage agent
2625
- [ ] Single-config and multi-config generator paths both tested where relevant
2726
- [ ] `yarn lint` passes
2827
- [ ] `CHANGELOG.md` has an entry under `Improvements:`
28+
- [ ] Tests are in `test/unit-tests/backend/` (runnable without VS Code host)
2929

3030
### Coverage delta (paste `c8` text report here)
3131
```

.github/copilot-instructions.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,10 @@ One entry under the current version in `CHANGELOG.md`, in the appropriate sectio
111111
- [ ] Behavior verified with **single-config** and **multi-config** generators
112112
- [ ] Windows/macOS/Linux differences considered (paths, env vars, MSVC toolchain, generator availability)
113113

114+
## Test coverage improvements
115+
116+
When working on issues labeled `test-coverage`, read `.github/copilot-test-coverage.md` before starting — it contains the mandatory self-audit protocol, test quality rules, and scope constraints for coverage work.
117+
114118
## Where to start
115119

116120
- **Configure/build/test behavior**`src/cmakeProject.ts` + `src/drivers/`

.github/copilot-test-coverage.md

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
---
22
description: "Instructions for the Copilot coding agent when working on test-coverage issues."
3-
applyTo: "test/unit-tests/**/*.ts"
3+
applyTo: "test/unit-tests/**/*.ts,src/**/*.ts"
44
---
55

66
# Test Coverage Agent — Self-Audit Protocol
@@ -11,9 +11,10 @@ This file contains mandatory protocol. Read all of it before writing code.
1111
## Step 1 — Orient before writing
1212

1313
- Read every source file you will test in full
14-
- Read existing tests for that module (if any) in `test/unit-tests/`
14+
- Read existing tests for that module (if any) in `test/unit-tests/backend/`
1515
- Identify every exported function, class, and branch condition
1616
- Do **not** write tests for private implementation details — test the public API
17+
- If a source file deeply depends on `vscode` APIs, skip it — note in the PR that it needs integration-test coverage
1718

1819
## Step 2 — Write real tests, not stubs
1920

@@ -33,22 +34,23 @@ npx tsc -p test.tsconfig.json --noEmit
3334
# 2. Lint
3435
yarn lint
3536

36-
# 3. Run backend tests (fast — no xvfb needed)
37+
# 3. Run backend tests (this is the primary validation step)
3738
yarn backendTests
3839

39-
# 4. Run unit tests
40-
yarn unitTests
41-
42-
# 5. Confirm coverage improved for the specific file
43-
npx c8 --reporter=text --src=src \
40+
# 4. Confirm coverage improved for the specific file
41+
npx c8 --all --reporter=text --src=src \
4442
node ./node_modules/mocha/bin/_mocha \
4543
-u tdd --timeout 999999 --colors \
4644
-r ts-node/register \
4745
-r tsconfig-paths/register \
4846
"./test/unit-tests/backend/**/*.test.ts"
4947
```
5048

51-
All five steps must pass. If any fail, fix the failures before opening the PR.
49+
All steps must pass. If any fail, fix the failures before opening the PR.
50+
51+
> **Note:** `yarn unitTests` requires a VS Code extension host and a display server.
52+
> It cannot be run in headless agent environments. The CI workflow validates those
53+
> separately — you only need to run `yarn backendTests` and `yarn lint` locally.
5254
5355
## Step 4 — Coverage bar
5456

.github/scripts/check-coverage-and-open-issue.mjs

Lines changed: 65 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,19 @@
11
#!/usr/bin/env node
22
// @ts-check
3-
import { readFileSync } from 'fs';
4-
import { execSync } from 'child_process';
5-
import { relative, resolve } from 'path';
3+
import { readFileSync, writeFileSync, unlinkSync } from 'fs';
4+
import { execSync, spawnSync } from 'child_process';
5+
import { relative, resolve, join } from 'path';
6+
import { tmpdir } from 'os';
67

78
const THRESHOLD = Number(process.env.THRESHOLD ?? 60);
89
const SUMMARY_PATH = 'coverage/coverage-summary.json';
910
const REPO = process.env.GITHUB_REPOSITORY; // e.g. "microsoft/vscode-cmake-tools"
11+
12+
if (!REPO) {
13+
console.error('GITHUB_REPOSITORY is not set — are you running outside GitHub Actions?');
14+
process.exit(1);
15+
}
16+
1017
const RUN_URL = `https://github.com/${REPO}/actions/runs/${process.env.GITHUB_RUN_ID}`;
1118

1219
// ── 1. Read Istanbul JSON summary ────────────────────────────────────────────
@@ -53,15 +60,12 @@ if (belowThreshold.length === 0 && totalLines >= THRESHOLD) {
5360
}
5461

5562
// ── 3. Check for existing open coverage issue (avoid duplicates) ─────────────
63+
let existingIssueNumber = null;
5664
try {
57-
const existing = execSync(
65+
existingIssueNumber = execSync(
5866
`gh issue list --repo ${REPO} --label test-coverage --state open --json number --jq '.[0].number'`,
5967
{ encoding: 'utf8' }
60-
).trim();
61-
if (existing) {
62-
console.log(`\n⏭️ Open coverage issue already exists: #${existing} — skipping.`);
63-
process.exit(0);
64-
}
68+
).trim() || null;
6569
} catch {
6670
// gh CLI may fail if label doesn't exist yet — continue to create
6771
}
@@ -79,6 +83,11 @@ const fileList = belowThreshold
7983
.map(f => `- \`${f.file}\` — ${f.linePct}% line coverage`)
8084
.join('\n');
8185

86+
const remainingCount = belowThreshold.length - Math.min(belowThreshold.length, 20);
87+
const remainingNote = remainingCount > 0
88+
? `\n\n> **${remainingCount} additional files** are also below threshold. They will appear here once the files above improve.`
89+
: '';
90+
8291
const issueBody = `\
8392
## Coverage below ${THRESHOLD}% threshold
8493
@@ -108,14 +117,13 @@ ${fileList}
108117
For each file:
109118
1. Read the source file fully before writing any test
110119
2. Identify the module's exported API surface
111-
3. Write tests in \`test/unit-tests/\` that cover the uncovered branches
120+
3. Write tests in \`test/unit-tests/backend/\` that cover the uncovered branches
112121
4. Run the self-audit steps from \`copilot-test-coverage.md\`
113122
5. Only open the PR after every self-audit step passes
114123
115124
### Self-audit checklist (must be checked before opening PR)
116125
117126
- [ ] \`yarn backendTests\` passes with no new failures
118-
- [ ] \`yarn unitTests\` passes with no new failures
119127
- [ ] Each file listed above improved by ≥ 10 percentage points OR reached ≥ ${THRESHOLD}% line coverage
120128
- [ ] No test uses \`assert.ok(true)\` or is an empty stub
121129
- [ ] Test names describe behavior: \`'expandString handles undefined variable'\` not \`'test 1'\`
@@ -125,34 +133,64 @@ For each file:
125133
- [ ] \`yarn lint\` passes
126134
- [ ] \`CHANGELOG.md\` has an entry under \`Improvements:\`
127135
136+
### Scope and testability
137+
138+
Coverage is collected only from \`yarn backendTests\` (\`test/unit-tests/backend/\`).
139+
New tests **must** go in \`test/unit-tests/backend/\` and run in plain Node.js (no VS Code host).
140+
141+
- If a \`src/\` file can be imported directly (no \`vscode\` dependency at import time), write backend tests for it.
142+
- If a file deeply depends on \`vscode\` APIs (e.g., \`extension.ts\`, \`projectController.ts\`, UI modules), **skip it** — add a comment in the PR noting it needs integration-test coverage instead.
143+
- Pure logic modules (\`expand.ts\`, \`shlex.ts\`, \`cache.ts\`, \`diagnostics/\`, \`preset.ts\`) are ideal targets.
144+
${remainingNote}
145+
128146
### Constraints
129147
130-
- Tests go in \`test/unit-tests/\` — use Mocha \`suite\`/\`test\` with \`assert\`
148+
- Tests go in \`test/unit-tests/backend/\` — use Mocha \`suite\`/\`test\` with \`assert\`
149+
- Validate with \`yarn backendTests\`, not \`yarn unitTests\` (which requires a VS Code host)
131150
- Import source under test via the \`@cmt/\` path alias
132151
- Do **not** open the PR as a draft if the self-audit fails — fix it first
133152
- Do **not** touch source files outside \`test/\`
134153
`;
135154

136155
// ── 5. Ensure the test-coverage label exists ─────────────────────────────────
137156
try {
138-
execSync(
139-
`gh label create test-coverage --repo ${REPO} --color 0075ca --description "Opened by the coverage agent" --force`,
140-
{ stdio: 'inherit' }
141-
);
157+
spawnSync('gh', [
158+
'label', 'create', 'test-coverage',
159+
'--repo', REPO,
160+
'--color', '0075ca',
161+
'--description', 'Opened by the coverage agent',
162+
'--force'
163+
], { stdio: 'inherit' });
142164
} catch {
143165
// --force handles existing labels; ignore unexpected errors
144166
}
145167

146-
// ── 6. Open the issue via gh CLI ──────────────────────────────────────────────
147-
const title = `chore: improve test coverage -- ${belowThreshold.length} files below ${THRESHOLD}% (run ${new Date().toISOString().slice(0, 10)})`;
168+
// ── 6. Create or update the coverage issue ───────────────────────────────────
169+
const title = `Test coverage below ${THRESHOLD}% threshold — ${belowThreshold.length} files need tests (${new Date().toISOString().slice(0, 10)})`;
148170

149-
const cmd = [
150-
'gh', 'issue', 'create',
151-
'--repo', REPO,
152-
'--title', JSON.stringify(title),
153-
'--body', JSON.stringify(issueBody),
154-
'--label', 'test-coverage',
155-
].join(' ');
171+
// Write body to a temp file to avoid shell injection via file paths in the body
172+
const bodyFile = join(tmpdir(), `coverage-issue-body-${Date.now()}.md`);
173+
writeFileSync(bodyFile, issueBody, 'utf8');
156174

157-
console.log(`\nOpening issue: ${title}`);
158-
execSync(cmd, { stdio: 'inherit' });
175+
try {
176+
if (existingIssueNumber) {
177+
console.log(`\nUpdating existing issue #${existingIssueNumber}`);
178+
spawnSync('gh', [
179+
'issue', 'edit', existingIssueNumber,
180+
'--repo', REPO,
181+
'--title', title,
182+
'--body-file', bodyFile
183+
], { stdio: 'inherit' });
184+
} else {
185+
console.log(`\nOpening issue: ${title}`);
186+
spawnSync('gh', [
187+
'issue', 'create',
188+
'--repo', REPO,
189+
'--title', title,
190+
'--body-file', bodyFile,
191+
'--label', 'test-coverage'
192+
], { stdio: 'inherit' });
193+
}
194+
} finally {
195+
try { unlinkSync(bodyFile); } catch { /* cleanup best-effort */ }
196+
}

.github/workflows/coverage-agent.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ jobs:
1717
coverage:
1818
runs-on: ubuntu-latest
1919
permissions:
20-
contents: write
20+
contents: read
2121
issues: write
2222

2323
steps:
@@ -40,6 +40,7 @@ jobs:
4040
- name: Run backend tests with coverage
4141
run: |
4242
npx c8 \
43+
--all \
4344
--reporter=json-summary \
4445
--reporter=text \
4546
--src=src \

0 commit comments

Comments
 (0)