Skip to content

Commit 90f7641

Browse files
authored
Use native VSCode coverage APIs, cover swift-testing tests (#807)
* Use native VSCode coverage APIs, cover swift-testing Move to the new VSCode coverage APIs that were introduced in VSCode version 1.88. This allows us to remove the coverage renderer and coverage report files. This functionality is now built in to the test explorer and VSCode's document rendering. This patch also adds support for producing test coverage from swift-testing tests. XCTests and swift-testing tests are each run in their respective binaries and then the coverage data is merged into a profdata file before being exported to LCOV format. Instead of a build followed by a test run with --skip-build, use a SwiftExecution task to do swift test with the appropriate arguments. This shows the build progress in the status bar. Finally we transform the LCOV JSON into the coverage structures for VSCode and associate this data with the test run.
1 parent 7b76754 commit 90f7641

24 files changed

+1088
-1328
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,4 @@ default.profraw
55
*.vsix
66
.vscode-test
77
.build
8+
assets/test/**/Package.resolved

docs/images/coverage-render.png

16.6 KB
Loading

docs/images/coverage-report.png

27.7 KB
Loading

docs/images/coverage-run.png

4.68 KB
Loading

docs/test-coverage.md

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,14 @@
22

33
Test coverage is a measurement of how much of your code is tested by your tests. It defines how many lines of code were actually run when you ran your tests and how many were not. When a line of code is not run by your tests it will not have been tested and perhaps you need to extend your tests.
44

5-
The Swift extension has an option to run your tests and record what code has been hit or missed by your tests.
5+
The Swift extension integrates with VSCode's Code Coverage APIs to record what code has been hit or missed by your tests.
66

77
![](images/coverage-run.png)
88

9-
Once this is run an overview report will be displayed listing all the source files in your project and how many lines were hit by tests, how many lines were missed, how lines of source code there is in total and a percentage of those that were hit. You can click on each file to open that file in Visual Studio Code. If you close the report you can always get it back by running the command `Show Test Coverage Report`. There is also a setting `Coverage: Display Report after Run` to control whether the test coverage report is shown immediately after running tests.
9+
Once you've performed a code coverage run a coverage report will be displayed in a section of the primary side bar. This report lists all the source files in your project and what percentage of lines were hit by tests. You can click on each file to open that file in the code editor. If you close the report you can always get it back by running the command `Test: Open Coverage`.
1010

1111
![](images/coverage-report.png)
1212

13-
If you would like a more detailed view of the results there is a command `Toggle Display of Test Coverage Results` to toggle an in editor view of the coverage results. This will color the background of hit and missed lines of code with different colours. By default a hit line of code gets a green background and a missed line get a red background, although these are editable in the settings.
13+
After generating code coverage lines numbers in covered files will be coloured red or green depending on if they ran during the test run. Hovering over the line numbers shows how many times each line was run. Hitting the "Toggle Inline Coverage" link that appears when hovering over the line numbers will keep this information visible.
1414

1515
![](images/coverage-render.png)
16-
17-
An additional UI status item is displayed in the status bar at the bottom of the screen showing the hit percentage for the currently open file. This status item can be set to be always visible or only when coverage information is available. If you have it set to be visible all the time it can be used as a button to toggle the display of the in editor coverage results.
18-

package-lock.json

Lines changed: 8 additions & 8 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
"url": "https://github.com/swift-server/vscode-swift"
1111
},
1212
"engines": {
13-
"vscode": "^1.71.0"
13+
"vscode": "^1.88.0"
1414
},
1515
"categories": [
1616
"Programming Languages",
@@ -160,16 +160,6 @@
160160
"title": "Insert Function Comment",
161161
"category": "Swift"
162162
},
163-
{
164-
"command": "swift.showTestCoverageReport",
165-
"title": "Show Test Coverage Report",
166-
"category": "Swift"
167-
},
168-
{
169-
"command": "swift.toggleTestCoverage",
170-
"title": "Toggle Display of Test Coverage Results",
171-
"category": "Swift"
172-
},
173163
{
174164
"command": "swift.attachDebugger",
175165
"title": "Attach to Process...",
@@ -453,47 +443,6 @@
453443
}
454444
}
455445
},
456-
{
457-
"title": "Test Coverage",
458-
"properties": {
459-
"swift.coverage.displayReportAfterRun": {
460-
"type": "boolean",
461-
"default": true,
462-
"description": "Should test coverage report be shown after running tests",
463-
"order": 1
464-
},
465-
"swift.coverage.alwaysShowStatusItem": {
466-
"type": "boolean",
467-
"default": true,
468-
"description": "Always show the test coverage status item. If this is set to true the status item can be clicked on to toggle test coverage display on and off.",
469-
"order": 2
470-
},
471-
"swift.coverage.colors.lightMode.hit": {
472-
"type": "string",
473-
"default": "#c0ffc0",
474-
"description": "Light mode theme background color for line of code hit during test coverage.",
475-
"order": 3
476-
},
477-
"swift.coverage.colors.lightMode.miss": {
478-
"type": "string",
479-
"default": "#ffc0c0",
480-
"description": "Light mode theme background color for line of code missed during test coverage.",
481-
"order": 4
482-
},
483-
"swift.coverage.colors.darkMode.hit": {
484-
"type": "string",
485-
"default": "#003000",
486-
"description": "Dark mode theme background color for line of code hit during test coverage.",
487-
"order": 5
488-
},
489-
"swift.coverage.colors.darkMode.miss": {
490-
"type": "string",
491-
"default": "#400000",
492-
"description": "Dark mode theme background color for line of code missed during test coverage.",
493-
"order": 6
494-
}
495-
}
496-
},
497446
{
498447
"title": "Debugger",
499448
"properties": {
@@ -1177,7 +1126,7 @@
11771126
"@types/mocha": "^10.0.1",
11781127
"@types/node": "^18.19.33",
11791128
"@types/plist": "^3.0.5",
1180-
"@types/vscode": "^1.71.0",
1129+
"@types/vscode": "^1.88.0",
11811130
"@typescript-eslint/eslint-plugin": "^7.8.0",
11821131
"@typescript-eslint/parser": "^7.8.0",
11831132
"@vscode/test-electron": "^2.3.8",

src/FolderContext.ts

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ import { TestExplorer } from "./TestExplorer/TestExplorer";
2121
import { WorkspaceContext, FolderEvent } from "./WorkspaceContext";
2222
import { BackgroundCompilation } from "./BackgroundCompilation";
2323
import { TaskQueue } from "./tasks/TaskQueue";
24-
import { LcovResults } from "./coverage/LcovResults";
2524
import { isPathInsidePath } from "./utilities/utilities";
2625

2726
export class FolderContext implements vscode.Disposable {
@@ -30,7 +29,6 @@ export class FolderContext implements vscode.Disposable {
3029
public hasResolveErrors = false;
3130
public testExplorer?: TestExplorer;
3231
public taskQueue: TaskQueue;
33-
public lcovResults: LcovResults;
3432

3533
/**
3634
* FolderContext constructor
@@ -49,15 +47,13 @@ export class FolderContext implements vscode.Disposable {
4947
this.packageWatcher.install();
5048
this.backgroundCompilation = new BackgroundCompilation(this);
5149
this.taskQueue = new TaskQueue(this);
52-
this.lcovResults = new LcovResults(this);
5350
}
5451

5552
/** dispose of any thing FolderContext holds */
5653
dispose() {
5754
this.linuxMain?.dispose();
5855
this.packageWatcher.dispose();
5956
this.testExplorer?.dispose();
60-
this.lcovResults.dispose();
6157
}
6258

6359
/**

src/TestExplorer/TestParsers/XCTestOutputParser.ts

Lines changed: 65 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515
import { ITestRunState } from "./TestRunState";
1616
import { sourceLocationToVSCodeLocation } from "../../utilities/utilities";
17+
import { MarkdownString, Location } from "vscode";
1718

1819
/** Regex for parsing XCTest output */
1920
interface TestRegex {
@@ -67,7 +68,69 @@ export const nonDarwinTestRegex = {
6768
failedSuite: /^Test Suite '(.*)' failed/,
6869
};
6970

70-
export class XCTestOutputParser {
71+
export interface IXCTestOutputParser {
72+
parseResult(output: string, runState: ITestRunState): void;
73+
}
74+
75+
export class ParallelXCTestOutputParser implements IXCTestOutputParser {
76+
private outputParser: XCTestOutputParser;
77+
78+
/**
79+
* Create an ParallelXCTestOutputParser.
80+
* Optional regex can be supplied for tests.
81+
*/
82+
constructor(
83+
private hasMultiLineParallelTestOutput: boolean,
84+
regex?: TestRegex
85+
) {
86+
this.outputParser = new XCTestOutputParser(regex);
87+
}
88+
89+
public parseResult(output: string, runState: ITestRunState) {
90+
// From 5.7 to 5.10 running with the --parallel option dumps the test results out
91+
// to the console with no newlines, so it isn't possible to distinguish where errors
92+
// begin and end. Consequently we can't record them, and so we manually mark them
93+
// as passed or failed here with a manufactured issue.
94+
// Don't attempt to parse the console output of parallel tests between 5.7 and 5.10
95+
// as it doesn't have newlines. You might get lucky and find the output is split
96+
// in the right spot, but more often than not we wont be able to parse it.
97+
if (!this.hasMultiLineParallelTestOutput) {
98+
return;
99+
}
100+
101+
// For parallel XCTest runs we get pass/fail results from the xunit XML
102+
// produced at the end of the run, but we still want to monitor the output
103+
// for the individual assertion failures. Wrap the run state and only forward
104+
// along the issues captured during a test run, and let the `TestXUnitParser`
105+
// handle marking tests as completed.
106+
this.outputParser.parseResult(output, new ParallelXCTestRunStateProxy(runState));
107+
}
108+
}
109+
110+
/* eslint-disable @typescript-eslint/no-unused-vars */
111+
class ParallelXCTestRunStateProxy implements ITestRunState {
112+
constructor(private runState: ITestRunState) {}
113+
114+
getTestItemIndex(id: string, filename: string | undefined): number {
115+
return this.runState.getTestItemIndex(id, filename);
116+
}
117+
recordIssue(
118+
index: number,
119+
message: string | MarkdownString,
120+
location?: Location | undefined
121+
): void {
122+
this.runState.recordIssue(index, message, location);
123+
}
124+
started(index: number, startTime?: number | undefined): void {}
125+
completed(index: number, timing: { duration: number } | { timestamp: number }): void {}
126+
skipped(index: number): void {}
127+
startedSuite(name: string): void {}
128+
passedSuite(name: string): void {}
129+
failedSuite(name: string): void {}
130+
}
131+
/* eslint-enable @typescript-eslint/no-unused-vars */
132+
133+
export class XCTestOutputParser implements IXCTestOutputParser {
71134
private regex: TestRegex;
72135

73136
/**
@@ -93,7 +156,7 @@ export class XCTestOutputParser {
93156
lines.pop();
94157
}
95158
// if submitted text does not end with a newline then pop that off and store in excess
96-
// for next call of parseResultDarwin
159+
// for next call of parseResult
97160
if (output2[output2.length - 1] !== "\n") {
98161
runState.excess = lines.pop();
99162
} else {

0 commit comments

Comments
 (0)