Skip to content

Commit 775944b

Browse files
committed
Proxy state through parallel parser too
1 parent b829057 commit 775944b

File tree

3 files changed

+203
-153
lines changed

3 files changed

+203
-153
lines changed

src/TestExplorer/TestParsers/XCTestOutputParser.ts

Lines changed: 198 additions & 153 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,9 @@
1414

1515
import { ITestRunState, TestIssueDiff } from "./TestRunState";
1616
import { sourceLocationToVSCodeLocation } from "../../utilities/utilities";
17-
import { MarkdownString, Location } from "vscode";
1817
// eslint-disable-next-line @typescript-eslint/no-require-imports
1918
import stripAnsi = require("strip-ansi");
19+
import { Location, MarkdownString } from "vscode";
2020

2121
/* eslint-disable no-console */
2222

@@ -94,8 +94,9 @@ export class ParallelXCTestOutputParser implements IXCTestOutputParser {
9494
public parseResult(output: string, runState: ITestRunState) {
9595
// From 5.7 to 5.10 running with the --parallel option dumps the test results out
9696
// to the console with no newlines, so it isn't possible to distinguish where errors
97-
// begin and end. Consequently we can't record them, and so we manually mark them
98-
// as passed or failed here with a manufactured issue.
97+
// begin and end. Consequently we can't record them. For these versions we rely on the
98+
// generated xunit XML, which we can parse and mark tests as passed or failed here with
99+
// manufactured issues.
99100
// Don't attempt to parse the console output of parallel tests between 5.7 and 5.10
100101
// as it doesn't have newlines. You might get lucky and find the output is split
101102
// in the right spot, but more often than not we wont be able to parse it.
@@ -116,6 +117,56 @@ export class ParallelXCTestOutputParser implements IXCTestOutputParser {
116117
class ParallelXCTestRunStateProxy implements ITestRunState {
117118
constructor(private runState: ITestRunState) {}
118119

120+
get excess(): string | undefined {
121+
return this.runState.excess;
122+
}
123+
124+
set excess(value: string | undefined) {
125+
this.runState.excess = value;
126+
}
127+
128+
get activeSuite(): string | undefined {
129+
return this.runState.activeSuite;
130+
}
131+
132+
set activeSuite(value: string | undefined) {
133+
this.runState.activeSuite = value;
134+
}
135+
136+
get pendingSuiteOutput(): string[] | undefined {
137+
return this.runState.pendingSuiteOutput;
138+
}
139+
140+
set pendingSuiteOutput(value: string[] | undefined) {
141+
this.runState.pendingSuiteOutput = value;
142+
}
143+
144+
get failedTest():
145+
| {
146+
testIndex: number;
147+
message: string;
148+
file: string;
149+
lineNumber: number;
150+
complete: boolean;
151+
}
152+
| undefined {
153+
return this.runState.failedTest;
154+
}
155+
156+
set failedTest(
157+
value:
158+
| {
159+
testIndex: number;
160+
message: string;
161+
file: string;
162+
lineNumber: number;
163+
complete: boolean;
164+
}
165+
| undefined
166+
) {
167+
this.runState.failedTest = value;
168+
}
169+
119170
getTestItemIndex(id: string, filename: string | undefined): number {
120171
return this.runState.getTestItemIndex(id, filename);
121172
}
@@ -153,160 +204,154 @@ export class XCTestOutputParser implements IXCTestOutputParser {
153204
* @param output Output from `swift test`
154205
*/
155206
public parseResult(rawOutput: string, runState: ITestRunState) {
156-
try {
157-
console.log(
158-
">>>>>>>>>>>>>>>> RAW XCTEST OUTPUT >>>>>>>>>>>>>>>>>>> :\n" +
159-
rawOutput.split("\n").join("\n")
160-
);
161-
console.log(">>>>>>>>>>>>>>>> RAW XCTEST OUTPUT COMPLETE >>>>>>>>>>>>>>>>>>>");
162-
const annotated = rawOutput
163-
.replaceAll("\r\n", "🐀❌")
164-
.replaceAll("\n\r", "❌🐀")
165-
.replaceAll("\n", "❌")
166-
.replaceAll("\r", "🐀");
167-
console.log(
168-
">>>>>>>>>>>>>>>> ANNOTATED XCTEST OUTPUT >>>>>>>>>>>>>>>>>>> :\n" + annotated
169-
);
170-
console.log(">>>>>>>>>>>>>>>> ANNOTATED XCTEST OUTPUT COMPLETE >>>>>>>>>>>>>>>>>>>");
171-
172-
// Windows is inserting ANSI codes into the output to do things like clear the cursor,
173-
// which we don't care about.
174-
const output = process.platform === "win32" ? stripAnsi(rawOutput) : rawOutput;
175-
const output2 = output.replace(/\r\n/g, "\n");
176-
const lines = output2.split("\n");
177-
console.log(">>> Lines to Process:", lines);
178-
console.log(">>> Run State Excess |" + runState.excess + "|");
179-
if (runState.excess) {
180-
console.log(">>> !Had Excess from previous output |" + runState.excess + "|");
181-
lines[0] = runState.excess + lines[0];
182-
console.log(">>> !New line[0] is now |" + lines[0] + "|");
207+
console.log(
208+
">>>>>>>>>>>>>>>> RAW XCTEST OUTPUT >>>>>>>>>>>>>>>>>>> :\n" +
209+
rawOutput.split("\n").join("\n")
210+
);
211+
console.log(">>>>>>>>>>>>>>>> RAW XCTEST OUTPUT COMPLETE >>>>>>>>>>>>>>>>>>>");
212+
const annotated = rawOutput
213+
.replaceAll("\r\n", "🐀❌")
214+
.replaceAll("\n\r", "❌🐀")
215+
.replaceAll("\n", "❌")
216+
.replaceAll("\r", "🐀");
217+
console.log(">>>>>>>>>>>>>>>> ANNOTATED XCTEST OUTPUT >>>>>>>>>>>>>>>>>>> :\n" + annotated);
218+
console.log(">>>>>>>>>>>>>>>> ANNOTATED XCTEST OUTPUT COMPLETE >>>>>>>>>>>>>>>>>>>");
219+
220+
// Windows is inserting ANSI codes into the output to do things like clear the cursor,
221+
// which we don't care about.
222+
const output = process.platform === "win32" ? stripAnsi(rawOutput) : rawOutput;
223+
const output2 = output.replace(/\r\n/g, "\n");
224+
const lines = output2.split("\n");
225+
console.log(">>> Lines to Process:", lines);
226+
console.log(">>> Run State Excess |" + runState.excess + "|");
227+
if (runState.excess) {
228+
console.log(">>> !Had Excess from previous output |" + runState.excess + "|");
229+
lines[0] = runState.excess + lines[0];
230+
console.log(">>> !New line[0] is now |" + lines[0] + "|");
231+
}
232+
// pop empty string off the end of the lines array
233+
if (lines.length > 0 && lines[lines.length - 1] === "") {
234+
console.log(">>> Trimming last line");
235+
lines.pop();
236+
}
237+
// if submitted text does not end with a newline then pop that off and store in excess
238+
// for next call of parseResult
239+
if (output2[output2.length - 1] !== "\n") {
240+
runState.excess = lines.pop();
241+
console.log(">>> Saving Excess |" + runState.excess + "|");
242+
} else {
243+
console.log(">>> Unsetting Excess");
244+
runState.excess = undefined;
245+
}
246+
247+
console.log(">>> Lines to Process AFTER:", lines);
248+
249+
// Non-Darwin test output does not include the test target name. The only way to find out
250+
// the target for a test is when it fails and returns a file name. If we find failed tests
251+
// first and then remove them from the list we cannot set them to passed by mistake.
252+
// We extract the file name from the error and use that to check whether the file belongs
253+
// to the target associated with the TestItem. This does not work 100% as the error could
254+
// occur in another target, so we revert to just searching for class and function name if
255+
// the above method is unsuccessful.
256+
for (const line of lines) {
257+
console.log(">>> Process Line: |" + line + "|");
258+
// Regex "Test Case '-[<test target> <class.function>]' started"
259+
const startedMatch = this.regex.started.exec(line);
260+
if (startedMatch) {
261+
const testName = `${startedMatch[1]}/${startedMatch[2]}`;
262+
263+
// Save the active TestTarget.SuiteClass.
264+
// Note that TestTarget is only present on Darwin.
265+
runState.activeSuite = startedMatch[1];
266+
this.processPendingSuiteOutput(runState, startedMatch[1]);
267+
268+
const startedTestIndex = runState.getTestItemIndex(testName, undefined);
269+
this.startTest(startedTestIndex, runState);
270+
this.appendTestOutput(startedTestIndex, line, runState);
271+
continue;
183272
}
184-
// pop empty string off the end of the lines array
185-
if (lines.length > 0 && lines[lines.length - 1] === "") {
186-
console.log(">>> Trimming last line");
187-
lines.pop();
273+
// Regex "Test Case '-[<test target> <class.function>]' <completion_state> (<duration> seconds)"
274+
const finishedMatch = this.regex.finished.exec(line);
275+
if (finishedMatch) {
276+
const testName = `${finishedMatch[1]}/${finishedMatch[2]}`;
277+
const testIndex = runState.getTestItemIndex(testName, undefined);
278+
const state = finishedMatch[3] as TestCompletionState;
279+
const duration = +finishedMatch[4];
280+
console.log(
281+
"!!! >>>>> !!!! FINISHED MATCH ON LINE:",
282+
line,
283+
"...STATE IS",
284+
state,
285+
"... TEST INDEX IS",
286+
testIndex,
287+
"... FAILED TEST IS",
288+
runState.failedTest
289+
);
290+
switch (state) {
291+
case TestCompletionState.failed:
292+
this.failTest(testIndex, { duration }, runState);
293+
break;
294+
case TestCompletionState.passed:
295+
this.passTest(testIndex, { duration }, runState);
296+
break;
297+
case TestCompletionState.skipped:
298+
this.skipTest(testIndex, runState);
299+
break;
300+
}
301+
302+
this.appendTestOutput(testIndex, line, runState);
303+
continue;
188304
}
189-
// if submitted text does not end with a newline then pop that off and store in excess
190-
// for next call of parseResult
191-
if (output2[output2.length - 1] !== "\n") {
192-
runState.excess = lines.pop();
193-
console.log(">>> Saving Excess |" + runState.excess + "|");
194-
} else {
195-
console.log(">>> Unsetting Excess");
196-
runState.excess = undefined;
305+
// Regex "<path/to/test>:<line number>: error: <class>.<function> : <error>"
306+
const errorMatch = this.regex.error.exec(line);
307+
if (errorMatch) {
308+
const testName = `${errorMatch[3]}/${errorMatch[4]}`;
309+
const failedTestIndex = runState.getTestItemIndex(testName, errorMatch[1]);
310+
console.log(
311+
">>> ERROR MATCH ON LINE:",
312+
line,
313+
"...FAILED TEST INDEX IS",
314+
failedTestIndex
315+
);
316+
this.startErrorMessage(
317+
failedTestIndex,
318+
errorMatch[5],
319+
errorMatch[1],
320+
errorMatch[2],
321+
runState
322+
);
323+
this.appendTestOutput(failedTestIndex, line, runState);
324+
continue;
197325
}
198-
199-
console.log(">>> Lines to Process AFTER:", lines);
200-
201-
// Non-Darwin test output does not include the test target name. The only way to find out
202-
// the target for a test is when it fails and returns a file name. If we find failed tests
203-
// first and then remove them from the list we cannot set them to passed by mistake.
204-
// We extract the file name from the error and use that to check whether the file belongs
205-
// to the target associated with the TestItem. This does not work 100% as the error could
206-
// occur in another target, so we revert to just searching for class and function name if
207-
// the above method is unsuccessful.
208-
for (const line of lines) {
209-
console.log(">>> Process Line: |" + line + "|");
210-
// Regex "Test Case '-[<test target> <class.function>]' started"
211-
const startedMatch = this.regex.started.exec(line);
212-
if (startedMatch) {
213-
const testName = `${startedMatch[1]}/${startedMatch[2]}`;
214-
215-
// Save the active TestTarget.SuiteClass.
216-
// Note that TestTarget is only present on Darwin.
217-
runState.activeSuite = startedMatch[1];
218-
this.processPendingSuiteOutput(runState, startedMatch[1]);
219-
220-
const startedTestIndex = runState.getTestItemIndex(testName, undefined);
221-
this.startTest(startedTestIndex, runState);
222-
this.appendTestOutput(startedTestIndex, line, runState);
223-
continue;
224-
}
225-
// Regex "Test Case '-[<test target> <class.function>]' <completion_state> (<duration> seconds)"
226-
const finishedMatch = this.regex.finished.exec(line);
227-
if (finishedMatch) {
228-
const testName = `${finishedMatch[1]}/${finishedMatch[2]}`;
229-
const testIndex = runState.getTestItemIndex(testName, undefined);
230-
const state = finishedMatch[3] as TestCompletionState;
231-
const duration = +finishedMatch[4];
232-
console.log(
233-
"!!! >>>>> !!!! FINISHED MATCH ON LINE:",
234-
line,
235-
"...STATE IS",
236-
state,
237-
"... TEST INDEX IS",
238-
testIndex,
239-
"... FAILED TEST IS",
240-
runState.failedTest
241-
);
242-
switch (state) {
243-
case TestCompletionState.failed:
244-
this.failTest(testIndex, { duration }, runState);
245-
break;
246-
case TestCompletionState.passed:
247-
this.passTest(testIndex, { duration }, runState);
248-
break;
249-
case TestCompletionState.skipped:
250-
this.skipTest(testIndex, runState);
251-
break;
252-
}
253-
254-
this.appendTestOutput(testIndex, line, runState);
255-
continue;
256-
}
257-
// Regex "<path/to/test>:<line number>: error: <class>.<function> : <error>"
258-
const errorMatch = this.regex.error.exec(line);
259-
if (errorMatch) {
260-
const testName = `${errorMatch[3]}/${errorMatch[4]}`;
261-
const failedTestIndex = runState.getTestItemIndex(testName, errorMatch[1]);
262-
console.log(
263-
">>> ERROR MATCH ON LINE:",
264-
line,
265-
"...FAILED TEST INDEX IS",
266-
failedTestIndex
267-
);
268-
this.startErrorMessage(
269-
failedTestIndex,
270-
errorMatch[5],
271-
errorMatch[1],
272-
errorMatch[2],
273-
runState
274-
);
275-
this.appendTestOutput(failedTestIndex, line, runState);
276-
continue;
277-
}
278-
// Regex "<path/to/test>:<line number>: <class>.<function> : Test skipped"
279-
const skippedMatch = this.regex.skipped.exec(line);
280-
if (skippedMatch) {
281-
const testName = `${skippedMatch[3]}/${skippedMatch[4]}`;
282-
const skippedTestIndex = runState.getTestItemIndex(testName, skippedMatch[1]);
283-
this.skipTest(skippedTestIndex, runState);
284-
this.appendTestOutput(skippedTestIndex, line, runState);
285-
continue;
286-
}
287-
// Regex "Test Suite '-[<test target> <class.function>]' started"
288-
const startedSuiteMatch = this.regex.startedSuite.exec(line);
289-
if (startedSuiteMatch) {
290-
this.startTestSuite(startedSuiteMatch[1], line, runState);
291-
continue;
292-
}
293-
// Regex "Test Suite '-[<test target> <class.function>]' passed"
294-
const passedSuiteMatch = this.regex.passedSuite.exec(line);
295-
if (passedSuiteMatch) {
296-
this.completeSuite(runState, line, this.passTestSuite);
297-
continue;
298-
}
299-
// Regex "Test Suite '-[<test target> <class.function>]' failed"
300-
const failedSuiteMatch = this.regex.failedSuite.exec(line);
301-
if (failedSuiteMatch) {
302-
this.completeSuite(runState, line, this.failTestSuite);
303-
continue;
304-
}
305-
// unrecognised output could be the continuation of a previous error message
306-
this.continueErrorMessage(line, runState);
326+
// Regex "<path/to/test>:<line number>: <class>.<function> : Test skipped"
327+
const skippedMatch = this.regex.skipped.exec(line);
328+
if (skippedMatch) {
329+
const testName = `${skippedMatch[3]}/${skippedMatch[4]}`;
330+
const skippedTestIndex = runState.getTestItemIndex(testName, skippedMatch[1]);
331+
this.skipTest(skippedTestIndex, runState);
332+
this.appendTestOutput(skippedTestIndex, line, runState);
333+
continue;
334+
}
335+
// Regex "Test Suite '-[<test target> <class.function>]' started"
336+
const startedSuiteMatch = this.regex.startedSuite.exec(line);
337+
if (startedSuiteMatch) {
338+
this.startTestSuite(startedSuiteMatch[1], line, runState);
339+
continue;
340+
}
341+
// Regex "Test Suite '-[<test target> <class.function>]' passed"
342+
const passedSuiteMatch = this.regex.passedSuite.exec(line);
343+
if (passedSuiteMatch) {
344+
this.completeSuite(runState, line, this.passTestSuite);
345+
continue;
346+
}
347+
// Regex "Test Suite '-[<test target> <class.function>]' failed"
348+
const failedSuiteMatch = this.regex.failedSuite.exec(line);
349+
if (failedSuiteMatch) {
350+
this.completeSuite(runState, line, this.failTestSuite);
351+
continue;
307352
}
308-
} catch (e) {
309-
console.error("!!! >>>>> !!! ERROR PARSING OUTPUT", e);
353+
// unrecognised output could be the continuation of a previous error message
354+
this.continueErrorMessage(line, runState);
310355
}
311356
}
312357

src/TestExplorer/TestXUnitParser.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,8 @@ export class TestXUnitParser {
6464

6565
// eslint-disable-next-line @typescript-eslint/no-unused-vars
6666
async parseXUnit(xUnit: XUnit, runState: TestRunnerTestRunState): Promise<TestResults> {
67+
// eslint-disable-next-line no-console
68+
console.log(">>>>>> PARSE XUNIT!", xUnit);
6769
let tests = 0;
6870
let failures = 0;
6971
let errors = 0;

0 commit comments

Comments
 (0)