Skip to content
Closed
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
50 changes: 45 additions & 5 deletions src/runners/junitRunner/JUnitRunnerResultAnalyzer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,24 @@ import { IRunTestContext, TestKind, TestLevel, TestResultState } from '../../jav

export class JUnitRunnerResultAnalyzer extends RunnerResultAnalyzer {

private static readonly CONTROL_MESSAGE_PREFIXES: string[] = [
'%TSTTREE',
'%TESTS',
'%TESTE',
'%FAILED',
'%ERROR',
'%EXPECTS',
'%EXPECTE',
'%ACTUALS',
'%ACTUALE',
'%TRACES',
'%TRACEE',
'%TESTC',
'%RUNTIME',
'%MENTER',
'%MEXIT',
];

private testOutputMapping: Map<string, ITestInfo> = new Map();
private triggeredTestsMapping: Map<string, TestItem> = new Map();
private projectName: string;
Expand All @@ -25,9 +43,9 @@ export class JUnitRunnerResultAnalyzer extends RunnerResultAnalyzer {
private tracingItem: TestItem | undefined;
private traces: MarkdownString;
private assertionFailure: TestMessage | undefined;
private recordingType: RecordingType;
private expectString: string;
private actualString: string;
private recordingType: RecordingType = RecordingType.None;
private expectString: string = '';
private actualString: string = '';

constructor(protected testContext: IRunTestContext) {
super(testContext);
Expand All @@ -51,12 +69,34 @@ export class JUnitRunnerResultAnalyzer extends RunnerResultAnalyzer {

public analyzeData(data: string): void {
const lines: string[] = data.split(/\r?\n/);
for (const line of lines) {
const endsWithNewLine: boolean = /\r?\n$/.test(data);
for (let i: number = 0; i < lines.length; i++) {
const line: string = lines[i];
if (i === lines.length - 1 && endsWithNewLine && line === '') {
continue;
}
this.processData(line);
this.testContext.testRun.appendOutput(line + '\r\n');
// Hide Eclipse RemoteTestRunner protocol frames/payload already consumed by processData().
// Forward everything else, including stdout/stderr if present in the runner stream.
if (!this.isControlMessage(line) && !this.isRecordingProtocolPayload()) {
this.testContext.testRun.appendOutput(line + '\r\n');
}
}
}

/**
* Whether the given line is an Eclipse RemoteTestRunner control message.
* Only known protocol frame prefixes are filtered so user output that merely starts
* with '%' (for example, "%OK" or "%STATUS 200") remains visible.
*/
private isControlMessage(line: string): boolean {
return JUnitRunnerResultAnalyzer.CONTROL_MESSAGE_PREFIXES.some((prefix: string) => line.startsWith(prefix));
}

private isRecordingProtocolPayload(): boolean {
return this.recordingType !== RecordingType.None;
}

public processData(data: string): void {
if (data.startsWith(MessageId.TestTree)) {
this.enlistToTestMapping(data.substring(MessageId.TestTree.length).trim());
Expand Down
71 changes: 70 additions & 1 deletion test/suite/JUnitAnalyzer.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ at org.junit.Assert.assertTrue(Assert.java:53)
at junit4.TestAnnotation.shouldFail(TestAnnotation.java:15)
%TRACEE
%TESTE 1,shouldFail(junit4.TestAnnotation)
%RUNTIME20;`;
%RUNTIME20\n`;
const runnerContext: IRunTestContext = {
isDebug: false,
kind: TestKind.JUnit,
Expand All @@ -89,6 +89,75 @@ at junit4.TestAnnotation.shouldFail(TestAnnotation.java:15)
assert.ok((testMessage.message as MarkdownString).value.includes('junit4.TestAnnotation.shouldFail([TestAnnotation.java:15](command:_java.test.openStackTrace?%5B%22at%20junit4.TestAnnotation.shouldFail(TestAnnotation.java%3A15)%22%2C%22junit%22%5D))'));
});

test("control protocol frames should not be echoed to the output", () => {
const testItem = generateTestItem(testController, 'junit@junit4.TestAnnotation#shouldFail', TestKind.JUnit);
const testRunRequest = new TestRunRequest([testItem], []);
const testRun = testController.createTestRun(testRunRequest);
const appendOutputSpy = sinon.spy(testRun, 'appendOutput');
const testRunnerOutput = `%TESTC 1 v2
%TSTTREE1,shouldFail(junit4.TestAnnotation),false,1,false,-1,shouldFail(junit4.TestAnnotation),,
%TESTS 1,shouldFail(junit4.TestAnnotation)
Hello from System.out
%OK
%ABC
%STATUS 200
%FAILED 1,shouldFail(junit4.TestAnnotation)
%EXPECTS
expected
%EXPECTE
%ACTUALS
actual
%ACTUALE
%TRACES
java.lang.AssertionError
at junit4.TestAnnotation.shouldFail(TestAnnotation.java:15)
%TRACEE
%TESTE 1,shouldFail(junit4.TestAnnotation)
%RUNTIME20\n`;
const runnerContext: IRunTestContext = {
isDebug: false,
kind: TestKind.JUnit,
projectName: 'junit',
testItems: [testItem],
testRun: testRun,
workspaceFolder: workspace.workspaceFolders?.[0]!,
};

const analyzer = new JUnitRunnerResultAnalyzer(runnerContext);
analyzer.analyzeData(testRunnerOutput);

const echoed: string[] = appendOutputSpy.getCalls().map((call) => call.args[0] as string);
const echoedLines: string[] = echoed.map((output) => output.replace(/\r?\n$/, ''));

const controlFrames: string[] = [
'%TESTC 1 v2',
'%TSTTREE1,shouldFail(junit4.TestAnnotation),false,1,false,-1,shouldFail(junit4.TestAnnotation),,',
'%TESTS 1,shouldFail(junit4.TestAnnotation)',
'%FAILED 1,shouldFail(junit4.TestAnnotation)',
'%EXPECTS',
'%EXPECTE',
'%ACTUALS',
'%ACTUALE',
'%TRACES',
'%TRACEE',
'%TESTE 1,shouldFail(junit4.TestAnnotation)',
'%RUNTIME20',
];
for (const frame of controlFrames) {
assert.ok(!echoedLines.includes(frame), `Control frame leaked to output: ${frame}`);
}
// Genuine program output should still be forwarded.
assert.ok(echoedLines.includes('Hello from System.out'), 'Program output was dropped');
assert.ok(echoedLines.includes('%OK'), 'Percent-prefixed program output was dropped');
assert.ok(echoedLines.includes('%ABC'), 'Percent-prefixed program output was dropped');
assert.ok(echoedLines.includes('%STATUS 200'), 'Percent-prefixed program output was dropped');
assert.ok(!echoedLines.includes('expected'), 'Expected-value protocol payload leaked to output');
assert.ok(!echoedLines.includes('actual'), 'Actual-value protocol payload leaked to output');
assert.ok(!echoedLines.includes('java.lang.AssertionError'), 'Trace protocol payload leaked to output');
assert.ok(!echoedLines.includes('at junit4.TestAnnotation.shouldFail(TestAnnotation.java:15)'), 'Trace protocol payload leaked to output');
assert.notStrictEqual(echoedLines[echoedLines.length - 1], '', 'Trailing newline produced a blank output line');
});

test("test stacktrace should be simplified", () => {
const testItem = generateTestItem(testController, 'junit@App#name', TestKind.JUnit);
const testRunRequest = new TestRunRequest([testItem], []);
Expand Down
Loading