diff --git a/CHANGELOG.md b/CHANGELOG.md index 32df7ef5..fad43a45 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,12 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ## [Unreleased] +## [4.0.1] + +### Changed + +- Improved Output formatting and colorization + ## [4.0.0] - 2021-11-22 Replaced [Test Explorer](https://marketplace.visualstudio.com/items?itemName=hbenl.vscode-test-explorer) with native API. diff --git a/src/AbstractExecutable.ts b/src/AbstractExecutable.ts index b29189ae..251e6689 100644 --- a/src/AbstractExecutable.ts +++ b/src/AbstractExecutable.ts @@ -625,6 +625,8 @@ export abstract class AbstractExecutable implements Disposable { cancellationToken, ); + testRun.appendOutput(runInfo.getProcStartLine()); + this.shared.log.info('proc started', runInfo.process.pid, this.properties.path, this.properties, execParams); runInfo.setPriorityAsync(this.shared.log); @@ -679,6 +681,8 @@ export abstract class AbstractExecutable implements Disposable { ); const result = await runInfo.result; + testRun.appendOutput(runInfo.getProcStopLine(result)); + if (result.value === ExecutableRunResultValue.Errored) { this.shared.log.warn(result.toString(), result, runInfo, this); testRun.appendOutput('❌ Executable run is finished with error.'); diff --git a/src/RunningExecutable.ts b/src/RunningExecutable.ts index 3b3010ad..c1bf7b20 100644 --- a/src/RunningExecutable.ts +++ b/src/RunningExecutable.ts @@ -1,13 +1,13 @@ import * as os from 'os'; +import * as ansi from 'ansi-colors'; import { ChildProcessWithoutNullStreams } from './util/FSWrapper'; import { AbstractTest } from './AbstractTest'; import { LoggerWrapper } from './LoggerWrapper'; import { promisify } from 'util'; -import { CancellationToken } from './Util'; -import { debugAssert } from './util/DevelopmentHelper'; +import { CancellationToken, generateId } from './Util'; import { SpawnBuilder } from './Spawner'; - +import { assert } from './util/DevelopmentHelper'; /// export enum ExecutableRunResultValue { @@ -20,12 +20,7 @@ export enum ExecutableRunResultValue { /// export class ExecutableRunResult { - private constructor(public readonly value: ExecutableRunResultValue, private readonly _error: Error | undefined) {} - - public get error(): Error { - debugAssert(this._error); - return this._error!; - } + private constructor(public readonly value: ExecutableRunResultValue, private readonly _error: string | undefined) {} public get Ok(): boolean { return this.value === ExecutableRunResultValue.Ok; @@ -40,7 +35,8 @@ export class ExecutableRunResult { case ExecutableRunResultValue.TimeoutByUser: return 'TimeoutByUser'; case ExecutableRunResultValue.Errored: - return this.error.toString(); + assert(this._error); + return this._error!; } } @@ -49,11 +45,11 @@ export class ExecutableRunResult { } public static Error(message: string): ExecutableRunResult { - return new ExecutableRunResult(ExecutableRunResultValue.Errored, Error(message)); + return new ExecutableRunResult(ExecutableRunResultValue.Errored, message); } public static createFromSignal(signal: string): ExecutableRunResult { - return new ExecutableRunResult(ExecutableRunResultValue.Errored, Error('Signal received: ' + signal)); + return new ExecutableRunResult(ExecutableRunResultValue.Errored, 'Signal received: ' + signal); } public static createFromErrorCode(code: number): ExecutableRunResult { @@ -107,6 +103,8 @@ export class RunningExecutable { }); } + public readonly runPrefix = ansi.gray(`$${generateId()}| `); + public killProcess(timeout: number | null = null): void { try { if (!this._closed && !this._killed) { @@ -174,6 +172,21 @@ export class RunningExecutable { private _killed = false; public readonly result: Promise; + + public getProcStartLine(): string { + return ( + this.runPrefix + + ansi.gray(`Started PID#${this.pid} - \`${this.process.spawnfile}\`\r\n`) + + this.runPrefix + + '\r\n' + ); + } + + public getProcStopLine(result: ExecutableRunResult): string { + return ( + this.runPrefix + ansi.gray(`Stopped PID#${this.pid} - ${result.toString()} - \`${this.process.spawnfile}\`\r\n`) + ); + } } /// diff --git a/src/TestResultBuilder.ts b/src/TestResultBuilder.ts index 92b1b3bc..803aad8f 100644 --- a/src/TestResultBuilder.ts +++ b/src/TestResultBuilder.ts @@ -1,11 +1,10 @@ import * as vscode from 'vscode'; import * as ansi from 'ansi-colors'; -import { generateId, parseLine } from './Util'; +import { parseLine } from './Util'; import { LoggerWrapper } from './LoggerWrapper'; import { AbstractTest, SubTest } from './AbstractTest'; import { debugBreak } from './util/DevelopmentHelper'; -import { RunningExecutable } from './RunningExecutable'; type TestResult = 'skipped' | 'failed' | 'errored' | 'passed'; @@ -23,11 +22,6 @@ export class TestResultBuilder { this._log = test.shared.log; } - public static calcRunPrefix(_runInfo: RunningExecutable): string { - //return `[#${runInfo.pid}] `; - return `[#${generateId()}] `; - } - private readonly _log: LoggerWrapper; private readonly _message: vscode.TestMessage[] = []; private _result: TestResult | undefined = undefined; @@ -39,7 +33,7 @@ export class TestResultBuilder { if (this.addBeginEndMsg) { const locStr = TestResultBuilder.getLocationAtStr(this.test.file, this.test.line); if (this.level === 0) { - this.addOutputLine(ansi.bold(`# \`${this.test.label}\` started`) + `${locStr}`); + this.addOutputLine(ansi.bold(`# \`${ansi.italic(this.test.label)}\` started`) + `${locStr}`); } else { this.addOutputLine(-1, prefixForNewSubCase(this.level) + '`' + ansi.italic(this.test.label) + '`' + locStr); } @@ -105,9 +99,9 @@ export class TestResultBuilder { if (file) { const lineP = parseLine(line); if (typeof lineP == 'number') { - return ansi.grey(` (at ${file}:${lineP})`); + return ansi.grey(` @ ${file}:${lineP}`); } else { - return ansi.grey(` (at ${file})`); + return ansi.grey(` @ ${file}`); } } return ''; @@ -135,7 +129,7 @@ export class TestResultBuilder { this.addMessage(file, line, 'Expanded: `' + expanded + '`'); const loc = TestResultBuilder.getLocationAtStr(file, line); - this.addOutputLine(1, `Expression failed${loc}:`); + this.addOutputLine(1, 'Expression ' + ansi.red('failed') + loc + ':'); this.addOutputLine(2, '❕Original: ' + original); this.addOutputLine(2, '❗️Expanded: ' + expanded); } @@ -173,7 +167,6 @@ export class TestResultBuilder { const loc = TestResultBuilder.getLocationAtStr(file, line); this.addOutputLine(1, `${title}${loc}${message.length ? ':' : ''}`); this.addOutputLine(2, ...message); - //if (message.length) this.addOutputLine(1, `⬆ ${title}`); } /// @@ -183,11 +176,11 @@ export class TestResultBuilder { case 'passed': return ansi.green(this._result); case 'failed': - return ansi.red(this._result); + return ansi.bold.red(this._result); case 'skipped': return this._result; case 'errored': - return ansi.bgRedBright(this._result); + return ansi.bold.bgRed(this._result); case undefined: return ''; } @@ -198,10 +191,11 @@ export class TestResultBuilder { const d = this._duration ? ansi.grey(` in ${Math.round(this._duration * 1000) / 1000000} second(s)`) : ''; if (this.level === 0) { - this.addOutputLine(ansi.bold(`# \`${this.test.label}\` ${this.coloredResult()}`) + `${d}`, ''); - } else if (this._result !== 'passed') { - this.addOutputLine(ansi.bold(`# ${this.coloredResult()}`) + `${d}`); + this.addOutputLine(`# \`${ansi.italic(this.test.label)}\` ${this.coloredResult()}` + `${d}`, ''); } + // else if (this._result !== 'passed') { + // this.addOutputLine(`# ${this.coloredResult()}${d}`); + // } } } @@ -263,11 +257,11 @@ export class TestResultBuilder { /// -const indentPrefix = '| '; +const indentPrefix = ansi.grey('| '); const prefixForNewSubCase = (indentLevel: number): string => { if (indentLevel === 0) return ''; - else return indentPrefix.repeat(indentLevel - 1) + '| '; + else return indentPrefix.repeat(indentLevel - 1) + ansi.grey('| '); }; const reindentLines = (indentLevel: number, lines: string[]): string[] => { diff --git a/src/framework/Catch2Executable.ts b/src/framework/Catch2Executable.ts index 89cd8c77..63693ff7 100644 --- a/src/framework/Catch2Executable.ts +++ b/src/framework/Catch2Executable.ts @@ -330,7 +330,6 @@ export class Catch2Executable extends AbstractExecutable { const executable = this; //eslint-disable-line const log = this.shared.log; let rngSeed: number | undefined = undefined; - const runPrefix = TestResultBuilder.calcRunPrefix(runInfo); const parser = new XmlParser( this.shared.log, @@ -356,7 +355,7 @@ export class Catch2Executable extends AbstractExecutable { } else { expectedToRunAndFoundTests.push(test); } - const builder = new TestResultBuilder(test, testRun, runPrefix, true); + const builder = new TestResultBuilder(test, testRun, runInfo.runPrefix, true); return new TestCaseTagProcessor(executable.shared, builder, test, tag.attribs); } } @@ -374,7 +373,7 @@ export class Catch2Executable extends AbstractExecutable { parser.writeStdErr(c).then(hasHandled => { if (!hasHandled) { - executable.processStdErr(testRun, runPrefix, c); + executable.processStdErr(testRun, runInfo.runPrefix, c); } }); }); diff --git a/src/framework/DOCExecutable.ts b/src/framework/DOCExecutable.ts index a7ccb557..80b2af77 100644 --- a/src/framework/DOCExecutable.ts +++ b/src/framework/DOCExecutable.ts @@ -185,7 +185,6 @@ export class DOCExecutable extends AbstractExecutable { const expectedToRunAndFoundTests: DOCTest[] = []; const executable = this; //eslint-disable-line let options: Option = {}; - const runPrefix = TestResultBuilder.calcRunPrefix(runInfo); const parser = new XmlParser( this.shared.log, @@ -200,7 +199,7 @@ export class DOCExecutable extends AbstractExecutable { return new TestSuiteTagProcessor( executable.shared, testRun, - runPrefix, + runInfo.runPrefix, (testNameAsId: string) => executable._getTest(testNameAsId), executable._createAndAddTest, unexpectedTests, diff --git a/src/framework/GoogleBenchmarkExecutable.ts b/src/framework/GoogleBenchmarkExecutable.ts index 9f229872..7f210a71 100644 --- a/src/framework/GoogleBenchmarkExecutable.ts +++ b/src/framework/GoogleBenchmarkExecutable.ts @@ -128,7 +128,6 @@ export class GoogleBenchmarkExecutable extends AbstractExecutable { protected async _handleProcess(testRun: vscode.TestRun, runInfo: RunningExecutable): Promise { // at first it's good enough runInfo.childrenToRun.forEach(test => testRun.started(test.item)); - const runPrefix = TestResultBuilder.calcRunPrefix(runInfo); const unexpectedTests: GoogleBenchmarkTest[] = []; const expectedToRunAndFoundTests: GoogleBenchmarkTest[] = []; @@ -185,7 +184,7 @@ export class GoogleBenchmarkExecutable extends AbstractExecutable { expectedToRunAndFoundTests.push(test); } - const builder = new TestResultBuilder(test, testRun, runPrefix, true); + const builder = new TestResultBuilder(test, testRun, runInfo.runPrefix, true); parseAndProcessTestCase(this.shared.log, builder, benchmark); data.lastProcessedBenchmarkIndex = i; @@ -199,7 +198,7 @@ export class GoogleBenchmarkExecutable extends AbstractExecutable { data.sequentialProcessP = data.sequentialProcessP.then(() => processChunk(chunk.toLocaleString())); }); runInfo.process.stderr.on('data', (chunk: Uint8Array) => - this.processStdErr(testRun, runPrefix, chunk.toLocaleString()), + this.processStdErr(testRun, runInfo.runPrefix, chunk.toLocaleString()), ); await runInfo.result; diff --git a/src/framework/GoogleTestExecutable.ts b/src/framework/GoogleTestExecutable.ts index 64296425..cd282d4f 100644 --- a/src/framework/GoogleTestExecutable.ts +++ b/src/framework/GoogleTestExecutable.ts @@ -238,7 +238,6 @@ export class GoogleTestExecutable extends AbstractExecutable { const executable = this; //eslint-disable-line const log = this.shared.log; const data = { lastBuilder: undefined as TestResultBuilder | undefined }; - const runPrefix = TestResultBuilder.calcRunPrefix(runInfo); // we dont need this now: const rngSeed: number | undefined = typeof this._shared.rngSeed === 'number' ? this._shared.rngSeed : undefined; const parser = new TextStreamParser(this.shared.log, { @@ -256,18 +255,20 @@ export class GoogleTestExecutable extends AbstractExecutable { } else { expectedToRunAndFoundTests.push(test); } - data.lastBuilder = new TestResultBuilder(test, testRun, runPrefix, false); + data.lastBuilder = new TestResultBuilder(test, testRun, runInfo.runPrefix, false); return new TestCaseProcessor(executable.shared, testEndRe(test.id), data.lastBuilder); } else if (line.startsWith('[----------] Global test environment tear-down')) { return new NoOpLineProcessor(); } else { if ( line === '' || - ['Note: Google Test filter =', '[==========]', '[----------]'].some(x => line.startsWith(x)) + ['Running main()', 'Note: Google Test filter =', '[==========]', '[----------]'].some(x => + line.startsWith(x), + ) ) { //skip } else { - testRun.appendOutput(runPrefix + line + '\r\n'); + testRun.appendOutput(runInfo.runPrefix + line + '\r\n'); } } }, @@ -278,7 +279,7 @@ export class GoogleTestExecutable extends AbstractExecutable { runInfo.process.stdout.on('data', (chunk: Uint8Array) => parser.write(chunk.toLocaleString())); runInfo.process.stderr.on('data', (chunk: Uint8Array) => - this.processStdErr(testRun, runPrefix, chunk.toLocaleString()), + this.processStdErr(testRun, runInfo.runPrefix, chunk.toLocaleString()), ); await runInfo.result; @@ -360,11 +361,7 @@ class TestCaseSharedData { /// class TestCaseProcessor implements LineProcessor { - constructor( - shared: WorkspaceShared, - private readonly testEndRe: RegExp, - private readonly builder: TestResultBuilder, - ) { + constructor(shared: WorkspaceShared, private readonly testEndRe: RegExp, builder: TestResultBuilder) { this.testCaseShared = new TestCaseSharedData(shared, builder); builder.started(); } @@ -372,8 +369,11 @@ class TestCaseProcessor implements LineProcessor { private readonly testCaseShared: TestCaseSharedData; begin(line: string): void { - const loc = TestResultBuilder.getLocationAtStr(this.builder.test.file, this.builder.test.line); - this.builder.addOutputLine(ansi.bold(line) + loc); //TODO: color line + const loc = TestResultBuilder.getLocationAtStr( + this.testCaseShared.builder.test.file, + this.testCaseShared.builder.test.line, + ); + this.testCaseShared.builder.addOutputLine(ansi.bold(line) + loc); } online(line: string): void | true | LineProcessor { @@ -387,7 +387,7 @@ class TestCaseProcessor implements LineProcessor { let styleFunc = (s: string) => s; if (result === 'OK') { - styleFunc = (s: string) => ansi.green.bold(s); + styleFunc = (s: string) => ansi.green(s); this.testCaseShared.builder.passed(); } else if (result === 'FAILED') { styleFunc = (s: string) => ansi.red.bold(s); @@ -401,19 +401,21 @@ class TestCaseProcessor implements LineProcessor { if (this.testCaseShared.gMockWarningCount) { this.testCaseShared.builder.addOutputLine( + 1, '⚠️' + this.testCaseShared.gMockWarningCount + ' GMock warning(s) in the output!', ); } this.testCaseShared.builder.build(); - this.builder.addOutputLine( + this.testCaseShared.builder.addOutputLine( testEndMatch[1] + styleFunc(testEndMatch[2]) + testEndMatch[3] + ansi.bold(testEndMatch[4]) + ansi.grey(testEndMatch[5]), - ); //TODO: color line + '', + ); return true; } @@ -425,7 +427,8 @@ class TestCaseProcessor implements LineProcessor { const line = failureMatch[3]; const fullMsg = failureMatch[5]; - this.builder.addOutputLine( + this.testCaseShared.builder.addOutputLine( + 1, ansi.gray(failureMatch[1]) + failureMatch[4] + ansi.red(failureMatch[6]) + failureMatch[7], ); @@ -491,7 +494,7 @@ class FailureProcessor implements LineProcessor { return false; } - this.testCaseShared.builder.addOutputLine(line); //TODO: color line + this.testCaseShared.builder.addOutputLine(2, line); } end(): void { @@ -567,7 +570,7 @@ class ExpectCallProcessor implements LineProcessor { return false; } - this.testCaseShared.builder.addOutputLine(line); //TODO: color line + this.testCaseShared.builder.addOutputLine(2, line); } end(): void { diff --git a/src/util/DevelopmentHelper.ts b/src/util/DevelopmentHelper.ts index fbf8d4f3..37a60e1e 100644 --- a/src/util/DevelopmentHelper.ts +++ b/src/util/DevelopmentHelper.ts @@ -4,12 +4,11 @@ export function debugBreak(_note?: string): void { } // TODO:release make it removed from webpack -// eslint-disable-next-line -export function debugAssert(condition: any, _msg?: string): void { +export function debugAssert(condition: unknown, _msg?: string): void { if (!condition) debugger; } -export function assert(condition: boolean): void { +export function assert(condition: unknown): void { if (!condition) { debugBreak(); throw Error('Assertion');