From 23b6ccbb04be833848b581fd44f568938bd94853 Mon Sep 17 00:00:00 2001 From: Mate Pek Date: Tue, 20 Nov 2018 20:26:10 +0100 Subject: [PATCH 1/6] stability and performance impovements --- CHANGELOG.md | 6 +- README.md | 2 +- src/C2AllTestSuiteInfo.ts | 23 ++++--- src/C2ExecutableInfo.ts | 13 ++-- src/C2TestAdapter.ts | 113 +++++++++++++++------------------ src/C2TestSuiteInfo.ts | 33 ++++------ src/test/C2TestAdapter.test.ts | 1 + 7 files changed, 91 insertions(+), 100 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3bcd7e6e..472c7c05 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,10 +9,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [2.0.3] - 2018-11-20 -## Fixed +### Fixed -- It reload suite if it finds any new tests. -- A bug in package.json. It couldn't load the tests. +- It reloads suite if it finds any new tests. +- A bug in package.json. It couldn't load the tests by default. ## [2.0.2] - 2018-11-19 diff --git a/README.md b/README.md index 51665e59..73ecd69b 100644 --- a/README.md +++ b/README.md @@ -60,7 +60,7 @@ Variables which can be used in `name`, `cwd` and `env` of `executables`: | `${ext2Filename}` | Filename's second level extension. ("d/a.b.c" => ".b") | | `${base3Filename}` | Filename without extension ("d/a.b.c" => "a") | | `${ext3Filename}` | Filename's second level extension. ("d/a.b.c" => "") | -| `${workspaceDirectory}` | (You can only guess one.) | +| `${workspaceDirectory}` | (You can only guess once.) | | `${workspaceFolder}` | Alias of `${workspaceDirectory}` | Examples: diff --git a/src/C2AllTestSuiteInfo.ts b/src/C2AllTestSuiteInfo.ts index 84f137ac..9c92c4f3 100644 --- a/src/C2AllTestSuiteInfo.ts +++ b/src/C2AllTestSuiteInfo.ts @@ -24,12 +24,13 @@ export class C2AllTestSuiteInfo implements TestSuiteInfo, vscode.Disposable { private _isDisposed = false; constructor( - private readonly _allTasks: QueueGraphNode, + public readonly allTasks: QueueGraphNode, public readonly log: util.Log, public readonly workspaceFolder: vscode.WorkspaceFolder, - public readonly testsEmitter: + private readonly _loadFinishedEmitter: vscode.EventEmitter, + private readonly _testsEmitter: vscode.EventEmitter, - public readonly testStatesEmitter: + private readonly _testStatesEmitter: vscode.EventEmitter, public readonly variableToValue: [string, string][], @@ -46,17 +47,17 @@ export class C2AllTestSuiteInfo implements TestSuiteInfo, vscode.Disposable { } sendLoadEvents(task: (() => Promise)) { - return this._allTasks.then(() => { + return this.allTasks.then(() => { if (this._isDisposed) { return task(); } else { - this.testsEmitter.fire({type: 'started'}); + this._testsEmitter.fire({type: 'started'}); return task().then( () => { - this.testsEmitter.fire({type: 'finished', suite: this}); + this._loadFinishedEmitter.fire(); }, (reason: any) => { - this.testsEmitter.fire({type: 'finished', suite: this}); + this._loadFinishedEmitter.fire(); this.log.warn(inspect(reason)); throw reason; }); @@ -64,6 +65,10 @@ export class C2AllTestSuiteInfo implements TestSuiteInfo, vscode.Disposable { }); } + sendTestStateEvent(ev: TestSuiteEvent|TestEvent) { + this._testStatesEmitter.fire(ev); + } + removeChild(child: C2TestSuiteInfo): boolean { const i = this.children.findIndex(val => val.id == child.id); if (i != -1) { @@ -129,7 +134,7 @@ export class C2AllTestSuiteInfo implements TestSuiteInfo, vscode.Disposable { } run(tests: string[], workerMaxNumber: number): Promise { - this.testStatesEmitter.fire( + this._testStatesEmitter.fire( {type: 'started', tests: tests}); const taskPool = new TaskPool(workerMaxNumber); @@ -155,7 +160,7 @@ export class C2AllTestSuiteInfo implements TestSuiteInfo, vscode.Disposable { } const always = () => { - this.testStatesEmitter.fire({type: 'finished'}); + this._testStatesEmitter.fire({type: 'finished'}); }; return Promise.all(ps).then(always, always); diff --git a/src/C2ExecutableInfo.ts b/src/C2ExecutableInfo.ts index 80f92321..ba358e98 100644 --- a/src/C2ExecutableInfo.ts +++ b/src/C2ExecutableInfo.ts @@ -172,13 +172,12 @@ export class C2ExecutableInfo implements vscode.Disposable { return Promise.resolve(); } if (Date.now() - lastEventArrivedAt! > timeout) { - this._lastEventArrivedAt.delete(uri.fsPath); - this._executables.delete(uri.fsPath); - this._allTest.testsEmitter.fire({type: 'started'}); - this._allTest.removeChild(suite!); - this._allTest.testsEmitter.fire( - {type: 'finished', suite: this._allTest}); - return Promise.resolve(); + return this._allTest.sendLoadEvents(() => { + this._lastEventArrivedAt.delete(uri.fsPath); + this._executables.delete(uri.fsPath); + this._allTest.removeChild(suite!); + return Promise.resolve(); + }); } else if (exists) { return this._allTest.sendLoadEvents(() => { this._lastEventArrivedAt.delete(uri.fsPath); diff --git a/src/C2TestAdapter.ts b/src/C2TestAdapter.ts index faa3a168..45ae6c41 100644 --- a/src/C2TestAdapter.ts +++ b/src/C2TestAdapter.ts @@ -27,8 +27,8 @@ export class C2TestAdapter implements TestAdapter, vscode.Disposable { ['${workspaceFolder}', this._workspaceFolder.uri.fsPath] ]; - private _isDebugging: boolean = false; - private _isRunning: number = 0; + // because we always want to return with the current allTests suite + private readonly _loadFinishedEmitter = new vscode.EventEmitter(); private _allTasks = new QueueGraphNode(); private _allTests: C2AllTestSuiteInfo; @@ -42,6 +42,11 @@ export class C2TestAdapter implements TestAdapter, vscode.Disposable { this._disposables.push(this._testStatesEmitter); this._disposables.push(this._autorunEmitter); + this._disposables.push(this._loadFinishedEmitter); + this._disposables.push(this._loadFinishedEmitter.event(() => { + this._testsEmitter.fire({type: 'finished', suite: this._allTests}); + })); + this._disposables.push( vscode.workspace.onDidChangeConfiguration(configChange => { if (configChange.affectsConfiguration( @@ -80,9 +85,9 @@ export class C2TestAdapter implements TestAdapter, vscode.Disposable { const config = this._getConfiguration(); this._allTests = new C2AllTestSuiteInfo( - this._allTasks, this._log, this._workspaceFolder, this._testsEmitter, - this._testStatesEmitter, this._variableToValue, - this._getEnableSourceDecoration(config), + this._allTasks, this._log, this._workspaceFolder, + this._loadFinishedEmitter, this._testsEmitter, this._testStatesEmitter, + this._variableToValue, this._getEnableSourceDecoration(config), this._getDefaultRngSeed(config), this._getDefaultExecWatchTimeout(config)); } @@ -116,9 +121,9 @@ export class C2TestAdapter implements TestAdapter, vscode.Disposable { this._allTasks = new QueueGraphNode(); this._allTests = new C2AllTestSuiteInfo( - this._allTasks, this._log, this._workspaceFolder, this._testsEmitter, - this._testStatesEmitter, this._variableToValue, - this._getEnableSourceDecoration(config), + this._allTasks, this._log, this._workspaceFolder, + this._loadFinishedEmitter, this._testsEmitter, this._testStatesEmitter, + this._variableToValue, this._getEnableSourceDecoration(config), this._getDefaultRngSeed(config), this._getDefaultExecWatchTimeout(config)); @@ -146,44 +151,38 @@ export class C2TestAdapter implements TestAdapter, vscode.Disposable { } run(tests: string[]): Promise { - if (this._isDebugging) { - throw 'Catch2: Test is currently being debugged.'; + if (this._allTasks.size > 0) { + this._log.info(__filename + 'run is busy'); + throw 'Catch2 is busy. Try it again now or a slightly later.'; } - if (this._isRunning == 0) { - this._isRunning += 1; - const always = () => { - this._isRunning -= 1; - }; - return this._allTasks.then(() => { - return this._allTests - .run(tests, this._getWorkerMaxNumber(this._getConfiguration())) - .then(always, always); - }); - } - - throw Error('Catch2 Test Adapter: Test(s) are currently doing something.'); + return this._allTasks.then(() => { + return this._allTests.run( + tests, this._getWorkerMaxNumber(this._getConfiguration())); + }); } - async debug(tests: string[]): Promise { - if (this._isDebugging) { - throw 'Catch2: Test is currently being debugged.'; + debug(tests: string[]): Promise { + if (this._allTasks.size > 0) { + this._log.info(__filename + 'debug is busy'); + throw 'Catch2 is busy. Try it again now or a slightly later.'; } - if (this._isRunning > 0) { - throw 'Catch2: Test(s) are currently being run.'; - } + this._log.info('Debug...'); console.assert(tests.length === 1); const info = this._allTests.findChildById(tests[0]); console.assert(info !== undefined); if (!(info instanceof C2TestInfo)) { + this._log.info(__filename + ' !(info instanceof C2TestInfo)'); throw 'Can\'t choose a group, only a single test.'; } const testInfo = info; + this._log.info('testInfo: ' + inspect([testInfo, tests])); + const getDebugConfiguration = (): vscode.DebugConfiguration => { const debug: vscode.DebugConfiguration = { name: 'Catch2: ' + testInfo.label, @@ -233,39 +232,31 @@ export class C2TestAdapter implements TestAdapter, vscode.Disposable { }; const debugConfig = getDebugConfiguration(); + this._log.info('Debug config: ' + inspect(debugConfig)); - this._isDebugging = true; - - const debugSessionStarted = - await vscode.debug.startDebugging(this._workspaceFolder, debugConfig); - - if (!debugSessionStarted) { - console.error('Failed starting the debug session - aborting'); - this._isDebugging = false; - return; - } - - const currentSession = vscode.debug.activeDebugSession; - if (!currentSession) { - console.error('No active debug session - aborting'); - this._isDebugging = false; - return; - } - - const always = () => { - this._isDebugging = false; - }; - - return new Promise((resolve) => { - const subscription = - vscode.debug.onDidTerminateDebugSession(session => { - if (currentSession != session) return; - console.info('Debug session ended'); - resolve(); - subscription.dispose(); - }); - }) - .then(always, always); + return this._allTasks.then(() => { + return vscode.debug.startDebugging(this._workspaceFolder, debugConfig) + .then((debugSessionStarted: boolean) => { + const currentSession = vscode.debug.activeDebugSession; + + if (!debugSessionStarted || !currentSession) { + this._log.info('Failed starting the debug session - aborting'); + return Promise.resolve(); + } + + this._log.info('debugSessionStarted'); + + return new Promise((resolve) => { + const subscription = + vscode.debug.onDidTerminateDebugSession(session => { + if (currentSession != session) return; + this._log.info('Debug session ended.'); + resolve(); + subscription.dispose(); + }); + }); + }); + }); } private _getConfiguration(): vscode.WorkspaceConfiguration { diff --git a/src/C2TestSuiteInfo.ts b/src/C2TestSuiteInfo.ts index 63e1c17c..da469968 100644 --- a/src/C2TestSuiteInfo.ts +++ b/src/C2TestSuiteInfo.ts @@ -5,7 +5,7 @@ import {ChildProcess, spawn, SpawnOptions} from 'child_process'; import * as path from 'path'; import {inspect} from 'util'; -import {TestEvent, TestSuiteEvent, TestSuiteInfo} from 'vscode-test-adapter-api'; +import {TestEvent, TestSuiteInfo} from 'vscode-test-adapter-api'; import * as xml2js from 'xml2js'; import {C2AllTestSuiteInfo} from './C2AllTestSuiteInfo'; @@ -105,8 +105,8 @@ export class C2TestSuiteInfo implements TestSuiteInfo { }); } - this.allTests.testStatesEmitter.fire( - {type: 'suite', suite: this, state: 'running'}); + this.allTests.sendTestStateEvent( + {type: 'suite', suite: this, state: 'running'}); const execParams: string[] = []; if (childrenToRun != 'all') { @@ -121,8 +121,8 @@ export class C2TestSuiteInfo implements TestSuiteInfo { for (let i = 0; i < this.children.length; i++) { const c = this.children[i]; if (c.skipped) { - this.allTests.testStatesEmitter.fire(c.getStartEvent()); - this.allTests.testStatesEmitter.fire(c.getSkippedEvent()); + this.allTests.sendTestStateEvent(c.getStartEvent()); + this.allTests.sendTestStateEvent(c.getSkippedEvent()); } } } @@ -195,7 +195,7 @@ export class C2TestSuiteInfo implements TestSuiteInfo { if (data.currentChild !== undefined) { const ev = data.currentChild.getStartEvent(); - this.allTests.testStatesEmitter.fire(ev); + this.allTests.sendTestStateEvent(ev); } else { this.allTests.log.error('TestCase not found in children: ' + name); } @@ -213,21 +213,16 @@ export class C2TestSuiteInfo implements TestSuiteInfo { data.rngSeed); if (!this.allTests.isEnabledSourceDecoration) ev.decorations = undefined; - this.allTests.testStatesEmitter.fire(ev); + this.allTests.sendTestStateEvent(ev); } catch (e) { this.allTests.log.error( 'Parsing and processing test: ' + data.currentChild.label); } } else { - this.allTests - .sendLoadEvents(() => { - return this.reloadChildren(); - }) - .then( - () => { - // TODO send event states: find newChild and parseXml - }); - + this.allTests.sendLoadEvents(() => { + return this.reloadChildren(); + }); + //.then... // TODO send event state: find newChild and parseXml this.allTests.log.info(' found without TestInfo.'); } @@ -258,8 +253,8 @@ export class C2TestSuiteInfo implements TestSuiteInfo { const suiteFinally = () => { this.proc = undefined; taskPool.release(); - this.allTests.testStatesEmitter.fire( - {type: 'suite', suite: this, state: 'completed'}); + this.allTests.sendTestStateEvent( + {type: 'suite', suite: this, state: 'completed'}); }; return p.then( () => { @@ -267,7 +262,7 @@ export class C2TestSuiteInfo implements TestSuiteInfo { }, (err: Error) => { if (data.inTestCase) { - this.allTests.testStatesEmitter.fire({ + this.allTests.sendTestStateEvent({ type: 'test', test: data.currentChild!, state: 'failed', diff --git a/src/test/C2TestAdapter.test.ts b/src/test/C2TestAdapter.test.ts index 57db7ac6..5d9ae00b 100644 --- a/src/test/C2TestAdapter.test.ts +++ b/src/test/C2TestAdapter.test.ts @@ -1321,6 +1321,7 @@ describe('C2TestAdapter', function() { }) specify('arriving for missing TestInfo', async function() { + this.slow(500); await updateConfig('executables', example1.suite1.execPath); const adapter = createAdapterAndSubscribe(); From e7dbebf724c4d583e773f04bb29c0eb652bbd8b8 Mon Sep 17 00:00:00 2001 From: Mate Pek Date: Tue, 20 Nov 2018 20:48:38 +0100 Subject: [PATCH 2/6] debugBreakOnFailure --- CHANGELOG.md | 10 ++++++++ README.md | 7 +---- package.json | 6 +++++ src/C2ExecutableInfo.ts | 57 ++++++++++++++++++++--------------------- src/C2TestAdapter.ts | 17 +++++++----- 5 files changed, 56 insertions(+), 41 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 472c7c05..87052310 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [2.1.0] + +### Added + +- `catch2TestExplorer.debugBreakOnFailure` option. It is true by default. + +### Fixed + +Performance and stability improvements. + ## [2.0.3] - 2018-11-20 ### Fixed diff --git a/README.md b/README.md index 73ecd69b..467d6588 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,3 @@ -## **Warning**: Version 2 has been released: **API has been changed**. - -Update your settings! ([See changelog](CHANGELOG.md) or [configuration below](#Configuration).) - ---- - ## Catch2 Test Explorer for Visual Studio Code [![Build Status](https://travis-ci.org/matepek/vscode-catch2-test-adapter.svg?branch=master)](https://travis-ci.org/matepek/vscode-catch2-test-adapter) @@ -28,6 +22,7 @@ This extension allows you to run your [Catch2 tests](https://github.com/catchorg | `catch2TestExplorer.workerMaxNumber` | The variable maximize the number of the parallel test execution. | | `catch2TestExplorer.enableSourceDecoration` | Sets the source code decorations: Errored lines will be highlited. | | `catch2TestExplorer.debugConfigTemplate` | Set the necessary debug configuraitons and the debug button will work. Details: [below](#catch2TestExplorer.debugConfigTemplate) | +| `catch2TestExplorer.debugBreakOnFailure` | Debugger breaks on failure while debugging the test. This is a Catch2 parameter: --break | | `testExplorer.onStart` | (This is part of the [dependency extension](https://github.com/hbenl/vscode-test-explorer#configuration)'s settings.) | | `testExplorer.onReload` | (This is part of the [dependency extension](https://github.com/hbenl/vscode-test-explorer#configuration)'s settings.) | diff --git a/package.json b/package.json index 9da366e7..6ce73cb7 100644 --- a/package.json +++ b/package.json @@ -148,6 +148,12 @@ "default": null, "scope": "resource" }, + "catch2TestExplorer.debugBreakOnFailure": { + "description": "Debugger breaks on failure while debugging the test. This is a Catch2 parameter: --break", + "type": "boolean", + "default": true, + "scope": "resource" + }, "catch2TestExplorer.logpanel": { "description": "Create a new output channel and write the log messages there.", "type": "boolean", diff --git a/src/C2ExecutableInfo.ts b/src/C2ExecutableInfo.ts index ba358e98..3e773bbe 100644 --- a/src/C2ExecutableInfo.ts +++ b/src/C2ExecutableInfo.ts @@ -163,37 +163,36 @@ export class C2ExecutableInfo implements vscode.Disposable { if (isRunning) return; - const x = - (exists: boolean, timeout: number, delay: number): Promise => { - let lastEventArrivedAt = this._lastEventArrivedAt.get(uri.fsPath); - if (lastEventArrivedAt === undefined) { - this._allTest.log.error('assert in ' + __filename); - debugger; - return Promise.resolve(); - } - if (Date.now() - lastEventArrivedAt! > timeout) { - return this._allTest.sendLoadEvents(() => { - this._lastEventArrivedAt.delete(uri.fsPath); - this._executables.delete(uri.fsPath); - this._allTest.removeChild(suite!); - return Promise.resolve(); - }); - } else if (exists) { - return this._allTest.sendLoadEvents(() => { - this._lastEventArrivedAt.delete(uri.fsPath); - return suite!.reloadChildren().catch(() => { - return x(false, timeout, Math.min(delay * 2, 2000)); - }); - }); - } - return promisify(setTimeout)(Math.min(delay * 2, 2000)).then(() => { - return c2fs.existsAsync(uri.fsPath).then((exists: boolean) => { - return x(exists, timeout, Math.min(delay * 2, 2000)); - }); + const x = (exists: boolean, delay: number): Promise => { + let lastEventArrivedAt = this._lastEventArrivedAt.get(uri.fsPath); + if (lastEventArrivedAt === undefined) { + this._allTest.log.error('assert in ' + __filename); + debugger; + return Promise.resolve(); + } + if (Date.now() - lastEventArrivedAt! > this._allTest.execWatchTimeout) { + return this._allTest.sendLoadEvents(() => { + this._lastEventArrivedAt.delete(uri.fsPath); + this._executables.delete(uri.fsPath); + this._allTest.removeChild(suite!); + return Promise.resolve(); + }); + } else if (exists) { + return this._allTest.sendLoadEvents(() => { + this._lastEventArrivedAt.delete(uri.fsPath); + return suite!.reloadChildren().catch(() => { + return x(false, Math.min(delay * 2, 2000)); }); - }; + }); + } + return promisify(setTimeout)(Math.min(delay * 2, 2000)).then(() => { + return c2fs.existsAsync(uri.fsPath).then((exists: boolean) => { + return x(exists, Math.min(delay * 2, 2000)); + }); + }); + }; // change event can arrive during debug session on osx (why?) - x(false, this._allTest.execWatchTimeout, 64); + x(false, 64); } private _handleCreate(uri: vscode.Uri) { diff --git a/src/C2TestAdapter.ts b/src/C2TestAdapter.ts index 45ae6c41..235d1306 100644 --- a/src/C2TestAdapter.ts +++ b/src/C2TestAdapter.ts @@ -153,7 +153,7 @@ export class C2TestAdapter implements TestAdapter, vscode.Disposable { run(tests: string[]): Promise { if (this._allTasks.size > 0) { this._log.info(__filename + 'run is busy'); - throw 'Catch2 is busy. Try it again now or a slightly later.'; + throw 'Catch2 is busy. Try it again a bit later.'; } return this._allTasks.then(() => { @@ -165,7 +165,7 @@ export class C2TestAdapter implements TestAdapter, vscode.Disposable { debug(tests: string[]): Promise { if (this._allTasks.size > 0) { this._log.info(__filename + 'debug is busy'); - throw 'Catch2 is busy. Try it again now or a slightly later.'; + throw 'Catch2 is busy. Try it again a bit later.'; } this._log.info('Debug...'); @@ -190,11 +190,11 @@ export class C2TestAdapter implements TestAdapter, vscode.Disposable { type: '' }; - const template = - this._getDebugConfigurationTemplate(this._getConfiguration()); + const config = this._getConfiguration(); + const template = this._getDebugConfigurationTemplate(config); let resolveDebugVariables: [string, any][] = this._variableToValue; - const args = - [testInfo.getEscapedTestName(), '--reporter', 'console', '--break']; + const args = [testInfo.getEscapedTestName(), '--reporter', 'console']; + if (this._getDebugBreakOnFailure(config)) args.push('--break'); resolveDebugVariables = resolveDebugVariables.concat([ ['${label}', testInfo.label], @@ -283,6 +283,11 @@ export class C2TestAdapter implements TestAdapter, vscode.Disposable { return result; } + private _getDebugBreakOnFailure(config: vscode.WorkspaceConfiguration): + boolean { + return config.get('debugBreakOnFailure', true); + } + private _getGlobalAndDefaultEnvironmentVariables( config: vscode.WorkspaceConfiguration): {[prop: string]: string|undefined} { From 94fb7b7d2360f2951bb364f44ed1630b1b91cba8 Mon Sep 17 00:00:00 2001 From: Mate Pek Date: Tue, 20 Nov 2018 21:10:54 +0100 Subject: [PATCH 3/6] autorun in case of fswatcher event --- CHANGELOG.md | 4 +++ README.md | 2 +- src/C2AllTestSuiteInfo.ts | 1 + src/C2ExecutableInfo.ts | 55 ++++++++++++++++-------------- src/C2TestAdapter.ts | 6 ++-- src/test/C2TestAdapter.cpp.test.ts | 20 +++++++++-- src/test/C2TestAdapter.test.ts | 4 +-- 7 files changed, 60 insertions(+), 32 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 87052310..daccf287 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - `catch2TestExplorer.debugBreakOnFailure` option. It is true by default. +- 🤩 Change of test file (ex.:recompilation) triggers "**autorun**" feature. + (Right click on test/suite in text explorer -> Enable to enable autorun for the selected test/suite.) + This basically means that the selected test/suite will run in case of any filesystem event related to the `executables` variable. + It can be really useful if one would like to run a test (or suite) automatically after recompiliation. 💙 ### Fixed diff --git a/README.md b/README.md index 467d6588..1a832aee 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -## Catch2 Test Explorer for Visual Studio Code +# Catch2 Test Explorer for Visual Studio Code [![Build Status](https://travis-ci.org/matepek/vscode-catch2-test-adapter.svg?branch=master)](https://travis-ci.org/matepek/vscode-catch2-test-adapter) [![Build status](https://ci.appveyor.com/api/projects/status/p6uuyg21cwxcnlv9/branch/master?svg=true)](https://ci.appveyor.com/project/matepek/vscode-catch2-test-adapter/branch/master) diff --git a/src/C2AllTestSuiteInfo.ts b/src/C2AllTestSuiteInfo.ts index 9c92c4f3..9e7dbfcc 100644 --- a/src/C2AllTestSuiteInfo.ts +++ b/src/C2AllTestSuiteInfo.ts @@ -33,6 +33,7 @@ export class C2AllTestSuiteInfo implements TestSuiteInfo, vscode.Disposable { private readonly _testStatesEmitter: vscode.EventEmitter, + public readonly autorunEmitter: vscode.EventEmitter, public readonly variableToValue: [string, string][], public isEnabledSourceDecoration: boolean, public rngSeed: string|number|null, diff --git a/src/C2ExecutableInfo.ts b/src/C2ExecutableInfo.ts index 3e773bbe..e2453c79 100644 --- a/src/C2ExecutableInfo.ts +++ b/src/C2ExecutableInfo.ts @@ -13,7 +13,7 @@ import {resolveVariables} from './Helpers'; export class C2ExecutableInfo implements vscode.Disposable { constructor( - private _allTest: C2AllTestSuiteInfo, public readonly name: string, + private _allTests: C2AllTestSuiteInfo, public readonly name: string, public readonly pattern: string, public readonly cwd: string, public readonly env: {[prop: string]: any}) {} @@ -33,7 +33,7 @@ export class C2ExecutableInfo implements vscode.Disposable { } async load(): Promise { - const wsUri = this._allTest.workspaceFolder.uri; + const wsUri = this._allTests.workspaceFolder.uri; const pattern = this.pattern.startsWith('./') ? this.pattern.substr(2) : this.pattern; const isAbsolute = path.isAbsolute(pattern); @@ -44,10 +44,10 @@ export class C2ExecutableInfo implements vscode.Disposable { const isPartOfWs = !relativeToWs.startsWith('..'); if (isAbsolute && isPartOfWs) - this._allTest.log.info( + this._allTests.log.info( 'Absolute path is used for workspace directory: ' + inspect([this])); if (this.pattern.indexOf('\\') != -1) - this._allTest.log.warn( + this._allTests.log.warn( 'Pattern contains backslash character: ' + this.pattern); let fileUris: vscode.Uri[] = []; @@ -56,10 +56,10 @@ export class C2ExecutableInfo implements vscode.Disposable { let relativePattern: vscode.RelativePattern; if (isAbsolute) relativePattern = new vscode.RelativePattern( - this._allTest.workspaceFolder, relativeToWs); + this._allTests.workspaceFolder, relativeToWs); else relativePattern = - new vscode.RelativePattern(this._allTest.workspaceFolder, pattern); + new vscode.RelativePattern(this._allTests.workspaceFolder, pattern); try { fileUris = await vscode.workspace.findFiles(relativePattern, undefined, 1000); @@ -75,7 +75,7 @@ export class C2ExecutableInfo implements vscode.Disposable { this._disposables.push( this._watcher.onDidDelete(this._handleDelete, this)); } catch (e) { - this._allTest.log.error(inspect([e, this])); + this._allTests.log.error(inspect([e, this])); } } else { fileUris.push(absPatternAsUri); @@ -93,7 +93,7 @@ export class C2ExecutableInfo implements vscode.Disposable { for (const suite of this._executables.values()) { await suite.reloadChildren().catch((err: any) => { - this._allTest.log.error( + this._allTests.log.error( 'Couldn\'t load suite: ' + inspect([err, suite])); // we could remove it, but now the user still sees the dead leaf }); @@ -101,7 +101,7 @@ export class C2ExecutableInfo implements vscode.Disposable { } private _addFile(file: vscode.Uri) { - const wsUri = this._allTest.workspaceFolder.uri; + const wsUri = this._allTests.workspaceFolder.uri; let resolvedName = this.name; let resolvedCwd = this.cwd; @@ -118,7 +118,7 @@ export class C2ExecutableInfo implements vscode.Disposable { const base3Filename = path.basename(base2Filename, ext3Filename); const varToValue: [string, string][] = [ - ...this._allTest.variableToValue, + ...this._allTests.variableToValue, ['${absPath}', file.fsPath], ['${relPath}', relPath], ['${absDirpath}', path.dirname(file.fsPath)], @@ -133,16 +133,17 @@ export class C2ExecutableInfo implements vscode.Disposable { ]; resolvedName = resolveVariables(this.name, varToValue); if (resolvedName.match(/\$\{.*\}/)) - this._allTest.log.warn('Possibly unresolved variable: ' + resolvedName); + this._allTests.log.warn( + 'Possibly unresolved variable: ' + resolvedName); resolvedCwd = path.normalize(resolveVariables(this.cwd, varToValue)); if (resolvedCwd.match(/\$\{.*\}/)) - this._allTest.log.warn('Possibly unresolved variable: ' + resolvedCwd); + this._allTests.log.warn('Possibly unresolved variable: ' + resolvedCwd); resolvedEnv = resolveVariables(this.env, varToValue); } catch (e) { - this._allTest.log.error(inspect([e, this])); + this._allTests.log.error(inspect([e, this])); } - const suite = this._allTest.createChildSuite( + const suite = this._allTests.createChildSuite( resolvedName, file.fsPath, {cwd: resolvedCwd, env: resolvedEnv}); return suite; @@ -166,24 +167,28 @@ export class C2ExecutableInfo implements vscode.Disposable { const x = (exists: boolean, delay: number): Promise => { let lastEventArrivedAt = this._lastEventArrivedAt.get(uri.fsPath); if (lastEventArrivedAt === undefined) { - this._allTest.log.error('assert in ' + __filename); + this._allTests.log.error('assert in ' + __filename); debugger; return Promise.resolve(); } - if (Date.now() - lastEventArrivedAt! > this._allTest.execWatchTimeout) { - return this._allTest.sendLoadEvents(() => { + if (Date.now() - lastEventArrivedAt! > this._allTests.execWatchTimeout) { + return this._allTests.sendLoadEvents(() => { this._lastEventArrivedAt.delete(uri.fsPath); this._executables.delete(uri.fsPath); - this._allTest.removeChild(suite!); + this._allTests.removeChild(suite!); return Promise.resolve(); }); } else if (exists) { - return this._allTest.sendLoadEvents(() => { - this._lastEventArrivedAt.delete(uri.fsPath); - return suite!.reloadChildren().catch(() => { - return x(false, Math.min(delay * 2, 2000)); - }); - }); + return this._allTests + .sendLoadEvents(() => { + this._lastEventArrivedAt.delete(uri.fsPath); + return suite!.reloadChildren().catch(() => { + return x(false, Math.min(delay * 2, 2000)); + }); + }) + .then(() => { + this._allTests.autorunEmitter.fire(); + }); } return promisify(setTimeout)(Math.min(delay * 2, 2000)).then(() => { return c2fs.existsAsync(uri.fsPath).then((exists: boolean) => { @@ -235,7 +240,7 @@ export class C2ExecutableInfo implements vscode.Disposable { return res.stdout.indexOf('Catch v2.') != -1; }) .catch(e => { - this._allTest.log.error(inspect(e)); + this._allTests.log.error(inspect(e)); return false; }); } diff --git a/src/C2TestAdapter.ts b/src/C2TestAdapter.ts index 235d1306..12fa63c7 100644 --- a/src/C2TestAdapter.ts +++ b/src/C2TestAdapter.ts @@ -87,7 +87,8 @@ export class C2TestAdapter implements TestAdapter, vscode.Disposable { this._allTests = new C2AllTestSuiteInfo( this._allTasks, this._log, this._workspaceFolder, this._loadFinishedEmitter, this._testsEmitter, this._testStatesEmitter, - this._variableToValue, this._getEnableSourceDecoration(config), + this._autorunEmitter, this._variableToValue, + this._getEnableSourceDecoration(config), this._getDefaultRngSeed(config), this._getDefaultExecWatchTimeout(config)); } @@ -123,7 +124,8 @@ export class C2TestAdapter implements TestAdapter, vscode.Disposable { this._allTests = new C2AllTestSuiteInfo( this._allTasks, this._log, this._workspaceFolder, this._loadFinishedEmitter, this._testsEmitter, this._testStatesEmitter, - this._variableToValue, this._getEnableSourceDecoration(config), + this._autorunEmitter, this._variableToValue, + this._getEnableSourceDecoration(config), this._getDefaultRngSeed(config), this._getDefaultExecWatchTimeout(config)); diff --git a/src/test/C2TestAdapter.cpp.test.ts b/src/test/C2TestAdapter.cpp.test.ts index f5f8b815..75fa041e 100644 --- a/src/test/C2TestAdapter.cpp.test.ts +++ b/src/test/C2TestAdapter.cpp.test.ts @@ -206,7 +206,7 @@ describe('C2TestAdapter.cpp', function() { context('example1', function() { it('should be found and run withouth error', async function() { if (process.env['TRAVIS'] == 'true') this.skip(); - this.timeout(5000); + this.timeout(8000); this.slow(2000); await updateConfig( 'executables', [{ @@ -231,7 +231,7 @@ describe('C2TestAdapter.cpp', function() { it('should be notified by watcher', async function() { if (process.env['TRAVIS'] == 'true') this.skip(); - this.timeout(5000); + this.timeout(8000); this.slow(4000); await updateConfig( 'executables', [{ @@ -241,26 +241,40 @@ describe('C2TestAdapter.cpp', function() { }]); adapter = createAdapterAndSubscribe(); + let autorunCounter = 0; + adapter.autorun(() => { + ++autorunCounter; + }); const root = await load(adapter); assert.strictEqual(root.children.length, 0); + assert.strictEqual(autorunCounter, 0); await copy('../suite1.exe', 'out/suite1.exe'); await waitFor(this, () => { return root.children.length == 1; }, 2000); + await waitFor(this, () => { + return autorunCounter == 1; + }, 2000); await copy('../suite2.exe', 'out/sub/suite2X.exe'); await waitFor(this, () => { return root.children.length == 2; }, 2000); + await waitFor(this, () => { + return autorunCounter == 2; + }, 2000); await copy('../suite2.exe', 'out/sub/suite2.exe'); await waitFor(this, () => { return root.children.length == 3; }, 2000); + await waitFor(this, () => { + return autorunCounter == 3; + }, 2000); await updateConfig('defaultWatchTimeoutSec', 1); @@ -269,10 +283,12 @@ describe('C2TestAdapter.cpp', function() { await waitFor(this, () => { return root.children.length == 2; }, 3100); + assert.strictEqual(autorunCounter, 3); const eventCount = testStatesEvents.length; await adapter.run([root.id]); assert.strictEqual(testStatesEvents.length, eventCount + 16); + assert.strictEqual(autorunCounter, 3); }) }) }) \ No newline at end of file diff --git a/src/test/C2TestAdapter.test.ts b/src/test/C2TestAdapter.test.ts index 5d9ae00b..f7a09bbd 100644 --- a/src/test/C2TestAdapter.test.ts +++ b/src/test/C2TestAdapter.test.ts @@ -1459,8 +1459,8 @@ describe('C2TestAdapter', function() { async function() { const watchTimeout = 5; await updateConfig('defaultWatchTimeoutSec', watchTimeout); - this.timeout(watchTimeout * 1000 + 5500 /* because of 'delay' */); - this.slow(watchTimeout * 1000 + 2500 /* because of 'delay' */); + this.timeout(watchTimeout * 1000 + 6500 /* because of 'delay' */); + this.slow(watchTimeout * 1000 + 3500 /* because of 'delay' */); const execPath2CopyPath = path.join(workspaceFolderUri.fsPath, 'execPath2Copy'); From 81c459ddf027560875aef6f2f935b73657306cea Mon Sep 17 00:00:00 2001 From: Mate Pek Date: Tue, 20 Nov 2018 23:31:04 +0100 Subject: [PATCH 4/6] update @types/sinon --- package-lock.json | 14 ++++---- package.json | 3 +- src/test/C2TestAdapter.test.ts | 60 ++++++++++++++++++++-------------- 3 files changed, 44 insertions(+), 33 deletions(-) diff --git a/package-lock.json b/package-lock.json index 287eed3d..23399995 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "vscode-catch2-test-adapter", - "version": "2.0.2", + "version": "2.0.3", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -99,9 +99,9 @@ "dev": true }, "@types/node": { - "version": "10.12.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-10.12.0.tgz", - "integrity": "sha512-3TUHC3jsBAB7qVRGxT6lWyYo2v96BMmD2PTcl47H25Lu7UXtFH/2qqmKiVrnel6Ne//0TFYf6uvNX+HW2FRkLQ==", + "version": "10.12.9", + "resolved": "https://registry.npmjs.org/@types/node/-/node-10.12.9.tgz", + "integrity": "sha512-eajkMXG812/w3w4a1OcBlaTwsFPO5F7fJ/amy+tieQxEMWBlbV1JGSjkFM+zkHNf81Cad+dfIRA+IBkvmvdAeA==", "dev": true }, "@types/request": { @@ -127,9 +127,9 @@ } }, "@types/sinon": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/@types/sinon/-/sinon-5.0.5.tgz", - "integrity": "sha512-Wnuv66VhvAD2LEJfZkq8jowXGxe+gjVibeLCYcVBp7QLdw0BFx2sRkKzoiiDkYEPGg5VyqO805Rcj0stVjQwCQ==", + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/@types/sinon/-/sinon-5.0.7.tgz", + "integrity": "sha512-opwMHufhUwkn/UUDk35LDbKJpA2VBsZT8WLU8NjayvRLGPxQkN+8XmfC2Xl35MAscBE8469koLLBjaI3XLEIww==", "dev": true }, "@types/tough-cookie": { diff --git a/package.json b/package.json index 6ce73cb7..a85258c2 100644 --- a/package.json +++ b/package.json @@ -57,7 +57,8 @@ "@types/fs-extra": "^5.0.4", "@types/mocha": "^5.2.5", "@types/request-promise": "4.1.42", - "@types/sinon": "5.0.5", + "@types/sinon": "^5.0.7", + "@types/node": "^10.12.9", "@types/xml2js": "^0.4.3", "deep-equal": "^1.0.1", "fs-extra": "^7.0.1", diff --git a/src/test/C2TestAdapter.test.ts b/src/test/C2TestAdapter.test.ts index f7a09bbd..1bef8ec8 100644 --- a/src/test/C2TestAdapter.test.ts +++ b/src/test/C2TestAdapter.test.ts @@ -2,9 +2,9 @@ // vscode-catch2-test-adapter was written by Mate Pek, and is placed in the // public domain. The author hereby disclaims copyright to this source code. -const child_process = require('child_process'); const deepStrictEqual = require('deep-equal'); +import * as child_process from 'child_process'; import * as path from 'path'; import * as fs from 'fs'; import * as fse from 'fs-extra'; @@ -59,11 +59,6 @@ describe('C2TestAdapter', function() { let testsEventsConnection: vscode.Disposable|undefined; let testStatesEventsConnection: vscode.Disposable|undefined; - let spawnStub: sinon.SinonStub; - let vsfsWatchStub: sinon.SinonStub; - let fsStatStub: sinon.SinonStub; - let vsFindFilesStub: sinon.SinonStub; - function resetConfig(): Thenable { const packageJson = fse.readJSONSync( path.join(workspaceFolderUri.fsPath, '../..', 'package.json')); @@ -167,14 +162,23 @@ describe('C2TestAdapter', function() { testsEvents = []; } + let spawnStub: sinon.SinonStub; + let vsfsWatchStub: sinon.SinonStub; + let fsStatStub: sinon.SinonStub; + let vsFindFilesStub: sinon.SinonStub< + [vscode.GlobPattern | sinon.SinonMatcher], Thenable>; + before(function() { - spawnStub = sinonSandbox.stub(child_process, 'spawn').named('spawnStub'); + spawnStub = + sinonSandbox.stub(child_process, 'spawn').named('spawnStub'); vsfsWatchStub = - sinonSandbox.stub(vscode.workspace, 'createFileSystemWatcher') + sinonSandbox.stub(vscode.workspace, 'createFileSystemWatcher') .named('vscode.createFileSystemWatcher'); - fsStatStub = sinonSandbox.stub(fs, 'stat').named('fsStat'); - vsFindFilesStub = sinonSandbox.stub(vscode.workspace, 'findFiles') - .named('vsFindFilesStub'); + fsStatStub = sinonSandbox.stub(fs, 'stat').named('fsStat'); + vsFindFilesStub = + >> + sinonSandbox.stub(vscode.workspace, 'findFiles') + .named('vsFindFilesStub'); }) after(function() { @@ -335,9 +339,11 @@ describe('C2TestAdapter', function() { dirContent.forEach((v: vscode.Uri[], k: string) => { assert.equal(workspaceFolderUri.fsPath, k); - vsFindFilesStub.withArgs(matchRelativePattern(k)).returns(v); + vsFindFilesStub.withArgs(matchRelativePattern(k)).resolves(v); for (const p of v) { - vsFindFilesStub.withArgs(matchRelativePattern(p.fsPath)).returns([p]); + vsFindFilesStub.withArgs(matchRelativePattern(p.fsPath)).resolves([ + p + ]); } }); }) @@ -905,12 +911,13 @@ describe('C2TestAdapter', function() { // since taskQueue/allTasks has benn added it works differently, so it // wont test anything really, but i dont want to delete it either await loadAdapterAndAssert(); - let spyKill1: sinon.SinonSpy; - let spyKill2: sinon.SinonSpy; + let spyKill1: sinon.SinonSpy; + let spyKill2: sinon.SinonSpy; { const spawnEvent = new ChildProcessStub(example1.suite1.outputs[2][1]); - spyKill1 = sinon.spy(spawnEvent, 'kill'); + spyKill1 = + >sinon.spy(spawnEvent, 'kill'); const withArgs = spawnStub.withArgs( example1.suite1.execPath, example1.suite1.outputs[2][0]); withArgs.onCall(withArgs.callCount).returns(spawnEvent); @@ -918,7 +925,8 @@ describe('C2TestAdapter', function() { { const spawnEvent = new ChildProcessStub(example1.suite2.outputs[2][1]); - spyKill2 = sinon.spy(spawnEvent, 'kill'); + spyKill2 = + >sinon.spy(spawnEvent, 'kill'); const withArgs = spawnStub.withArgs( example1.suite2.execPath, example1.suite2.outputs[2][0]); withArgs.onCall(withArgs.callCount).returns(spawnEvent); @@ -958,12 +966,13 @@ describe('C2TestAdapter', function() { it('cancel after run finished', async function() { await loadAdapterAndAssert(); - let spyKill1: sinon.SinonSpy; - let spyKill2: sinon.SinonSpy; + let spyKill1: sinon.SinonSpy; + let spyKill2: sinon.SinonSpy; { const spawnEvent = new ChildProcessStub(example1.suite1.outputs[2][1]); - spyKill1 = sinon.spy(spawnEvent, 'kill'); + spyKill1 = + >sinon.spy(spawnEvent, 'kill'); const withArgs = spawnStub.withArgs( example1.suite1.execPath, example1.suite1.outputs[2][0]); withArgs.onCall(withArgs.callCount).returns(spawnEvent); @@ -971,7 +980,8 @@ describe('C2TestAdapter', function() { { const spawnEvent = new ChildProcessStub(example1.suite2.outputs[2][1]); - spyKill2 = sinon.spy(spawnEvent, 'kill'); + spyKill2 = + >sinon.spy(spawnEvent, 'kill'); const withArgs = spawnStub.withArgs( example1.suite2.execPath, example1.suite2.outputs[2][0]); withArgs.onCall(withArgs.callCount).returns(spawnEvent); @@ -1217,7 +1227,7 @@ describe('C2TestAdapter', function() { vsFindFilesStub .withArgs(matchRelativePattern( path.join(workspaceFolderUri.fsPath, 'execPath{1,2}'))) - .returns([ + .resolves([ vscode.Uri.file(example1.suite1.execPath), vscode.Uri.file(example1.suite2.execPath), ]); @@ -1411,7 +1421,7 @@ describe('C2TestAdapter', function() { .callsFake(handleCreateWatcherCb); vsFindFilesStub.withArgs(matchRelativePattern(execPath2CopyPath)) - .returns([vscode.Uri.file(execPath2CopyPath)]); + .resolves([vscode.Uri.file(execPath2CopyPath)]); await updateConfig('executables', ['execPath1', 'execPath2Copy']); adapter = createAdapterAndSubscribe(); @@ -1478,7 +1488,7 @@ describe('C2TestAdapter', function() { .callsFake(handleCreateWatcherCb); vsFindFilesStub.withArgs(matchRelativePattern(execPath2CopyPath)) - .returns([vscode.Uri.file(execPath2CopyPath)]); + .resolves([vscode.Uri.file(execPath2CopyPath)]); await updateConfig('executables', ['execPath1', 'execPath2Copy']); adapter = createAdapterAndSubscribe(); @@ -1578,7 +1588,7 @@ describe('C2TestAdapter', function() { .callsFake(handleCreateWatcherCb); vsFindFilesStub.withArgs(matchRelativePattern(execPath2CopyPath)) - .returns([vscode.Uri.file(execPath2CopyPath)]); + .resolves([vscode.Uri.file(execPath2CopyPath)]); adapter = createAdapterAndSubscribe(); From c3fc8d7e751e9889cb8ea26d13dbdef01389ed7a Mon Sep 17 00:00:00 2001 From: Mate Pek Date: Wed, 21 Nov 2018 00:06:50 +0100 Subject: [PATCH 5/6] increase test timeout --- src/test/C2TestAdapter.test.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/test/C2TestAdapter.test.ts b/src/test/C2TestAdapter.test.ts index 1bef8ec8..6f5494f7 100644 --- a/src/test/C2TestAdapter.test.ts +++ b/src/test/C2TestAdapter.test.ts @@ -185,7 +185,8 @@ describe('C2TestAdapter', function() { sinonSandbox.restore(); }) - beforeEach(function reset() { + beforeEach(function() { + this.timeout(6000); adapter = undefined; fse.removeSync(dotVscodePath); @@ -203,7 +204,8 @@ describe('C2TestAdapter', function() { return resetConfig(); }) - afterEach(async function() { + afterEach(function() { + this.timeout(6000); disposeAdapterAndSubscribers(); }) From d141a38562c87902b8ca209dc9562406ad4227a1 Mon Sep 17 00:00:00 2001 From: Mate Pek Date: Wed, 21 Nov 2018 00:14:09 +0100 Subject: [PATCH 6/6] travis runs: npm audit --- .travis.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index 2f4ed4d8..85b2368a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -25,6 +25,7 @@ before_install: install: - npm install + - npm audit - npm run compile script: @@ -32,6 +33,7 @@ script: before_deploy: - npm install + - npm audit - npm run compile deploy: