Skip to content

Use swift-testing stdout as test results log verbatim #1058

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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
65 changes: 8 additions & 57 deletions src/TestExplorer/TestParsers/SwiftTestingOutputParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import {
} from "./TestEventStreamReader";
import { ITestRunState } from "./TestRunState";
import { TestClass } from "../TestDiscovery";
import { regexEscapedString, sourceLocationToVSCodeLocation } from "../../utilities/utilities";
import { sourceLocationToVSCodeLocation } from "../../utilities/utilities";
import { exec } from "child_process";

// All events produced by a swift-testing run will be one of these three types.
Expand Down Expand Up @@ -222,44 +222,13 @@ export class SwiftTestingOutputParser {
* a JSON event and injecting them in to the test run output.
* @param chunk A chunk of stdout emitted during a test run.
*/
public parseStdout = (() => {
const values = [
...Object.values(TestSymbol)
.filter(symbol => symbol !== TestSymbol.none)
.map(symbol =>
regexEscapedString(
// Trim the ANSI reset code from the search since some lines
// are fully colorized from the symbol to the end of line.
SymbolRenderer.eventMessageSymbol(symbol).replace(
SymbolRenderer.resetANSIEscapeCode,
""
)
)
),
// It is possible there is no symbol for a line produced by swift-testing,
// for instance if the user has a multi line comment before a failing expectation
// only the first line of the printed comment will have a symbol, but to make the
// indentation consistent the subsequent lines will have three spaces. We don't want
// to treat this as output produced by the user during the test run, so omit these.
// This isn't ideal since this will swallow lines the user prints if they start with
// three spaces, but until we have user output as part of the JSON event stream we have
// this workaround.
" ",
];

// Build a regex of all the line beginnings that come out of swift-testing events.
const isSwiftTestingLineBeginning = new RegExp(`^${values.join("|")}`);

return (chunk: string, runState: ITestRunState) => {
for (const line of chunk.split("\n")) {
// Any line in stdout that fails to match as a swift-testing line is treated
// as a user printed value and recorded to the test run output with no associated test.
if (line.trim().length > 0 && isSwiftTestingLineBeginning.test(line) === false) {
runState.recordOutput(undefined, `${line}\r\n`);
}
public parseStdout(chunk: string, runState: ITestRunState) {
for (const line of chunk.split("\n")) {
if (line.trim().length > 0) {
runState.recordOutput(undefined, `${line}\r\n`);
}
};
})();
}
}

private createReader(path: string): INamedPipeReader {
return process.platform === "win32"
Expand Down Expand Up @@ -328,16 +297,6 @@ export class SwiftTestingOutputParser {
return fullNameIndex;
}

private recordOutput(
runState: ITestRunState,
messages: EventMessage[],
testIndex: number | undefined
) {
messages.forEach(message => {
runState.recordOutput(testIndex, `${MessageRenderer.render(message)}\r\n`);
});
}

/**
* Partitions a collection of messages in to issues and details about the issues.
* This is used to print the issues first, followed by the details.
Expand Down Expand Up @@ -411,11 +370,11 @@ export class SwiftTestingOutputParser {
// Notify the runner that we've recieved all the test cases and
// are going to start running tests now.
this.testRunStarted();
return;
} else if (item.payload.kind === "testStarted") {
const testName = this.testName(item.payload.testID);
const testIndex = runState.getTestItemIndex(testName, undefined);
runState.started(testIndex, item.payload.instant.absolute);
this.recordOutput(runState, item.payload.messages, testIndex);
return;
} else if (item.payload.kind === "testCaseStarted") {
const testID = this.idFromOptionalTestCase(
Expand All @@ -424,13 +383,11 @@ export class SwiftTestingOutputParser {
);
const testIndex = this.getTestCaseIndex(runState, testID);
runState.started(testIndex, item.payload.instant.absolute);
this.recordOutput(runState, item.payload.messages, testIndex);
return;
} else if (item.payload.kind === "testSkipped") {
const testName = this.testName(item.payload.testID);
const testIndex = runState.getTestItemIndex(testName, undefined);
runState.skipped(testIndex);
this.recordOutput(runState, item.payload.messages, testIndex);
return;
} else if (item.payload.kind === "issueRecorded") {
const testID = this.idFromOptionalTestCase(
Expand Down Expand Up @@ -466,8 +423,6 @@ export class SwiftTestingOutputParser {
);
});

this.recordOutput(runState, messages, testIndex);

if (item.payload._testCase && testID !== item.payload.testID) {
const testIndex = this.getTestCaseIndex(runState, item.payload.testID);
messages.forEach(message => {
Expand All @@ -478,7 +433,6 @@ export class SwiftTestingOutputParser {
} else if (item.payload.kind === "testEnded") {
const testName = this.testName(item.payload.testID);
const testIndex = runState.getTestItemIndex(testName, undefined);
this.recordOutput(runState, item.payload.messages, testIndex);

// When running a single test the testEnded and testCaseEnded events
// have the same ID, and so we'd end the same test twice.
Expand All @@ -494,7 +448,6 @@ export class SwiftTestingOutputParser {
item.payload._testCase
);
const testIndex = this.getTestCaseIndex(runState, testID);
this.recordOutput(runState, item.payload.messages, testIndex);

// When running a single test the testEnded and testCaseEnded events
// have the same ID, and so we'd end the same test twice.
Expand All @@ -505,8 +458,6 @@ export class SwiftTestingOutputParser {
runState.completed(testIndex, { timestamp: item.payload.instant.absolute });
return;
}

this.recordOutput(runState, item.payload.messages, undefined);
}
}
}
Expand Down
14 changes: 3 additions & 11 deletions test/suite/testexplorer/SwiftTestingOutputParser.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ import {
EventMessage,
SourceLocation,
TestSymbol,
SymbolRenderer,
MessageRenderer,
} from "../../../src/TestExplorer/TestParsers/SwiftTestingOutputParser";
import { TestRunState, TestStatus } from "./MockTestRunState";
Expand Down Expand Up @@ -153,7 +152,7 @@ suite("SwiftTestingOutputParser Suite", () => {
timing: {
timestamp: 0,
},
output: renderedMessages.map(message => `${message}\r\n`),
output: [],
},
]);
}
Expand Down Expand Up @@ -274,7 +273,6 @@ suite("SwiftTestingOutputParser Suite", () => {
true
);
const symbol = TestSymbol.pass;
const renderedSymbol = SymbolRenderer.symbol(symbol);
const makeEvent = (kind: ExtractPayload<EventRecord>["kind"], testId?: string) =>
testEvent(kind, testId, [{ text: kind, symbol }]);

Expand All @@ -292,21 +290,15 @@ suite("SwiftTestingOutputParser Suite", () => {
assert.deepEqual(testRunState.tests, [
{
name: "MyTests.MyTests/testOutput()",
output: [
`\u001b[92m${renderedSymbol}\u001b[0m testCaseStarted\r\n`,
`\u001b[92m${renderedSymbol}\u001b[0m testCaseEnded\r\n`,
],
output: [],
status: TestStatus.passed,
timing: {
timestamp: 0,
},
},
{
name: "MyTests.MyTests/testOutput2()",
output: [
`\u001b[92m${renderedSymbol}\u001b[0m testCaseStarted\r\n`,
`\u001b[92m${renderedSymbol}\u001b[0m testCaseEnded\r\n`,
],
output: [],
status: TestStatus.passed,
timing: {
timestamp: 0,
Expand Down