Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
- Fix extension displaying SwiftPM's project view and automatic build tasks even when `disableSwiftPMIntegration` was true ([#2011](https://github.com/swiftlang/vscode-swift/pull/2011))
- Validate extension settings and warn if they are invalid ([#2016](https://github.com/swiftlang/vscode-swift/pull/2016))
- Show the Test Results panel when tests fail to compile and the user has `testing.automaticallyOpenTestResults` set to `openOnTestFailure` ([#2035](https://github.com/swiftlang/vscode-swift/pull/2035))
- Swift-testing test runs are marked as 'started' in the UI immediately, not after compilation finishes ([#2039](https://github.com/swiftlang/vscode-swift/pull/2039))

## 2.14.3 - 2025-12-15

Expand Down
18 changes: 5 additions & 13 deletions src/TestExplorer/TestParsers/SwiftTestingOutputParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -191,8 +191,7 @@ export class SwiftTestingOutputParser {
private path?: string;

constructor(
public testRunStarted: () => void,
public addParameterizedTestCase: (testClass: TestClass, parentIndex: number) => void,
public addParameterizedTestCases: (testClasses: TestClass[], parentIndex: number) => void,
public onAttachment: (testIndex: number, path: string) => void
) {}

Expand Down Expand Up @@ -280,7 +279,6 @@ export class SwiftTestingOutputParser {
private handleEventRecord(payload: EventRecordPayload, runState: ITestRunState) {
switch (payload.kind) {
case "runStarted":
this.handleRunStarted();
break;
case "testStarted":
this.handleTestStarted(payload, runState);
Expand Down Expand Up @@ -322,9 +320,8 @@ export class SwiftTestingOutputParser {

const testIndex = this.testItemIndexFromTestID(item.payload.id, runState);
// If a test has test cases it is paramterized and we need to notify
// the caller that the TestClass should be added to the vscode.TestRun
// before it starts.
item.payload._testCases
// the caller that the TestClass should be added to the vscode.TestRun.
const parameterizedTestCases = item.payload._testCases
.map((testCase, index) =>
this.parameterizedFunctionTestCaseToTestClass(
item.payload.id,
Expand All @@ -337,14 +334,9 @@ export class SwiftTestingOutputParser {
index
)
)
.flatMap(testClass => (testClass ? [testClass] : []))
.forEach(testClass => this.addParameterizedTestCase(testClass, testIndex));
}
.flatMap(testClass => (testClass ? [testClass] : []));

private handleRunStarted() {
// Notify the runner that we've received all the test cases and
// are going to start running tests now.
this.testRunStarted();
this.addParameterizedTestCases(parameterizedTestCases, testIndex);
}

private handleTestStarted(payload: TestStarted, runState: ITestRunState) {
Expand Down
62 changes: 26 additions & 36 deletions src/TestExplorer/TestRunner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,6 @@ export interface TestRunState {

export class TestRunProxy {
private testRun?: vscode.TestRun;
private addedTestItems: { testClass: TestClass; parentIndex: number }[] = [];
private runStarted: boolean = false;
private queuedOutput: string[] = [];
private _testItems: vscode.TestItem[];
Expand Down Expand Up @@ -146,14 +145,23 @@ export class TestRunProxy {
this.resetTags(this.controller);
this.runStarted = true;

// When a test run starts we need to do several things:
// - Create new TestItems for each paramterized test that was added
// and attach them to their parent TestItem.
// - Create a new test run from the TestRunArguments + newly created TestItems.
// - Mark all of these test items as enqueued on the test run.
this.testRun = this.controller.createTestRun(this.testRunRequest);
this.token.add(this.testRun.token);

// Forward any output captured before the testRun was created.
for (const outputLine of this.queuedOutput) {
this.performAppendOutput(this.testRun, outputLine);
}
this.queuedOutput = [];

const addedTestItems = this.addedTestItems
.map(({ testClass, parentIndex }) => {
for (const test of this.testItems) {
this.enqueued(test);
}
};

public addParameterizedTestCases = (testClasses: TestClass[], parentIndex: number) => {
const addedTestItems = testClasses
.map(testClass => {
const parent = this.args.testItems[parentIndex];
// clear out the children before we add the new ones.
parent.children.replace([]);
Expand Down Expand Up @@ -189,34 +197,17 @@ export class TestRunProxy {

return added;
});

this.testRun = this.controller.createTestRun(this.testRunRequest);
this.token.add(this.testRun.token);

const existingTestItemCount = this.testItems.length;
this._testItems = [...this.testItems, ...addedTestItems];

if (this._testItems.length !== existingTestItemCount) {
// Recreate a test item finder with the added test items
this.testItemFinder =
process.platform === "darwin"
? new DarwinTestItemFinder(this.testItems)
: new NonDarwinTestItemFinder(this.testItems, this.folderContext);
}

// Forward any output captured before the testRun was created.
for (const outputLine of this.queuedOutput) {
this.performAppendOutput(this.testRun, outputLine);
}
this.queuedOutput = [];

for (const test of this.testItems) {
for (const test of addedTestItems) {
this.enqueued(test);
}
};

public addParameterizedTestCase = (testClass: TestClass, parentIndex: number) => {
this.addedTestItems.push({ testClass, parentIndex });
// Recreate a test item finder with the added test items
this.testItemFinder =
process.platform === "darwin"
? new DarwinTestItemFinder(this.testItems)
: new NonDarwinTestItemFinder(this.testItems, this.folderContext);
};

public addAttachment = (testIndex: number, attachment: string) => {
Expand Down Expand Up @@ -486,8 +477,7 @@ export class TestRunner {
)
: new XCTestOutputParser();
this.swiftTestOutputParser = new SwiftTestingOutputParser(
this.testRun.testRunStarted,
this.testRun.addParameterizedTestCase,
this.testRun.addParameterizedTestCases,
this.testRun.addAttachment
);
this.onDebugSessionTerminated = this.debugSessionTerminatedEmitter.event;
Expand All @@ -501,8 +491,7 @@ export class TestRunner {
public setIteration(iteration: number) {
// The SwiftTestingOutputParser holds state and needs to be reset between iterations.
this.swiftTestOutputParser = new SwiftTestingOutputParser(
this.testRun.testRunStarted,
this.testRun.addParameterizedTestCase,
this.testRun.addParameterizedTestCases,
this.testRun.addAttachment
);
this.testRun.setIteration(iteration);
Expand Down Expand Up @@ -830,6 +819,8 @@ export class TestRunner {
// The await simply waits for the watching to be configured.
await this.swiftTestOutputParser.watch(fifoPipePath, runState);

this.testRun.testRunStarted();

await this.launchTests(
runState,
this.testKind === TestKind.parallel ? TestKind.standard : this.testKind,
Expand Down Expand Up @@ -858,7 +849,6 @@ export class TestRunner {
return this.testRun.runState;
}

// XCTestRuns are started immediately
this.testRun.testRunStarted();

await this.launchTests(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,6 @@ suite("SwiftTestingOutputParser Suite", () => {

beforeEach(() => {
outputParser = new SwiftTestingOutputParser(
() => {},
() => {},
() => {}
);
Expand Down Expand Up @@ -234,13 +233,14 @@ suite("SwiftTestingOutputParser Suite", () => {
]);

const outputParser = new SwiftTestingOutputParser(
() => {},
testClass => {
testRunState.testItemFinder.tests.push({
name: testClass.id,
status: TestStatus.enqueued,
output: [],
});
testClasses => {
testClasses.forEach(testClass =>
testRunState.testItemFinder.tests.push({
name: testClass.id,
status: TestStatus.enqueued,
output: [],
})
);
},
() => {}
);
Expand Down