Skip to content

Commit b829057

Browse files
committed
More debugging
1 parent 83901a4 commit b829057

File tree

5 files changed

+191
-133
lines changed

5 files changed

+191
-133
lines changed

src/TestExplorer/TestParsers/XCTestOutputParser.ts

Lines changed: 169 additions & 108 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ import { MarkdownString, Location } from "vscode";
1818
// eslint-disable-next-line @typescript-eslint/no-require-imports
1919
import stripAnsi = require("strip-ansi");
2020

21+
/* eslint-disable no-console */
22+
2123
/** Regex for parsing XCTest output */
2224
interface TestRegex {
2325
started: RegExp;
@@ -151,117 +153,160 @@ export class XCTestOutputParser implements IXCTestOutputParser {
151153
* @param output Output from `swift test`
152154
*/
153155
public parseResult(rawOutput: string, runState: ITestRunState) {
154-
console.log("RAW XCTEST OUTPUT:", rawOutput.split("\n").join("\n>>>> "));
155-
156-
// Windows is inserting ANSI codes into the output to do things like clear the cursor,
157-
// which we don't care about.
158-
const output = process.platform === "win32" ? stripAnsi(rawOutput) : rawOutput;
159-
const output2 = output.replace(/\r\n/g, "\n");
160-
const lines = output2.split("\n");
161-
if (runState.excess) {
162-
lines[0] = runState.excess + lines[0];
163-
}
164-
// pop empty string off the end of the lines array
165-
if (lines.length > 0 && lines[lines.length - 1] === "") {
166-
lines.pop();
167-
}
168-
// if submitted text does not end with a newline then pop that off and store in excess
169-
// for next call of parseResult
170-
if (output2[output2.length - 1] !== "\n") {
171-
runState.excess = lines.pop();
172-
} else {
173-
runState.excess = undefined;
174-
}
175-
176-
// Non-Darwin test output does not include the test target name. The only way to find out
177-
// the target for a test is when it fails and returns a file name. If we find failed tests
178-
// first and then remove them from the list we cannot set them to passed by mistake.
179-
// We extract the file name from the error and use that to check whether the file belongs
180-
// to the target associated with the TestItem. This does not work 100% as the error could
181-
// occur in another target, so we revert to just searching for class and function name if
182-
// the above method is unsuccessful.
183-
for (const line of lines) {
184-
// Regex "Test Case '-[<test target> <class.function>]' started"
185-
const startedMatch = this.regex.started.exec(line);
186-
if (startedMatch) {
187-
const testName = `${startedMatch[1]}/${startedMatch[2]}`;
188-
189-
// Save the active TestTarget.SuiteClass.
190-
// Note that TestTarget is only present on Darwin.
191-
runState.activeSuite = startedMatch[1];
192-
this.processPendingSuiteOutput(runState, startedMatch[1]);
193-
194-
const startedTestIndex = runState.getTestItemIndex(testName, undefined);
195-
this.startTest(startedTestIndex, runState);
196-
this.appendTestOutput(startedTestIndex, line, runState);
197-
continue;
198-
}
199-
// Regex "Test Case '-[<test target> <class.function>]' <completion_state> (<duration> seconds)"
200-
const finishedMatch = this.regex.finished.exec(line);
201-
if (finishedMatch) {
202-
const testName = `${finishedMatch[1]}/${finishedMatch[2]}`;
203-
const testIndex = runState.getTestItemIndex(testName, undefined);
204-
const state = finishedMatch[3] as TestCompletionState;
205-
const duration = +finishedMatch[4];
206-
switch (state) {
207-
case TestCompletionState.failed:
208-
this.failTest(testIndex, { duration }, runState);
209-
break;
210-
case TestCompletionState.passed:
211-
this.passTest(testIndex, { duration }, runState);
212-
break;
213-
case TestCompletionState.skipped:
214-
this.skipTest(testIndex, runState);
215-
break;
216-
}
217-
218-
this.appendTestOutput(testIndex, line, runState);
219-
continue;
220-
}
221-
// Regex "<path/to/test>:<line number>: error: <class>.<function> : <error>"
222-
const errorMatch = this.regex.error.exec(line);
223-
if (errorMatch) {
224-
const testName = `${errorMatch[3]}/${errorMatch[4]}`;
225-
const failedTestIndex = runState.getTestItemIndex(testName, errorMatch[1]);
226-
this.startErrorMessage(
227-
failedTestIndex,
228-
errorMatch[5],
229-
errorMatch[1],
230-
errorMatch[2],
231-
runState
232-
);
233-
this.appendTestOutput(failedTestIndex, line, runState);
234-
continue;
235-
}
236-
// Regex "<path/to/test>:<line number>: <class>.<function> : Test skipped"
237-
const skippedMatch = this.regex.skipped.exec(line);
238-
if (skippedMatch) {
239-
const testName = `${skippedMatch[3]}/${skippedMatch[4]}`;
240-
const skippedTestIndex = runState.getTestItemIndex(testName, skippedMatch[1]);
241-
this.skipTest(skippedTestIndex, runState);
242-
this.appendTestOutput(skippedTestIndex, line, runState);
243-
continue;
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] + "|");
244183
}
245-
// Regex "Test Suite '-[<test target> <class.function>]' started"
246-
const startedSuiteMatch = this.regex.startedSuite.exec(line);
247-
if (startedSuiteMatch) {
248-
this.startTestSuite(startedSuiteMatch[1], line, runState);
249-
continue;
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();
250188
}
251-
// Regex "Test Suite '-[<test target> <class.function>]' passed"
252-
const passedSuiteMatch = this.regex.passedSuite.exec(line);
253-
if (passedSuiteMatch) {
254-
this.completeSuite(runState, line, this.passTestSuite);
255-
continue;
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;
256197
}
257-
// Regex "Test Suite '-[<test target> <class.function>]' failed"
258-
const failedSuiteMatch = this.regex.failedSuite.exec(line);
259-
if (failedSuiteMatch) {
260-
this.completeSuite(runState, line, this.failTestSuite);
261-
continue;
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);
262307
}
263-
// unrecognised output could be the continuation of a previous error message
264-
this.continueErrorMessage(line, runState);
308+
} catch (e) {
309+
console.error("!!! >>>>> !!! ERROR PARSING OUTPUT", e);
265310
}
266311
}
267312

@@ -342,6 +387,7 @@ export class XCTestOutputParser implements IXCTestOutputParser {
342387
private startTest(testIndex: number, runState: ITestRunState) {
343388
runState.started(testIndex);
344389
// clear error state
390+
console.log(">>>>> startTest resetting failed test", testIndex);
345391
runState.failedTest = undefined;
346392
}
347393

@@ -352,6 +398,7 @@ export class XCTestOutputParser implements IXCTestOutputParser {
352398
runState: ITestRunState
353399
) {
354400
runState.completed(testIndex, timing);
401+
console.log(">>>>> passTest resetting failed test", testIndex);
355402
runState.failedTest = undefined;
356403
}
357404

@@ -363,6 +410,14 @@ export class XCTestOutputParser implements IXCTestOutputParser {
363410
lineNumber: string,
364411
runState: ITestRunState
365412
) {
413+
console.log(
414+
"!!! >>>>> !! START ERROR MESSAGE",
415+
testIndex,
416+
message,
417+
file,
418+
lineNumber,
419+
!!runState.failedTest
420+
);
366421
// If we were already capturing an error record it and start a new one
367422
if (runState.failedTest) {
368423
const location = sourceLocationToVSCodeLocation(
@@ -379,6 +434,7 @@ export class XCTestOutputParser implements IXCTestOutputParser {
379434
lineNumber: parseInt(lineNumber),
380435
complete: false,
381436
};
437+
console.log(">>>>>>> SET FAILED TEST TO", runState.failedTest);
382438
}
383439

384440
/** continue capturing error message */
@@ -407,16 +463,21 @@ export class XCTestOutputParser implements IXCTestOutputParser {
407463
const diff = this.extractDiff(message);
408464
runState.recordIssue(testIndex, message, false, location, diff);
409465
} else {
410-
console.log(">>>>>>>>>> MARKED FAILED TEST WITH NO ATTACHED FAILED TEST >>>>>>>>>>>>>");
466+
console.log(
467+
`>>>>>>>>>> MARKED FAILED TEST WITH NO ATTACHED FAILED TEST ${testIndex} >>>>>>>>>>>>>`
468+
);
411469
runState.recordIssue(testIndex, "Failed", false);
412470
}
413471
runState.completed(testIndex, timing);
472+
473+
console.log(">>>>> failTest resetting failed test", testIndex);
414474
runState.failedTest = undefined;
415475
}
416476

417477
/** Flag we have skipped a test */
418478
private skipTest(testIndex: number, runState: ITestRunState) {
419479
runState.skipped(testIndex);
480+
console.log(">>>>> skipTest resetting failed test", testIndex);
420481
runState.failedTest = undefined;
421482
}
422483

src/TestExplorer/TestRunner.ts

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,6 @@ export interface TestRunState {
6767
errored: vscode.TestItem[];
6868
unknown: number;
6969
output: string[];
70-
buildConfig: string;
7170
}
7271

7372
export class TestRunProxy {
@@ -95,7 +94,6 @@ export class TestRunProxy {
9594
errored: [],
9695
unknown: 0,
9796
output: [],
98-
buildConfig: "",
9997
};
10098
}
10199

@@ -120,10 +118,6 @@ export class TestRunProxy {
120118
this.onTestRunComplete = this.testRunCompleteEmitter.event;
121119
}
122120

123-
public setBuildConfiguration(buildConfig: vscode.DebugConfiguration) {
124-
this.runState.buildConfig += "\n" + JSON.stringify(buildConfig, null, 2);
125-
}
126-
127121
public testRunStarted = () => {
128122
if (this.runStarted) {
129123
return;
@@ -595,8 +589,6 @@ export class TestRunner {
595589
return this.testRun.runState;
596590
}
597591

598-
this.testRun.setBuildConfiguration(testBuildConfig);
599-
600592
const outputStream = this.testOutputWritable(TestLibrary.swiftTesting, runState);
601593

602594
// Watch the pipe for JSONL output and parse the events into test explorer updates.
@@ -631,8 +623,6 @@ export class TestRunner {
631623
return this.testRun.runState;
632624
}
633625

634-
this.testRun.setBuildConfiguration(testBuildConfig);
635-
636626
// XCTestRuns are started immediately
637627
this.testRun.testRunStarted();
638628

@@ -967,8 +957,6 @@ export class TestRunner {
967957
});
968958
subscriptions.push(startSession);
969959

970-
this.testRun.setBuildConfiguration(config);
971-
972960
vscode.debug
973961
.startDebugging(this.folderContext.workspaceFolder, config)
974962
.then(

0 commit comments

Comments
 (0)