From cca226e758efaa216207223099a27919b5a7e710 Mon Sep 17 00:00:00 2001 From: Mate Pek Date: Thu, 25 Oct 2018 08:13:24 +0200 Subject: [PATCH 01/49] small tweaks --- CHANGELOG.md | 4 +++- package.json | 4 ++-- src/C2TestAdapter.ts | 5 +++++ 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7ad85ff5..fe481909 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] -## [1.2.0] +## [1.2.1] + +## [1.2.0] - 2018-10-24 ### Added diff --git a/package.json b/package.json index d6c61b77..48b91f32 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "icon": "resources/icon.png", "author": "Mate Pek", "publisher": "matepek", - "version": "1.2.0", + "version": "1.2.1", "license": "Unlicense", "homepage": "https://github.com/matepek/vscode-catch2-test-adapter", "repository": { @@ -160,4 +160,4 @@ } } } -} +} \ No newline at end of file diff --git a/src/C2TestAdapter.ts b/src/C2TestAdapter.ts index 118cf884..653dc211 100644 --- a/src/C2TestAdapter.ts +++ b/src/C2TestAdapter.ts @@ -43,6 +43,10 @@ export class C2TestAdapter implements TestAdapter, vscode.Disposable { constructor( public readonly workspaceFolder: vscode.WorkspaceFolder, public readonly log: util.Log) { + this.disposables.push(this.testsEmitter); + this.disposables.push(this.testStatesEmitter); + this.disposables.push(this.autorunEmitter); + this.disposables.push( vscode.workspace.onDidChangeConfiguration(configChange => { if (configChange.affectsConfiguration( @@ -70,6 +74,7 @@ export class C2TestAdapter implements TestAdapter, vscode.Disposable { 'catch2TestExplorer.defaultRngSeed', this.workspaceFolder.uri)) { this.rngSeedStr = this.getDefaultRngSeed(this.getConfiguration()); + this.autorunEmitter.fire(); } })); From b164bad553ddc9aacf6f2d8674338ef17c86c6f5 Mon Sep 17 00:00:00 2001 From: Mate Pek Date: Thu, 25 Oct 2018 08:29:36 +0200 Subject: [PATCH 02/49] less reload, more coding --- src/C2AllTestSuiteInfo.ts | 14 +++++++------- src/C2TestAdapter.ts | 12 +++++------- src/C2TestSuiteInfo.ts | 34 +++++++++------------------------- src/test/C2TestAdapter.test.ts | 6 ------ 4 files changed, 21 insertions(+), 45 deletions(-) diff --git a/src/C2AllTestSuiteInfo.ts b/src/C2AllTestSuiteInfo.ts index de546efd..e4e26a52 100644 --- a/src/C2AllTestSuiteInfo.ts +++ b/src/C2AllTestSuiteInfo.ts @@ -16,11 +16,9 @@ export class C2AllTestSuiteInfo implements TestSuiteInfo { readonly id: string; readonly label: string = 'AllTests'; readonly children: C2TestSuiteInfo[] = []; - private readonly taskPool: TaskPool; - constructor(private readonly adapter: C2TestAdapter, slotCount: number) { + constructor(private readonly adapter: C2TestAdapter) { this.id = generateUniqueId(); - this.taskPool = new TaskPool(slotCount); } removeChild(child: C2TestSuiteInfo): boolean { @@ -58,8 +56,8 @@ export class C2AllTestSuiteInfo implements TestSuiteInfo { createChildSuite(label: string, execPath: string, execOptions: SpawnOptions): C2TestSuiteInfo { - const suite = new C2TestSuiteInfo( - label, this.adapter, [this.taskPool], execPath, execOptions); + const suite = + new C2TestSuiteInfo(label, this.adapter, execPath, execOptions); let i = this.children.findIndex((v: C2TestSuiteInfo) => { return suite.label.trim().localeCompare(v.label.trim()) < 0; @@ -76,10 +74,12 @@ export class C2AllTestSuiteInfo implements TestSuiteInfo { }); } - run(tests: string[]): Promise { + run(tests: string[], workerMaxNumber: number): Promise { this.adapter.testStatesEmitter.fire( {type: 'started', tests: tests}); + const taskPool = new TaskPool(workerMaxNumber); + // everybody should remove what they use from it. // and put their children into if they are in it const testSet = new Set(tests); @@ -93,7 +93,7 @@ export class C2AllTestSuiteInfo implements TestSuiteInfo { const ps: Promise[] = []; for (let i = 0; i < this.children.length; i++) { const child = this.children[i]; - ps.push(child.run(testSet)); + ps.push(child.run(testSet, taskPool)); } if (testSet.size > 0) { diff --git a/src/C2TestAdapter.ts b/src/C2TestAdapter.ts index 653dc211..a9d573ce 100644 --- a/src/C2TestAdapter.ts +++ b/src/C2TestAdapter.ts @@ -53,9 +53,6 @@ export class C2TestAdapter implements TestAdapter, vscode.Disposable { 'catch2TestExplorer.defaultEnv', this.workspaceFolder.uri) || configChange.affectsConfiguration( 'catch2TestExplorer.defaultCwd', this.workspaceFolder.uri) || - configChange.affectsConfiguration( - 'catch2TestExplorer.workerMaxNumber', - this.workspaceFolder.uri) || configChange.affectsConfiguration( 'catch2TestExplorer.executables', this.workspaceFolder.uri)) { this.load(); @@ -78,7 +75,7 @@ export class C2TestAdapter implements TestAdapter, vscode.Disposable { } })); - this.allTests = new C2AllTestSuiteInfo(this, 1); + this.allTests = new C2AllTestSuiteInfo(this); } dispose() { @@ -185,8 +182,7 @@ export class C2TestAdapter implements TestAdapter, vscode.Disposable { this.rngSeedStr = this.getDefaultRngSeed(config); - this.allTests = - new C2AllTestSuiteInfo(this, this.getWorkerMaxNumber(config)); + this.allTests = new C2AllTestSuiteInfo(this); const executables = this.getExecutables(config); @@ -229,7 +225,9 @@ export class C2TestAdapter implements TestAdapter, vscode.Disposable { const always = () => { this.isRunning -= 1; }; - return this.allTests.run(tests).then(always, always); + return this.allTests + .run(tests, this.getWorkerMaxNumber(this.getConfiguration())) + .then(always, always); } throw Error('Catch2 Test Adapter: Test(s) are currently being run.'); diff --git a/src/C2TestSuiteInfo.ts b/src/C2TestSuiteInfo.ts index a3535056..31ff7bfd 100644 --- a/src/C2TestSuiteInfo.ts +++ b/src/C2TestSuiteInfo.ts @@ -25,7 +25,7 @@ export class C2TestSuiteInfo implements TestSuiteInfo { constructor( public readonly label: string, private readonly adapter: C2TestAdapter, - private readonly taskPools: TaskPool[], public readonly execPath: string, + public readonly execPath: string, public readonly execOptions: SpawnOptions) { this.id = generateUniqueId(); } @@ -49,23 +49,6 @@ export class C2TestSuiteInfo implements TestSuiteInfo { return test; } - private acquireSlot(): boolean { - let i: number = 0; - while (i < this.taskPools.length && this.taskPools[i].acquire()) ++i; - - if (i == this.taskPools.length) return true; - - while (--i >= 0) this.taskPools[i].release(); // rollback - - return false; - } - - private releaseSlot(): void { - let i: number = this.taskPools.length; - - while (--i >= 0) this.taskPools[i].release(); - } - cancel(): void { this.isKill = true; @@ -75,7 +58,7 @@ export class C2TestSuiteInfo implements TestSuiteInfo { } } - run(tests: Set): Promise { + run(tests: Set, taskPool: TaskPool): Promise { this.isKill = false; this.proc = undefined; @@ -85,7 +68,7 @@ export class C2TestSuiteInfo implements TestSuiteInfo { tests.delete(c.id); } - return this.runInner('all'); + return this.runInner('all', taskPool); } else { let childrenToRun: C2TestInfo[] = []; @@ -96,16 +79,17 @@ export class C2TestSuiteInfo implements TestSuiteInfo { if (childrenToRun.length == 0) return Promise.resolve(); - return this.runInner(childrenToRun); + return this.runInner(childrenToRun, taskPool); } } - private runInner(childrenToRun: C2TestInfo[]|'all'): Promise { + private runInner(childrenToRun: C2TestInfo[]|'all', taskPool: TaskPool): + Promise { if (this.isKill) return Promise.reject(Error('Test was killed.')); - if (!this.acquireSlot()) { + if (!taskPool.acquire()) { return new Promise(resolve => setTimeout(resolve, 64)).then(() => { - return this.runInner(childrenToRun); + return this.runInner(childrenToRun, taskPool); }); } @@ -242,7 +226,7 @@ export class C2TestSuiteInfo implements TestSuiteInfo { const suiteFinally = () => { this.proc = undefined; - this.releaseSlot(); + taskPool.release(); this.adapter.testStatesEmitter.fire( {type: 'suite', suite: this, state: 'completed'}); }; diff --git a/src/test/C2TestAdapter.test.ts b/src/test/C2TestAdapter.test.ts index 4bb99c2c..0265fd1f 100644 --- a/src/test/C2TestAdapter.test.ts +++ b/src/test/C2TestAdapter.test.ts @@ -184,12 +184,6 @@ describe('C2TestAdapter', function() { return resetConfig(); }) - it('workerMaxNumber', function() { - return doAndWaitForReloadEvent(() => { - return updateConfig('workerMaxNumber', 42); - }); - }) - it('defaultEnv', function() { return doAndWaitForReloadEvent(() => { return updateConfig('defaultEnv', {'APPLE': 'apple'}); From b9406903dc5db5c8a932e5ad384c32dc702963f8 Mon Sep 17 00:00:00 2001 From: Mate Pek Date: Thu, 25 Oct 2018 12:44:44 +0200 Subject: [PATCH 03/49] using vscode's watcher --- src/C2TestAdapter.ts | 118 ++++++----- src/FsWrapper.ts | 7 - src/test/C2TestAdapter.test.ts | 376 ++++++++++++++++----------------- src/test/Helpers.ts | 39 ++++ 4 files changed, 285 insertions(+), 255 deletions(-) diff --git a/src/C2TestAdapter.ts b/src/C2TestAdapter.ts index a9d573ce..c26d6479 100644 --- a/src/C2TestAdapter.ts +++ b/src/C2TestAdapter.ts @@ -20,7 +20,7 @@ export class C2TestAdapter implements TestAdapter, vscode.Disposable { TestSuiteEvent|TestEvent>(); private readonly autorunEmitter = new vscode.EventEmitter(); - private readonly watchers: Map = new Map(); + private readonly watchers: Map = new Map(); private isRunning: number = 0; private isDebugging: boolean = false; @@ -83,7 +83,7 @@ export class C2TestAdapter implements TestAdapter, vscode.Disposable { d.dispose(); }); this.watchers.forEach((v) => { - v.close(); + v.dispose(); }); } @@ -106,61 +106,65 @@ export class C2TestAdapter implements TestAdapter, vscode.Disposable { let watcher = this.watchers.get(suite.execPath); - if (watcher != undefined) { - watcher.close(); - } - try { - watcher = c2fs.watch(suite.execPath); - this.watchers.set(suite.execPath, watcher); - const allTests = this.allTests; // alltest may has changed - - watcher.on('change', (eventType: string, filename: string) => { - const x = - (exists: boolean, startTime: number, timeout: number, - delay: number): Promise => { - if ((Date.now() - startTime) > timeout) { - watcher!.close(); - this.watchers.delete(suite.execPath); - this.testsEmitter.fire({type: 'started'}); - allTests.removeChild(suite); - this.testsEmitter.fire( - {type: 'finished', suite: this.allTests}); - return Promise.resolve(); - } else if (exists) { - this.testsEmitter.fire({type: 'started'}); - return suite.reloadChildren().then( - () => { - this.testsEmitter.fire( - {type: 'finished', suite: this.allTests}); - }, - (err: any) => { - this.testsEmitter.fire( - {type: 'finished', suite: this.allTests}); - this.log.warn(inspect(err)); - return x( - false, startTime, timeout, Math.min(delay * 2, 2000)); + if (watcher === undefined && + suite.execPath.startsWith(this.workspaceFolder.uri.path)) { + try { + const watcher = vscode.workspace.createFileSystemWatcher( + suite.execPath, true, false, false); + this.watchers.set(suite.execPath, watcher); + const allTests = this.allTests; // alltest may has changed + + const handler = (uri: vscode.Uri) => { + const x = + (exists: boolean, startTime: number, timeout: number, + delay: number): Promise => { + if ((Date.now() - startTime) > timeout) { + watcher!.dispose(); + this.watchers.delete(suite.execPath); + this.testsEmitter.fire({type: 'started'}); + allTests.removeChild(suite); + this.testsEmitter.fire( + {type: 'finished', suite: this.allTests}); + return Promise.resolve(); + } else if (exists) { + this.testsEmitter.fire({type: 'started'}); + return suite.reloadChildren().then( + () => { + this.testsEmitter.fire( + {type: 'finished', suite: this.allTests}); + }, + (err: any) => { + this.testsEmitter.fire( + {type: 'finished', suite: this.allTests}); + this.log.warn(inspect(err)); + return x( + false, startTime, timeout, + Math.min(delay * 2, 2000)); + }); + } + return promisify(setTimeout)(Math.min(delay * 2, 2000)) + .then(() => { + return c2fs.existsAsync(suite.execPath) + .then((exists: boolean) => { + return x( + exists, startTime, timeout, + Math.min(delay * 2, 2000)); + }); }); - } - return promisify(setTimeout)(Math.min(delay * 2, 2000)) - .then(() => { - return c2fs.existsAsync(suite.execPath) - .then((exists: boolean) => { - return x( - exists, startTime, timeout, - Math.min(delay * 2, 2000)); - }); - }); - }; - - // change event can arrive during debug session on osx (why?) - if (!this.isDebugging) { - // TODO filter multiple events and dont mess with 'load' - x(false, Date.now(), - this.getDefaultExecWatchTimeout(this.getConfiguration()), 64); - } - }); - } catch (e) { - this.log.warn('watcher couldn\'t watch: ' + suite.execPath); + }; + + // change event can arrive during debug session on osx (why?) + if (!this.isDebugging) { + // TODO filter multiple events and dont mess with 'load' + x(false, Date.now(), + this.getDefaultExecWatchTimeout(this.getConfiguration()), 64); + } + }; + this.disposables.push(watcher.onDidChange(handler)); // TODO nem szep + this.disposables.push(watcher.onDidDelete(handler)); + } catch (e) { + this.log.warn('watcher couldn\'t watch: ' + suite.execPath); + } } return suite.reloadChildren().catch((err: any) => { this.log.warn(inspect(err)); @@ -174,7 +178,7 @@ export class C2TestAdapter implements TestAdapter, vscode.Disposable { this.testsEmitter.fire({type: 'started'}); this.watchers.forEach((value, key) => { - value.close(); + value.dispose(); }); this.watchers.clear(); diff --git a/src/FsWrapper.ts b/src/FsWrapper.ts index 3ce59a66..5550d13e 100644 --- a/src/FsWrapper.ts +++ b/src/FsWrapper.ts @@ -61,17 +61,10 @@ export function existsAsync(path: string): Promise { }); } - export function existsSync(path: string): boolean { return fs.existsSync(path); } export function readdirSync(path: string): string[] { return fs.readdirSync(path, 'utf8'); -} - -export type FSWatcher = fs.FSWatcher; - -export function watch(path: string): FSWatcher { - return fs.watch(path); } \ No newline at end of file diff --git a/src/test/C2TestAdapter.test.ts b/src/test/C2TestAdapter.test.ts index 0265fd1f..f922031c 100644 --- a/src/test/C2TestAdapter.test.ts +++ b/src/test/C2TestAdapter.test.ts @@ -11,14 +11,13 @@ import * as fse from 'fs-extra'; import * as assert from 'assert'; import * as vscode from 'vscode'; import * as sinon from 'sinon'; -import {EventEmitter} from 'events'; import {TestEvent, TestLoadFinishedEvent, TestLoadStartedEvent, TestRunFinishedEvent, TestRunStartedEvent, TestSuiteEvent, TestSuiteInfo, TestInfo, TestAdapter} from 'vscode-test-adapter-api'; import {Log} from 'vscode-test-adapter-util'; import {inspect, promisify} from 'util'; import {C2TestAdapter} from '../C2TestAdapter'; import {example1} from './example1'; -import {ChildProcessStub} from './Helpers'; +import {ChildProcessStub, FileSystemWatcherStub} from './Helpers'; import * as c2fs from '../FsWrapper'; import * as Mocha from 'mocha'; @@ -57,7 +56,7 @@ describe('C2TestAdapter', function() { let testStatesEventsConnection: vscode.Disposable|undefined; let spawnStub: sinon.SinonStub; - let fsWatchStub: sinon.SinonStub; + let vsfsWatchStub: sinon.SinonStub; let c2fsStatStub: sinon.SinonStub; let c2fsReaddirSyncStub: sinon.SinonStub; @@ -115,12 +114,13 @@ describe('C2TestAdapter', function() { } async function doAndWaitForReloadEvent( - action: Function, timeout: number = 1000): Promise { + test: Mocha.Context, action: Function, + timeout: number = 1000): Promise { const origCount = testsEvents.length; await action(); const start = Date.now(); while (testsEvents.length != origCount + 2 && - (Date.now() - start) < timeout) + ((Date.now() - start) < timeout || !test.enableTimeouts())) await promisify(setTimeout)(10); assert.equal(testsEvents.length, origCount + 2); const e = testsEvents[testsEvents.length - 1]!; @@ -140,8 +140,8 @@ describe('C2TestAdapter', function() { function stubsResetToMyDefault() { spawnStub.reset(); spawnStub.callThrough(); - fsWatchStub.reset(); - fsWatchStub.callThrough(); + vsfsWatchStub.reset(); + vsfsWatchStub.callThrough(); c2fsStatStub.reset(); c2fsStatStub.callThrough(); c2fsReaddirSyncStub.reset(); @@ -153,7 +153,9 @@ describe('C2TestAdapter', function() { adapter = undefined; spawnStub = sinonSandbox.stub(child_process, 'spawn').named('spawnStub'); - fsWatchStub = sinonSandbox.stub(fs, 'watch').named('fsWatchStub'); + vsfsWatchStub = + sinonSandbox.stub(vscode.workspace, 'createFileSystemWatcher') + .named('vscode.createFileSystemWatcher'); c2fsStatStub = sinonSandbox.stub(fs, 'stat').named('fsStat'); c2fsReaddirSyncStub = sinonSandbox.stub(c2fs, 'readdirSync').named('c2fsReaddirSyncStub'); @@ -185,13 +187,13 @@ describe('C2TestAdapter', function() { }) it('defaultEnv', function() { - return doAndWaitForReloadEvent(() => { + return doAndWaitForReloadEvent(this, () => { return updateConfig('defaultEnv', {'APPLE': 'apple'}); }); }) it('defaultCwd', function() { - return doAndWaitForReloadEvent(() => { + return doAndWaitForReloadEvent(this, () => { return updateConfig('defaultCwd', 'apple/peach'); }); }) @@ -222,7 +224,17 @@ describe('C2TestAdapter', function() { }) context('example1', function() { - const watchers: Map = new Map(); + const watchers: Map = new Map(); + + function handleCreateWatcherCb( + path: string, ignoreCreateEvents: boolean, ignoreChangeEvents: boolean, + ignoreDeleteEvents: boolean) { + const e = new FileSystemWatcherStub( + vscode.Uri.file(path), ignoreCreateEvents, ignoreChangeEvents, + ignoreDeleteEvents); + watchers.set(path, e); + return e; + } before(function() { for (let suite of example1.outputs) { @@ -246,13 +258,8 @@ describe('C2TestAdapter', function() { }); }); - fsWatchStub.withArgs(suite[0]).callsFake((path: string) => { - const e = new class extends EventEmitter { - close() {} - }; - watchers.set(path, e); - return e; - }); + vsfsWatchStub.withArgs(suite[0], true, false, false) + .callsFake(handleCreateWatcherCb); } const dirContent: Map = new Map(); @@ -505,7 +512,7 @@ describe('C2TestAdapter', function() { }) context('suite1 and suite2 are used', function() { - let suite1Watcher: EventEmitter; + let suite1Watcher: FileSystemWatcherStub; beforeEach(function() { assert.equal(root.children.length, 2); @@ -1004,127 +1011,6 @@ describe('C2TestAdapter', function() { assert.equal(spyKill2.callCount, 0); }); }), - new Mocha.Test( - 'reload because of fswatcher event: touch', - async function(this: Mocha.Context) { - this.slow(200); - const newRoot = await doAndWaitForReloadEvent(async () => { - suite1Watcher.emit( - 'change', 'dummyEvent', example1.suite1.execPath); - }); - assert.deepStrictEqual(newRoot, root); - }), - new Mocha.Test( - 'reload because of fswatcher event: touch, retry 5 times', - async function(this: Mocha.Context) { - this.timeout(10000); - this.slow(6500); - const newRoot = await doAndWaitForReloadEvent(async () => { - const w = c2fsStatStub.withArgs(example1.suite1.execPath); - for (let cc = 0; cc < 5; cc++) { - w.onCall(w.callCount + cc) - .callsFake( - (path: string, - cb: ( - err: NodeJS.ErrnoException|null|any, - stats: fs.Stats|undefined) => void) => { - cb({ - code: 'ENOENT', - errno: -2, - message: 'ENOENT', - path: path, - syscall: 'stat' - }, - undefined); - }); - } - assert.ok(suite1Watcher.emit( - 'change', 'dummyEvent', example1.suite1.execPath)); - }, 10000); - assert.deepStrictEqual(newRoot, root); - }), - new Mocha.Test( - 'reload because of fswatcher event: test added', - async function(this: Mocha.Context) { - this.slow(200); - const testListOutput = - example1.suite1.outputs[1][1].split('\n'); - assert.equal(testListOutput.length, 10); - testListOutput.splice( - 1, 0, ' s1t0', ' suite1.cpp:6', ' tag1'); - const withArgs = spawnStub.withArgs( - example1.suite1.execPath, example1.suite1.outputs[1][0]); - withArgs.onCall(withArgs.callCount) - .returns(new ChildProcessStub(testListOutput.join('\n'))); - - const oldRootChildren = [...root.children]; - const oldSuite1Children = [...suite1.children]; - const oldSuite2Children = [...suite2.children]; - - const newRoot = await doAndWaitForReloadEvent(async () => { - suite1Watcher.emit( - 'change', 'dummyEvent', example1.suite1.execPath); - }); - - assert.equal(newRoot, root); - assert.equal(root.children.length, oldRootChildren.length); - for (let i = 0; i < oldRootChildren.length; i++) { - assert.equal(root.children[i], oldRootChildren[i]); - } - - assert.equal( - suite1.children.length, oldSuite1Children.length + 1); - for (let i = 0; i < suite1.children.length; i++) { - assert.equal(suite1.children[i + 1], oldSuite1Children[i]); - } - const newTest = suite1.children[0]; - assert.ok(!uniqueIdC.has(newTest.id)); - assert.equal(newTest.label, 's1t0'); - - assert.equal(suite2.children.length, oldSuite2Children.length); - for (let i = 0; i < suite2.children.length; i++) { - assert.equal(suite2.children[i], oldSuite2Children[i]); - } - }), - new Mocha.Test( - 'reload because of fswatcher event: test deleted', - async function(this: Mocha.Context) { - this.slow(200); - const testListOutput = - example1.suite1.outputs[1][1].split('\n'); - assert.equal(testListOutput.length, 10); - testListOutput.splice(1, 3); - const withArgs = spawnStub.withArgs( - example1.suite1.execPath, example1.suite1.outputs[1][0]); - withArgs.onCall(withArgs.callCount) - .returns(new ChildProcessStub(testListOutput.join('\n'))); - - const oldRootChildren = [...root.children]; - const oldSuite1Children = [...suite1.children]; - const oldSuite2Children = [...suite2.children]; - - const newRoot = await doAndWaitForReloadEvent(async () => { - suite1Watcher.emit( - 'change', 'dummyEvent', example1.suite1.execPath); - }); - - assert.equal(newRoot, root); - assert.equal(root.children.length, oldRootChildren.length); - for (let i = 0; i < oldRootChildren.length; i++) { - assert.equal(root.children[i], oldRootChildren[i]); - } - - assert.equal( - suite1.children.length + 1, oldSuite1Children.length); - for (let i = 0; i < suite1.children.length; i++) { - assert.equal(suite1.children[i], oldSuite1Children[i + 1]); - } - - assert.equal(suite2.children.length, oldSuite2Children.length); - for (let i = 0; i < suite2.children.length; i++) { - assert.equal(suite2.children[i], oldSuite2Children[i]); - } - }), ]; context( @@ -1151,6 +1037,114 @@ describe('C2TestAdapter', function() { for (let t of testsForAdapterWithSuite1AndSuite2) this.addTest( t.clone()); + + it('reload because of fswatcher event: touch(changed)', + async function() { + this.slow(200); + const newRoot = + await doAndWaitForReloadEvent(this, async () => { + suite1Watcher.sendChange(); + }); + assert.deepStrictEqual(newRoot, root); + }) + + it('reload because of fswatcher event: touch(delete,create)', + async function() { + this.slow(200); + const newRoot = + await doAndWaitForReloadEvent(this, async () => { + suite1Watcher.sendDelete(); + suite1Watcher.sendCreate(); + }); + assert.deepStrictEqual(newRoot, root); + }) + + it('reload because of fswatcher event: test added', + async function(this: Mocha.Context) { + this.slow(200); + const testListOutput = + example1.suite1.outputs[1][1].split('\n'); + assert.equal(testListOutput.length, 10); + testListOutput.splice( + 1, 0, ' s1t0', ' suite1.cpp:6', ' tag1'); + const withArgs = spawnStub.withArgs( + example1.suite1.execPath, example1.suite1.outputs[1][0]); + withArgs.onCall(withArgs.callCount) + .returns( + new ChildProcessStub(testListOutput.join('\n'))); + + const oldRootChildren = [...root.children]; + const oldSuite1Children = [...suite1.children]; + const oldSuite2Children = [...suite2.children]; + + const newRoot = + await doAndWaitForReloadEvent(this, async () => { + suite1Watcher.sendDelete(); + suite1Watcher.sendCreate(); + }); + + assert.equal(newRoot, root); + assert.equal(root.children.length, oldRootChildren.length); + for (let i = 0; i < oldRootChildren.length; i++) { + assert.equal(root.children[i], oldRootChildren[i]); + } + + assert.equal( + suite1.children.length, oldSuite1Children.length + 1); + for (let i = 0; i < suite1.children.length; i++) { + assert.equal(suite1.children[i + 1], oldSuite1Children[i]); + } + const newTest = suite1.children[0]; + assert.ok(!uniqueIdC.has(newTest.id)); + assert.equal(newTest.label, 's1t0'); + + assert.equal( + suite2.children.length, oldSuite2Children.length); + for (let i = 0; i < suite2.children.length; i++) { + assert.equal(suite2.children[i], oldSuite2Children[i]); + } + }) + it('reload because of fswatcher event: test deleted', + async function(this: Mocha.Context) { + this.slow(200); + const testListOutput = + example1.suite1.outputs[1][1].split('\n'); + assert.equal(testListOutput.length, 10); + testListOutput.splice(1, 3); + const withArgs = spawnStub.withArgs( + example1.suite1.execPath, example1.suite1.outputs[1][0]); + withArgs.onCall(withArgs.callCount) + .returns( + new ChildProcessStub(testListOutput.join('\n'))); + + const oldRootChildren = [...root.children]; + const oldSuite1Children = [...suite1.children]; + const oldSuite2Children = [...suite2.children]; + + const newRoot = + await doAndWaitForReloadEvent(this, async () => { + suite1Watcher.sendDelete(); + suite1Watcher.sendCreate(); + }); + + assert.equal(newRoot, root); + assert.equal(root.children.length, oldRootChildren.length); + for (let i = 0; i < oldRootChildren.length; i++) { + assert.equal(root.children[i], oldRootChildren[i]); + } + + assert.equal( + suite1.children.length + 1, oldSuite1Children.length); + for (let i = 0; i < suite1.children.length; i++) { + assert.equal(suite1.children[i], oldSuite1Children[i + 1]); + } + + assert.equal( + suite2.children.length, oldSuite2Children.length); + for (let i = 0; i < suite2.children.length; i++) { + assert.equal(suite2.children[i], oldSuite2Children[i]); + } + }) }) context('executables=[{}] and env={...}', function() { @@ -1242,7 +1236,7 @@ describe('C2TestAdapter', function() { }); it('run suite3 one-by-one', async function() { - this.slow(200); + this.slow(300); assert.equal(root.children.length, 3); assert.equal(root.children[0].type, 'suite'); const suite3 = root.children[2]; @@ -1321,42 +1315,40 @@ describe('C2TestAdapter', function() { }) specify( - 'load executables=["execPath1", "execPath2Copy"] and delete second because of fswatcher event', + 'load executables=["execPath1", "execPath2Copy"]; delete second because of fswatcher event', async function(this: Mocha.Context) { const watchTimeout = 5000; await updateConfig('defaultExecWatchTimeout', watchTimeout); this.timeout(watchTimeout + 2500 /* because of 'delay' */); this.slow(watchTimeout + 2500 /* because of 'delay' */); - const fullPath = path.join(workspaceFolderUri.path, 'execPath2Copy'); + const execPath2CopyPath = + path.join(workspaceFolderUri.path, 'execPath2Copy'); for (let scenario of example1.suite2.outputs) { - spawnStub.withArgs(fullPath, scenario[0]).callsFake(function() { - return new ChildProcessStub(scenario[1]); - }); + spawnStub.withArgs(execPath2CopyPath, scenario[0]) + .callsFake(function() { + return new ChildProcessStub(scenario[1]); + }); } - c2fsStatStub.withArgs(fullPath).callsFake( - (path: string, - cb: ( - err: NodeJS.ErrnoException|null, - stats: fs.Stats|undefined) => void) => { - cb(null, { - isFile() { - return true; - }, - isDirectory() { - return false; - } - }); - }); + c2fsStatStub.withArgs(execPath2CopyPath) + .callsFake( + (path: string, + cb: ( + err: NodeJS.ErrnoException|null, + stats: fs.Stats|undefined) => void) => { + cb(null, { + isFile() { + return true; + }, + isDirectory() { + return false; + } + }); + }); - fsWatchStub.withArgs(fullPath).callsFake((path: string) => { - const e = new class extends EventEmitter { - close() {} - }; - watchers.set(path, e); - return e; - }); + vsfsWatchStub.withArgs(execPath2CopyPath, true, false, false) + .callsFake(handleCreateWatcherCb); await updateConfig('executables', ['execPath1', 'execPath2Copy']) adapter = createAdapterAndSubscribe(); @@ -1367,27 +1359,28 @@ describe('C2TestAdapter', function() { (testsEvents[testsEvents.length - 1]) .suite!.children.length, 2); - assert.ok(watchers.has(fullPath)); - const watcher = watchers.get(fullPath)!; - const start = Date.now(); - - const newRoot = await doAndWaitForReloadEvent(async () => { - c2fsStatStub.withArgs(fullPath).callsFake( - (path: string, - cb: ( - err: NodeJS.ErrnoException|null|any, - stats: fs.Stats|undefined) => void) => { - cb({ - code: 'ENOENT', - errno: -2, - message: 'ENOENT', - path: path, - syscall: 'stat' - }, - undefined); - }); - assert.ok( - watcher.emit('change', 'dummyEvent', example1.suite1.execPath)); + assert.ok(watchers.has(execPath2CopyPath)); + const watcher = watchers.get(execPath2CopyPath)!; + let start: number = 0; + + const newRoot = await doAndWaitForReloadEvent(this, async () => { + c2fsStatStub.withArgs(execPath2CopyPath) + .callsFake( + (path: string, + cb: ( + err: NodeJS.ErrnoException|null|any, + stats: fs.Stats|undefined) => void) => { + cb({ + code: 'ENOENT', + errno: -2, + message: 'ENOENT', + path: path, + syscall: 'stat' + }, + undefined); + }); + start = Date.now(); + watcher.sendDelete(); }, 40000); const elapsed = Date.now() - start; @@ -1397,10 +1390,11 @@ describe('C2TestAdapter', function() { // restore for (let scenario of example1.suite2.outputs) { - spawnStub.withArgs(fullPath, scenario[0]).throws(); + spawnStub.withArgs(execPath2CopyPath, scenario[0]).throws(); } - c2fsStatStub.withArgs(fullPath).throws(); - fsWatchStub.withArgs(fullPath).throws(); + c2fsStatStub.withArgs(execPath2CopyPath).throws(); + vsfsWatchStub.withArgs(execPath2CopyPath, true, false, false) + .throws(); disposeAdapterAndSubscribers(); await updateConfig('executables', undefined); await updateConfig('defaultExecWatchTimeout', undefined); diff --git a/src/test/Helpers.ts b/src/test/Helpers.ts index 01600375..8a09ed3e 100644 --- a/src/test/Helpers.ts +++ b/src/test/Helpers.ts @@ -1,5 +1,6 @@ import {EventEmitter} from 'events'; import {Stream} from 'stream'; +import * as vscode from 'vscode'; export class ChildProcessStub extends EventEmitter { readonly stdout = new Stream.Readable(); @@ -39,4 +40,42 @@ export class ChildProcessStub extends EventEmitter { }); this.stdout.push(null); } +}; + +export class FileSystemWatcherStub implements vscode.FileSystemWatcher { + constructor( + private readonly path: vscode.Uri, + readonly ignoreCreateEvents: boolean = false, + readonly ignoreChangeEvents: boolean = false, + readonly ignoreDeleteEvents: boolean = false) {} + + private readonly _onDidCreateEmitter = new vscode.EventEmitter(); + private readonly _onDidChangeEmitter = new vscode.EventEmitter(); + private readonly _onDidDeleteEmitter = new vscode.EventEmitter(); + + sendCreate() { + this._onDidCreateEmitter.fire(this.path); + } + sendChange() { + this._onDidChangeEmitter.fire(this.path); + } + sendDelete() { + this._onDidDeleteEmitter.fire(this.path); + } + + get onDidCreate(): vscode.Event { + return this._onDidCreateEmitter.event; + } + get onDidChange(): vscode.Event { + return this._onDidChangeEmitter.event; + } + get onDidDelete(): vscode.Event { + return this._onDidDeleteEmitter.event; + } + + dispose() { + this._onDidCreateEmitter.dispose(); + this._onDidChangeEmitter.dispose(); + this._onDidDeleteEmitter.dispose(); + } }; \ No newline at end of file From b1fef44b2308b68901a5a4b3623b6fbcc2a1d105 Mon Sep 17 00:00:00 2001 From: Mate Pek Date: Thu, 25 Oct 2018 16:00:05 +0200 Subject: [PATCH 04/49] more fswatcher test and minor fixes --- src/C2TestAdapter.ts | 24 ++-- src/test/C2TestAdapter.test.ts | 229 ++++++++++++++++++++++++--------- 2 files changed, 185 insertions(+), 68 deletions(-) diff --git a/src/C2TestAdapter.ts b/src/C2TestAdapter.ts index c26d6479..2ffbcc68 100644 --- a/src/C2TestAdapter.ts +++ b/src/C2TestAdapter.ts @@ -160,7 +160,7 @@ export class C2TestAdapter implements TestAdapter, vscode.Disposable { this.getDefaultExecWatchTimeout(this.getConfiguration()), 64); } }; - this.disposables.push(watcher.onDidChange(handler)); // TODO nem szep + this.disposables.push(watcher.onDidChange(handler)); // TODO not nice this.disposables.push(watcher.onDidDelete(handler)); } catch (e) { this.log.warn('watcher couldn\'t watch: ' + suite.execPath); @@ -194,11 +194,14 @@ export class C2TestAdapter implements TestAdapter, vscode.Disposable { .then((execs: ExecutableConfig[]) => { let testListReaders = Promise.resolve(); - execs.forEach(exe => { + for (let i = 0; i < execs.length; i++) { testListReaders = testListReaders.then(() => { - return this.loadSuite(exe); - }); - }); + return this.loadSuite(execs[i]).catch((err) => { + this.log.error(inspect(err)); + debugger; + }); + }) + } return testListReaders; }) @@ -541,9 +544,14 @@ export class C2TestAdapter implements TestAdapter, vscode.Disposable { } verifyIsCatch2TestExecutable(path: string): Promise { - return c2fs.spawnAsync(path, ['--help']).then((res) => { - return res.stdout.indexOf('Catch v2.') != -1; - }); + return c2fs.spawnAsync(path, ['--help']) + .then((res) => { + return res.stdout.indexOf('Catch v2.') != -1; + }) + .catch((e) => { + this.log.error(inspect(e)); + return false; + }); } private filterVerifiedCatch2TestExecutables(executables: ExecutableConfig[]): diff --git a/src/test/C2TestAdapter.test.ts b/src/test/C2TestAdapter.test.ts index f922031c..0caff96b 100644 --- a/src/test/C2TestAdapter.test.ts +++ b/src/test/C2TestAdapter.test.ts @@ -39,20 +39,26 @@ const sinonSandbox = sinon.createSandbox(); /// describe('C2TestAdapter', function() { + this.enableTimeouts(false); // TODO + + let testsEvents: (TestLoadStartedEvent|TestLoadFinishedEvent)[] = []; + let testStatesEvents: (TestRunStartedEvent|TestRunFinishedEvent| + TestSuiteEvent|TestEvent)[] = []; + function getConfig() { return vscode.workspace.getConfiguration( 'catch2TestExplorer', workspaceFolderUri) }; - function updateConfig(key: string, value: any) { - return getConfig().update(key, value) + async function updateConfig(key: string, value: any) { + let count = testsEvents.length; + await getConfig().update(key, value); + // cleanup + while (testsEvents.length < count--) testsEvents.pop(); } let adapter: C2TestAdapter|undefined; - let testsEvents: (TestLoadStartedEvent|TestLoadFinishedEvent)[]; let testsEventsConnection: vscode.Disposable|undefined; - let testStatesEvents: (TestRunStartedEvent|TestRunFinishedEvent| - TestSuiteEvent|TestEvent)[]; let testStatesEventsConnection: vscode.Disposable|undefined; let spawnStub: sinon.SinonStub; @@ -97,7 +103,6 @@ describe('C2TestAdapter', function() { function createAdapterAndSubscribe() { adapter = new C2TestAdapter(workspaceFolder, logger); - testsEvents = []; testsEventsConnection = adapter.tests((e: TestLoadStartedEvent|TestLoadFinishedEvent) => { testsEvents.push(e); @@ -129,12 +134,29 @@ describe('C2TestAdapter', function() { return e.suite!; } - function disposeAdapterAndSubscribers() { + function disposeAdapterAndSubscribers(check: boolean = true) { adapter && adapter.dispose(); testsEventsConnection && testsEventsConnection.dispose(); testStatesEventsConnection && testStatesEventsConnection.dispose(); - testsEvents = []; testStatesEvents = []; + if (check) { + for (let i = 0; i < testsEvents.length; i++) { + assert.deepStrictEqual( + {type: 'started'}, testsEvents[i], + inspect({index: i, testsEvents: testsEvents})); + i++; + assert.ok( + i < testsEvents.length, + inspect({index: i, testsEvents: testsEvents})); + assert.equal( + testsEvents[i].type, 'finished', + inspect({index: i, testsEvents: testsEvents})); + assert.ok( + (testsEvents[i]).suite, + inspect({index: i, testsEvents: testsEvents})); + } + } + testsEvents = []; } function stubsResetToMyDefault() { @@ -236,6 +258,34 @@ describe('C2TestAdapter', function() { return e; } + function handleStatExistsFile( + path: string, + cb: (err: NodeJS.ErrnoException|null, stats: fs.Stats|undefined) => + void) { + cb(null, { + isFile() { + return true; + }, + isDirectory() { + return false; + } + }); + } + + function handleStatNotExists( + path: string, + cb: (err: NodeJS.ErrnoException|null|any, stats: fs.Stats|undefined) => + void) { + cb({ + code: 'ENOENT', + errno: -2, + message: 'ENOENT', + path: path, + syscall: 'stat' + }, + undefined); + }; + before(function() { for (let suite of example1.outputs) { for (let scenario of suite[1]) { @@ -244,19 +294,7 @@ describe('C2TestAdapter', function() { }); } - c2fsStatStub.withArgs(suite[0]).callsFake( - (path: string, - cb: (err: NodeJS.ErrnoException|null, stats: fs.Stats|undefined) => - void) => { - cb(null, { - isFile() { - return true; - }, - isDirectory() { - return false; - } - }); - }); + c2fsStatStub.withArgs(suite[0]).callsFake(handleStatExistsFile); vsfsWatchStub.withArgs(suite[0], true, false, false) .callsFake(handleCreateWatcherCb); @@ -268,8 +306,9 @@ describe('C2TestAdapter', function() { let children: string[] = []; if (dirContent.has(parent)) children = dirContent.get(parent)!; - else + else { dirContent.set(parent, children); + } children.push(path.basename(p[0])); } @@ -1288,12 +1327,17 @@ describe('C2TestAdapter', function() { this.slow(300); await updateConfig('executables', example1.suite1.execPath); adapter = createAdapterAndSubscribe(); + await adapter.load(); assert.equal(testsEvents.length, 2); assert.equal( (testsEvents[testsEvents.length - 1]) .suite!.children.length, 1); + testsEvents.pop(); + testsEvents.pop(); + + disposeAdapterAndSubscribers(); await updateConfig('executables', undefined); }) @@ -1310,14 +1354,17 @@ describe('C2TestAdapter', function() { 'dummy error for testing (should be handled)'); await adapter.load(); + testsEvents.pop(); + testsEvents.pop(); + disposeAdapterAndSubscribers(); await updateConfig('executables', undefined); }) specify( - 'load executables=["execPath1", "execPath2Copy"]; delete second because of fswatcher event', + 'load executables=["execPath1", "execPath2Copy"]; delete; sleep 3; create', async function(this: Mocha.Context) { - const watchTimeout = 5000; + const watchTimeout = 5500; await updateConfig('defaultExecWatchTimeout', watchTimeout); this.timeout(watchTimeout + 2500 /* because of 'delay' */); this.slow(watchTimeout + 2500 /* because of 'delay' */); @@ -1332,20 +1379,7 @@ describe('C2TestAdapter', function() { } c2fsStatStub.withArgs(execPath2CopyPath) - .callsFake( - (path: string, - cb: ( - err: NodeJS.ErrnoException|null, - stats: fs.Stats|undefined) => void) => { - cb(null, { - isFile() { - return true; - }, - isDirectory() { - return false; - } - }); - }); + .callsFake(handleStatExistsFile); vsfsWatchStub.withArgs(execPath2CopyPath, true, false, false) .callsFake(handleCreateWatcherCb); @@ -1359,45 +1393,120 @@ describe('C2TestAdapter', function() { (testsEvents[testsEvents.length - 1]) .suite!.children.length, 2); + testsEvents.pop(); + testsEvents.pop(); + assert.ok(watchers.has(execPath2CopyPath)); const watcher = watchers.get(execPath2CopyPath)!; - let start: number = 0; + let start: number = 0; const newRoot = await doAndWaitForReloadEvent(this, async () => { c2fsStatStub.withArgs(execPath2CopyPath) - .callsFake( - (path: string, - cb: ( - err: NodeJS.ErrnoException|null|any, - stats: fs.Stats|undefined) => void) => { - cb({ - code: 'ENOENT', - errno: -2, - message: 'ENOENT', - path: path, - syscall: 'stat' - }, - undefined); - }); + .callsFake(handleStatNotExists); start = Date.now(); watcher.sendDelete(); + setTimeout(() => { + assert.equal(testsEvents.length, 0); + }, 1500); + setTimeout(() => { + c2fsStatStub.withArgs(execPath2CopyPath) + .callsFake(handleStatExistsFile); + watcher.sendCreate(); + }, 3000); }, 40000); - const elapsed = Date.now() - start; - assert.equal(newRoot.children.length, 1); - assert.ok(watchTimeout < elapsed, inspect(elapsed)); + + assert.equal(testsEvents.length, 2); + testsEvents.pop(); + testsEvents.pop(); + for (let scenario of example1.suite2.outputs) { + spawnStub.withArgs(execPath2CopyPath, scenario[0]).callsFake(() => { + throw Error('restore'); + }); + } + c2fsStatStub.withArgs(execPath2CopyPath).callsFake(() => { + throw Error('restore'); + }); + vsfsWatchStub.withArgs(execPath2CopyPath, true, false, false) + .callsFake(() => { + throw Error('restore'); + }); + disposeAdapterAndSubscribers(); + await updateConfig('executables', undefined); + await updateConfig('defaultExecWatchTimeout', undefined); + + assert.equal(newRoot.children.length, 2); + assert.ok(3000 < elapsed, inspect(elapsed)); assert.ok(elapsed < watchTimeout + 2400, inspect(elapsed)); + }) + + specify( + 'load executables=["execPath1", "execPath2Copy"]; delete second', + async function() { + const watchTimeout = 5000; + await updateConfig('defaultExecWatchTimeout', watchTimeout); + this.timeout(watchTimeout + 2500 /* because of 'delay' */); + this.slow(watchTimeout + 2500 /* because of 'delay' */); + const execPath2CopyPath = + path.join(workspaceFolderUri.path, 'execPath2Copy'); - // restore for (let scenario of example1.suite2.outputs) { - spawnStub.withArgs(execPath2CopyPath, scenario[0]).throws(); + spawnStub.withArgs(execPath2CopyPath, scenario[0]) + .callsFake(function() { + return new ChildProcessStub(scenario[1]); + }); } - c2fsStatStub.withArgs(execPath2CopyPath).throws(); + + c2fsStatStub.withArgs(execPath2CopyPath) + .callsFake(handleStatExistsFile); + vsfsWatchStub.withArgs(execPath2CopyPath, true, false, false) - .throws(); + .callsFake(handleCreateWatcherCb); + + await updateConfig('executables', ['execPath1', 'execPath2Copy']) + adapter = createAdapterAndSubscribe(); + + await adapter.load(); + + assert.equal( + (testsEvents[testsEvents.length - 1]) + .suite!.children.length, + 2); + testsEvents.pop(); + testsEvents.pop(); + + assert.ok(watchers.has(execPath2CopyPath)); + const watcher = watchers.get(execPath2CopyPath)!; + + let start: number = 0; + const newRoot = await doAndWaitForReloadEvent(this, async () => { + c2fsStatStub.withArgs(execPath2CopyPath) + .callsFake(handleStatNotExists); + start = Date.now(); + watcher.sendDelete(); + }, 40000); + const elapsed = Date.now() - start; + testsEvents.pop(); + testsEvents.pop(); + for (let scenario of example1.suite2.outputs) { + spawnStub.withArgs(execPath2CopyPath, scenario[0]).callsFake(() => { + throw Error('restore'); + }); + } + c2fsStatStub.withArgs(execPath2CopyPath).callsFake(() => { + throw Error('restore'); + }); + vsfsWatchStub.withArgs(execPath2CopyPath, true, false, false) + .callsFake(() => { + throw Error('restore'); + }); disposeAdapterAndSubscribers(); await updateConfig('executables', undefined); await updateConfig('defaultExecWatchTimeout', undefined); + + assert.equal(newRoot.children.length, 1); + assert.ok(watchTimeout < elapsed, inspect(elapsed)); + assert.ok(elapsed < watchTimeout + 2400, inspect(elapsed)); }) }) }) \ No newline at end of file From 68d01c3e8f57063ccf2a67b0dc1ab7b7d4ce5c8a Mon Sep 17 00:00:00 2001 From: Mate Pek Date: Thu, 25 Oct 2018 17:21:09 +0200 Subject: [PATCH 05/49] refactor wathcer --- src/test/C2TestAdapter.test.ts | 43 ++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/src/test/C2TestAdapter.test.ts b/src/test/C2TestAdapter.test.ts index 0caff96b..b01d6ae3 100644 --- a/src/test/C2TestAdapter.test.ts +++ b/src/test/C2TestAdapter.test.ts @@ -1085,6 +1085,26 @@ describe('C2TestAdapter', function() { suite1Watcher.sendChange(); }); assert.deepStrictEqual(newRoot, root); + assert.deepStrictEqual(testsEvents, [ + {type: 'started'}, + {type: 'finished', suite: root}, + ]); + }) + + it('reload because of fswatcher event: double touch(changed)', + async function() { + this.slow(200); + const newRoot = + await doAndWaitForReloadEvent(this, async () => { + suite1Watcher.sendChange(); + }); + assert.deepStrictEqual(newRoot, root); + assert.deepStrictEqual(testsEvents, [ + {type: 'started'}, + {type: 'finished', suite: root}, + {type: 'started'}, + {type: 'finished', suite: root}, + ]); }) it('reload because of fswatcher event: touch(delete,create)', @@ -1096,6 +1116,29 @@ describe('C2TestAdapter', function() { suite1Watcher.sendCreate(); }); assert.deepStrictEqual(newRoot, root); + assert.deepStrictEqual(testsEvents, [ + {type: 'started'}, + {type: 'finished', suite: root}, + ]); + }) + + it('reload because of fswatcher event: double touch(delete,create)', + async function() { + this.slow(200); + const newRoot = + await doAndWaitForReloadEvent(this, async () => { + suite1Watcher.sendDelete(); + suite1Watcher.sendCreate(); + suite1Watcher.sendDelete(); + suite1Watcher.sendCreate(); + }); + assert.deepStrictEqual(newRoot, root); + assert.deepStrictEqual(testsEvents, [ + {type: 'started'}, + {type: 'finished', suite: root}, + {type: 'started'}, + {type: 'finished', suite: root}, + ]); }) it('reload because of fswatcher event: test added', From b44ba942911aa61ac87970fff3c2523ddfc90403 Mon Sep 17 00:00:00 2001 From: Mate Pek Date: Thu, 25 Oct 2018 23:41:04 +0200 Subject: [PATCH 06/49] QueueGraph in progress --- src/QueueGraph.ts | 52 +++++++++ src/test/C2TestAdapter.test.ts | 76 +++++++------ src/test/FsWrapper.test.ts | 4 + src/test/Helpers.ts | 4 + src/test/QueueGraph.test.ts | 201 +++++++++++++++++++++++++++++++++ 5 files changed, 300 insertions(+), 37 deletions(-) create mode 100644 src/QueueGraph.ts create mode 100644 src/test/QueueGraph.test.ts diff --git a/src/QueueGraph.ts b/src/QueueGraph.ts new file mode 100644 index 00000000..eb90084e --- /dev/null +++ b/src/QueueGraph.ts @@ -0,0 +1,52 @@ +//----------------------------------------------------------------------------- +// 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. + +export class QueueGraphNode { + constructor( + public readonly name?: string, depends: Iterable < QueueGraphNode >= [], + private readonly _handleError?: ((reason: any) => void)) { + this._depends = [...depends]; + // TODO check circular dependency + } + + empty(): boolean { + return this._count == 0; + } + + get size(): number { + return this._count; + } + + then( + task?: (() => void|PromiseLike)|undefined|null, + taskErrorHandler?: ((reason: any) => void|PromiseLike)|undefined| + null) { + this._count++; + + const previous = this._queue; + this._queue = Promise.all(this._depends.map(v => v._queue)).then(() => { + return previous.then(task); + }); + + if (taskErrorHandler) + this._queue = this._queue.catch(taskErrorHandler); + else if (this._handleError) + this._queue = this._queue.catch(this._handleError); + this._queue = this._queue.then(() => { + this._count--; + }); + } + + dependsOn(depends: Iterable): void { + for (const dep of depends) { + this._depends.push(dep); + } + // TODO check recursion + } + + + private _count: number = 0; + private _queue: Promise = Promise.resolve(); + private readonly _depends: Array; +} \ No newline at end of file diff --git a/src/test/C2TestAdapter.test.ts b/src/test/C2TestAdapter.test.ts index b01d6ae3..50eaa3dc 100644 --- a/src/test/C2TestAdapter.test.ts +++ b/src/test/C2TestAdapter.test.ts @@ -39,7 +39,7 @@ const sinonSandbox = sinon.createSandbox(); /// describe('C2TestAdapter', function() { - this.enableTimeouts(false); // TODO + // this.enableTimeouts(false); // TODO let testsEvents: (TestLoadStartedEvent|TestLoadFinishedEvent)[] = []; let testStatesEvents: (TestRunStartedEvent|TestRunFinishedEvent| @@ -1091,21 +1091,22 @@ describe('C2TestAdapter', function() { ]); }) - it('reload because of fswatcher event: double touch(changed)', - async function() { - this.slow(200); - const newRoot = - await doAndWaitForReloadEvent(this, async () => { - suite1Watcher.sendChange(); - }); - assert.deepStrictEqual(newRoot, root); - assert.deepStrictEqual(testsEvents, [ - {type: 'started'}, - {type: 'finished', suite: root}, - {type: 'started'}, - {type: 'finished', suite: root}, - ]); - }) + // TODO + // it('reload because of fswatcher event: double touch(changed)', + // async function() { + // this.slow(200); + // const newRoot = + // await doAndWaitForReloadEvent(this, async () => { + // suite1Watcher.sendChange(); + // }); + // assert.deepStrictEqual(newRoot, root); + // assert.deepStrictEqual(testsEvents, [ + // {type: 'started'}, + // {type: 'finished', suite: root}, + // {type: 'started'}, + // {type: 'finished', suite: root}, + // ]); + // }) it('reload because of fswatcher event: touch(delete,create)', async function() { @@ -1117,29 +1118,30 @@ describe('C2TestAdapter', function() { }); assert.deepStrictEqual(newRoot, root); assert.deepStrictEqual(testsEvents, [ - {type: 'started'}, - {type: 'finished', suite: root}, - ]); + {type: 'started'}, + {type: 'finished', suite: root}, + ]); }) - it('reload because of fswatcher event: double touch(delete,create)', - async function() { - this.slow(200); - const newRoot = - await doAndWaitForReloadEvent(this, async () => { - suite1Watcher.sendDelete(); - suite1Watcher.sendCreate(); - suite1Watcher.sendDelete(); - suite1Watcher.sendCreate(); - }); - assert.deepStrictEqual(newRoot, root); - assert.deepStrictEqual(testsEvents, [ - {type: 'started'}, - {type: 'finished', suite: root}, - {type: 'started'}, - {type: 'finished', suite: root}, - ]); - }) + // TODO + // it('reload because of fswatcher event: double touch(delete,create)', + // async function() { + // this.slow(200); + // const newRoot = + // await doAndWaitForReloadEvent(this, async () => { + // suite1Watcher.sendDelete(); + // suite1Watcher.sendCreate(); + // suite1Watcher.sendDelete(); + // suite1Watcher.sendCreate(); + // }); + // assert.deepStrictEqual(newRoot, root); + // assert.deepStrictEqual(testsEvents, [ + // {type: 'started'}, + // {type: 'finished', suite: root}, + // {type: 'started'}, + // {type: 'finished', suite: root}, + // ]); + // }) it('reload because of fswatcher event: test added', async function(this: Mocha.Context) { diff --git a/src/test/FsWrapper.test.ts b/src/test/FsWrapper.test.ts index c4a9fa10..d31d50c7 100644 --- a/src/test/FsWrapper.test.ts +++ b/src/test/FsWrapper.test.ts @@ -1,3 +1,7 @@ +//----------------------------------------------------------------------------- +// 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. + import * as assert from 'assert'; import * as path from 'path'; import * as vscode from 'vscode'; diff --git a/src/test/Helpers.ts b/src/test/Helpers.ts index 8a09ed3e..f1db0124 100644 --- a/src/test/Helpers.ts +++ b/src/test/Helpers.ts @@ -1,3 +1,7 @@ +//----------------------------------------------------------------------------- +// 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. + import {EventEmitter} from 'events'; import {Stream} from 'stream'; import * as vscode from 'vscode'; diff --git a/src/test/QueueGraph.test.ts b/src/test/QueueGraph.test.ts new file mode 100644 index 00000000..6b8a7fcb --- /dev/null +++ b/src/test/QueueGraph.test.ts @@ -0,0 +1,201 @@ +//----------------------------------------------------------------------------- +// 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. + +import * as assert from 'assert'; +import {promisify} from 'util'; + +import {QueueGraphNode} from '../QueueGraph'; + +describe.only('QueueGraphNode', function() { + this.enableTimeouts(false); // TODO + + async function waitFor( + test: Mocha.Context, condition: Function, timeout: number = 1000) { + const start = Date.now(); + let c = await condition(); + while (!c && ((Date.now() - start) < timeout || !test.enableTimeouts())) { + await promisify(setTimeout)(10); + c = await condition(); + } + return c; + } + + it('promise practice 1', async function() { + let resolve: Function; + let second = false; + new Promise(r => { + resolve = r; + }).then(() => { + second = true; + }); + assert.ok(!second); + + resolve!(); + await waitFor(this, () => { + return second; + }); + assert.ok(second); + }) + + it('promise practice 2', async function() { + let resolve: Function; + let second = false; + const p = new Promise(r => { + resolve = r; + }); + assert.ok(!second); + + p.then(() => { + second = true; + }); + assert.ok(!second); + + resolve!(); + await waitFor(this, () => { + return second; + }); + assert.ok(second); + }) + + // it('promise practice 3', async function() { + // let resolve: Function; + // let second = false; + // let p = new Promise(r => { + // resolve = r; + // }); + // assert.ok(!second); + + // let third = false; + // p = p.then(() => { + // return new Promise(() => {}) + // .then(() => { + // second = true; + // }) + // .then(() => { + // third = true; + // }); + // }); + // assert.ok(!second); + + // resolve!(); + // await waitFor(this, () => { + // return second; + // }); + // assert.ok(second); + + // await waitFor(this, () => { + // return third; + // }); + // assert.ok(third); + // }) + + context('example 1', function() { + /** + * node1 <___ node + * node2 <___/ + */ + const node1 = new QueueGraphNode('node1'); + const node2 = new QueueGraphNode('node2'); + const nodeD = new QueueGraphNode('nodeD', [node1, node2]); + + it('add:depends before', async function() { + let startD: Function; + let hasRunDatOnce = false; + nodeD.then(() => { + return new Promise(r => { + startD = r; + hasRunDatOnce = true; + }); + }); + assert.equal(nodeD.size, 1); + + let start1: Function; + let hasRun1atOnce = false; + let hasRun1afterStart = false; + node1.then(() => { + return new Promise(r => { + start1 = r; + hasRun1atOnce = true; + }); + }); + assert.equal(node1.size, 1); + node1.then(() => { + hasRun1afterStart = true; + }); + assert.equal(node1.size, 2); + + let start2: Function; + let hasRun2atOnce = false; + let hasRun2afterStart = false; + node2.then(() => { + return new Promise(r => { + start2 = r; + hasRun2atOnce = true; + }); + }); + assert.equal(node2.size, 1); + node2.then(() => { + hasRun2afterStart = true; + }); + assert.equal(node2.size, 2); + + assert.ok(!hasRunDatOnce); + assert.ok(!hasRun1atOnce); + assert.ok(!hasRun2atOnce); + + await promisify(setTimeout)(20); + + assert.ok(await waitFor(this, async () => { + return hasRunDatOnce; + })); + assert.equal(nodeD.size, 1); + assert.ok(await waitFor(this, async () => { + return hasRun1atOnce; + })); + assert.equal(node1.size, 2); + assert.ok(await waitFor(this, async () => { + return hasRun2atOnce; + })); + assert.equal(node2.size, 2); + + let hasRunD2second = false; + nodeD.then(() => { + assert.ok(hasRun1afterStart); + assert.ok(hasRun2afterStart); + hasRunD2second = true; + }); + assert.equal(nodeD.size, 2); + + startD!(); + await promisify(setTimeout)(20); + + assert.ok(!hasRun1afterStart); + assert.ok(!hasRun2afterStart); + assert.ok(!hasRunD2second); + + start1!(); + await promisify(setTimeout)(20); + assert.ok(await waitFor(this, async () => { + return hasRun1afterStart; + })); + assert.equal(node1.size, 0); + assert.equal(node2.size, 2); + assert.equal(nodeD.size, 1); + assert.ok(!hasRun2afterStart); + assert.ok(!hasRunD2second); + + start2!(); + await promisify(setTimeout)(20); + assert.ok(await waitFor(this, async () => { + return hasRun2afterStart; + })); + assert.equal(node2.size, 0); + + assert.ok(await waitFor(this, async () => { + return hasRunD2second; + })); + assert.equal(nodeD.size, 0); + }) + }) +}) \ No newline at end of file From bd27babccce75270aca18e7199ce26591bde0298 Mon Sep 17 00:00:00 2001 From: Mate Pek Date: Fri, 26 Oct 2018 20:59:09 +0200 Subject: [PATCH 07/49] C2ExecutableInfo --- package.json | 2 +- src/C2AllTestSuiteInfo.ts | 22 +- src/C2ExecutableInfo.ts | 194 +++ src/C2TestAdapter.ts | 352 +--- src/C2TestSuiteInfo.ts | 6 +- src/FsWrapper.ts | 4 - src/Helpers.ts | 24 + src/test/C2TestAdapter.test.ts | 2913 +++++++++++++++++--------------- src/test/QueueGraph.test.ts | 35 +- 9 files changed, 1867 insertions(+), 1685 deletions(-) create mode 100644 src/C2ExecutableInfo.ts create mode 100644 src/Helpers.ts diff --git a/package.json b/package.json index 48b91f32..b562b706 100644 --- a/package.json +++ b/package.json @@ -83,7 +83,7 @@ ], "default": [ { - "name": "${dirname} : ${name}", + "name": "${relDirname} : ${name}", "path": "build", "regex": "(t|T)est", "recursiveRegex": false, diff --git a/src/C2AllTestSuiteInfo.ts b/src/C2AllTestSuiteInfo.ts index e4e26a52..93c8c78e 100644 --- a/src/C2AllTestSuiteInfo.ts +++ b/src/C2AllTestSuiteInfo.ts @@ -3,24 +3,32 @@ // public domain. The author hereby disclaims copyright to this source code. import {SpawnOptions} from 'child_process'; +import {inspect} from 'util'; +import * as vscode from 'vscode'; import {TestRunFinishedEvent, TestRunStartedEvent, TestSuiteInfo} from 'vscode-test-adapter-api'; +import {C2ExecutableInfo} from './C2ExecutableInfo' import {C2TestAdapter} from './C2TestAdapter'; import {C2TestInfo} from './C2TestInfo'; import {C2TestSuiteInfo} from './C2TestSuiteInfo'; import {generateUniqueId} from './IdGenerator'; import {TaskPool} from './TaskPool'; -export class C2AllTestSuiteInfo implements TestSuiteInfo { +export class C2AllTestSuiteInfo implements TestSuiteInfo, vscode.Disposable { readonly type: 'suite' = 'suite'; readonly id: string; readonly label: string = 'AllTests'; readonly children: C2TestSuiteInfo[] = []; + private readonly _executables: C2ExecutableInfo[] = []; constructor(private readonly adapter: C2TestAdapter) { this.id = generateUniqueId(); } + dispose() { + while (this._executables.length) this._executables.pop()!.dispose(); + } + removeChild(child: C2TestSuiteInfo): boolean { const i = this.children.findIndex(val => val.id == child.id); if (i != -1) { @@ -74,6 +82,18 @@ export class C2AllTestSuiteInfo implements TestSuiteInfo { }); } + async load(executables: C2ExecutableInfo[]) { + for (let i = 0; i < executables.length; i++) { + const executable = executables[i]; + try { + await executable.load(); + this._executables.push(executable); + } catch (e) { + this.adapter.log.error(inspect([e, i, executables])); + } + } + } + run(tests: string[], workerMaxNumber: number): Promise { this.adapter.testStatesEmitter.fire( {type: 'started', tests: tests}); diff --git a/src/C2ExecutableInfo.ts b/src/C2ExecutableInfo.ts new file mode 100644 index 00000000..1cc21e3d --- /dev/null +++ b/src/C2ExecutableInfo.ts @@ -0,0 +1,194 @@ +//----------------------------------------------------------------------------- +// 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. + +import * as path from 'path'; +import {inspect, promisify} from 'util'; +import * as vscode from 'vscode'; + +import {C2AllTestSuiteInfo} from './C2AllTestSuiteInfo'; +import {C2TestAdapter} from './C2TestAdapter'; +import {C2TestSuiteInfo} from './C2TestSuiteInfo'; +import * as c2fs from './FsWrapper'; +import {resolveVariables} from './Helpers'; + + +export class C2ExecutableInfo implements vscode.Disposable { + constructor( + private _adapter: C2TestAdapter, + private readonly _allTests: C2AllTestSuiteInfo, + public readonly name: string, public readonly pattern: string, + public readonly cwd: string, public readonly env: {[prop: string]: any}) { + } + + private _disposables: vscode.Disposable[] = []; + private _watcher: vscode.FileSystemWatcher|undefined = undefined; + + private readonly _executables: Map = + new Map(); + + dispose() { + if (this._watcher) this._watcher.dispose(); + while (this._disposables.length) this._disposables.pop()!.dispose(); + } + + async load(): Promise { + try { + if (this.pattern.startsWith(this._adapter.workspaceFolder.uri + .path)) { // TODO ez az if nem a legjobb + this._watcher = vscode.workspace.createFileSystemWatcher( + this.pattern, false, false, false); + this._disposables.push(this._watcher); + this._disposables.push( + this._watcher.onDidCreate(this._handleCreate, this)); + this._disposables.push( + this._watcher.onDidChange(this._handleChange, this)); + this._disposables.push( + this._watcher.onDidDelete(this._handleDelete, this)); + } + } catch (e) { + this._adapter.log.error(inspect([e, this])); + } + + let fileUris; + try { + fileUris = await vscode.workspace.findFiles(this.pattern); + } catch (e) { + fileUris = [vscode.Uri.file(this.pattern)]; + debugger; + } + + for (let i = 0; i < fileUris.length; i++) { + const file = fileUris[i]; + if (await this._verifyIsCatch2TestExecutable(file.path)) { + let resolvedName = this.name; + let resolvedCwd = this.cwd; + try { + const varToValue: [string, string][] = [ + ['${name}', file.path], + ['${basename}', path.basename(file.path)], + ['${absDirname}', path.dirname(file.path)], + [ + '${relDirname}', + path.dirname(path.relative( + this._adapter.workspaceFolder.uri.fsPath, file.path)) + ], + ...this._adapter.variableToValue, + ]; + resolvedName = resolveVariables(this.name, varToValue); + resolvedCwd = path.normalize(resolveVariables(this.cwd, varToValue)); + } catch (e) { + this._adapter.log.error(inspect([e, this])); + } + + const suite = this._allTests.createChildSuite( + resolvedName, file.path, {cwd: resolvedCwd, env: this.env}); + + this._executables.set(file.path, suite); + } + } + + this._uniquifySuiteNames(); + + for (const suite of this._executables.values()) { + await suite.reloadChildren(); + } + } + + private _handleEverything(uri: vscode.Uri) { + let suite = this._executables.get(uri.path); + + if (suite == undefined) { + suite = this._allTests.createChildSuite( + this.name, this.pattern, {cwd: this.cwd, env: this.env}); + this._executables.set(uri.path, suite); + } + + const x = + (exists: boolean, startTime: number, timeout: number, + delay: number): Promise => { + if ((Date.now() - startTime) > timeout) { + this._executables.delete(uri.path); + this._adapter.testsEmitter.fire({type: 'started'}); + this._allTests.removeChild(suite!); + this._adapter.testsEmitter.fire( + {type: 'finished', suite: this._allTests}); + return Promise.resolve(); + } else if (exists) { + this._adapter.testsEmitter.fire({type: 'started'}); + return suite!.reloadChildren().then( + () => { + this._adapter.testsEmitter.fire( + {type: 'finished', suite: this._allTests}); + }, + (err: any) => { + this._adapter.testsEmitter.fire( + {type: 'finished', suite: this._allTests}); + this._adapter.log.warn(inspect(err)); + return x( + false, startTime, timeout, Math.min(delay * 2, 2000)); + }); + } + return promisify(setTimeout)(Math.min(delay * 2, 2000)).then(() => { + return c2fs.existsAsync(uri.path).then((exists: boolean) => { + return x(exists, startTime, timeout, Math.min(delay * 2, 2000)); + }); + }); + }; + // change event can arrive during debug session on osx (why?) + // if (!this.isDebugging) { + // TODO filter multiple events and dont mess with 'load' + x(false, Date.now(), this._adapter.getExecWatchTimeout(), 64); + //} + } + + private _handleCreate(uri: vscode.Uri) { + if (this._executables.has(uri.path)) { + // we are fine: the delete event is still running until it's timeout + return; + } + + return this._handleEverything(uri); + } + + private _handleChange(uri: vscode.Uri) { + return this._handleEverything(uri); + } + + private _handleDelete(uri: vscode.Uri) { + return this._handleEverything(uri); + } + + private _uniquifySuiteNames() { + const uniqueNames: Map = new Map(); + + for (const suite of this._executables.values()) { + const suites = uniqueNames.get(suite.origLabel); + if (suites) { + suites.push(suite); + } else { + uniqueNames.set(suite.origLabel, [suite]); + } + } + + for (const suites of uniqueNames.values()) { + if (suites.length > 1) { + let i = 1; + for (const suite of suites) { + suite.label = suite.origLabel + '(' + String(i++) + ')'; + } + } + } + } + + private _verifyIsCatch2TestExecutable(path: string): Promise { + return c2fs.spawnAsync(path, ['--help']) + .then((res) => { + return res.stdout.indexOf('Catch v2.') != -1; + }) + .catch((e) => { + this._adapter.log.error(inspect(e)); + return false; + }); + } +} \ No newline at end of file diff --git a/src/C2TestAdapter.ts b/src/C2TestAdapter.ts index 2ffbcc68..f885f4af 100644 --- a/src/C2TestAdapter.ts +++ b/src/C2TestAdapter.ts @@ -3,42 +3,31 @@ // public domain. The author hereby disclaims copyright to this source code. import * as path from 'path'; -import {inspect, promisify} from 'util'; +import {inspect} from 'util'; import * as vscode from 'vscode'; import {TestAdapter, TestEvent, TestLoadFinishedEvent, TestLoadStartedEvent, TestRunFinishedEvent, TestRunStartedEvent, TestSuiteEvent} from 'vscode-test-adapter-api'; import * as util from 'vscode-test-adapter-util'; import {C2AllTestSuiteInfo} from './C2AllTestSuiteInfo'; +import {C2ExecutableInfo} from './C2ExecutableInfo'; import {C2TestInfo} from './C2TestInfo'; -import * as c2fs from './FsWrapper'; +import {resolveVariables} from './Helpers'; export class C2TestAdapter implements TestAdapter, vscode.Disposable { - private readonly testsEmitter = + readonly testsEmitter = new vscode.EventEmitter(); readonly testStatesEmitter = new vscode.EventEmitter(); private readonly autorunEmitter = new vscode.EventEmitter(); - private readonly watchers: Map = new Map(); - private isRunning: number = 0; - private isDebugging: boolean = false; + readonly variableToValue: [string, string][] = + [['${workspaceFolder}', this.workspaceFolder.uri.fsPath]]; private allTests: C2AllTestSuiteInfo; private readonly disposables: Array = new Array(); private isEnabledSourceDecoration = true; - private rngSeedStr: string|number|null = null; - private readonly variableResolvedPair: [string, string][] = - [['${workspaceFolder}', this.workspaceFolder.uri.fsPath]]; - - getIsEnabledSourceDecoration(): boolean { - return this.isEnabledSourceDecoration; - } - - getRngSeed(): string|number|null { - return this.rngSeedStr; - } constructor( public readonly workspaceFolder: vscode.WorkspaceFolder, @@ -70,7 +59,6 @@ export class C2TestAdapter implements TestAdapter, vscode.Disposable { if (configChange.affectsConfiguration( 'catch2TestExplorer.defaultRngSeed', this.workspaceFolder.uri)) { - this.rngSeedStr = this.getDefaultRngSeed(this.getConfiguration()); this.autorunEmitter.fire(); } })); @@ -82,9 +70,7 @@ export class C2TestAdapter implements TestAdapter, vscode.Disposable { this.disposables.forEach(d => { d.dispose(); }); - this.watchers.forEach((v) => { - v.dispose(); - }); + this.allTests.dispose(); } get testStates(): vscode.Event { - const suite = this.allTests.createChildSuite( - exe.name, exe.path, {cwd: exe.cwd, env: exe.env}); - - let watcher = this.watchers.get(suite.execPath); - - if (watcher === undefined && - suite.execPath.startsWith(this.workspaceFolder.uri.path)) { - try { - const watcher = vscode.workspace.createFileSystemWatcher( - suite.execPath, true, false, false); - this.watchers.set(suite.execPath, watcher); - const allTests = this.allTests; // alltest may has changed - - const handler = (uri: vscode.Uri) => { - const x = - (exists: boolean, startTime: number, timeout: number, - delay: number): Promise => { - if ((Date.now() - startTime) > timeout) { - watcher!.dispose(); - this.watchers.delete(suite.execPath); - this.testsEmitter.fire({type: 'started'}); - allTests.removeChild(suite); - this.testsEmitter.fire( - {type: 'finished', suite: this.allTests}); - return Promise.resolve(); - } else if (exists) { - this.testsEmitter.fire({type: 'started'}); - return suite.reloadChildren().then( - () => { - this.testsEmitter.fire( - {type: 'finished', suite: this.allTests}); - }, - (err: any) => { - this.testsEmitter.fire( - {type: 'finished', suite: this.allTests}); - this.log.warn(inspect(err)); - return x( - false, startTime, timeout, - Math.min(delay * 2, 2000)); - }); - } - return promisify(setTimeout)(Math.min(delay * 2, 2000)) - .then(() => { - return c2fs.existsAsync(suite.execPath) - .then((exists: boolean) => { - return x( - exists, startTime, timeout, - Math.min(delay * 2, 2000)); - }); - }); - }; - - // change event can arrive during debug session on osx (why?) - if (!this.isDebugging) { - // TODO filter multiple events and dont mess with 'load' - x(false, Date.now(), - this.getDefaultExecWatchTimeout(this.getConfiguration()), 64); - } - }; - this.disposables.push(watcher.onDidChange(handler)); // TODO not nice - this.disposables.push(watcher.onDidDelete(handler)); - } catch (e) { - this.log.warn('watcher couldn\'t watch: ' + suite.execPath); - } - } - return suite.reloadChildren().catch((err: any) => { - this.log.warn(inspect(err)); - this.allTests.removeChild(suite); - }); + getIsEnabledSourceDecoration(): boolean { + return this.isEnabledSourceDecoration; } - load(): Promise { - this.cancel(); + getRngSeed(): string|number|null { + return this.getDefaultRngSeed(this.getConfiguration()); + } - this.testsEmitter.fire({type: 'started'}); + getExecWatchTimeout(): number { + return this.getDefaultExecWatchTimeout(this.getConfiguration()); + } - this.watchers.forEach((value, key) => { - value.dispose(); - }); - this.watchers.clear(); + private isDebugging: boolean = false; + private isRunning: number = 0; - const config = this.getConfiguration(); - this.rngSeedStr = this.getDefaultRngSeed(config); + async load(): Promise { + try { + this.cancel(); - this.allTests = new C2AllTestSuiteInfo(this); + this.allTests.dispose(); + this.allTests = new C2AllTestSuiteInfo(this); - const executables = this.getExecutables(config); + this.testsEmitter.fire({type: 'started'}); - return executables - .then((execs: ExecutableConfig[]) => { - let testListReaders = Promise.resolve(); + const config = this.getConfiguration(); - for (let i = 0; i < execs.length; i++) { - testListReaders = testListReaders.then(() => { - return this.loadSuite(execs[i]).catch((err) => { - this.log.error(inspect(err)); - debugger; - }); - }) - } + await this.allTests.load(this.getExecutables(config, this.allTests)); - return testListReaders; - }) - .then( - () => { - this.testsEmitter.fire({type: 'finished', suite: this.allTests}); - }, - (err: Error) => { - this.testsEmitter.fire({ - type: 'finished', - suite: undefined, - errorMessage: err.message - }); - }); + this.testsEmitter.fire({type: 'finished', suite: this.allTests}); + } catch (e) { + this.testsEmitter.fire( + {type: 'finished', suite: undefined, errorMessage: e.message}); + } } cancel(): void { @@ -268,7 +172,7 @@ export class C2TestAdapter implements TestAdapter, vscode.Disposable { const template = this.getDebugConfigurationTemplate(this.getConfiguration()); - let resolveDebugVariables: [string, any][] = this.variableResolvedPair; + let resolveDebugVariables: [string, any][] = this.variableToValue; resolveDebugVariables = resolveDebugVariables.concat([ ['${label}', testInfo.label], ['${exec}', testInfo.parent.execPath], [ @@ -283,7 +187,7 @@ export class C2TestAdapter implements TestAdapter, vscode.Disposable { for (const prop in template) { const val = template[prop]; if (val !== undefined && val !== null) { - debug[prop] = this.resolveVariables(val, resolveDebugVariables); + debug[prop] = resolveVariables(val, resolveDebugVariables); } } @@ -348,8 +252,7 @@ export class C2TestAdapter implements TestAdapter, vscode.Disposable { if (val === undefined || val === null) { delete result.prop; } else { - result[prop] = - this.resolveVariables(String(val), this.variableResolvedPair); + result[prop] = resolveVariables(String(val), this.variableToValue); } } return result; @@ -368,8 +271,7 @@ export class C2TestAdapter implements TestAdapter, vscode.Disposable { if (val === undefined || val === null) { delete resultEnv.prop; } else { - resultEnv[prop] = - this.resolveVariables(String(val), this.variableResolvedPair); + resultEnv[prop] = resolveVariables(String(val), this.variableToValue); } } @@ -378,8 +280,8 @@ export class C2TestAdapter implements TestAdapter, vscode.Disposable { private getDefaultCwd(config: vscode.WorkspaceConfiguration): string { const dirname = this.workspaceFolder.uri.fsPath; - const cwd = this.resolveVariables( - config.get('defaultCwd', dirname), this.variableResolvedPair); + const cwd = resolveVariables( + config.get('defaultCwd', dirname), this.variableToValue); if (path.isAbsolute(cwd)) { return cwd; } else { @@ -392,8 +294,7 @@ export class C2TestAdapter implements TestAdapter, vscode.Disposable { return config.get('defaultRngSeed', null); } - private getDefaultExecWatchTimeout(config: vscode.WorkspaceConfiguration): - number { + getDefaultExecWatchTimeout(config: vscode.WorkspaceConfiguration): number { return config.get('defaultExecWatchTimeout', 10000); } @@ -412,8 +313,7 @@ export class C2TestAdapter implements TestAdapter, vscode.Disposable { if (val === undefined || val === null) { delete resultEnv.prop; } else { - resultEnv[prop] = - this.resolveVariables(String(val), this.variableResolvedPair); + resultEnv[prop] = resolveVariables(String(val), this.variableToValue); } } @@ -425,11 +325,12 @@ export class C2TestAdapter implements TestAdapter, vscode.Disposable { return config.get('enableSourceDecoration', true); } - private async getExecutables(config: vscode.WorkspaceConfiguration): - Promise { + private getExecutables( + config: vscode.WorkspaceConfiguration, + allTests: C2AllTestSuiteInfo): C2ExecutableInfo[] { const globalWorkingDirectory = this.getDefaultCwd(config); - let executables: ExecutableConfig[] = []; + let executables: C2ExecutableInfo[] = []; const configExecs:|undefined|string|string[]|{[prop: string]: any}| {[prop: string]: any}[] = config.get('executables'); @@ -438,158 +339,62 @@ export class C2TestAdapter implements TestAdapter, vscode.Disposable { return path.isAbsolute(p) ? p : this.resolveRelPath(p); }; - const addObject = async(o: Object): Promise => { - const name: string = - o.hasOwnProperty('name') ? (o)['name'] : '${dirname} : ${name}'; + const createFromObject = (o: Object): C2ExecutableInfo => { + const name: string = o.hasOwnProperty('name') ? (o)['name'] : + '${relDirname} : ${name}'; if (!o.hasOwnProperty('path') || (o)['path'] === null) { console.warn(Error('\'path\' is a requireds property.')); - return; + throw Error('Wrong object: ' + inspect(o)); } - const p: string = fullPath( - this.resolveVariables((o)['path'], this.variableResolvedPair)); - const regex: string = o.hasOwnProperty('regex') ? (o)['regex'] : ''; + const p: string = + fullPath(resolveVariables((o)['path'], this.variableToValue)); const cwd: string = o.hasOwnProperty('cwd') ? (o)['cwd'] : globalWorkingDirectory; const env: {[prop: string]: any} = o.hasOwnProperty('env') ? this.getGlobalAndCurrentEnvironmentVariables( config, (o)['env']) : this.getGlobalAndDefaultEnvironmentVariables(config); - const regexRecursive: boolean = o.hasOwnProperty('recursiveRegex') ? - (o)['recursiveRegex'] : - false; - - if (regex.length > 0) { - const recursiveAdd = async(directory: string): Promise => { - const children = c2fs.readdirSync(directory); - for (let i = 0; i < children.length; ++i) { - const child = children[i]; - const childPath = path.resolve(directory, child); - const childStat = await c2fs.statAsync(childPath); - if (childPath.match(regex) && childStat.isFile()) { - let resolvedName = name + ' : ' + child; - let resolvedCwd = cwd; - try { - resolvedName = this.resolveVariables(name, [ - ['${absDirname}', p], - [ - '${dirname}', - path.relative(this.workspaceFolder.uri.fsPath, p) - ], - ['${name}', child] - ]); - resolvedCwd = this.resolveVariables( - cwd, this.variableResolvedPair.concat([ - ['${absDirname}', p], - [ - '${dirname}', - path.relative(this.workspaceFolder.uri.fsPath, p) - ] - ])); - } catch (e) { - } - executables.push(new ExecutableConfig( - resolvedName, childPath, regex, fullPath(resolvedCwd), env)); - } else if (childStat.isDirectory() && regexRecursive) { - await recursiveAdd(childPath); - } - } - }; - try { - const stat = await c2fs.statAsync(p); - if (stat.isDirectory()) { - await recursiveAdd(p); - } else if (stat.isFile()) { - executables.push(new ExecutableConfig(name, p, regex, cwd, env)); - } else { - // do nothing - } - } catch (e) { - this.log.error(e.message); - } - } else { - executables.push(new ExecutableConfig(name, p, regex, cwd, env)); - } + + return new C2ExecutableInfo(this, allTests, name, p, cwd, env); }; if (typeof configExecs === 'string') { - if (configExecs.length == 0) return Promise.resolve([]); - executables.push(new ExecutableConfig( - configExecs, - fullPath( - this.resolveVariables(configExecs, this.variableResolvedPair)), - '', globalWorkingDirectory, [])); + if (configExecs.length == 0) return []; + executables.push(new C2ExecutableInfo( + this, allTests, configExecs, + fullPath(resolveVariables(configExecs, this.variableToValue)), + globalWorkingDirectory, {})); } else if (Array.isArray(configExecs)) { for (var i = 0; i < configExecs.length; ++i) { - if (typeof configExecs[i] == 'string') { - const configExecsStr = String(configExecs[i]); - if (configExecsStr.length > 0) { - executables.push(new ExecutableConfig( - this.resolveVariables( - configExecsStr, this.variableResolvedPair), - fullPath(this.resolveVariables( - configExecsStr, this.variableResolvedPair)), - '', globalWorkingDirectory, [])); + const configExe = configExecs[i]; + if (typeof configExe == 'string') { + const configExecsName = String(configExe); + if (configExecsName.length > 0) { + const resolvedName = + resolveVariables(configExecsName, this.variableToValue); + executables.push(new C2ExecutableInfo( + this, allTests, resolvedName, fullPath(resolvedName), + globalWorkingDirectory, {})); } } else { - await addObject(configExecs[i]); + try { + executables.push(createFromObject(configExe)); + } catch (e) { + this.log.error(inspect(e)); + } } } } else if (configExecs instanceof Object) { - await addObject(configExecs); + try { + executables.push(createFromObject(configExecs)); + } catch (e) { + this.log.error(inspect(e)); + } } else { throw 'Catch2 config error: wrong type: executables'; } - return this.filterVerifiedCatch2TestExecutables(await executables); - } - - verifyIsCatch2TestExecutable(path: string): Promise { - return c2fs.spawnAsync(path, ['--help']) - .then((res) => { - return res.stdout.indexOf('Catch v2.') != -1; - }) - .catch((e) => { - this.log.error(inspect(e)); - return false; - }); - } - - private filterVerifiedCatch2TestExecutables(executables: ExecutableConfig[]): - Promise { - const verified: ExecutableConfig[] = []; - const promises: Promise[] = []; - - executables.forEach(exec => { - promises.push(this.verifyIsCatch2TestExecutable(exec.path).then( - (isCatch2: boolean) => { - if (isCatch2) verified.push(exec); - })); - }); - - return Promise.all(promises).then(() => { - return verified; - }); - } - - private resolveVariables(value: any, varValue: [string, any][]): any { - if (typeof value == 'string') { - for (let i = 0; i < varValue.length; ++i) { - if (value === varValue[i][0] && typeof varValue[i][1] != 'string') { - return varValue[i][1]; - } - value = value.replace(varValue[i][0], varValue[i][1]); - } - return value; - } else if (Array.isArray(value)) { - return (value).map((v: any) => this.resolveVariables(v, varValue)); - } else if (typeof value == 'object') { - const newValue: any = {}; - for (const prop in value) { - newValue[prop] = this.resolveVariables(value[prop], varValue); - } - return newValue; - } - return value; + return executables; } private resolveRelPath(relPath: string): string { @@ -597,10 +402,3 @@ export class C2TestAdapter implements TestAdapter, vscode.Disposable { return path.resolve(dirname, relPath); } } - -class ExecutableConfig { - constructor( - public readonly name: string, public readonly path: string, - public readonly regex: string, public readonly cwd: string, - public readonly env: {[prop: string]: any}) {} -} diff --git a/src/C2TestSuiteInfo.ts b/src/C2TestSuiteInfo.ts index 31ff7bfd..ab7a986d 100644 --- a/src/C2TestSuiteInfo.ts +++ b/src/C2TestSuiteInfo.ts @@ -16,6 +16,7 @@ import {TaskPool} from './TaskPool'; export class C2TestSuiteInfo implements TestSuiteInfo { readonly type: 'suite' = 'suite'; readonly id: string; + label: string; children: C2TestInfo[] = []; file?: string = undefined; line?: number = undefined; @@ -24,9 +25,10 @@ export class C2TestSuiteInfo implements TestSuiteInfo { private proc: ChildProcess|undefined = undefined; constructor( - public readonly label: string, private readonly adapter: C2TestAdapter, - public readonly execPath: string, + public readonly origLabel: string, + private readonly adapter: C2TestAdapter, public readonly execPath: string, public readonly execOptions: SpawnOptions) { + this.label = origLabel; this.id = generateUniqueId(); } diff --git a/src/FsWrapper.ts b/src/FsWrapper.ts index 5550d13e..ab8047fe 100644 --- a/src/FsWrapper.ts +++ b/src/FsWrapper.ts @@ -63,8 +63,4 @@ export function existsAsync(path: string): Promise { export function existsSync(path: string): boolean { return fs.existsSync(path); -} - -export function readdirSync(path: string): string[] { - return fs.readdirSync(path, 'utf8'); } \ No newline at end of file diff --git a/src/Helpers.ts b/src/Helpers.ts new file mode 100644 index 00000000..8b965c69 --- /dev/null +++ b/src/Helpers.ts @@ -0,0 +1,24 @@ +//----------------------------------------------------------------------------- +// 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. + +export function resolveVariables(value: any, varValue: [string, any][]): any { + if (typeof value == 'string') { + for (let i = 0; i < varValue.length; ++i) { + if (value === varValue[i][0] && typeof varValue[i][1] != 'string') { + return varValue[i][1]; + } + value = value.replace(varValue[i][0], varValue[i][1]); + } + return value; + } else if (Array.isArray(value)) { + return (value).map((v: any) => resolveVariables(v, varValue)); + } else if (typeof value == 'object') { + const newValue: any = {}; + for (const prop in value) { + newValue[prop] = resolveVariables(value[prop], varValue); + } + return newValue; + } + return value; +} \ No newline at end of file diff --git a/src/test/C2TestAdapter.test.ts b/src/test/C2TestAdapter.test.ts index 50eaa3dc..80df5786 100644 --- a/src/test/C2TestAdapter.test.ts +++ b/src/test/C2TestAdapter.test.ts @@ -18,7 +18,6 @@ import {inspect, promisify} from 'util'; import {C2TestAdapter} from '../C2TestAdapter'; import {example1} from './example1'; import {ChildProcessStub, FileSystemWatcherStub} from './Helpers'; -import * as c2fs from '../FsWrapper'; import * as Mocha from 'mocha'; assert.notEqual(vscode.workspace.workspaceFolders, undefined); @@ -38,721 +37,514 @@ const sinonSandbox = sinon.createSandbox(); /// -describe('C2TestAdapter', function() { - // this.enableTimeouts(false); // TODO - - let testsEvents: (TestLoadStartedEvent|TestLoadFinishedEvent)[] = []; - let testStatesEvents: (TestRunStartedEvent|TestRunFinishedEvent| - TestSuiteEvent|TestEvent)[] = []; - - function getConfig() { - return vscode.workspace.getConfiguration( - 'catch2TestExplorer', workspaceFolderUri) - }; - - async function updateConfig(key: string, value: any) { - let count = testsEvents.length; - await getConfig().update(key, value); - // cleanup - while (testsEvents.length < count--) testsEvents.pop(); - } - - let adapter: C2TestAdapter|undefined; - let testsEventsConnection: vscode.Disposable|undefined; - let testStatesEventsConnection: vscode.Disposable|undefined; - - let spawnStub: sinon.SinonStub; - let vsfsWatchStub: sinon.SinonStub; - let c2fsStatStub: sinon.SinonStub; - let c2fsReaddirSyncStub: sinon.SinonStub; - - function resetConfig(): Thenable { - const packageJson = fse.readJSONSync( - path.join(workspaceFolderUri.path, '../..', 'package.json')); - const properties: {[prop: string]: any}[] = - packageJson['contributes']['configuration']['properties']; - let t: Thenable = Promise.resolve(); - Object.keys(properties).forEach(key => { - assert.ok(key.startsWith('catch2TestExplorer.')); - const k = key.replace('catch2TestExplorer.', '') - t = t.then(function() { - return getConfig().update(k, undefined); - }); - }); - return t; - } - - function testStatesEvI(o: any) { - const i = testStatesEvents.findIndex( - (v: TestRunStartedEvent|TestRunFinishedEvent|TestSuiteEvent| - TestEvent) => { - if (o.type == v.type) - if (o.type == 'suite' || o.type == 'test') - return o.state === (v).state && - o[o.type] === (v)[v.type]; - return deepStrictEqual(o, v); - }); - assert.notEqual( - i, -1, - 'testStatesEvI failed to find: ' + inspect(o) + '\n\nin\n\n' + - inspect(testStatesEvents)); - assert.deepStrictEqual(testStatesEvents[i], o); - return i; - }; - - function createAdapterAndSubscribe() { - adapter = new C2TestAdapter(workspaceFolder, logger); - - testsEventsConnection = - adapter.tests((e: TestLoadStartedEvent|TestLoadFinishedEvent) => { - testsEvents.push(e); - }); +describe( + 'C2TestAdapter', + function() { + this.enableTimeouts(false); // TODO + + let testsEvents: (TestLoadStartedEvent|TestLoadFinishedEvent)[] = []; + let testStatesEvents: (TestRunStartedEvent|TestRunFinishedEvent| + TestSuiteEvent|TestEvent)[] = []; + + function getConfig() { + return vscode.workspace.getConfiguration( + 'catch2TestExplorer', workspaceFolderUri) + }; + + async function updateConfig(key: string, value: any) { + let count = testsEvents.length; + await getConfig().update(key, value); + // cleanup + while (testsEvents.length < count--) testsEvents.pop(); + } - testStatesEvents = []; - testStatesEventsConnection = adapter.testStates( - (e: TestRunStartedEvent|TestRunFinishedEvent|TestSuiteEvent| - TestEvent) => { - testStatesEvents.push(e); + let adapter: C2TestAdapter|undefined; + let testsEventsConnection: vscode.Disposable|undefined; + let testStatesEventsConnection: vscode.Disposable|undefined; + + let spawnStub: sinon.SinonStub; + let vsfsWatchStub: sinon.SinonStub; + let c2fsStatStub: sinon.SinonStub; + let vsFindFilesStub: sinon.SinonStub; + + function resetConfig(): Thenable { + const packageJson = fse.readJSONSync( + path.join(workspaceFolderUri.path, '../..', 'package.json')); + const properties: {[prop: string]: any}[] = + packageJson['contributes']['configuration']['properties']; + let t: Thenable = Promise.resolve(); + Object.keys(properties).forEach(key => { + assert.ok(key.startsWith('catch2TestExplorer.')); + const k = key.replace('catch2TestExplorer.', '') + t = t.then(function() { + return getConfig().update(k, undefined); + }); }); - - return adapter!; - } - - async function doAndWaitForReloadEvent( - test: Mocha.Context, action: Function, - timeout: number = 1000): Promise { - const origCount = testsEvents.length; - await action(); - const start = Date.now(); - while (testsEvents.length != origCount + 2 && - ((Date.now() - start) < timeout || !test.enableTimeouts())) - await promisify(setTimeout)(10); - assert.equal(testsEvents.length, origCount + 2); - const e = testsEvents[testsEvents.length - 1]!; - assert.equal(e.type, 'finished'); - assert.ok(e.suite != undefined); - return e.suite!; - } - - function disposeAdapterAndSubscribers(check: boolean = true) { - adapter && adapter.dispose(); - testsEventsConnection && testsEventsConnection.dispose(); - testStatesEventsConnection && testStatesEventsConnection.dispose(); - testStatesEvents = []; - if (check) { - for (let i = 0; i < testsEvents.length; i++) { - assert.deepStrictEqual( - {type: 'started'}, testsEvents[i], - inspect({index: i, testsEvents: testsEvents})); - i++; - assert.ok( - i < testsEvents.length, - inspect({index: i, testsEvents: testsEvents})); - assert.equal( - testsEvents[i].type, 'finished', - inspect({index: i, testsEvents: testsEvents})); - assert.ok( - (testsEvents[i]).suite, - inspect({index: i, testsEvents: testsEvents})); + return t; } - } - testsEvents = []; - } - - function stubsResetToMyDefault() { - spawnStub.reset(); - spawnStub.callThrough(); - vsfsWatchStub.reset(); - vsfsWatchStub.callThrough(); - c2fsStatStub.reset(); - c2fsStatStub.callThrough(); - c2fsReaddirSyncStub.reset(); - c2fsReaddirSyncStub.throws('Test isnt set properly error.'); - } - - before(function() { - fse.removeSync(dotVscodePath); - adapter = undefined; - - spawnStub = sinonSandbox.stub(child_process, 'spawn').named('spawnStub'); - vsfsWatchStub = - sinonSandbox.stub(vscode.workspace, 'createFileSystemWatcher') - .named('vscode.createFileSystemWatcher'); - c2fsStatStub = sinonSandbox.stub(fs, 'stat').named('fsStat'); - c2fsReaddirSyncStub = - sinonSandbox.stub(c2fs, 'readdirSync').named('c2fsReaddirSyncStub'); - - stubsResetToMyDefault(); - - // reset config can cause problem with fse.removeSync(dotVscodePath); - return resetConfig(); - }); - - after(function() { - disposeAdapterAndSubscribers(); - sinonSandbox.restore(); - }); - - describe('detect config change', function() { - this.slow(150); - - let adapter: C2TestAdapter; - - before(function() { - adapter = createAdapterAndSubscribe(); - assert.deepStrictEqual(testsEvents, []); - }) - after(function() { - disposeAdapterAndSubscribers(); - return resetConfig(); - }) - - it('defaultEnv', function() { - return doAndWaitForReloadEvent(this, () => { - return updateConfig('defaultEnv', {'APPLE': 'apple'}); - }); - }) - - it('defaultCwd', function() { - return doAndWaitForReloadEvent(this, () => { - return updateConfig('defaultCwd', 'apple/peach'); - }); - }) + function testStatesEvI(o: any) { + const i = testStatesEvents.findIndex( + (v: TestRunStartedEvent|TestRunFinishedEvent|TestSuiteEvent| + TestEvent) => { + if (o.type == v.type) + if (o.type == 'suite' || o.type == 'test') + return o.state === (v).state && + o[o.type] === (v)[v.type]; + return deepStrictEqual(o, v); + }); + assert.notEqual( + i, -1, + 'testStatesEvI failed to find: ' + inspect(o) + '\n\nin\n\n' + + inspect(testStatesEvents)); + assert.deepStrictEqual(testStatesEvents[i], o); + return i; + }; + + function createAdapterAndSubscribe() { + adapter = new C2TestAdapter(workspaceFolder, logger); + + testsEventsConnection = + adapter.tests((e: TestLoadStartedEvent|TestLoadFinishedEvent) => { + testsEvents.push(e); + }); - it('enableSourceDecoration', function() { - return updateConfig('enableSourceDecoration', false).then(function() { - assert.ok(!adapter.getIsEnabledSourceDecoration()); - }); - }) + testStatesEvents = []; + testStatesEventsConnection = adapter.testStates( + (e: TestRunStartedEvent|TestRunFinishedEvent|TestSuiteEvent| + TestEvent) => { + testStatesEvents.push(e); + }); - it('defaultRngSeed', function() { - return updateConfig('defaultRngSeed', 987).then(function() { - assert.equal(adapter.getRngSeed(), 987); - }); - }) - }) - - it('load with empty config', async function() { - const adapter = createAdapterAndSubscribe(); - await adapter.load(); - assert.equal(testsEvents.length, 2); - assert.equal(testsEvents[0].type, 'started'); - assert.equal(testsEvents[1].type, 'finished'); - const suite = (testsEvents[1]).suite; - assert.notEqual(suite, undefined); - assert.equal(suite!.children.length, 0); - disposeAdapterAndSubscribers(); - }) - - context('example1', function() { - const watchers: Map = new Map(); - - function handleCreateWatcherCb( - path: string, ignoreCreateEvents: boolean, ignoreChangeEvents: boolean, - ignoreDeleteEvents: boolean) { - const e = new FileSystemWatcherStub( - vscode.Uri.file(path), ignoreCreateEvents, ignoreChangeEvents, - ignoreDeleteEvents); - watchers.set(path, e); - return e; - } - - function handleStatExistsFile( - path: string, - cb: (err: NodeJS.ErrnoException|null, stats: fs.Stats|undefined) => - void) { - cb(null, { - isFile() { - return true; - }, - isDirectory() { - return false; - } - }); - } - - function handleStatNotExists( - path: string, - cb: (err: NodeJS.ErrnoException|null|any, stats: fs.Stats|undefined) => - void) { - cb({ - code: 'ENOENT', - errno: -2, - message: 'ENOENT', - path: path, - syscall: 'stat' - }, - undefined); - }; - - before(function() { - for (let suite of example1.outputs) { - for (let scenario of suite[1]) { - spawnStub.withArgs(suite[0], scenario[0]).callsFake(function() { - return new ChildProcessStub(scenario[1]); - }); - } + return adapter!; + } - c2fsStatStub.withArgs(suite[0]).callsFake(handleStatExistsFile); + async function waitFor( + test: Mocha.Context, condition: Function, + timeout: number = 1000): Promise { + const start = Date.now(); + let c = await condition(); + while (!(c = await condition()) && + ((Date.now() - start) < timeout || !test.enableTimeouts())) + await promisify(setTimeout)(10); + assert.ok(c); + } - vsfsWatchStub.withArgs(suite[0], true, false, false) - .callsFake(handleCreateWatcherCb); + async function doAndWaitForReloadEvent( + test: Mocha.Context, action: Function, + timeout: number = 1000): Promise { + const origCount = testsEvents.length; + await action(); + await waitFor(test, () => { + return testsEvents.length == origCount + 2; + }, timeout); + assert.equal(testsEvents.length, origCount + 2); + const e = testsEvents[testsEvents.length - 1]!; + assert.equal(e.type, 'finished'); + assert.ok(e.suite != undefined); + return e.suite!; } - const dirContent: Map = new Map(); - for (let p of example1.outputs) { - const parent = path.dirname(p[0]); - let children: string[] = []; - if (dirContent.has(parent)) - children = dirContent.get(parent)!; - else { - dirContent.set(parent, children); + function disposeAdapterAndSubscribers(check: boolean = true) { + adapter && adapter.dispose(); + testsEventsConnection && testsEventsConnection.dispose(); + testStatesEventsConnection && testStatesEventsConnection.dispose(); + testStatesEvents = []; + if (check) { + for (let i = 0; i < testsEvents.length; i++) { + assert.deepStrictEqual( + {type: 'started'}, testsEvents[i], + inspect({index: i, testsEvents: testsEvents})); + i++; + assert.ok( + i < testsEvents.length, + inspect({index: i, testsEvents: testsEvents})); + assert.equal( + testsEvents[i].type, 'finished', + inspect({index: i, testsEvents: testsEvents})); + assert.ok( + (testsEvents[i]).suite, + inspect({index: i, testsEvents: testsEvents})); + } } - children.push(path.basename(p[0])); + testsEvents = []; } - dirContent.forEach((v: string[], k: string) => { - c2fsReaddirSyncStub.withArgs(k).returns(v); - }); - }) + function stubsResetToMyDefault() { + spawnStub.reset(); + spawnStub.callThrough(); + vsfsWatchStub.reset(); + vsfsWatchStub.callThrough(); + c2fsStatStub.reset(); + c2fsStatStub.callThrough(); + vsFindFilesStub.reset(); + vsFindFilesStub.callThrough(); + } - after(function() { - stubsResetToMyDefault(); - }) + before(function() { + fse.removeSync(dotVscodePath); + adapter = undefined; - afterEach(function() { - watchers.clear(); - }) + spawnStub = + sinonSandbox.stub(child_process, 'spawn').named('spawnStub'); + vsfsWatchStub = + sinonSandbox.stub(vscode.workspace, 'createFileSystemWatcher') + .named('vscode.createFileSystemWatcher'); + c2fsStatStub = sinonSandbox.stub(fs, 'stat').named('fsStat'); + vsFindFilesStub = sinonSandbox.stub(vscode.workspace, 'findFiles') + .named('vsFindFilesStub'); - describe('load', function() { - const uniqueIdC = new Set(); - let adapter: TestAdapter; - - let root: TestSuiteInfo; - let suite1: TestSuiteInfo|any; - let s1t1: TestInfo|any; - let s1t2: TestInfo|any; - let suite2: TestSuiteInfo|any; - let s2t1: TestInfo|any; - let s2t2: TestInfo|any; - let s2t3: TestInfo|any; - - beforeEach(async function() { - adapter = createAdapterAndSubscribe(); - await adapter.load(); + stubsResetToMyDefault(); - assert.equal(testsEvents.length, 2, inspect(testsEvents)); - assert.equal(testsEvents[1].type, 'finished'); - assert.ok((testsEvents[1]).suite); - root = (testsEvents[1]).suite!; - testsEvents.pop(); - testsEvents.pop(); - - suite1 = undefined; - s1t1 = undefined; - s1t2 = undefined; - suite2 = undefined; - s2t1 = undefined; - s2t2 = undefined; - s2t3 = undefined; - - example1.assertWithoutChildren(root, uniqueIdC); - assert.deepStrictEqual(testStatesEvents, []); + // reset config can cause problem with fse.removeSync(dotVscodePath); + return resetConfig(); }); - afterEach(function() { - uniqueIdC.clear(); + after(function() { disposeAdapterAndSubscribers(); + sinonSandbox.restore(); }); - context('executables="execPath1"', function() { + describe('detect config change', function() { + this.slow(150); + + let adapter: C2TestAdapter; + before(function() { - return updateConfig('executables', 'execPath1'); - }); + adapter = createAdapterAndSubscribe(); + assert.deepStrictEqual(testsEvents, []); + }) after(function() { - return updateConfig('executables', undefined); - }); - - beforeEach(async function() { - assert.deepStrictEqual( - getConfig().get('executables'), 'execPath1'); - assert.equal(root.children.length, 1); - assert.equal(root.children[0].type, 'suite'); - suite1 = root.children[0]; - example1.suite1.assert( - 'execPath1', ['s1t1', 's1t2'], suite1, uniqueIdC); - assert.equal(suite1.children.length, 2); - assert.equal(suite1.children[0].type, 'test'); - s1t1 = suite1.children[0]; - assert.equal(suite1.children[1].type, 'test'); - s1t2 = suite1.children[1]; - }); + disposeAdapterAndSubscribers(); + return resetConfig(); + }) - it('should run with not existing test id', async function() { - await adapter.run(['not existing id']); + it('defaultEnv', function() { + return doAndWaitForReloadEvent(this, () => { + return updateConfig('defaultEnv', {'APPLE': 'apple'}); + }); + }) - assert.deepStrictEqual(testStatesEvents, [ - {type: 'started', tests: ['not existing id']}, - {type: 'finished'}, - ]); - }); + it('defaultCwd', function() { + return doAndWaitForReloadEvent(this, () => { + return updateConfig('defaultCwd', 'apple/peach'); + }); + }) - it('should run s1t1 with success', async function() { - assert.equal(getConfig().get('executables'), 'execPath1'); - await adapter.run([s1t1.id]); - const expected = [ - {type: 'started', tests: [s1t1.id]}, - {type: 'suite', state: 'running', suite: suite1}, - {type: 'test', state: 'running', test: s1t1}, - { - type: 'test', - state: 'passed', - test: s1t1, - decorations: undefined, - message: 'Duration: 0.000112 second(s)\n' - }, - {type: 'suite', state: 'completed', suite: suite1}, - {type: 'finished'}, - ]; - assert.deepStrictEqual(testStatesEvents, expected); + it('enableSourceDecoration', function() { + return updateConfig('enableSourceDecoration', false).then(function() { + assert.ok(!adapter.getIsEnabledSourceDecoration()); + }); + }) - await adapter.run([s1t1.id]); - assert.deepStrictEqual(testStatesEvents, [...expected, ...expected]); - }); + it('defaultRngSeed', function() { + return updateConfig('defaultRngSeed', 987).then(function() { + assert.equal(adapter.getRngSeed(), 987); + }); + }) + }) - it('should run suite1', async function() { - await adapter.run([suite1.id]); - const expected = [ - {type: 'started', tests: [suite1.id]}, - {type: 'suite', state: 'running', suite: suite1}, - {type: 'test', state: 'running', test: s1t1}, - { - type: 'test', - state: 'passed', - test: s1t1, - decorations: undefined, - message: 'Duration: 0.000132 second(s)\n' - }, - {type: 'test', state: 'running', test: s1t2}, - { - type: 'test', - state: 'failed', - test: s1t2, - decorations: [{line: 14, message: 'Expanded: false'}], - message: - 'Duration: 0.000204 second(s)\n>>> s1t2(line: 13) REQUIRE (line: 15) \n Original:\n std::false_type::value\n Expanded:\n false\n<<<\n' - }, - {type: 'suite', state: 'completed', suite: suite1}, - {type: 'finished'}, - ]; - assert.deepStrictEqual(testStatesEvents, expected); + it('load with empty config', async function() { + this.slow(400); + const adapter = createAdapterAndSubscribe(); + await adapter.load(); + assert.equal(testsEvents.length, 2); + assert.equal(testsEvents[0].type, 'started'); + assert.equal(testsEvents[1].type, 'finished'); + const suite = (testsEvents[1]).suite; + assert.notEqual(suite, undefined); + assert.equal(suite!.children.length, 0); + disposeAdapterAndSubscribers(); + }) - await adapter.run([suite1.id]); - assert.deepStrictEqual(testStatesEvents, [...expected, ...expected]); - }); + context('example1', function() { + const watchers: Map = new Map(); + + function handleCreateWatcherCb( + path: string, ignoreCreateEvents: boolean, + ignoreChangeEvents: boolean, ignoreDeleteEvents: boolean) { + const e = new FileSystemWatcherStub( + vscode.Uri.file(path), ignoreCreateEvents, ignoreChangeEvents, + ignoreDeleteEvents); + watchers.set(path, e); + return e; + } - it('should run all', async function() { - await adapter.run([root.id]); - const expected = [ - {type: 'started', tests: [root.id]}, - {type: 'suite', state: 'running', suite: suite1}, - {type: 'test', state: 'running', test: s1t1}, - { - type: 'test', - state: 'passed', - test: s1t1, - decorations: undefined, - message: 'Duration: 0.000132 second(s)\n' + function handleStatExistsFile( + path: string, + cb: (err: NodeJS.ErrnoException|null, stats: fs.Stats|undefined) => + void) { + cb(null, { + isFile() { + return true; }, - {type: 'test', state: 'running', test: s1t2}, - { - type: 'test', - state: 'failed', - test: s1t2, - decorations: [{line: 14, message: 'Expanded: false'}], - message: - 'Duration: 0.000204 second(s)\n>>> s1t2(line: 13) REQUIRE (line: 15) \n Original:\n std::false_type::value\n Expanded:\n false\n<<<\n' - }, - {type: 'suite', state: 'completed', suite: suite1}, - {type: 'finished'}, - ]; - assert.deepStrictEqual(testStatesEvents, expected); + isDirectory() { + return false; + } + }); + } - await adapter.run([root.id]); - assert.deepStrictEqual(testStatesEvents, [...expected, ...expected]); - }); + function handleStatNotExists( + path: string, + cb: ( + err: NodeJS.ErrnoException|null|any, + stats: fs.Stats|undefined) => void) { + cb({ + code: 'ENOENT', + errno: -2, + message: 'ENOENT', + path: path, + syscall: 'stat' + }, + undefined); + }; - it('cancels without any problem', async function() { - adapter.cancel(); - assert.deepStrictEqual(testsEvents, []); - assert.deepStrictEqual(testStatesEvents, []); + before(function() { + for (let suite of example1.outputs) { + for (let scenario of suite[1]) { + spawnStub.withArgs(suite[0], scenario[0]).callsFake(function() { + return new ChildProcessStub(scenario[1]); + }); + } - adapter.cancel(); - assert.deepStrictEqual(testsEvents, []); - assert.deepStrictEqual(testStatesEvents, []); - - await adapter.run([s1t1.id]); - const expected = [ - {type: 'started', tests: [s1t1.id]}, - {type: 'suite', state: 'running', suite: suite1}, - {type: 'test', state: 'running', test: s1t1}, - { - type: 'test', - state: 'passed', - test: s1t1, - decorations: undefined, - message: 'Duration: 0.000112 second(s)\n' - }, - {type: 'suite', state: 'completed', suite: suite1}, - {type: 'finished'}, - ]; - assert.deepStrictEqual(testStatesEvents, expected); + c2fsStatStub.withArgs(suite[0]).callsFake(handleStatExistsFile); - adapter.cancel(); - assert.deepStrictEqual(testsEvents, []); - assert.deepStrictEqual(testStatesEvents, expected); - }); + vsfsWatchStub.withArgs(suite[0]).callsFake(handleCreateWatcherCb); + } - context('with config: defaultRngSeed=2', function() { - before(function() { - return updateConfig('defaultRngSeed', 2); - }) + const dirContent: Map = new Map(); + for (let p of example1.outputs) { + const parent = path.dirname(p[0]); + let children: vscode.Uri[] = []; + if (dirContent.has(parent)) + children = dirContent.get(parent)!; + else { + dirContent.set(parent, children); + } + children.push(vscode.Uri.file(p[0])); + } - after(function() { - return updateConfig('defaultRngSeed', undefined); - }) + dirContent.forEach((v: vscode.Uri[], k: string) => { + assert.equal(workspaceFolderUri.path, k); + vsFindFilesStub.withArgs(k).returns(v); + for (const p of v) { + vsFindFilesStub.withArgs(p.path).returns([p]); + } + }); + }) - it('should run s1t1 with success', async function() { - await adapter.run([s1t1.id]); - const expected = [ - {type: 'started', tests: [s1t1.id]}, - {type: 'suite', state: 'running', suite: suite1}, - {type: 'test', state: 'running', test: s1t1}, - { - type: 'test', - state: 'passed', - test: s1t1, - decorations: undefined, - message: - 'Randomness seeded to: 2\nDuration: 0.000327 second(s)\n' - }, - {type: 'suite', state: 'completed', suite: suite1}, - {type: 'finished'}, - ]; - assert.deepStrictEqual(testStatesEvents, expected); + after(function() { + stubsResetToMyDefault(); + }) - await adapter.run([s1t1.id]); - assert.deepStrictEqual( - testStatesEvents, [...expected, ...expected]); - }) + afterEach(function() { + watchers.clear(); }) - }) - context('suite1 and suite2 are used', function() { - let suite1Watcher: FileSystemWatcherStub; - - beforeEach(function() { - assert.equal(root.children.length, 2); - - assert.equal(root.children[0].type, 'suite'); - assert.equal(root.children[1].type, 'suite'); - assert.equal(example1.suite1.outputs.length, 4 + 2 * 2); - assert.equal(example1.suite2.outputs.length, 4 + 2 * 3); - suite1 = root.children[0]; - suite2 = root.children[1]; - if (suite2.children.length == 2) { - suite1 = root.children[1]; - suite2 = root.children[0]; - } + describe('load', function() { + const uniqueIdC = new Set(); + let adapter: TestAdapter; - assert.equal(suite1.children.length, 2); - assert.equal(suite1.children[0].type, 'test'); - s1t1 = suite1.children[0]; - assert.equal(suite1.children[1].type, 'test'); - s1t2 = suite1.children[1]; - - assert.equal(suite2.children.length, 3); - assert.equal(suite2.children[0].type, 'test'); - s2t1 = suite2.children[0]; - assert.equal(suite2.children[1].type, 'test'); - s2t2 = suite2.children[1]; - assert.equal(suite2.children[2].type, 'test'); - s2t3 = suite2.children[2]; - - assert.equal(watchers.size, 2); - assert.ok(watchers.has(example1.suite1.execPath)); - suite1Watcher = watchers.get(example1.suite1.execPath)!; - }) + let root: TestSuiteInfo; + let suite1: TestSuiteInfo|any; + let s1t1: TestInfo|any; + let s1t2: TestInfo|any; + let suite2: TestSuiteInfo|any; + let s2t1: TestInfo|any; + let s2t2: TestInfo|any; + let s2t3: TestInfo|any; - const testsForAdapterWithSuite1AndSuite2: Mocha.Test[] = [ - new Mocha.Test( - 'test variables are fine, suite1 and suite1 are loaded', - function() { - assert.equal(root.children.length, 2); - assert.ok(suite1 != undefined); - assert.ok(s1t1 != undefined); - assert.ok(s1t2 != undefined); - assert.ok(suite2 != undefined); - assert.ok(s2t1 != undefined); - assert.ok(s2t2 != undefined); - assert.ok(s2t3 != undefined); - }), - new Mocha.Test( - 'should run all', - async function() { - assert.equal(root.children.length, 2); - await adapter.run([root.id]); - - const running = {type: 'started', tests: [root.id]}; - - const s1running = { - type: 'suite', - state: 'running', - suite: suite1 - }; - const s1finished = { - type: 'suite', - state: 'completed', - suite: suite1 - }; - assert.ok(testStatesEvI(running) < testStatesEvI(s1running)); - assert.ok(testStatesEvI(s1running) < testStatesEvI(s1finished)); - - const s2running = { - type: 'suite', - state: 'running', - suite: suite2 - }; - const s2finished = { - type: 'suite', - state: 'completed', - suite: suite2 - }; - assert.ok(testStatesEvI(running) < testStatesEvI(s1running)); - assert.ok(testStatesEvI(s2running) < testStatesEvI(s2finished)); - - const s1t1running = { - type: 'test', - state: 'running', - test: s1t1 - }; - assert.ok( - testStatesEvI(s1running) < testStatesEvI(s1t1running)); + beforeEach(async function() { + adapter = createAdapterAndSubscribe(); + await adapter.load(); + + assert.equal(testsEvents.length, 2, inspect(testsEvents)); + assert.equal(testsEvents[1].type, 'finished'); + assert.ok((testsEvents[1]).suite); + root = (testsEvents[1]).suite!; + testsEvents.pop(); + testsEvents.pop(); + + suite1 = undefined; + s1t1 = undefined; + s1t2 = undefined; + suite2 = undefined; + s2t1 = undefined; + s2t2 = undefined; + s2t3 = undefined; + + example1.assertWithoutChildren(root, uniqueIdC); + assert.deepStrictEqual(testStatesEvents, []); + }); + + afterEach(function() { + uniqueIdC.clear(); + disposeAdapterAndSubscribers(); + }); + + context('executables="execPath1"', function() { + before(function() { + return updateConfig('executables', 'execPath1'); + }); - const s1t1finished = { + after(function() { + return updateConfig('executables', undefined); + }); + + beforeEach(async function() { + assert.deepStrictEqual( + getConfig().get('executables'), 'execPath1'); + assert.equal(root.children.length, 1); + assert.equal(root.children[0].type, 'suite'); + suite1 = root.children[0]; + example1.suite1.assert( + 'execPath1', ['s1t1', 's1t2'], suite1, uniqueIdC); + assert.equal(suite1.children.length, 2); + assert.equal(suite1.children[0].type, 'test'); + s1t1 = suite1.children[0]; + assert.equal(suite1.children[1].type, 'test'); + s1t2 = suite1.children[1]; + }); + + it('should run with not existing test id', async function() { + await adapter.run(['not existing id']); + + assert.deepStrictEqual(testStatesEvents, [ + {type: 'started', tests: ['not existing id']}, + {type: 'finished'}, + ]); + }); + + it('should run s1t1 with success', async function() { + assert.equal(getConfig().get('executables'), 'execPath1'); + await adapter.run([s1t1.id]); + const expected = [ + {type: 'started', tests: [s1t1.id]}, + {type: 'suite', state: 'running', suite: suite1}, + {type: 'test', state: 'running', test: s1t1}, + { type: 'test', state: 'passed', test: s1t1, decorations: undefined, - message: 'Duration: 0.000132 second(s)\n' - }; - assert.ok( - testStatesEvI(s1t1running) < testStatesEvI(s1t1finished)); - assert.ok( - testStatesEvI(s1t1finished) < testStatesEvI(s1finished)); + message: 'Duration: 0.000112 second(s)\n' + }, + {type: 'suite', state: 'completed', suite: suite1}, + {type: 'finished'}, + ]; + assert.deepStrictEqual(testStatesEvents, expected); + + await adapter.run([s1t1.id]); + assert.deepStrictEqual( + testStatesEvents, [...expected, ...expected]); + }); - const s1t2running = { + it('should run suite1', async function() { + await adapter.run([suite1.id]); + const expected = [ + {type: 'started', tests: [suite1.id]}, + {type: 'suite', state: 'running', suite: suite1}, + {type: 'test', state: 'running', test: s1t1}, + { type: 'test', - state: 'running', - test: s1t2 - }; - assert.ok( - testStatesEvI(s1running) < testStatesEvI(s1t2running)); - - const s1t2finished = { + state: 'passed', + test: s1t1, + decorations: undefined, + message: 'Duration: 0.000132 second(s)\n' + }, + {type: 'test', state: 'running', test: s1t2}, + { type: 'test', state: 'failed', test: s1t2, decorations: [{line: 14, message: 'Expanded: false'}], message: 'Duration: 0.000204 second(s)\n>>> s1t2(line: 13) REQUIRE (line: 15) \n Original:\n std::false_type::value\n Expanded:\n false\n<<<\n' - }; - assert.ok( - testStatesEvI(s1t2running) < testStatesEvI(s1t2finished)); - assert.ok( - testStatesEvI(s1t2finished) < testStatesEvI(s1finished)); + }, + {type: 'suite', state: 'completed', suite: suite1}, + {type: 'finished'}, + ]; + assert.deepStrictEqual(testStatesEvents, expected); - const s2t1running = { - type: 'test', - state: 'running', - test: s2t1 - }; - assert.ok( - testStatesEvI(s2running) < testStatesEvI(s2t1running)); + await adapter.run([suite1.id]); + assert.deepStrictEqual( + testStatesEvents, [...expected, ...expected]); + }); - const s2t1finished = { + it('should run all', async function() { + await adapter.run([root.id]); + const expected = [ + {type: 'started', tests: [root.id]}, + {type: 'suite', state: 'running', suite: suite1}, + {type: 'test', state: 'running', test: s1t1}, + { type: 'test', state: 'passed', - test: s2t1, + test: s1t1, decorations: undefined, - message: 'Duration: 0.00037 second(s)\n' - }; - assert.ok( - testStatesEvI(s2t1running) < testStatesEvI(s2t1finished)); - assert.ok( - testStatesEvI(s2t1finished) < testStatesEvI(s2finished)); - - const s2t2running = { + message: 'Duration: 0.000132 second(s)\n' + }, + {type: 'test', state: 'running', test: s1t2}, + { type: 'test', - state: 'running', - test: s2t2 - }; - assert.ok( - testStatesEvI(s2running) < testStatesEvI(s2t2running)); + state: 'failed', + test: s1t2, + decorations: [{line: 14, message: 'Expanded: false'}], + message: + 'Duration: 0.000204 second(s)\n>>> s1t2(line: 13) REQUIRE (line: 15) \n Original:\n std::false_type::value\n Expanded:\n false\n<<<\n' + }, + {type: 'suite', state: 'completed', suite: suite1}, + {type: 'finished'}, + ]; + assert.deepStrictEqual(testStatesEvents, expected); + + await adapter.run([root.id]); + assert.deepStrictEqual( + testStatesEvents, [...expected, ...expected]); + }); - const s2t2finished = { - type: 'test', - state: 'skipped', - test: s2t2 - }; - assert.ok( - testStatesEvI(s2t2running) < testStatesEvI(s2t2finished)); - assert.ok( - testStatesEvI(s2t2finished) < testStatesEvI(s2finished)); - - const s2t3running = { - type: 'test', - state: 'running', - test: s2t3 - }; - assert.ok( - testStatesEvI(s2running) < testStatesEvI(s2t3running)); + it('cancels without any problem', async function() { + adapter.cancel(); + assert.deepStrictEqual(testsEvents, []); + assert.deepStrictEqual(testStatesEvents, []); + + adapter.cancel(); + assert.deepStrictEqual(testsEvents, []); + assert.deepStrictEqual(testStatesEvents, []); - const s2t3finished = { + await adapter.run([s1t1.id]); + const expected = [ + {type: 'started', tests: [s1t1.id]}, + {type: 'suite', state: 'running', suite: suite1}, + {type: 'test', state: 'running', test: s1t1}, + { type: 'test', - state: 'failed', - test: s2t3, - decorations: [{line: 20, message: 'Expanded: false'}], - message: - 'Duration: 0.000178 second(s)\n>>> s2t3(line: 19) REQUIRE (line: 21) \n Original:\n std::false_type::value\n Expanded:\n false\n<<<\n' - }; - assert.ok( - testStatesEvI(s2t3running) < testStatesEvI(s2t3finished)); - assert.ok( - testStatesEvI(s2t3finished) < testStatesEvI(s2finished)); - - const finished = {type: 'finished'}; - assert.ok(testStatesEvI(s1finished) < testStatesEvI(finished)); - assert.ok(testStatesEvI(s2finished) < testStatesEvI(finished)); - - assert.equal( - testStatesEvents.length, 16, inspect(testStatesEvents)); - }), - new Mocha.Test( - 'should run with not existing test id', - async function() { - await adapter.run(['not existing id']); + state: 'passed', + test: s1t1, + decorations: undefined, + message: 'Duration: 0.000112 second(s)\n' + }, + {type: 'suite', state: 'completed', suite: suite1}, + {type: 'finished'}, + ]; + assert.deepStrictEqual(testStatesEvents, expected); + + adapter.cancel(); + assert.deepStrictEqual(testsEvents, []); + assert.deepStrictEqual(testStatesEvents, expected); + }); - assert.deepStrictEqual(testStatesEvents, [ - {type: 'started', tests: ['not existing id']}, - {type: 'finished'}, - ]); - }), - new Mocha.Test( - 'should run s1t1', - async function() { + context('with config: defaultRngSeed=2', function() { + before(function() { + return updateConfig('defaultRngSeed', 2); + }) + + after(function() { + return updateConfig('defaultRngSeed', undefined); + }) + + it('should run s1t1 with success', async function() { await adapter.run([s1t1.id]); const expected = [ {type: 'started', tests: [s1t1.id]}, @@ -763,7 +555,8 @@ describe('C2TestAdapter', function() { state: 'passed', test: s1t1, decorations: undefined, - message: 'Duration: 0.000112 second(s)\n' + message: + 'Randomness seeded to: 2\nDuration: 0.000327 second(s)\n' }, {type: 'suite', state: 'completed', suite: suite1}, {type: 'finished'}, @@ -773,632 +566,982 @@ describe('C2TestAdapter', function() { await adapter.run([s1t1.id]); assert.deepStrictEqual( testStatesEvents, [...expected, ...expected]); - }), - new Mocha.Test( - 'should run skipped s2t2', - async function() { - await adapter.run([s2t2.id]); - const expected = [ - {type: 'started', tests: [s2t2.id]}, - {type: 'suite', state: 'running', suite: suite2}, - {type: 'test', state: 'running', test: s2t2}, - { - type: 'test', - state: 'passed', - test: s2t2, - decorations: undefined, - message: 'Duration: 0.001294 second(s)\n' - }, - {type: 'suite', state: 'completed', suite: suite2}, - {type: 'finished'}, - ]; - assert.deepStrictEqual(testStatesEvents, expected); + }) + }) + }) - await adapter.run([s2t2.id]); - assert.deepStrictEqual( - testStatesEvents, [...expected, ...expected]); - }), - new Mocha.Test( - 'should run failing test s2t3', - async function() { - await adapter.run([s2t3.id]); - const expected = [ - {type: 'started', tests: [s2t3.id]}, - {type: 'suite', state: 'running', suite: suite2}, - {type: 'test', state: 'running', test: s2t3}, - { - type: 'test', - state: 'failed', - test: s2t3, - decorations: [{line: 20, message: 'Expanded: false'}], - message: - 'Duration: 0.000596 second(s)\n>>> s2t3(line: 19) REQUIRE (line: 21) \n Original:\n std::false_type::value\n Expanded:\n false\n<<<\n' - }, - {type: 'suite', state: 'completed', suite: suite2}, - {type: 'finished'}, - ]; - assert.deepStrictEqual(testStatesEvents, expected); + context('suite1 and suite2 are used', function() { + beforeEach(function() { + assert.equal(root.children.length, 2); - await adapter.run([s2t3.id]); - assert.deepStrictEqual( - testStatesEvents, [...expected, ...expected]); - }), - new Mocha.Test( - 'should run failing test s2t3 with chunks', - async function() { - const withArgs = spawnStub.withArgs( - example1.suite2.execPath, example1.suite2.t3.outputs[0][0]); - withArgs.onCall(withArgs.callCount) - .returns( - new ChildProcessStub(example1.suite2.t3.outputs[0][1])); + assert.equal(root.children[0].type, 'suite'); + assert.equal(root.children[1].type, 'suite'); + assert.equal(example1.suite1.outputs.length, 4 + 2 * 2); + assert.equal(example1.suite2.outputs.length, 4 + 2 * 3); + suite1 = root.children[0]; + suite2 = root.children[1]; + if (suite2.children.length == 2) { + suite1 = root.children[1]; + suite2 = root.children[0]; + } - await adapter.run([s2t3.id]); - const expected = [ - {type: 'started', tests: [s2t3.id]}, - {type: 'suite', state: 'running', suite: suite2}, - {type: 'test', state: 'running', test: s2t3}, - { - type: 'test', - state: 'failed', - test: s2t3, - decorations: [{line: 20, message: 'Expanded: false'}], - message: - 'Duration: 0.000596 second(s)\n>>> s2t3(line: 19) REQUIRE (line: 21) \n Original:\n std::false_type::value\n Expanded:\n false\n<<<\n' - }, - {type: 'suite', state: 'completed', suite: suite2}, - {type: 'finished'}, - ]; - assert.deepStrictEqual(testStatesEvents, expected); + assert.equal(suite1.children.length, 2); + assert.equal(suite1.children[0].type, 'test'); + s1t1 = suite1.children[0]; + assert.equal(suite1.children[1].type, 'test'); + s1t2 = suite1.children[1]; + + assert.equal(suite2.children.length, 3); + assert.equal(suite2.children[0].type, 'test'); + s2t1 = suite2.children[0]; + assert.equal(suite2.children[1].type, 'test'); + s2t2 = suite2.children[1]; + assert.equal(suite2.children[2].type, 'test'); + s2t3 = suite2.children[2]; + }) - await adapter.run([s2t3.id]); - assert.deepStrictEqual( - testStatesEvents, [...expected, ...expected]); - }), - new Mocha.Test( - 'should run suite1', - async function() { - await adapter.run([suite1.id]); - const expected = [ - {type: 'started', tests: [suite1.id]}, - {type: 'suite', state: 'running', suite: suite1}, - {type: 'test', state: 'running', test: s1t1}, - { - type: 'test', - state: 'passed', - test: s1t1, - decorations: undefined, - message: 'Duration: 0.000132 second(s)\n' - }, - {type: 'test', state: 'running', test: s1t2}, - { - type: 'test', - state: 'failed', - test: s1t2, - decorations: [{line: 14, message: 'Expanded: false'}], - message: - 'Duration: 0.000204 second(s)\n>>> s1t2(line: 13) REQUIRE (line: 15) \n Original:\n std::false_type::value\n Expanded:\n false\n<<<\n' - }, - {type: 'suite', state: 'completed', suite: suite1}, - {type: 'finished'}, - ]; - assert.deepStrictEqual(testStatesEvents, expected); + const testsForAdapterWithSuite1AndSuite2: Mocha.Test[] = [ + new Mocha.Test( + 'test variables are fine, suite1 and suite1 are loaded', + function() { + assert.equal(root.children.length, 2); + assert.ok(suite1 != undefined); + assert.ok(s1t1 != undefined); + assert.ok(s1t2 != undefined); + assert.ok(suite2 != undefined); + assert.ok(s2t1 != undefined); + assert.ok(s2t2 != undefined); + assert.ok(s2t3 != undefined); + }), + new Mocha.Test( + 'should run all', + async function() { + assert.equal(root.children.length, 2); + await adapter.run([root.id]); + + const running = {type: 'started', tests: [root.id]}; + + const s1running = { + type: 'suite', + state: 'running', + suite: suite1 + }; + const s1finished = { + type: 'suite', + state: 'completed', + suite: suite1 + }; + assert.ok( + testStatesEvI(running) < testStatesEvI(s1running)); + assert.ok( + testStatesEvI(s1running) < testStatesEvI(s1finished)); + + const s2running = { + type: 'suite', + state: 'running', + suite: suite2 + }; + const s2finished = { + type: 'suite', + state: 'completed', + suite: suite2 + }; + assert.ok( + testStatesEvI(running) < testStatesEvI(s1running)); + assert.ok( + testStatesEvI(s2running) < testStatesEvI(s2finished)); + + const s1t1running = { + type: 'test', + state: 'running', + test: s1t1 + }; + assert.ok( + testStatesEvI(s1running) < testStatesEvI(s1t1running)); + + const s1t1finished = { + type: 'test', + state: 'passed', + test: s1t1, + decorations: undefined, + message: 'Duration: 0.000132 second(s)\n' + }; + assert.ok( + testStatesEvI(s1t1running) < + testStatesEvI(s1t1finished)); + assert.ok( + testStatesEvI(s1t1finished) < + testStatesEvI(s1finished)); + + const s1t2running = { + type: 'test', + state: 'running', + test: s1t2 + }; + assert.ok( + testStatesEvI(s1running) < testStatesEvI(s1t2running)); + + const s1t2finished = { + type: 'test', + state: 'failed', + test: s1t2, + decorations: [{line: 14, message: 'Expanded: false'}], + message: + 'Duration: 0.000204 second(s)\n>>> s1t2(line: 13) REQUIRE (line: 15) \n Original:\n std::false_type::value\n Expanded:\n false\n<<<\n' + }; + assert.ok( + testStatesEvI(s1t2running) < + testStatesEvI(s1t2finished)); + assert.ok( + testStatesEvI(s1t2finished) < + testStatesEvI(s1finished)); + + const s2t1running = { + type: 'test', + state: 'running', + test: s2t1 + }; + assert.ok( + testStatesEvI(s2running) < testStatesEvI(s2t1running)); + + const s2t1finished = { + type: 'test', + state: 'passed', + test: s2t1, + decorations: undefined, + message: 'Duration: 0.00037 second(s)\n' + }; + assert.ok( + testStatesEvI(s2t1running) < + testStatesEvI(s2t1finished)); + assert.ok( + testStatesEvI(s2t1finished) < + testStatesEvI(s2finished)); + + const s2t2running = { + type: 'test', + state: 'running', + test: s2t2 + }; + assert.ok( + testStatesEvI(s2running) < testStatesEvI(s2t2running)); + + const s2t2finished = { + type: 'test', + state: 'skipped', + test: s2t2 + }; + assert.ok( + testStatesEvI(s2t2running) < + testStatesEvI(s2t2finished)); + assert.ok( + testStatesEvI(s2t2finished) < + testStatesEvI(s2finished)); + + const s2t3running = { + type: 'test', + state: 'running', + test: s2t3 + }; + assert.ok( + testStatesEvI(s2running) < testStatesEvI(s2t3running)); + + const s2t3finished = { + type: 'test', + state: 'failed', + test: s2t3, + decorations: [{line: 20, message: 'Expanded: false'}], + message: + 'Duration: 0.000178 second(s)\n>>> s2t3(line: 19) REQUIRE (line: 21) \n Original:\n std::false_type::value\n Expanded:\n false\n<<<\n' + }; + assert.ok( + testStatesEvI(s2t3running) < + testStatesEvI(s2t3finished)); + assert.ok( + testStatesEvI(s2t3finished) < + testStatesEvI(s2finished)); + + const finished = {type: 'finished'}; + assert.ok( + testStatesEvI(s1finished) < testStatesEvI(finished)); + assert.ok( + testStatesEvI(s2finished) < testStatesEvI(finished)); - await adapter.run([suite1.id]); - assert.deepStrictEqual( - testStatesEvents, [...expected, ...expected]); - }), - new Mocha.Test( - 'should run with wrong xml', - async function() { - const m = - example1.suite1.t1.outputs[0][1].match(']+>'); - assert.notEqual(m, undefined); - assert.notEqual(m!.input, undefined); - assert.notEqual(m!.index, undefined); - const part = m!.input!.substr(0, m!.index! + m![0].length); - const withArgs = spawnStub.withArgs( - example1.suite1.execPath, example1.suite1.t1.outputs[0][0]); - withArgs.onCall(withArgs.callCount) - .returns(new ChildProcessStub(part)); + assert.equal( + testStatesEvents.length, 16, inspect(testStatesEvents)); + }), + new Mocha.Test( + 'should run with not existing test id', + async function() { + await adapter.run(['not existing id']); + + assert.deepStrictEqual(testStatesEvents, [ + {type: 'started', tests: ['not existing id']}, + {type: 'finished'}, + ]); + }), + new Mocha.Test( + 'should run s1t1', + async function() { + await adapter.run([s1t1.id]); + const expected = [ + {type: 'started', tests: [s1t1.id]}, + {type: 'suite', state: 'running', suite: suite1}, + {type: 'test', state: 'running', test: s1t1}, + { + type: 'test', + state: 'passed', + test: s1t1, + decorations: undefined, + message: 'Duration: 0.000112 second(s)\n' + }, + {type: 'suite', state: 'completed', suite: suite1}, + {type: 'finished'}, + ]; + assert.deepStrictEqual(testStatesEvents, expected); + + await adapter.run([s1t1.id]); + assert.deepStrictEqual( + testStatesEvents, [...expected, ...expected]); + }), + new Mocha.Test( + 'should run skipped s2t2', + async function() { + await adapter.run([s2t2.id]); + const expected = [ + {type: 'started', tests: [s2t2.id]}, + {type: 'suite', state: 'running', suite: suite2}, + {type: 'test', state: 'running', test: s2t2}, + { + type: 'test', + state: 'passed', + test: s2t2, + decorations: undefined, + message: 'Duration: 0.001294 second(s)\n' + }, + {type: 'suite', state: 'completed', suite: suite2}, + {type: 'finished'}, + ]; + assert.deepStrictEqual(testStatesEvents, expected); + + await adapter.run([s2t2.id]); + assert.deepStrictEqual( + testStatesEvents, [...expected, ...expected]); + }), + new Mocha.Test( + 'should run failing test s2t3', + async function() { + await adapter.run([s2t3.id]); + const expected = [ + {type: 'started', tests: [s2t3.id]}, + {type: 'suite', state: 'running', suite: suite2}, + {type: 'test', state: 'running', test: s2t3}, + { + type: 'test', + state: 'failed', + test: s2t3, + decorations: [{line: 20, message: 'Expanded: false'}], + message: + 'Duration: 0.000596 second(s)\n>>> s2t3(line: 19) REQUIRE (line: 21) \n Original:\n std::false_type::value\n Expanded:\n false\n<<<\n' + }, + {type: 'suite', state: 'completed', suite: suite2}, + {type: 'finished'}, + ]; + assert.deepStrictEqual(testStatesEvents, expected); + + await adapter.run([s2t3.id]); + assert.deepStrictEqual( + testStatesEvents, [...expected, ...expected]); + }), + new Mocha.Test( + 'should run failing test s2t3 with chunks', + async function() { + const withArgs = spawnStub.withArgs( + example1.suite2.execPath, + example1.suite2.t3.outputs[0][0]); + withArgs.onCall(withArgs.callCount) + .returns(new ChildProcessStub( + example1.suite2.t3.outputs[0][1])); + + await adapter.run([s2t3.id]); + const expected = [ + {type: 'started', tests: [s2t3.id]}, + {type: 'suite', state: 'running', suite: suite2}, + {type: 'test', state: 'running', test: s2t3}, + { + type: 'test', + state: 'failed', + test: s2t3, + decorations: [{line: 20, message: 'Expanded: false'}], + message: + 'Duration: 0.000596 second(s)\n>>> s2t3(line: 19) REQUIRE (line: 21) \n Original:\n std::false_type::value\n Expanded:\n false\n<<<\n' + }, + {type: 'suite', state: 'completed', suite: suite2}, + {type: 'finished'}, + ]; + assert.deepStrictEqual(testStatesEvents, expected); + + await adapter.run([s2t3.id]); + assert.deepStrictEqual( + testStatesEvents, [...expected, ...expected]); + }), + new Mocha.Test( + 'should run suite1', + async function() { + await adapter.run([suite1.id]); + const expected = [ + {type: 'started', tests: [suite1.id]}, + {type: 'suite', state: 'running', suite: suite1}, + {type: 'test', state: 'running', test: s1t1}, + { + type: 'test', + state: 'passed', + test: s1t1, + decorations: undefined, + message: 'Duration: 0.000132 second(s)\n' + }, + {type: 'test', state: 'running', test: s1t2}, + { + type: 'test', + state: 'failed', + test: s1t2, + decorations: [{line: 14, message: 'Expanded: false'}], + message: + 'Duration: 0.000204 second(s)\n>>> s1t2(line: 13) REQUIRE (line: 15) \n Original:\n std::false_type::value\n Expanded:\n false\n<<<\n' + }, + {type: 'suite', state: 'completed', suite: suite1}, + {type: 'finished'}, + ]; + assert.deepStrictEqual(testStatesEvents, expected); + + await adapter.run([suite1.id]); + assert.deepStrictEqual( + testStatesEvents, [...expected, ...expected]); + }), + new Mocha.Test( + 'should run with wrong xml', + async function() { + const m = example1.suite1.t1.outputs[0][1].match( + ']+>'); + assert.notEqual(m, undefined); + assert.notEqual(m!.input, undefined); + assert.notEqual(m!.index, undefined); + const part = m!.input!.substr(0, m!.index! + m![0].length); + const withArgs = spawnStub.withArgs( + example1.suite1.execPath, + example1.suite1.t1.outputs[0][0]); + withArgs.onCall(withArgs.callCount) + .returns(new ChildProcessStub(part)); + + await adapter.run([s1t1.id]); + + const expected = [ + {type: 'started', tests: [s1t1.id]}, + {type: 'suite', state: 'running', suite: suite1}, + {type: 'test', state: 'running', test: s1t1}, + { + type: 'test', + state: 'failed', + test: s1t1, + message: 'Unexpected test error. (Is Catch2 crashed?)\n' + }, + {type: 'suite', state: 'completed', suite: suite1}, + {type: 'finished'}, + ]; + assert.deepStrictEqual(testStatesEvents, expected); + + // this tests the sinon stubs too + await adapter.run([s1t1.id]); + assert.deepStrictEqual(testStatesEvents, [ + ...expected, + {type: 'started', tests: [s1t1.id]}, + {type: 'suite', state: 'running', suite: suite1}, + {type: 'test', state: 'running', test: s1t1}, + { + type: 'test', + state: 'passed', + test: s1t1, + decorations: undefined, + message: 'Duration: 0.000112 second(s)\n' + }, + {type: 'suite', state: 'completed', suite: suite1}, + {type: 'finished'}, + ]); + }), + new Mocha.Test( + 'should cancel without error', + function() { + adapter.cancel(); + }), + new Mocha.Test( + 'cancel', + async function() { + let spyKill1: sinon.SinonSpy; + let spyKill2: sinon.SinonSpy; + { + const spawnEvent = + new ChildProcessStub(example1.suite1.outputs[2][1]); + spyKill1 = sinon.spy(spawnEvent, 'kill'); + const withArgs = spawnStub.withArgs( + example1.suite1.execPath, + example1.suite1.outputs[2][0]); + withArgs.onCall(withArgs.callCount).returns(spawnEvent); + } + { + const spawnEvent = + new ChildProcessStub(example1.suite2.outputs[2][1]); + spyKill2 = sinon.spy(spawnEvent, 'kill'); + const withArgs = spawnStub.withArgs( + example1.suite2.execPath, + example1.suite2.outputs[2][0]); + withArgs.onCall(withArgs.callCount).returns(spawnEvent); + } + const run = adapter.run([root.id]); + adapter.cancel(); + await run; + + assert.equal(spyKill1.callCount, 1); + assert.equal(spyKill2.callCount, 1); + + const running = {type: 'started', tests: [root.id]}; + + const s1running = { + type: 'suite', + state: 'running', + suite: suite1 + }; + const s1finished = { + type: 'suite', + state: 'completed', + suite: suite1 + }; + assert.ok( + testStatesEvI(running) < testStatesEvI(s1running)); + assert.ok( + testStatesEvI(s1running) < testStatesEvI(s1finished)); + + const s2running = { + type: 'suite', + state: 'running', + suite: suite2 + }; + const s2finished = { + type: 'suite', + state: 'completed', + suite: suite2 + }; + assert.ok( + testStatesEvI(running) < testStatesEvI(s1running)); + assert.ok( + testStatesEvI(s2running) < testStatesEvI(s2finished)); + + const s2t2running = { + type: 'test', + state: 'running', + test: s2t2 + }; + assert.ok( + testStatesEvI(s2running) < testStatesEvI(s2t2running)); + + const s2t2finished = { + type: 'test', + state: 'skipped', + test: s2t2 + }; + assert.ok( + testStatesEvI(s2t2running) < + testStatesEvI(s2t2finished)); + assert.ok( + testStatesEvI(s2t2finished) < + testStatesEvI(s2finished)); + + const finished = {type: 'finished'}; + assert.ok( + testStatesEvI(s1finished) < testStatesEvI(finished)); + assert.ok( + testStatesEvI(s2finished) < testStatesEvI(finished)); - await adapter.run([s1t1.id]); + assert.equal( + testStatesEvents.length, 8, inspect(testStatesEvents)); + }), + new Mocha.Test( + 'cancel after run finished', + function() { + let spyKill1: sinon.SinonSpy; + let spyKill2: sinon.SinonSpy; + { + const spawnEvent = + new ChildProcessStub(example1.suite1.outputs[2][1]); + spyKill1 = sinon.spy(spawnEvent, 'kill'); + const withArgs = spawnStub.withArgs( + example1.suite1.execPath, + example1.suite1.outputs[2][0]); + withArgs.onCall(withArgs.callCount).returns(spawnEvent); + } + { + const spawnEvent = + new ChildProcessStub(example1.suite2.outputs[2][1]); + spyKill2 = sinon.spy(spawnEvent, 'kill'); + const withArgs = spawnStub.withArgs( + example1.suite2.execPath, + example1.suite2.outputs[2][0]); + withArgs.onCall(withArgs.callCount).returns(spawnEvent); + } + const run = adapter.run([root.id]); + return run.then(function() { + adapter.cancel(); + assert.equal(spyKill1.callCount, 0); + assert.equal(spyKill2.callCount, 0); + }); + }), + ]; - const expected = [ - {type: 'started', tests: [s1t1.id]}, - {type: 'suite', state: 'running', suite: suite1}, - {type: 'test', state: 'running', test: s1t1}, - { - type: 'test', - state: 'failed', - test: s1t1, - message: 'Unexpected test error. (Is Catch2 crashed?)\n' - }, - {type: 'suite', state: 'completed', suite: suite1}, - {type: 'finished'}, - ]; - assert.deepStrictEqual(testStatesEvents, expected); + context( + 'executables=["execPath1", "${workspaceFolder}/execPath2"]', + function() { + before(function() { + return updateConfig( + 'executables', + ['execPath1', '${workspaceFolder}/execPath2']); + }); - // this tests the sinon stubs too - await adapter.run([s1t1.id]); - assert.deepStrictEqual(testStatesEvents, [ - ...expected, - {type: 'started', tests: [s1t1.id]}, - {type: 'suite', state: 'running', suite: suite1}, - {type: 'test', state: 'running', test: s1t1}, - { - type: 'test', - state: 'passed', - test: s1t1, - decorations: undefined, - message: 'Duration: 0.000112 second(s)\n' - }, - {type: 'suite', state: 'completed', suite: suite1}, - {type: 'finished'}, - ]); - }), - new Mocha.Test( - 'should cancel without error', - function() { - adapter.cancel(); - }), - new Mocha.Test( - 'cancel', - async function() { - let spyKill1: sinon.SinonSpy; - let spyKill2: sinon.SinonSpy; - { - const spawnEvent = - new ChildProcessStub(example1.suite1.outputs[2][1]); - spyKill1 = sinon.spy(spawnEvent, 'kill'); - const withArgs = spawnStub.withArgs( - example1.suite1.execPath, example1.suite1.outputs[2][0]); - withArgs.onCall(withArgs.callCount).returns(spawnEvent); - } - { - const spawnEvent = - new ChildProcessStub(example1.suite2.outputs[2][1]); - spyKill2 = sinon.spy(spawnEvent, 'kill'); - const withArgs = spawnStub.withArgs( - example1.suite2.execPath, example1.suite2.outputs[2][0]); - withArgs.onCall(withArgs.callCount).returns(spawnEvent); - } - const run = adapter.run([root.id]); - adapter.cancel(); - await run; - - assert.equal(spyKill1.callCount, 1); - assert.equal(spyKill2.callCount, 1); - - const running = {type: 'started', tests: [root.id]}; - - const s1running = { - type: 'suite', - state: 'running', - suite: suite1 - }; - const s1finished = { - type: 'suite', - state: 'completed', - suite: suite1 - }; - assert.ok(testStatesEvI(running) < testStatesEvI(s1running)); - assert.ok(testStatesEvI(s1running) < testStatesEvI(s1finished)); - - const s2running = { - type: 'suite', - state: 'running', - suite: suite2 - }; - const s2finished = { - type: 'suite', - state: 'completed', - suite: suite2 - }; - assert.ok(testStatesEvI(running) < testStatesEvI(s1running)); - assert.ok(testStatesEvI(s2running) < testStatesEvI(s2finished)); - - const s2t2running = { - type: 'test', - state: 'running', - test: s2t2 - }; - assert.ok( - testStatesEvI(s2running) < testStatesEvI(s2t2running)); + after(function() { + return updateConfig('executables', undefined); + }); - const s2t2finished = { - type: 'test', - state: 'skipped', - test: s2t2 - }; - assert.ok( - testStatesEvI(s2t2running) < testStatesEvI(s2t2finished)); - assert.ok( - testStatesEvI(s2t2finished) < testStatesEvI(s2finished)); - - const finished = {type: 'finished'}; - assert.ok(testStatesEvI(s1finished) < testStatesEvI(finished)); - assert.ok(testStatesEvI(s2finished) < testStatesEvI(finished)); - - assert.equal( - testStatesEvents.length, 8, inspect(testStatesEvents)); - }), - new Mocha.Test( - 'cancel after run finished', - function() { - let spyKill1: sinon.SinonSpy; - let spyKill2: sinon.SinonSpy; - { - const spawnEvent = - new ChildProcessStub(example1.suite1.outputs[2][1]); - spyKill1 = sinon.spy(spawnEvent, 'kill'); - const withArgs = spawnStub.withArgs( - example1.suite1.execPath, example1.suite1.outputs[2][0]); - withArgs.onCall(withArgs.callCount).returns(spawnEvent); - } - { - const spawnEvent = - new ChildProcessStub(example1.suite2.outputs[2][1]); - spyKill2 = sinon.spy(spawnEvent, 'kill'); - const withArgs = spawnStub.withArgs( - example1.suite2.execPath, example1.suite2.outputs[2][0]); - withArgs.onCall(withArgs.callCount).returns(spawnEvent); - } - const run = adapter.run([root.id]); - return run.then(function() { - adapter.cancel(); - assert.equal(spyKill1.callCount, 0); - assert.equal(spyKill2.callCount, 0); + let suite1Watcher; + + beforeEach(async function() { + assert.equal(watchers.size, 2); + assert.ok(watchers.has(example1.suite1.execPath)); + suite1Watcher = watchers.get(example1.suite1.execPath)!; + + example1.suite1.assert( + 'execPath1', ['s1t1', 's1t2'], suite1, uniqueIdC); + + example1.suite2.assert( + path.join(workspaceFolderUri.path, 'execPath2'), + ['s2t1', 's2t2 [.]', 's2t3'], suite2, uniqueIdC); + }) + + for (let t of testsForAdapterWithSuite1AndSuite2) this + .addTest(t.clone()); + + it('reload because of fswatcher event: touch(changed)', + async function() { + this.slow(200); + const newRoot = + await doAndWaitForReloadEvent(this, async () => { + suite1Watcher.sendChange(); + }); + assert.deepStrictEqual(newRoot, root); + assert.deepStrictEqual(testsEvents, [ + {type: 'started'}, + {type: 'finished', suite: root}, + ]); + }) + + it('reload because of fswatcher event: double touch(changed)', + async function() { + this.slow(200); + const oldRoot = root; + suite1Watcher.sendChange(); + suite1Watcher.sendChange(); + await waitFor(this, async () => { + return testsEvents.length >= 4; + }); + assert.ok( + deepStrictEqual( + testsEvents, + [ + {type: 'started'}, + {type: 'finished', suite: oldRoot}, + {type: 'started'}, + {type: 'finished', suite: oldRoot}, + ]) || + deepStrictEqual(testsEvents, [ + {type: 'started'}, + {type: 'started'}, + {type: 'finished', suite: oldRoot}, + {type: 'finished', suite: oldRoot}, + ])); + testsEvents.pop(); + testsEvents.pop(); + testsEvents.pop(); + testsEvents.pop(); + }) + + it('reload because of fswatcher event: double touch(changed) with delay', + async function() { + this.slow(200); + const oldRoot = root; + suite1Watcher.sendChange(); + setTimeout(() => { + suite1Watcher.sendChange(); + }, 20); + await waitFor(this, async () => { + return testsEvents.length >= 4; + }); + assert.ok( + deepStrictEqual( + testsEvents, + [ + {type: 'started'}, + {type: 'finished', suite: oldRoot}, + {type: 'started'}, + {type: 'finished', suite: oldRoot}, + ]) || + deepStrictEqual(testsEvents, [ + {type: 'started'}, + {type: 'started'}, + {type: 'finished', suite: oldRoot}, + {type: 'finished', suite: oldRoot}, + ])); + testsEvents.pop(); + testsEvents.pop(); + testsEvents.pop(); + testsEvents.pop(); + }) + + it('reload because of fswatcher event: touch(delete,create)', + async function() { + this.slow(200); + const newRoot = + await doAndWaitForReloadEvent(this, async () => { + suite1Watcher.sendDelete(); + suite1Watcher.sendCreate(); + }); + assert.deepStrictEqual(newRoot, root); + assert.deepStrictEqual(testsEvents, [ + {type: 'started'}, + {type: 'finished', suite: root}, + ]); + }) + + it('reload because of fswatcher event: double touch(delete,create)', + async function() { + this.slow(200); + const oldRoot = root; + suite1Watcher.sendChange(); + suite1Watcher.sendChange(); + await waitFor(this, async () => { + return testsEvents.length >= 4; + }); + assert.ok( + deepStrictEqual( + testsEvents, + [ + {type: 'started'}, + {type: 'finished', suite: oldRoot}, + {type: 'started'}, + {type: 'finished', suite: oldRoot}, + ]) || + deepStrictEqual(testsEvents, [ + {type: 'started'}, + {type: 'started'}, + {type: 'finished', suite: oldRoot}, + {type: 'finished', suite: oldRoot}, + ])); + testsEvents.pop(); + testsEvents.pop(); + testsEvents.pop(); + testsEvents.pop(); + }) + + it('reload because of fswatcher event: double touch(delete,create) with delay', + async function() { + this.slow(200); + const oldRoot = root; + suite1Watcher.sendChange(); + setTimeout(() => { + suite1Watcher.sendChange(); + }, 20); + await waitFor(this, async () => { + return testsEvents.length >= 4; + }); + assert.ok( + deepStrictEqual( + testsEvents, + [ + {type: 'started'}, + {type: 'finished', suite: oldRoot}, + {type: 'started'}, + {type: 'finished', suite: oldRoot}, + ]) || + deepStrictEqual(testsEvents, [ + {type: 'started'}, + {type: 'started'}, + {type: 'finished', suite: oldRoot}, + {type: 'finished', suite: oldRoot}, + ])); + testsEvents.pop(); + testsEvents.pop(); + testsEvents.pop(); + testsEvents.pop(); + }) + + it('reload because of fswatcher event: test added', + async function(this: Mocha.Context) { + this.slow(200); + const testListOutput = + example1.suite1.outputs[1][1].split('\n'); + assert.equal(testListOutput.length, 10); + testListOutput.splice( + 1, 0, ' s1t0', ' suite1.cpp:6', ' tag1'); + const withArgs = spawnStub.withArgs( + example1.suite1.execPath, + example1.suite1.outputs[1][0]); + withArgs.onCall(withArgs.callCount) + .returns( + new ChildProcessStub(testListOutput.join('\n'))); + + const oldRootChildren = [...root.children]; + const oldSuite1Children = [...suite1.children]; + const oldSuite2Children = [...suite2.children]; + + const newRoot = + await doAndWaitForReloadEvent(this, async () => { + suite1Watcher.sendDelete(); + suite1Watcher.sendCreate(); + }); + + assert.equal(newRoot, root); + assert.equal( + root.children.length, oldRootChildren.length); + for (let i = 0; i < oldRootChildren.length; i++) { + assert.equal(root.children[i], oldRootChildren[i]); + } + + assert.equal( + suite1.children.length, + oldSuite1Children.length + 1); + for (let i = 0; i < suite1.children.length; i++) { + assert.equal( + suite1.children[i + 1], oldSuite1Children[i]); + } + const newTest = suite1.children[0]; + assert.ok(!uniqueIdC.has(newTest.id)); + assert.equal(newTest.label, 's1t0'); + + assert.equal( + suite2.children.length, oldSuite2Children.length); + for (let i = 0; i < suite2.children.length; i++) { + assert.equal(suite2.children[i], oldSuite2Children[i]); + } + }) + it('reload because of fswatcher event: test deleted', + async function(this: Mocha.Context) { + this.slow(200); + const testListOutput = + example1.suite1.outputs[1][1].split('\n'); + assert.equal(testListOutput.length, 10); + testListOutput.splice(1, 3); + const withArgs = spawnStub.withArgs( + example1.suite1.execPath, + example1.suite1.outputs[1][0]); + withArgs.onCall(withArgs.callCount) + .returns( + new ChildProcessStub(testListOutput.join('\n'))); + + const oldRootChildren = [...root.children]; + const oldSuite1Children = [...suite1.children]; + const oldSuite2Children = [...suite2.children]; + + const newRoot = + await doAndWaitForReloadEvent(this, async () => { + suite1Watcher.sendDelete(); + suite1Watcher.sendCreate(); + }); + + assert.equal(newRoot, root); + assert.equal( + root.children.length, oldRootChildren.length); + for (let i = 0; i < oldRootChildren.length; i++) { + assert.equal(root.children[i], oldRootChildren[i]); + } + + assert.equal( + suite1.children.length + 1, + oldSuite1Children.length); + for (let i = 0; i < suite1.children.length; i++) { + assert.equal( + suite1.children[i], oldSuite1Children[i + 1]); + } + + assert.equal( + suite2.children.length, oldSuite2Children.length); + for (let i = 0; i < suite2.children.length; i++) { + assert.equal(suite2.children[i], oldSuite2Children[i]); + } + }) + }) + + context('executables=[{}] and env={...}', function() { + before(async function() { + await updateConfig( + 'executables', [{ + name: '${relDirname}/${basename} (${absDirname})', + path: 'execPath{1,2}', + cwd: '${workspaceFolder}/cwd', + env: { + 'C2LOCALTESTENV': 'c2localtestenv', + 'C2OVERRIDETESTENV': 'c2overridetestenv-l', + } + }]); + + vsfsWatchStub + .withArgs( + path.join(workspaceFolderUri.path, 'execPath{1,2}')) + .callsFake(handleCreateWatcherCb); + + vsFindFilesStub + .withArgs( + path.join(workspaceFolderUri.path, 'execPath{1,2}')) + .returns([ + vscode.Uri.file(example1.suite1.execPath), + vscode.Uri.file(example1.suite2.execPath), + ]); + await updateConfig('defaultEnv', { + 'C2GLOBALTESTENV': 'c2globaltestenv', + 'C2OVERRIDETESTENV': 'c2overridetestenv-g', }); - }), - ]; - - context( - 'executables=["execPath1", "${workspaceFolder}/execPath2"]', - function() { - before(function() { - return updateConfig( - 'executables', - ['execPath1', '${workspaceFolder}/execPath2']); }); - after(function() { - return updateConfig('executables', undefined); + after(async function() { + await updateConfig('executables', undefined); + await updateConfig('defaultEnv', undefined); }); beforeEach(async function() { example1.suite1.assert( - 'execPath1', ['s1t1', 's1t2'], suite1, uniqueIdC); + './execPath1 (' + workspaceFolderUri.path + ')', + ['s1t1', 's1t2'], suite1, uniqueIdC); example1.suite2.assert( - path.join(workspaceFolderUri.path, 'execPath2'), + './execPath2 (' + workspaceFolderUri.path + ')', ['s2t1', 's2t2 [.]', 's2t3'], suite2, uniqueIdC); }) for (let t of testsForAdapterWithSuite1AndSuite2) this.addTest( t.clone()); - it('reload because of fswatcher event: touch(changed)', - async function() { - this.slow(200); - const newRoot = - await doAndWaitForReloadEvent(this, async () => { - suite1Watcher.sendChange(); - }); - assert.deepStrictEqual(newRoot, root); - assert.deepStrictEqual(testsEvents, [ - {type: 'started'}, - {type: 'finished', suite: root}, - ]); - }) - - // TODO - // it('reload because of fswatcher event: double touch(changed)', - // async function() { - // this.slow(200); - // const newRoot = - // await doAndWaitForReloadEvent(this, async () => { - // suite1Watcher.sendChange(); - // }); - // assert.deepStrictEqual(newRoot, root); - // assert.deepStrictEqual(testsEvents, [ - // {type: 'started'}, - // {type: 'finished', suite: root}, - // {type: 'started'}, - // {type: 'finished', suite: root}, - // ]); - // }) - - it('reload because of fswatcher event: touch(delete,create)', - async function() { - this.slow(200); - const newRoot = - await doAndWaitForReloadEvent(this, async () => { - suite1Watcher.sendDelete(); - suite1Watcher.sendCreate(); - }); - assert.deepStrictEqual(newRoot, root); - assert.deepStrictEqual(testsEvents, [ - {type: 'started'}, - {type: 'finished', suite: root}, - ]); - }) - - // TODO - // it('reload because of fswatcher event: double touch(delete,create)', - // async function() { - // this.slow(200); - // const newRoot = - // await doAndWaitForReloadEvent(this, async () => { - // suite1Watcher.sendDelete(); - // suite1Watcher.sendCreate(); - // suite1Watcher.sendDelete(); - // suite1Watcher.sendCreate(); - // }); - // assert.deepStrictEqual(newRoot, root); - // assert.deepStrictEqual(testsEvents, [ - // {type: 'started'}, - // {type: 'finished', suite: root}, - // {type: 'started'}, - // {type: 'finished', suite: root}, - // ]); - // }) - - it('reload because of fswatcher event: test added', - async function(this: Mocha.Context) { - this.slow(200); - const testListOutput = - example1.suite1.outputs[1][1].split('\n'); - assert.equal(testListOutput.length, 10); - testListOutput.splice( - 1, 0, ' s1t0', ' suite1.cpp:6', ' tag1'); - const withArgs = spawnStub.withArgs( - example1.suite1.execPath, example1.suite1.outputs[1][0]); - withArgs.onCall(withArgs.callCount) - .returns( - new ChildProcessStub(testListOutput.join('\n'))); - - const oldRootChildren = [...root.children]; - const oldSuite1Children = [...suite1.children]; - const oldSuite2Children = [...suite2.children]; - - const newRoot = - await doAndWaitForReloadEvent(this, async () => { - suite1Watcher.sendDelete(); - suite1Watcher.sendCreate(); - }); - - assert.equal(newRoot, root); - assert.equal(root.children.length, oldRootChildren.length); - for (let i = 0; i < oldRootChildren.length; i++) { - assert.equal(root.children[i], oldRootChildren[i]); - } - - assert.equal( - suite1.children.length, oldSuite1Children.length + 1); - for (let i = 0; i < suite1.children.length; i++) { - assert.equal(suite1.children[i + 1], oldSuite1Children[i]); - } - const newTest = suite1.children[0]; - assert.ok(!uniqueIdC.has(newTest.id)); - assert.equal(newTest.label, 's1t0'); - - assert.equal( - suite2.children.length, oldSuite2Children.length); - for (let i = 0; i < suite2.children.length; i++) { - assert.equal(suite2.children[i], oldSuite2Children[i]); - } - }) - it('reload because of fswatcher event: test deleted', - async function(this: Mocha.Context) { - this.slow(200); - const testListOutput = - example1.suite1.outputs[1][1].split('\n'); - assert.equal(testListOutput.length, 10); - testListOutput.splice(1, 3); - const withArgs = spawnStub.withArgs( - example1.suite1.execPath, example1.suite1.outputs[1][0]); - withArgs.onCall(withArgs.callCount) - .returns( - new ChildProcessStub(testListOutput.join('\n'))); - - const oldRootChildren = [...root.children]; - const oldSuite1Children = [...suite1.children]; - const oldSuite2Children = [...suite2.children]; - - const newRoot = - await doAndWaitForReloadEvent(this, async () => { - suite1Watcher.sendDelete(); - suite1Watcher.sendCreate(); - }); - - assert.equal(newRoot, root); - assert.equal(root.children.length, oldRootChildren.length); - for (let i = 0; i < oldRootChildren.length; i++) { - assert.equal(root.children[i], oldRootChildren[i]); - } - - assert.equal( - suite1.children.length + 1, oldSuite1Children.length); - for (let i = 0; i < suite1.children.length; i++) { - assert.equal(suite1.children[i], oldSuite1Children[i + 1]); - } - - assert.equal( - suite2.children.length, oldSuite2Children.length); - for (let i = 0; i < suite2.children.length; i++) { - assert.equal(suite2.children[i], oldSuite2Children[i]); - } - }) + it('should get execution options', async function() { + { + const withArgs = spawnStub.withArgs( + example1.suite1.execPath, example1.suite1.outputs[2][0]); + withArgs.onCall(withArgs.callCount) + .callsFake((p: string, args: string[], ops: any) => { + assert.equal( + ops.cwd, path.join(workspaceFolderUri.path, 'cwd')); + assert.equal(ops.env.C2LOCALTESTENV, 'c2localtestenv'); + assert.ok(!ops.env.hasOwnProperty('C2GLOBALTESTENV')); + assert.equal( + ops.env.C2OVERRIDETESTENV, 'c2overridetestenv-l'); + return new ChildProcessStub( + example1.suite1.outputs[2][1]); + }); + + const cc = withArgs.callCount; + await adapter.run([suite1.id]); + assert.equal(withArgs.callCount, cc + 1) + } + { + const withArgs = spawnStub.withArgs( + example1.suite2.execPath, example1.suite2.outputs[2][0]); + withArgs.onCall(withArgs.callCount) + .callsFake((p: string, args: string[], ops: any) => { + assert.equal( + ops.cwd, path.join(workspaceFolderUri.path, 'cwd')); + assert.equal(ops.env.C2LOCALTESTENV, 'c2localtestenv'); + assert.ok(!ops.env.hasOwnProperty('C2GLOBALTESTENV')); + assert.equal( + ops.env.C2OVERRIDETESTENV, 'c2overridetestenv-l'); + return new ChildProcessStub( + example1.suite2.outputs[2][1]); + }); + const cc = withArgs.callCount; + await adapter.run([suite2.id]); + assert.equal(withArgs.callCount, cc + 1) + } + }) }) - - context('executables=[{}] and env={...}', function() { - before(async function() { - await updateConfig('executables', [{ - name: '${dirname}: ${name} (${absDirname})', - path: '.', - regex: 'execPath(1|2)', - cwd: '${workspaceFolder}/cwd', - env: { - 'C2LOCALTESTENV': 'c2localtestenv', - 'C2OVERRIDETESTENV': 'c2overridetestenv-l', - } - }]); - await updateConfig('defaultEnv', { - 'C2GLOBALTESTENV': 'c2globaltestenv', - 'C2OVERRIDETESTENV': 'c2overridetestenv-g', - }); - }); - - after(async function() { - await updateConfig('executables', undefined); - await updateConfig('defaultEnv', undefined); - }); - - beforeEach(async function() { - example1.suite1.assert( - ': execPath1 (' + workspaceFolderUri.path + ')', - ['s1t1', 's1t2'], suite1, uniqueIdC); - - example1.suite2.assert( - ': execPath2 (' + workspaceFolderUri.path + ')', - ['s2t1', 's2t2 [.]', 's2t3'], suite2, uniqueIdC); - }) - - for (let t of testsForAdapterWithSuite1AndSuite2) this.addTest( - t.clone()); - - it('should get execution options', async function() { - { - const withArgs = spawnStub.withArgs( - example1.suite1.execPath, example1.suite1.outputs[2][0]); - withArgs.onCall(withArgs.callCount) - .callsFake((p: string, args: string[], ops: any) => { - assert.equal( - ops.cwd, path.join(workspaceFolderUri.path, 'cwd')); - assert.equal(ops.env.C2LOCALTESTENV, 'c2localtestenv'); - assert.ok(!ops.env.hasOwnProperty('C2GLOBALTESTENV')); - assert.equal( - ops.env.C2OVERRIDETESTENV, 'c2overridetestenv-l'); - return new ChildProcessStub(example1.suite1.outputs[2][1]); - }); - - const cc = withArgs.callCount; - await adapter.run([suite1.id]); - assert.equal(withArgs.callCount, cc + 1) - } - { - const withArgs = spawnStub.withArgs( - example1.suite2.execPath, example1.suite2.outputs[2][0]); - withArgs.onCall(withArgs.callCount) - .callsFake((p: string, args: string[], ops: any) => { - assert.equal( - ops.cwd, path.join(workspaceFolderUri.path, 'cwd')); - assert.equal(ops.env.C2LOCALTESTENV, 'c2localtestenv'); - assert.ok(!ops.env.hasOwnProperty('C2GLOBALTESTENV')); - assert.equal( - ops.env.C2OVERRIDETESTENV, 'c2overridetestenv-l'); - return new ChildProcessStub(example1.suite2.outputs[2][1]); - }); - const cc = withArgs.callCount; - await adapter.run([suite2.id]); - assert.equal(withArgs.callCount, cc + 1) - } }) - }) - }) - - context( - 'executables=["execPath1", "execPath2", "execPath3"]', - async function() { - before(function() { - return updateConfig( - 'executables', ['execPath1', 'execPath2', 'execPath3']); - }); - after(function() { - return updateConfig('executables', undefined); - }); - - it('run suite3 one-by-one', async function() { - this.slow(300); - assert.equal(root.children.length, 3); - assert.equal(root.children[0].type, 'suite'); - const suite3 = root.children[2]; - assert.equal(suite3.children.length, 33); - - spawnStub.withArgs(example1.suite3.execPath).throwsArg(1); - - const runAndCheckEvents = async (test: TestInfo) => { - assert.equal(testStatesEvents.length, 0); - - await adapter.run([test.id]); + context( + 'executables=["execPath1", "execPath2", "execPath3"]', + async function() { + before(function() { + return updateConfig( + 'executables', ['execPath1', 'execPath2', 'execPath3']); + }); - assert.equal(testStatesEvents.length, 6, inspect(test)); + after(function() { + return updateConfig('executables', undefined); + }); - assert.deepStrictEqual( - {type: 'started', tests: [test.id]}, testStatesEvents[0]); - assert.deepStrictEqual( - {type: 'suite', state: 'running', suite: suite3}, - testStatesEvents[1]); + it('run suite3 one-by-one', async function() { + this.slow(300); + assert.equal(root.children.length, 3); + assert.equal(root.children[0].type, 'suite'); + const suite3 = root.children[2]; + assert.equal(suite3.children.length, 33); - assert.equal(testStatesEvents[2].type, 'test'); - assert.equal((testStatesEvents[2]).state, 'running'); - assert.equal((testStatesEvents[2]).test, test); + spawnStub.withArgs(example1.suite3.execPath).throwsArg(1); - assert.equal(testStatesEvents[3].type, 'test'); - assert.ok( - (testStatesEvents[3]).state == 'passed' || - (testStatesEvents[3]).state == 'skipped' || - (testStatesEvents[3]).state == 'failed'); - assert.equal((testStatesEvents[3]).test, test); + const runAndCheckEvents = async (test: TestInfo) => { + assert.equal(testStatesEvents.length, 0); - assert.deepStrictEqual( - {type: 'suite', state: 'completed', suite: suite3}, - testStatesEvents[4]); - assert.deepStrictEqual({type: 'finished'}, testStatesEvents[5]); + await adapter.run([test.id]); - while (testStatesEvents.length) testStatesEvents.pop(); - }; + assert.equal(testStatesEvents.length, 6, inspect(test)); - for (let test of suite3.children) { - assert.equal(test.type, 'test'); - await runAndCheckEvents(test); - } - }) - }) - }) + assert.deepStrictEqual( + {type: 'started', tests: [test.id]}, + testStatesEvents[0]); + assert.deepStrictEqual( + {type: 'suite', state: 'running', suite: suite3}, + testStatesEvents[1]); - specify('load executables=', async function() { - this.slow(300); - await updateConfig('executables', example1.suite1.execPath); - adapter = createAdapterAndSubscribe(); - - await adapter.load(); - assert.equal(testsEvents.length, 2); - assert.equal( - (testsEvents[testsEvents.length - 1]) - .suite!.children.length, - 1); - testsEvents.pop(); - testsEvents.pop(); - - disposeAdapterAndSubscribers(); - await updateConfig('executables', undefined); - }) + assert.equal(testStatesEvents[2].type, 'test'); + assert.equal( + (testStatesEvents[2]).state, 'running'); + assert.equal((testStatesEvents[2]).test, test); + + assert.equal(testStatesEvents[3].type, 'test'); + assert.ok( + (testStatesEvents[3]).state == 'passed' || + (testStatesEvents[3]).state == 'skipped' || + (testStatesEvents[3]).state == 'failed'); + assert.equal((testStatesEvents[3]).test, test); + + assert.deepStrictEqual( + {type: 'suite', state: 'completed', suite: suite3}, + testStatesEvents[4]); + assert.deepStrictEqual( + {type: 'finished'}, testStatesEvents[5]); + + while (testStatesEvents.length) testStatesEvents.pop(); + }; + + for (let test of suite3.children) { + assert.equal(test.type, 'test'); + await runAndCheckEvents(test); + } + }) + }) + }) - specify( - 'load executables=["execPath1", "execPath2"] with error', - async function() { + specify('load executables=', async function() { this.slow(300); - await updateConfig('executables', ['execPath1', 'execPath2']) + await updateConfig('executables', example1.suite1.execPath); adapter = createAdapterAndSubscribe(); - const withArgs = spawnStub.withArgs( - example1.suite2.execPath, example1.suite2.outputs[1][0]); - withArgs.onCall(withArgs.callCount).throws( - 'dummy error for testing (should be handled)'); - await adapter.load(); + assert.equal(testsEvents.length, 2); + assert.equal( + (testsEvents[testsEvents.length - 1]) + .suite!.children.length, + 1); testsEvents.pop(); testsEvents.pop(); @@ -1406,152 +1549,188 @@ describe('C2TestAdapter', function() { await updateConfig('executables', undefined); }) - specify( - 'load executables=["execPath1", "execPath2Copy"]; delete; sleep 3; create', - async function(this: Mocha.Context) { - const watchTimeout = 5500; - await updateConfig('defaultExecWatchTimeout', watchTimeout); - this.timeout(watchTimeout + 2500 /* because of 'delay' */); - this.slow(watchTimeout + 2500 /* because of 'delay' */); - const execPath2CopyPath = - path.join(workspaceFolderUri.path, 'execPath2Copy'); - - for (let scenario of example1.suite2.outputs) { - spawnStub.withArgs(execPath2CopyPath, scenario[0]) - .callsFake(function() { - return new ChildProcessStub(scenario[1]); - }); - } + specify( + 'load executables=["execPath1", "execPath2"] with error', + async function() { + this.slow(300); + await updateConfig('executables', ['execPath1', 'execPath2']) + adapter = createAdapterAndSubscribe(); - c2fsStatStub.withArgs(execPath2CopyPath) - .callsFake(handleStatExistsFile); + const withArgs = spawnStub.withArgs( + example1.suite2.execPath, example1.suite2.outputs[1][0]); + withArgs.onCall(withArgs.callCount).throws( + 'dummy error for testing (should be handled)'); - vsfsWatchStub.withArgs(execPath2CopyPath, true, false, false) - .callsFake(handleCreateWatcherCb); + await adapter.load(); + testsEvents.pop(); + testsEvents.pop(); - await updateConfig('executables', ['execPath1', 'execPath2Copy']) - adapter = createAdapterAndSubscribe(); - - await adapter.load(); + disposeAdapterAndSubscribers(); + await updateConfig('executables', undefined); + }) - assert.equal( - (testsEvents[testsEvents.length - 1]) - .suite!.children.length, - 2); - testsEvents.pop(); - testsEvents.pop(); + specify( + 'load executables=["execPath1", "execPath2Copy"]; delete; sleep 3; create', + async function(this: Mocha.Context) { + const watchTimeout = 5500; + await updateConfig('defaultExecWatchTimeout', watchTimeout); + this.timeout(watchTimeout + 2500 /* because of 'delay' */); + this.slow(watchTimeout + 2500 /* because of 'delay' */); + const execPath2CopyPath = + path.join(workspaceFolderUri.path, 'execPath2Copy'); + + for (let scenario of example1.suite2.outputs) { + spawnStub.withArgs(execPath2CopyPath, scenario[0]) + .callsFake(function() { + return new ChildProcessStub(scenario[1]); + }); + } - assert.ok(watchers.has(execPath2CopyPath)); - const watcher = watchers.get(execPath2CopyPath)!; - - let start: number = 0; - const newRoot = await doAndWaitForReloadEvent(this, async () => { - c2fsStatStub.withArgs(execPath2CopyPath) - .callsFake(handleStatNotExists); - start = Date.now(); - watcher.sendDelete(); - setTimeout(() => { - assert.equal(testsEvents.length, 0); - }, 1500); - setTimeout(() => { c2fsStatStub.withArgs(execPath2CopyPath) .callsFake(handleStatExistsFile); - watcher.sendCreate(); - }, 3000); - }, 40000); - const elapsed = Date.now() - start; - assert.equal(testsEvents.length, 2); - testsEvents.pop(); - testsEvents.pop(); - for (let scenario of example1.suite2.outputs) { - spawnStub.withArgs(execPath2CopyPath, scenario[0]).callsFake(() => { - throw Error('restore'); - }); - } - c2fsStatStub.withArgs(execPath2CopyPath).callsFake(() => { - throw Error('restore'); - }); - vsfsWatchStub.withArgs(execPath2CopyPath, true, false, false) - .callsFake(() => { + vsfsWatchStub.withArgs(execPath2CopyPath) + .callsFake(handleCreateWatcherCb); + + vsFindFilesStub.withArgs(execPath2CopyPath).returns([ + vscode.Uri.file(execPath2CopyPath) + ]); + + await updateConfig('executables', ['execPath1', 'execPath2Copy']) + adapter = createAdapterAndSubscribe(); + + await adapter.load(); + + assert.equal( + (testsEvents[testsEvents.length - 1]) + .suite!.children.length, + 2); + testsEvents.pop(); + testsEvents.pop(); + + assert.ok(watchers.has(execPath2CopyPath)); + const watcher = watchers.get(execPath2CopyPath)!; + + let start: number = 0; + const newRoot = await doAndWaitForReloadEvent(this, async () => { + c2fsStatStub.withArgs(execPath2CopyPath) + .callsFake(handleStatNotExists); + start = Date.now(); + watcher.sendDelete(); + setTimeout(() => { + assert.equal(testsEvents.length, 0); + }, 1500); + setTimeout(() => { + c2fsStatStub.withArgs(execPath2CopyPath) + .callsFake(handleStatExistsFile); + watcher.sendCreate(); + }, 3000); + }, 40000); + const elapsed = Date.now() - start; + + assert.equal(testsEvents.length, 2); + testsEvents.pop(); + testsEvents.pop(); + for (let scenario of example1.suite2.outputs) { + spawnStub.withArgs(execPath2CopyPath, scenario[0]) + .callsFake(() => { + throw Error('restore'); + }); + } + c2fsStatStub.withArgs(execPath2CopyPath).callsFake(() => { throw Error('restore'); }); - disposeAdapterAndSubscribers(); - await updateConfig('executables', undefined); - await updateConfig('defaultExecWatchTimeout', undefined); - - assert.equal(newRoot.children.length, 2); - assert.ok(3000 < elapsed, inspect(elapsed)); - assert.ok(elapsed < watchTimeout + 2400, inspect(elapsed)); - }) - - specify( - 'load executables=["execPath1", "execPath2Copy"]; delete second', - async function() { - const watchTimeout = 5000; - await updateConfig('defaultExecWatchTimeout', watchTimeout); - this.timeout(watchTimeout + 2500 /* because of 'delay' */); - this.slow(watchTimeout + 2500 /* because of 'delay' */); - const execPath2CopyPath = - path.join(workspaceFolderUri.path, 'execPath2Copy'); - - for (let scenario of example1.suite2.outputs) { - spawnStub.withArgs(execPath2CopyPath, scenario[0]) - .callsFake(function() { - return new ChildProcessStub(scenario[1]); - }); - } - - c2fsStatStub.withArgs(execPath2CopyPath) - .callsFake(handleStatExistsFile); - - vsfsWatchStub.withArgs(execPath2CopyPath, true, false, false) - .callsFake(handleCreateWatcherCb); + vsfsWatchStub.withArgs(execPath2CopyPath).callsFake(() => { + throw Error('restore'); + }); + vsFindFilesStub.withArgs(execPath2CopyPath).callsFake(() => { + throw Error('restore'); + }); + disposeAdapterAndSubscribers(); + await updateConfig('executables', undefined); + await updateConfig('defaultExecWatchTimeout', undefined); - await updateConfig('executables', ['execPath1', 'execPath2Copy']) - adapter = createAdapterAndSubscribe(); + assert.equal(newRoot.children.length, 2); + assert.ok(3000 < elapsed, inspect(elapsed)); + assert.ok(elapsed < watchTimeout + 2400, inspect(elapsed)); + }) - await adapter.load(); + specify( + 'load executables=["execPath1", "execPath2Copy"]; delete second', + async function() { + const watchTimeout = 5000; + await updateConfig('defaultExecWatchTimeout', watchTimeout); + this.timeout(watchTimeout + 2500 /* because of 'delay' */); + this.slow(watchTimeout + 2500 /* because of 'delay' */); + const execPath2CopyPath = + path.join(workspaceFolderUri.path, 'execPath2Copy'); + + for (let scenario of example1.suite2.outputs) { + spawnStub.withArgs(execPath2CopyPath, scenario[0]) + .callsFake(function() { + return new ChildProcessStub(scenario[1]); + }); + } - assert.equal( - (testsEvents[testsEvents.length - 1]) - .suite!.children.length, - 2); - testsEvents.pop(); - testsEvents.pop(); + c2fsStatStub.withArgs(execPath2CopyPath) + .callsFake(handleStatExistsFile); - assert.ok(watchers.has(execPath2CopyPath)); - const watcher = watchers.get(execPath2CopyPath)!; - - let start: number = 0; - const newRoot = await doAndWaitForReloadEvent(this, async () => { - c2fsStatStub.withArgs(execPath2CopyPath) - .callsFake(handleStatNotExists); - start = Date.now(); - watcher.sendDelete(); - }, 40000); - const elapsed = Date.now() - start; - testsEvents.pop(); - testsEvents.pop(); - for (let scenario of example1.suite2.outputs) { - spawnStub.withArgs(execPath2CopyPath, scenario[0]).callsFake(() => { - throw Error('restore'); - }); - } - c2fsStatStub.withArgs(execPath2CopyPath).callsFake(() => { - throw Error('restore'); - }); - vsfsWatchStub.withArgs(execPath2CopyPath, true, false, false) - .callsFake(() => { + vsfsWatchStub.withArgs(execPath2CopyPath) + .callsFake(handleCreateWatcherCb); + + vsFindFilesStub.withArgs(execPath2CopyPath).returns([ + vscode.Uri.file(execPath2CopyPath) + ]); + + await updateConfig('executables', ['execPath1', 'execPath2Copy']) + adapter = createAdapterAndSubscribe(); + + await adapter.load(); + + assert.equal( + (testsEvents[testsEvents.length - 1]) + .suite!.children.length, + 2); + testsEvents.pop(); + testsEvents.pop(); + + assert.ok(watchers.has(execPath2CopyPath)); + const watcher = watchers.get(execPath2CopyPath)!; + + let start: number = 0; + const newRoot = await doAndWaitForReloadEvent(this, async () => { + c2fsStatStub.withArgs(execPath2CopyPath) + .callsFake(handleStatNotExists); + start = Date.now(); + watcher.sendDelete(); + }, 40000); + const elapsed = Date.now() - start; + testsEvents.pop(); + testsEvents.pop(); + for (let scenario of example1.suite2.outputs) { + spawnStub.withArgs(execPath2CopyPath, scenario[0]) + .callsFake(() => { + throw Error('restore'); + }); + } + c2fsStatStub.withArgs(execPath2CopyPath).callsFake(() => { throw Error('restore'); }); - disposeAdapterAndSubscribers(); - await updateConfig('executables', undefined); - await updateConfig('defaultExecWatchTimeout', undefined); + vsfsWatchStub.withArgs(execPath2CopyPath).callsFake(() => { + throw Error('restore'); + }); + vsFindFilesStub.withArgs(execPath2CopyPath).callsFake(() => { + throw Error('restore'); + }); + disposeAdapterAndSubscribers(); + await updateConfig('executables', undefined); + await updateConfig('defaultExecWatchTimeout', undefined); - assert.equal(newRoot.children.length, 1); - assert.ok(watchTimeout < elapsed, inspect(elapsed)); - assert.ok(elapsed < watchTimeout + 2400, inspect(elapsed)); - }) - }) -}) \ No newline at end of file + assert.equal(newRoot.children.length, 1); + assert.ok(watchTimeout < elapsed, inspect(elapsed)); + assert.ok(elapsed < watchTimeout + 2400, inspect(elapsed)); + }) + }) + }) + + // TODO tests outside working directory \ No newline at end of file diff --git a/src/test/QueueGraph.test.ts b/src/test/QueueGraph.test.ts index 6b8a7fcb..a1dd4fae 100644 --- a/src/test/QueueGraph.test.ts +++ b/src/test/QueueGraph.test.ts @@ -7,7 +7,7 @@ import {promisify} from 'util'; import {QueueGraphNode} from '../QueueGraph'; -describe.only('QueueGraphNode', function() { +describe('QueueGraphNode', function() { this.enableTimeouts(false); // TODO async function waitFor( @@ -58,38 +58,6 @@ describe.only('QueueGraphNode', function() { assert.ok(second); }) - // it('promise practice 3', async function() { - // let resolve: Function; - // let second = false; - // let p = new Promise(r => { - // resolve = r; - // }); - // assert.ok(!second); - - // let third = false; - // p = p.then(() => { - // return new Promise(() => {}) - // .then(() => { - // second = true; - // }) - // .then(() => { - // third = true; - // }); - // }); - // assert.ok(!second); - - // resolve!(); - // await waitFor(this, () => { - // return second; - // }); - // assert.ok(second); - - // await waitFor(this, () => { - // return third; - // }); - // assert.ok(third); - // }) - context('example 1', function() { /** * node1 <___ node @@ -100,6 +68,7 @@ describe.only('QueueGraphNode', function() { const nodeD = new QueueGraphNode('nodeD', [node1, node2]); it('add:depends before', async function() { + this.slow(150); let startD: Function; let hasRunDatOnce = false; nodeD.then(() => { From a032d521a9344ac05203a796011cd71c1d80664d Mon Sep 17 00:00:00 2001 From: Mate Pek Date: Sat, 27 Oct 2018 00:14:33 +0200 Subject: [PATCH 08/49] new watcher system based on vscode --- package.json | 6 +-- src/C2ExecutableInfo.ts | 72 ++++++++++++++++++------------ src/C2TestAdapter.ts | 22 ++++----- src/test/C2TestAdapter.test.ts | 81 ++++++++++++++++++++-------------- src/test/FsWrapper.test.ts | 10 +++++ src/test/QueueGraph.test.ts | 2 - 6 files changed, 115 insertions(+), 78 deletions(-) diff --git a/package.json b/package.json index b562b706..7564594a 100644 --- a/package.json +++ b/package.json @@ -75,7 +75,7 @@ "title": "Configuration for the Catch2 Test Explorer extension", "properties": { "catch2TestExplorer.executables": { - "description": "The location of your test executables (relative to the workspace folder or absolute path) and with a lot of other setting.", + "description": "The location of your test executables (relative to the workspace folder or absolute path) and with a lot of other setting. (https://code.visualstudio.com/docs/editor/codebasics#_advanced-search-options)", "type": [ "string", "array", @@ -83,8 +83,8 @@ ], "default": [ { - "name": "${relDirname} : ${name}", - "path": "build", + "name": "${basename} (${relDirname}/)", + "path": "{build|out}/**/*{test|Test|TEST}*", "regex": "(t|T)est", "recursiveRegex": false, "workerMaxNumber": 1, diff --git a/src/C2ExecutableInfo.ts b/src/C2ExecutableInfo.ts index 1cc21e3d..afe1e3c5 100644 --- a/src/C2ExecutableInfo.ts +++ b/src/C2ExecutableInfo.ts @@ -33,11 +33,33 @@ export class C2ExecutableInfo implements vscode.Disposable { } async load(): Promise { - try { - if (this.pattern.startsWith(this._adapter.workspaceFolder.uri - .path)) { // TODO ez az if nem a legjobb + const wsUri = this._adapter.workspaceFolder.uri; + + + const isAbsolute = path.isAbsolute(this.pattern); + const absPattern = + isAbsolute ? this.pattern : path.resolve(wsUri.fsPath, this.pattern); + const absPatternAsUri = vscode.Uri.file(absPattern); + const relativeToWs = path.relative(wsUri.fsPath, absPatternAsUri.fsPath); + const isPartOfWs = !relativeToWs.startsWith('..'); + + let fileUris: vscode.Uri[] = []; + + if (!isAbsolute || (isAbsolute && isPartOfWs)) { + let relativePattern: vscode.RelativePattern; + if (isAbsolute) { + relativePattern = new vscode.RelativePattern( + this._adapter.workspaceFolder, relativeToWs); + } else { + relativePattern = new vscode.RelativePattern( + this._adapter.workspaceFolder, this.pattern); + } + fileUris = + await vscode.workspace.findFiles(relativePattern, undefined, 1000); + try { + // abs path is required this._watcher = vscode.workspace.createFileSystemWatcher( - this.pattern, false, false, false); + relativePattern, false, false, false); this._disposables.push(this._watcher); this._disposables.push( this._watcher.onDidCreate(this._handleCreate, this)); @@ -45,34 +67,26 @@ export class C2ExecutableInfo implements vscode.Disposable { this._watcher.onDidChange(this._handleChange, this)); this._disposables.push( this._watcher.onDidDelete(this._handleDelete, this)); + } catch (e) { + this._adapter.log.error(inspect([e, this])); } - } catch (e) { - this._adapter.log.error(inspect([e, this])); - } - - let fileUris; - try { - fileUris = await vscode.workspace.findFiles(this.pattern); - } catch (e) { - fileUris = [vscode.Uri.file(this.pattern)]; - debugger; + } else { + fileUris.push(absPatternAsUri); } for (let i = 0; i < fileUris.length; i++) { const file = fileUris[i]; - if (await this._verifyIsCatch2TestExecutable(file.path)) { + if (await this._verifyIsCatch2TestExecutable(file.fsPath)) { let resolvedName = this.name; let resolvedCwd = this.cwd; try { + const relPath = path.relative(wsUri.path, file.path); const varToValue: [string, string][] = [ - ['${name}', file.path], + ['${absName}', file.path], + ['${relName}', relPath], ['${basename}', path.basename(file.path)], ['${absDirname}', path.dirname(file.path)], - [ - '${relDirname}', - path.dirname(path.relative( - this._adapter.workspaceFolder.uri.fsPath, file.path)) - ], + ['${relDirname}', path.dirname(relPath)], ...this._adapter.variableToValue, ]; resolvedName = resolveVariables(this.name, varToValue); @@ -84,7 +98,7 @@ export class C2ExecutableInfo implements vscode.Disposable { const suite = this._allTests.createChildSuite( resolvedName, file.path, {cwd: resolvedCwd, env: this.env}); - this._executables.set(file.path, suite); + this._executables.set(file.fsPath, suite); } } @@ -96,19 +110,19 @@ export class C2ExecutableInfo implements vscode.Disposable { } private _handleEverything(uri: vscode.Uri) { - let suite = this._executables.get(uri.path); + let suite = this._executables.get(uri.fsPath); if (suite == undefined) { suite = this._allTests.createChildSuite( - this.name, this.pattern, {cwd: this.cwd, env: this.env}); - this._executables.set(uri.path, suite); + this.name, uri.fsPath, {cwd: this.cwd, env: this.env}); + this._executables.set(uri.fsPath, suite); } const x = (exists: boolean, startTime: number, timeout: number, delay: number): Promise => { if ((Date.now() - startTime) > timeout) { - this._executables.delete(uri.path); + this._executables.delete(uri.fsPath); this._adapter.testsEmitter.fire({type: 'started'}); this._allTests.removeChild(suite!); this._adapter.testsEmitter.fire( @@ -130,7 +144,7 @@ export class C2ExecutableInfo implements vscode.Disposable { }); } return promisify(setTimeout)(Math.min(delay * 2, 2000)).then(() => { - return c2fs.existsAsync(uri.path).then((exists: boolean) => { + return c2fs.existsAsync(uri.fsPath).then((exists: boolean) => { return x(exists, startTime, timeout, Math.min(delay * 2, 2000)); }); }); @@ -143,7 +157,7 @@ export class C2ExecutableInfo implements vscode.Disposable { } private _handleCreate(uri: vscode.Uri) { - if (this._executables.has(uri.path)) { + if (this._executables.has(uri.fsPath)) { // we are fine: the delete event is still running until it's timeout return; } @@ -175,7 +189,7 @@ export class C2ExecutableInfo implements vscode.Disposable { if (suites.length > 1) { let i = 1; for (const suite of suites) { - suite.label = suite.origLabel + '(' + String(i++) + ')'; + suite.label = String(i++) + ') ' + suite.origLabel; } } } diff --git a/src/C2TestAdapter.ts b/src/C2TestAdapter.ts index f885f4af..ff924332 100644 --- a/src/C2TestAdapter.ts +++ b/src/C2TestAdapter.ts @@ -339,20 +339,20 @@ export class C2TestAdapter implements TestAdapter, vscode.Disposable { return path.isAbsolute(p) ? p : this.resolveRelPath(p); }; - const createFromObject = (o: Object): C2ExecutableInfo => { - const name: string = o.hasOwnProperty('name') ? (o)['name'] : - '${relDirname} : ${name}'; - if (!o.hasOwnProperty('path') || (o)['path'] === null) { + const createFromObject = (obj: Object): C2ExecutableInfo => { + const name: string = obj.hasOwnProperty('name') ? + (obj)['name'] : + '${basename} (${relDirname}/)'; + if (!obj.hasOwnProperty('path') || (obj)['path'] === null) { console.warn(Error('\'path\' is a requireds property.')); - throw Error('Wrong object: ' + inspect(o)); + throw Error('Wrong object: ' + inspect(obj)); } - const p: string = - fullPath(resolveVariables((o)['path'], this.variableToValue)); - const cwd: string = - o.hasOwnProperty('cwd') ? (o)['cwd'] : globalWorkingDirectory; - const env: {[prop: string]: any} = o.hasOwnProperty('env') ? + const p: string = (obj)['path']; + const cwd: string = obj.hasOwnProperty('cwd') ? (obj)['cwd'] : + globalWorkingDirectory; + const env: {[prop: string]: any} = obj.hasOwnProperty('env') ? this.getGlobalAndCurrentEnvironmentVariables( - config, (o)['env']) : + config, (obj)['env']) : this.getGlobalAndDefaultEnvironmentVariables(config); return new C2ExecutableInfo(this, allTests, name, p, cwd, env); diff --git a/src/test/C2TestAdapter.test.ts b/src/test/C2TestAdapter.test.ts index 80df5786..d8da1e1e 100644 --- a/src/test/C2TestAdapter.test.ts +++ b/src/test/C2TestAdapter.test.ts @@ -262,12 +262,13 @@ describe( const watchers: Map = new Map(); function handleCreateWatcherCb( - path: string, ignoreCreateEvents: boolean, + p: vscode.RelativePattern, ignoreCreateEvents: boolean, ignoreChangeEvents: boolean, ignoreDeleteEvents: boolean) { + const pp = path.join(p.base, p.pattern); const e = new FileSystemWatcherStub( - vscode.Uri.file(path), ignoreCreateEvents, ignoreChangeEvents, + vscode.Uri.file(pp), ignoreCreateEvents, ignoreChangeEvents, ignoreDeleteEvents); - watchers.set(path, e); + watchers.set(pp, e); return e; } @@ -298,7 +299,16 @@ describe( syscall: 'stat' }, undefined); - }; + } + + function matchRelativePattern(p: string) { + return sinon.match((actual: vscode.RelativePattern) => { + const required = new vscode.RelativePattern( + workspaceFolder, path.relative(workspaceFolderUri.path, p)); + return required.base == actual.base && + required.pattern == actual.pattern; + }); + } before(function() { for (let suite of example1.outputs) { @@ -310,7 +320,8 @@ describe( c2fsStatStub.withArgs(suite[0]).callsFake(handleStatExistsFile); - vsfsWatchStub.withArgs(suite[0]).callsFake(handleCreateWatcherCb); + vsfsWatchStub.withArgs(matchRelativePattern(suite[0])) + .callsFake(handleCreateWatcherCb); } const dirContent: Map = new Map(); @@ -327,9 +338,11 @@ describe( dirContent.forEach((v: vscode.Uri[], k: string) => { assert.equal(workspaceFolderUri.path, k); - vsFindFilesStub.withArgs(k).returns(v); + vsFindFilesStub.withArgs(matchRelativePattern(k)).returns(v); for (const p of v) { - vsFindFilesStub.withArgs(p.path).returns([p]); + vsFindFilesStub.withArgs(matchRelativePattern(p.path)).returns([ + p + ]); } }); }) @@ -1109,7 +1122,7 @@ describe( return updateConfig('executables', undefined); }); - let suite1Watcher; + let suite1Watcher: FileSystemWatcherStub; beforeEach(async function() { assert.equal(watchers.size, 2); @@ -1390,13 +1403,13 @@ describe( }]); vsfsWatchStub - .withArgs( - path.join(workspaceFolderUri.path, 'execPath{1,2}')) + .withArgs(matchRelativePattern( + path.join(workspaceFolderUri.path, 'execPath{1,2}'))) .callsFake(handleCreateWatcherCb); vsFindFilesStub - .withArgs( - path.join(workspaceFolderUri.path, 'execPath{1,2}')) + .withArgs(matchRelativePattern( + path.join(workspaceFolderUri.path, 'execPath{1,2}'))) .returns([ vscode.Uri.file(example1.suite1.execPath), vscode.Uri.file(example1.suite2.execPath), @@ -1589,12 +1602,11 @@ describe( c2fsStatStub.withArgs(execPath2CopyPath) .callsFake(handleStatExistsFile); - vsfsWatchStub.withArgs(execPath2CopyPath) + vsfsWatchStub.withArgs(matchRelativePattern(execPath2CopyPath)) .callsFake(handleCreateWatcherCb); - vsFindFilesStub.withArgs(execPath2CopyPath).returns([ - vscode.Uri.file(execPath2CopyPath) - ]); + vsFindFilesStub.withArgs(matchRelativePattern(execPath2CopyPath)) + .returns([vscode.Uri.file(execPath2CopyPath)]); await updateConfig('executables', ['execPath1', 'execPath2Copy']) adapter = createAdapterAndSubscribe(); @@ -1640,12 +1652,14 @@ describe( c2fsStatStub.withArgs(execPath2CopyPath).callsFake(() => { throw Error('restore'); }); - vsfsWatchStub.withArgs(execPath2CopyPath).callsFake(() => { - throw Error('restore'); - }); - vsFindFilesStub.withArgs(execPath2CopyPath).callsFake(() => { - throw Error('restore'); - }); + vsfsWatchStub.withArgs(matchRelativePattern(execPath2CopyPath)) + .callsFake(() => { + throw Error('restore'); + }); + vsFindFilesStub.withArgs(matchRelativePattern(execPath2CopyPath)) + .callsFake(() => { + throw Error('restore'); + }); disposeAdapterAndSubscribers(); await updateConfig('executables', undefined); await updateConfig('defaultExecWatchTimeout', undefined); @@ -1675,12 +1689,11 @@ describe( c2fsStatStub.withArgs(execPath2CopyPath) .callsFake(handleStatExistsFile); - vsfsWatchStub.withArgs(execPath2CopyPath) + vsfsWatchStub.withArgs(matchRelativePattern(execPath2CopyPath)) .callsFake(handleCreateWatcherCb); - vsFindFilesStub.withArgs(execPath2CopyPath).returns([ - vscode.Uri.file(execPath2CopyPath) - ]); + vsFindFilesStub.withArgs(matchRelativePattern(execPath2CopyPath)) + .returns([vscode.Uri.file(execPath2CopyPath)]); await updateConfig('executables', ['execPath1', 'execPath2Copy']) adapter = createAdapterAndSubscribe(); @@ -1716,12 +1729,14 @@ describe( c2fsStatStub.withArgs(execPath2CopyPath).callsFake(() => { throw Error('restore'); }); - vsfsWatchStub.withArgs(execPath2CopyPath).callsFake(() => { - throw Error('restore'); - }); - vsFindFilesStub.withArgs(execPath2CopyPath).callsFake(() => { - throw Error('restore'); - }); + vsfsWatchStub.withArgs(matchRelativePattern(execPath2CopyPath)) + .callsFake(() => { + throw Error('restore'); + }); + vsFindFilesStub.withArgs(matchRelativePattern(execPath2CopyPath)) + .callsFake(() => { + throw Error('restore'); + }); disposeAdapterAndSubscribers(); await updateConfig('executables', undefined); await updateConfig('defaultExecWatchTimeout', undefined); @@ -1733,4 +1748,4 @@ describe( }) }) - // TODO tests outside working directory \ No newline at end of file + // TODO tests outside working directory diff --git a/src/test/FsWrapper.test.ts b/src/test/FsWrapper.test.ts index d31d50c7..31600565 100644 --- a/src/test/FsWrapper.test.ts +++ b/src/test/FsWrapper.test.ts @@ -51,4 +51,14 @@ describe('FsWrapper.statAsync', function() { assert.ok(res.isFile()); assert.ok(!res.isDirectory()); }) +}) + +describe('path', function() { + context('Uri', function() { + it('sould resolve', function() { + const a = vscode.Uri.file('/a/b/c'); + const b = vscode.Uri.file('/a/b/c/d/e'); + assert.equal(path.relative(a.fsPath, b.fsPath), path.normalize('d/e')); + }) + }) }) \ No newline at end of file diff --git a/src/test/QueueGraph.test.ts b/src/test/QueueGraph.test.ts index a1dd4fae..28484e1f 100644 --- a/src/test/QueueGraph.test.ts +++ b/src/test/QueueGraph.test.ts @@ -8,8 +8,6 @@ import {promisify} from 'util'; import {QueueGraphNode} from '../QueueGraph'; describe('QueueGraphNode', function() { - this.enableTimeouts(false); // TODO - async function waitFor( test: Mocha.Context, condition: Function, timeout: number = 1000) { const start = Date.now(); From 76ff9b75d1ada6cca437333802f45803e15ad7c2 Mon Sep 17 00:00:00 2001 From: Mate Pek Date: Sat, 27 Oct 2018 02:21:56 +0200 Subject: [PATCH 09/49] documentation update --- CHANGELOG.md | 30 ++++++++++++- README.md | 77 +++++++++++++++++++++------------- package.json | 17 ++++---- src/C2ExecutableInfo.ts | 63 ++++++++++++++++------------ src/C2TestAdapter.ts | 37 ++++++++-------- src/test/C2TestAdapter.test.ts | 24 +++++------ 6 files changed, 151 insertions(+), 97 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fe481909..2db205ce 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,35 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] -## [1.2.1] +## [2.0.0] + +Lot of things new under the hood, but lets talk about the 'API' change. + +### Changed + +- Renamed `defaultExecWatchTimeout` --> `defaultWatchTimeoutSec`. + - Also the unit has changed from milisecond to **second**. + +- Renamed `debugConfigurationTemplate` --> `debugConfigTemplate`. +- Renamed `path` property of `executables` --> `pattern`. (Technically `path` still can be used as an alias.) +- Removed `regex` property of `executables`. +- Removed `recursiveRegex` property of `executables`. + +- Changed behaviour of `path` property of `executables`. + Now it can understand "VSCode patterns". ([Details](https://code.visualstudio.com/docs/editor/codebasics#_advanced-search-options)) + - These work for only path's inside the _workspace directory_. Paths outside of it can be used + with absolute path or with relative to _working directory_ (ex.: `../build/test.exe`), but + without patterns (and without file-watch). + - `*` to match one or more characters in a path segment + - `?` to match on one character in a path segment + - `**` to match any number of path segments, including none + - `{}` to group conditions (e.g. {`**/*.html,**/*.txt`} matches all HTML and text files) + - `[]` to declare a range of characters to match (e.g., `example.[0-9]` to match on example.0, example.1, …) + +- File system is watched through the previously mentioned pattern (only inside the _workspace directory_), and + newly created executables will be added autamtically, deleted ones will be removed and changed ones will be refresed. + +- Variable substitution has been changed. (See [README.md] for details.) ## [1.2.0] - 2018-10-24 diff --git a/README.md b/README.md index fdbb6a84..ab717e66 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,12 @@ -# Catch2 Test Explorer for Visual Studio Code +__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) [![GitHub issues](https://img.shields.io/github/issues/matepek/vscode-catch2-test-adapter.svg)](https://github.com/matepek/vscode-catch2-test-adapter/issues) @@ -7,6 +15,7 @@ [![Visual Studio Marketplace](https://img.shields.io/vscode-marketplace/v/matepek.vscode-catch2-test-adapter.svg)](https://marketplace.visualstudio.com/items?itemName=matepek.vscode-catch2-test-adapter) + This extension allows you to run your [Catch2 tests](https://github.com/catchorg/Catch2) using the [Test Explorer for VS Code](https://marketplace.visualstudio.com/items?itemName=hbenl.vscode-test-explorer). @@ -15,18 +24,18 @@ This adapter doesn't support everything. ## Configuration -| Property | Description | -| ----------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `catch2TestExplorer.executables` | The location of your test executables (relative to the workspace folder or absolute path) and with a lot of other setting. Details: [below](#catch2TestExplorer.executables) | -| `catch2TestExplorer.defaultEnv` | Default environment variables to be set when running the tests, if it isn't provided in 'executables'. (Resolves: ${workspaceFolder}) | -| `catch2TestExplorer.defaultCwd` | The working directory where the test is run (relative to the workspace folder or absolue path), if it isn't provided in 'executables'. (Resolves: ${workspaceFolder}) | -| `catch2TestExplorer.defaultRngSeed` | Specify a seed for the Random Number Generator. For details see [Catch2 documentation](https://github.com/catchorg/Catch2/blob/master/docs/command-line.md#rng-seed) | -| `catch2TestExplorer.defaultExecWatchTimeout` | Test executables are being watched. In case of one compiles too much this variable can help with it. | -| `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.debugConfigurationTemplate` | Set the necessary debug configuraitons and the debug button will work. Details: [below](#catch2TestExplorer.debugConfigurationTemplate) | -| `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.) | +| Property | Description | +| ------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `catch2TestExplorer.executables` | The location of your test executables (relative to the workspace folder or absolute path) and with a lot of other setting. Details: [below](#catch2TestExplorer.executables) | +| `catch2TestExplorer.defaultEnv` | Default environment variables to be set when running the tests, if it isn't provided in 'executables'. (Resolves: ${workspaceFolder}) | +| `catch2TestExplorer.defaultCwd` | The working directory where the test is run (relative to the workspace folder or absolue path), if it isn't provided in 'executables'. (Resolves: ${workspaceFolder}) | +| `catch2TestExplorer.defaultRngSeed` | Specify a seed for the Random Number Generator. For details see [Catch2 documentation](https://github.com/catchorg/Catch2/blob/master/docs/command-line.md#rng-seed) | +| `catch2TestExplorer.defaultWatchTimeoutSec` | Test executables are being watched. In case of one compiles too much this variable can help with it. Unit: second. | +| `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) | +| `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.) | ### catch2TestExplorer.executables @@ -34,12 +43,22 @@ This `catch2TestExplorer.executables` variable can be string, an array of string | Property | | Description | | ---------------- | ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| `name` | (optional) | The name of the test suite (file). Can contains `${dirname}`, `${absDirname}`, `${name}` if `regex` is provided. | -| `path` | (requierd) | A relative (to workspace) or an absolute directory- or file-path. If it is a directory, the matching children will be added (see `regex`). | -| `regex` | (optional) | If `path` is a directory all matching children (full path) will be added (if there is no error). | -| `recursiveRegex` | (optional) | If true and `path` is a directory, it will search for the `regex` pattern recursively. | -| `cwd` | (optional) | The current working directory for the test executable. If it isn't provided and `defaultCwd` does, then that will be used. Can contains `${dirname}` and `${absDirname}` if `regex` is provided. | -| `env` | (optional) | Environment variables for the test executable. If it isn't provided and `defaultEnv` does, then that will be used. | +| `name` | (optional) | The name of the test suite (file). Can contains variables. | +| `pattern` | (requierd) | A relative pattern (to workspace) or an absolute file-path. ([Details](https://code.visualstudio.com/docs/editor/codebasics#_advanced-search-options)). Example: `{build,out}/**/test*`. | +| `path` | (alias) | Alias of `pattern`. | +| `cwd` | (optional) | The current working directory for the test executable. If it isn't provided and `defaultCwd` does, then that will be used. Can contains variables. | +| `env` | (optional) | Environment variables for the test executable. If it isn't provided and `defaultEnv` does, then that will be used. Can contains variables. | + +Variables: + +| Variable | Description | +|----------------------- | --------------------------------------------------------------------------------- | +| `${absName}` | Absolute path of the test executable. | +| `${relName}` | Relative path of the test executable to the workspace folder. | +| `${basename}` | Filename (Path withouth directories). | +| `${absDirname}` | Absolute path of the test executable's parent directory. | +| `${relDirname}` | Relative path of the test executable's parent directory to the workspace folder. | +| `${workspaceFolder}` | (You can only guess one.) | Examples: @@ -53,11 +72,12 @@ Examples: ```json "catch2TestExplorer.executables": { - "name": "${dirname} : ${name}", - "path": "./build", - "regex": "(t|T)est", + "name": "${relName} (${relDirname}/)", + "path": "{build,Build,BUILD,out,Out,OUT}/**/*{test,Test,TEST}*", "cwd": "${absDirname}", - "env": {} + "env": { + "ExampleENV1": "You can use variables here too, like ${absName}" + } } ``` @@ -65,22 +85,19 @@ Examples: "catch2TestExplorer.executables": [ { "name": "Test1 suite", - "path": "dir/test.exe", - "cwd": ".", - "env": {} + "path": "dir/test.exe" }, "singleTest.exe", { "path": "dir2", - "regex": "(t|T)est", - "recursiveRegex": false, - "cwd": ".", + "regex": "{t,T}est", + "cwd": "out/tmp", "env": {} } ] ``` -### catch2TestExplorer.debugConfigurationTemplate +### catch2TestExplorer.debugConfigTemplate For help, see: [here](https://code.visualstudio.com/docs/editor/debugging#_launch-configurations) diff --git a/package.json b/package.json index 7564594a..40571015 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "icon": "resources/icon.png", "author": "Mate Pek", "publisher": "matepek", - "version": "1.2.1", + "version": "2.0.0", "license": "Unlicense", "homepage": "https://github.com/matepek/vscode-catch2-test-adapter", "repository": { @@ -83,14 +83,11 @@ ], "default": [ { - "name": "${basename} (${relDirname}/)", - "path": "{build|out}/**/*{test|Test|TEST}*", - "regex": "(t|T)est", - "recursiveRegex": false, - "workerMaxNumber": 1, + "name": "${relName} (${relDirname}/)", + "path": "{build,Build,BUILD,out,Out,OUT}/**/*{test,Test,TEST}*", "cwd": "${absDirname}", "env": { - "ExampleENV1": "ExampleValueofENV1" + "ExampleENV1": "You can use variables here too, like ${absName}" } } ], @@ -120,12 +117,12 @@ "default": null, "scope": "resource" }, - "catch2TestExplorer.defaultExecWatchTimeout": { + "catch2TestExplorer.defaultWatchTimeoutSec": { "description": "Test executables are being watched. In case of one compiles too much this variable can help with it.", "type": [ "number" ], - "default": 10000, + "default": 10, "scope": "resource" }, "catch2TestExplorer.workerMaxNumber": { @@ -140,7 +137,7 @@ "default": true, "scope": "resource" }, - "catch2TestExplorer.debugConfigurationTemplate": { + "catch2TestExplorer.debugConfigTemplate": { "description": "Set the necessary debug configuraitons and the debug button will work.", "type": [ "object", diff --git a/src/C2ExecutableInfo.ts b/src/C2ExecutableInfo.ts index afe1e3c5..7eac0c86 100644 --- a/src/C2ExecutableInfo.ts +++ b/src/C2ExecutableInfo.ts @@ -37,8 +37,8 @@ export class C2ExecutableInfo implements vscode.Disposable { const isAbsolute = path.isAbsolute(this.pattern); - const absPattern = - isAbsolute ? this.pattern : path.resolve(wsUri.fsPath, this.pattern); + const absPattern = isAbsolute ? path.normalize(this.pattern) : + path.resolve(wsUri.fsPath, this.pattern); const absPatternAsUri = vscode.Uri.file(absPattern); const relativeToWs = path.relative(wsUri.fsPath, absPatternAsUri.fsPath); const isPartOfWs = !relativeToWs.startsWith('..'); @@ -77,28 +77,7 @@ export class C2ExecutableInfo implements vscode.Disposable { for (let i = 0; i < fileUris.length; i++) { const file = fileUris[i]; if (await this._verifyIsCatch2TestExecutable(file.fsPath)) { - let resolvedName = this.name; - let resolvedCwd = this.cwd; - try { - const relPath = path.relative(wsUri.path, file.path); - const varToValue: [string, string][] = [ - ['${absName}', file.path], - ['${relName}', relPath], - ['${basename}', path.basename(file.path)], - ['${absDirname}', path.dirname(file.path)], - ['${relDirname}', path.dirname(relPath)], - ...this._adapter.variableToValue, - ]; - resolvedName = resolveVariables(this.name, varToValue); - resolvedCwd = path.normalize(resolveVariables(this.cwd, varToValue)); - } catch (e) { - this._adapter.log.error(inspect([e, this])); - } - - const suite = this._allTests.createChildSuite( - resolvedName, file.path, {cwd: resolvedCwd, env: this.env}); - - this._executables.set(file.fsPath, suite); + this._addFile(file); } } @@ -109,13 +88,43 @@ export class C2ExecutableInfo implements vscode.Disposable { } } + private _addFile(file: vscode.Uri) { + const wsUri = this._adapter.workspaceFolder.uri; + + let resolvedName = this.name; + let resolvedCwd = this.cwd; + let resolvedEnv: {[prop: string]: string} = this.env; + try { + const relPath = path.relative(wsUri.path, file.path); + const varToValue: [string, string][] = [ + ['${absName}', file.path], + ['${relName}', relPath], + ['${basename}', path.basename(file.path)], + ['${absDirname}', path.dirname(file.path)], + ['${relDirname}', path.dirname(relPath)], + ...this._adapter.variableToValue, + ]; + resolvedName = resolveVariables(this.name, varToValue); + resolvedCwd = path.normalize(resolveVariables(this.cwd, varToValue)); + resolvedEnv = resolveVariables(this.env, varToValue); + } catch (e) { + this._adapter.log.error(inspect([e, this])); + } + + const suite = this._allTests.createChildSuite( + resolvedName, file.path, {cwd: resolvedCwd, env: resolvedEnv}); + + this._executables.set(file.fsPath, suite); + + return suite; + } + private _handleEverything(uri: vscode.Uri) { let suite = this._executables.get(uri.fsPath); if (suite == undefined) { - suite = this._allTests.createChildSuite( - this.name, uri.fsPath, {cwd: this.cwd, env: this.env}); - this._executables.set(uri.fsPath, suite); + suite = this._addFile(uri); + this._uniquifySuiteNames(); } const x = diff --git a/src/C2TestAdapter.ts b/src/C2TestAdapter.ts index ff924332..a4ca27b5 100644 --- a/src/C2TestAdapter.ts +++ b/src/C2TestAdapter.ts @@ -197,7 +197,7 @@ export class C2TestAdapter implements TestAdapter, vscode.Disposable { vscode.extensions.getExtension(''); } - throw 'Catch2: For debug \'debugConfigurationTemplate\' should be set.'; + throw 'Catch2: For debug \'debugConfigTemplate\' should be set.'; }; const debugConfig = getDebugConfiguration(); @@ -241,7 +241,7 @@ export class C2TestAdapter implements TestAdapter, vscode.Disposable { private getDebugConfigurationTemplate(config: vscode.WorkspaceConfiguration): {[prop: string]: string}|null { - const o = config.get('debugConfigurationTemplate', null); + const o = config.get('debugConfigTemplate', null); if (o === null) return null; @@ -295,7 +295,7 @@ export class C2TestAdapter implements TestAdapter, vscode.Disposable { } getDefaultExecWatchTimeout(config: vscode.WorkspaceConfiguration): number { - return config.get('defaultExecWatchTimeout', 10000); + return config.get('defaultWatchTimeoutSec', 10) * 1000; } private getWorkerMaxNumber(config: vscode.WorkspaceConfiguration): number { @@ -339,23 +339,26 @@ export class C2TestAdapter implements TestAdapter, vscode.Disposable { return path.isAbsolute(p) ? p : this.resolveRelPath(p); }; - const createFromObject = (obj: Object): C2ExecutableInfo => { - const name: string = obj.hasOwnProperty('name') ? - (obj)['name'] : - '${basename} (${relDirname}/)'; - if (!obj.hasOwnProperty('path') || (obj)['path'] === null) { - console.warn(Error('\'path\' is a requireds property.')); - throw Error('Wrong object: ' + inspect(obj)); - } - const p: string = (obj)['path']; - const cwd: string = obj.hasOwnProperty('cwd') ? (obj)['cwd'] : - globalWorkingDirectory; + const createFromObject = (obj: {[prop: string]: any}): C2ExecutableInfo => { + const name: string = + obj.hasOwnProperty('name') ? obj.name : '${relName} (${relDirname}/)'; + + let pattern: string = ''; + if (obj.hasOwnProperty('pattern') && typeof obj.pattern == 'string') + pattern = obj.pattern; + else if (obj.hasOwnProperty('path') && typeof obj.path == 'string') + pattern = obj.path; + else + throw Error('Error: pattern or path property is required.'); + + const cwd: string = + obj.hasOwnProperty('cwd') ? obj.cwd : globalWorkingDirectory; + const env: {[prop: string]: any} = obj.hasOwnProperty('env') ? - this.getGlobalAndCurrentEnvironmentVariables( - config, (obj)['env']) : + this.getGlobalAndCurrentEnvironmentVariables(config, obj.env) : this.getGlobalAndDefaultEnvironmentVariables(config); - return new C2ExecutableInfo(this, allTests, name, p, cwd, env); + return new C2ExecutableInfo(this, allTests, name, pattern, cwd, env); }; if (typeof configExecs === 'string') { diff --git a/src/test/C2TestAdapter.test.ts b/src/test/C2TestAdapter.test.ts index d8da1e1e..b85cfec8 100644 --- a/src/test/C2TestAdapter.test.ts +++ b/src/test/C2TestAdapter.test.ts @@ -1585,10 +1585,10 @@ describe( specify( 'load executables=["execPath1", "execPath2Copy"]; delete; sleep 3; create', async function(this: Mocha.Context) { - const watchTimeout = 5500; - await updateConfig('defaultExecWatchTimeout', watchTimeout); - this.timeout(watchTimeout + 2500 /* because of 'delay' */); - this.slow(watchTimeout + 2500 /* because of 'delay' */); + const watchTimeout = 6; + await updateConfig('defaultWatchTimeoutSec', watchTimeout); + this.timeout(watchTimeout * 1000 + 2500 /* because of 'delay' */); + this.slow(watchTimeout * 1000 + 2500 /* because of 'delay' */); const execPath2CopyPath = path.join(workspaceFolderUri.path, 'execPath2Copy'); @@ -1662,19 +1662,19 @@ describe( }); disposeAdapterAndSubscribers(); await updateConfig('executables', undefined); - await updateConfig('defaultExecWatchTimeout', undefined); + await updateConfig('defaultWatchTimeoutSec', undefined); assert.equal(newRoot.children.length, 2); assert.ok(3000 < elapsed, inspect(elapsed)); - assert.ok(elapsed < watchTimeout + 2400, inspect(elapsed)); + assert.ok(elapsed < watchTimeout * 1000 + 2400, inspect(elapsed)); }) specify( 'load executables=["execPath1", "execPath2Copy"]; delete second', async function() { - const watchTimeout = 5000; - await updateConfig('defaultExecWatchTimeout', watchTimeout); - this.timeout(watchTimeout + 2500 /* because of 'delay' */); + const watchTimeout = 5; + await updateConfig('defaultWatchTimeoutSec', watchTimeout); + this.timeout(watchTimeout * 1000 + 2500 /* because of 'delay' */); this.slow(watchTimeout + 2500 /* because of 'delay' */); const execPath2CopyPath = path.join(workspaceFolderUri.path, 'execPath2Copy'); @@ -1739,11 +1739,11 @@ describe( }); disposeAdapterAndSubscribers(); await updateConfig('executables', undefined); - await updateConfig('defaultExecWatchTimeout', undefined); + await updateConfig('defaultWatchTimeoutSec', undefined); assert.equal(newRoot.children.length, 1); - assert.ok(watchTimeout < elapsed, inspect(elapsed)); - assert.ok(elapsed < watchTimeout + 2400, inspect(elapsed)); + assert.ok(watchTimeout * 1000 < elapsed, inspect(elapsed)); + assert.ok(elapsed < watchTimeout * 1000 + 2400, inspect(elapsed)); }) }) }) From 882a1afd4fbf65ae5476e3959613ff54a37249f8 Mon Sep 17 00:00:00 2001 From: Mate Pek Date: Sat, 27 Oct 2018 11:47:02 +0200 Subject: [PATCH 10/49] variable substitution change --- README.md | 23 ++++++++++------ package-lock.json | 10 +++---- package.json | 16 +++++------ src/C2ExecutableInfo.ts | 25 +++++++++++++---- src/C2TestAdapter.ts | 6 +++-- src/test/C2TestAdapter.test.ts | 2 +- src/test/FsWrapper.test.ts | 49 +++++++++++++++++++++++++++++++++- 7 files changed, 101 insertions(+), 30 deletions(-) diff --git a/README.md b/README.md index ab717e66..8f7c13b6 100644 --- a/README.md +++ b/README.md @@ -44,21 +44,28 @@ This `catch2TestExplorer.executables` variable can be string, an array of string | Property | | Description | | ---------------- | ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `name` | (optional) | The name of the test suite (file). Can contains variables. | -| `pattern` | (requierd) | A relative pattern (to workspace) or an absolute file-path. ([Details](https://code.visualstudio.com/docs/editor/codebasics#_advanced-search-options)). Example: `{build,out}/**/test*`. | +| `pattern` | (requierd) | A relative pattern (to workspace) or an absolute file-path. ([Details](https://code.visualstudio.com/docs/editor/codebasics#_advanced-search-options)). Example: `{build,out}/**/test[1-9]*` | | `path` | (alias) | Alias of `pattern`. | | `cwd` | (optional) | The current working directory for the test executable. If it isn't provided and `defaultCwd` does, then that will be used. Can contains variables. | | `env` | (optional) | Environment variables for the test executable. If it isn't provided and `defaultEnv` does, then that will be used. Can contains variables. | -Variables: +Variables which can be used in `name`, `cwd` and `env` of `executables`: | Variable | Description | |----------------------- | --------------------------------------------------------------------------------- | -| `${absName}` | Absolute path of the test executable. | -| `${relName}` | Relative path of the test executable to the workspace folder. | -| `${basename}` | Filename (Path withouth directories). | -| `${absDirname}` | Absolute path of the test executable's parent directory. | -| `${relDirname}` | Relative path of the test executable's parent directory to the workspace folder. | -| `${workspaceFolder}` | (You can only guess one.) | +| `${absPath}` | Absolute path of the test executable | +| `${relPath}` | Relative path of the test executable to the workspace folder | +| `${absDirpath}` | Absolute path of the test executable's parent directory | +| `${relDirpath}` | Relative path of the test executable's parent directory to the workspace folder | +| `${filename}` | Filename ( = Path withouth directories, "d/a.b.c" => "a.b.c") | +| `${baseFilename}` | Filename without extension ("d/a.b.c" => "a.b") | +| `${extFilename}` | Filename extension. ("d/a.b.c" => ".c") | +| `${base2Filename}` | Filename without extension ("d/a.b.c" => "a") | +| `${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" => "") | +| `${workspaceDirectoy}` | (You can only guess one.) | +| `${workspaceFolder}` | Alias of `${workspaceDirectoy}` | Examples: diff --git a/package-lock.json b/package-lock.json index 8b9f01d0..b75b121f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "vscode-catch2-test-adapter", - "version": "1.2.0", + "version": "2.0.0", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -2114,9 +2114,9 @@ "dev": true }, "sinon": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/sinon/-/sinon-7.0.0.tgz", - "integrity": "sha512-8OrSYFPZ9xaECfi1ayVMd0ihYCW2OZYgX3rBczrB990gHZMM+aftvhNTJazGz/luS0Us9NWgD5P3KGQ7kYZvGg==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-7.1.0.tgz", + "integrity": "sha512-ffASxced8xr8eU0EGyfj9K++bRCtv/NyOFOxl7UBD86YH97oZjVxvecMhObwRlXe27GRUa6rVFEn67khPZ29rQ==", "dev": true, "requires": { "@sinonjs/commons": "^1.0.2", @@ -2125,7 +2125,7 @@ "diff": "^3.5.0", "lodash.get": "^4.4.2", "lolex": "^3.0.0", - "nise": "^1.4.5", + "nise": "^1.4.6", "supports-color": "^5.5.0", "type-detect": "^4.0.8" } diff --git a/package.json b/package.json index 40571015..2cbeb746 100644 --- a/package.json +++ b/package.json @@ -48,17 +48,17 @@ "xml2js": "^0.4.19" }, "devDependencies": { + "@types/chai": "^4.1.6", + "@types/deep-equal": "^1.0.1", "@types/entities": "^1.1.0", + "@types/fs-extra": "^5.0.4", + "@types/mocha": "^5.2.5", + "@types/sinon": "^5.0.5", "@types/xml-parser": "^1.2.29", "@types/xml2js": "^0.4.3", - "@types/mocha": "^5.2.5", - "@types/chai": "^4.1.6", - "@types/fs-extra": "^5.0.4", - "fs-extra": "^7.0.0", - "@types/deep-equal": "^1.0.1", "deep-equal": "^1.0.1", - "@types/sinon": "^5.0.5", - "sinon": "^7.0.0", + "fs-extra": "^7.0.0", + "sinon": "^7.1.0", "typescript": "^2.9.2", "vsce": "^1.51.1", "vscode": "^1.1.21" @@ -157,4 +157,4 @@ } } } -} \ No newline at end of file +} diff --git a/src/C2ExecutableInfo.ts b/src/C2ExecutableInfo.ts index 7eac0c86..135dd725 100644 --- a/src/C2ExecutableInfo.ts +++ b/src/C2ExecutableInfo.ts @@ -96,13 +96,28 @@ export class C2ExecutableInfo implements vscode.Disposable { let resolvedEnv: {[prop: string]: string} = this.env; try { const relPath = path.relative(wsUri.path, file.path); + + const filename = path.basename(file.path); + const extFilename = path.extname(filename); + const baseFilename = path.basename(filename, extFilename); + const ext2Filename = path.extname(baseFilename); + const base2Filename = path.basename(baseFilename, ext2Filename); + const ext3Filename = path.extname(base2Filename); + const base3Filename = path.basename(base2Filename, ext3Filename); + const varToValue: [string, string][] = [ - ['${absName}', file.path], - ['${relName}', relPath], - ['${basename}', path.basename(file.path)], - ['${absDirname}', path.dirname(file.path)], - ['${relDirname}', path.dirname(relPath)], ...this._adapter.variableToValue, + ['${absPath}', file.path], + ['${relPath}', relPath], + ['${absDirpath}', path.dirname(file.path)], + ['${relDirpath}', path.dirname(relPath)], + ['${filename}', filename], + ['${extFilename}', extFilename], + ['${baseFilename}', baseFilename], + ['${ext2Filename}', ext2Filename], + ['${base2Filename}', base2Filename], + ['${ext3Filename}', ext3Filename], + ['${base3Filename}', base3Filename], ]; resolvedName = resolveVariables(this.name, varToValue); resolvedCwd = path.normalize(resolveVariables(this.cwd, varToValue)); diff --git a/src/C2TestAdapter.ts b/src/C2TestAdapter.ts index a4ca27b5..208a0738 100644 --- a/src/C2TestAdapter.ts +++ b/src/C2TestAdapter.ts @@ -21,8 +21,10 @@ export class C2TestAdapter implements TestAdapter, vscode.Disposable { TestSuiteEvent|TestEvent>(); private readonly autorunEmitter = new vscode.EventEmitter(); - readonly variableToValue: [string, string][] = - [['${workspaceFolder}', this.workspaceFolder.uri.fsPath]]; + readonly variableToValue: [string, string][] = [ + ['${workspaceDirectory}', this.workspaceFolder.uri.fsPath], + ['${workspaceFolder}', this.workspaceFolder.uri.fsPath], + ]; private allTests: C2AllTestSuiteInfo; private readonly disposables: Array = new Array(); diff --git a/src/test/C2TestAdapter.test.ts b/src/test/C2TestAdapter.test.ts index b85cfec8..d477e7c5 100644 --- a/src/test/C2TestAdapter.test.ts +++ b/src/test/C2TestAdapter.test.ts @@ -1393,7 +1393,7 @@ describe( before(async function() { await updateConfig( 'executables', [{ - name: '${relDirname}/${basename} (${absDirname})', + name: '${relDirpath}/${filename} (${absDirpath})', path: 'execPath{1,2}', cwd: '${workspaceFolder}/cwd', env: { diff --git a/src/test/FsWrapper.test.ts b/src/test/FsWrapper.test.ts index 31600565..6b2b9d9e 100644 --- a/src/test/FsWrapper.test.ts +++ b/src/test/FsWrapper.test.ts @@ -54,11 +54,58 @@ describe('FsWrapper.statAsync', function() { }) describe('path', function() { - context('Uri', function() { + describe('Uri', function() { it('sould resolve', function() { const a = vscode.Uri.file('/a/b/c'); const b = vscode.Uri.file('/a/b/c/d/e'); assert.equal(path.relative(a.fsPath, b.fsPath), path.normalize('d/e')); }) }) + describe('extname', function() { + it('extname', function() { + const filename = path.basename('bar/foo/base.ext2.ext1'); + assert.equal(filename, 'base.ext2.ext1'); + + const extFilename = path.extname(filename); + assert.equal(extFilename, '.ext1'); + + const baseFilename = path.basename(filename, extFilename); + assert.equal(baseFilename, 'base.ext2'); + + const ext2Filename = path.extname(baseFilename); + assert.equal(ext2Filename, '.ext2'); + + const base2Filename = path.basename(baseFilename, ext2Filename); + assert.equal(base2Filename, 'base'); + + const ext3Filename = path.extname(base2Filename); + assert.equal(ext3Filename, ''); + + const base3Filename = path.basename(base2Filename, ext3Filename); + assert.equal(base3Filename, 'base'); + }) + + it('.extname', function() { + const filename = path.basename('bar/foo/.base.ext2.ext1'); + assert.equal(filename, '.base.ext2.ext1'); + + const extFilename = path.extname(filename); + assert.equal(extFilename, '.ext1'); + + const baseFilename = path.basename(filename, extFilename); + assert.equal(baseFilename, '.base.ext2'); + + const ext2Filename = path.extname(baseFilename); + assert.equal(ext2Filename, '.ext2'); + + const base2Filename = path.basename(baseFilename, ext2Filename); + assert.equal(base2Filename, '.base'); + + const ext3Filename = path.extname(base2Filename); + assert.equal(ext3Filename, ''); + + const base3Filename = path.basename(base2Filename, ext3Filename); + assert.equal(base3Filename, '.base'); + }) + }) }) \ No newline at end of file From d166ba0ba47f65fc6f80cbf93633858fc8a4fb3b Mon Sep 17 00:00:00 2001 From: Mate Pek Date: Sat, 27 Oct 2018 15:20:52 +0200 Subject: [PATCH 11/49] test;new ts formatter --- CHANGELOG.md | 22 +- README.md | 64 +- package.json | 13 +- src/C2ExecutableInfo.ts | 15 +- src/C2TestAdapter.ts | 13 +- src/test/C2TestAdapter.test.ts | 3176 ++++++++++++++++---------------- 6 files changed, 1670 insertions(+), 1633 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2db205ce..fef8d34c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,32 +11,36 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 Lot of things new under the hood, but lets talk about the 'API' change. -### Changed +### Changed ⚠️ - Renamed `defaultExecWatchTimeout` --> `defaultWatchTimeoutSec`. + - Also the unit has changed from milisecond to **second**. - Renamed `debugConfigurationTemplate` --> `debugConfigTemplate`. - Renamed `path` property of `executables` --> `pattern`. (Technically `path` still can be used as an alias.) -- Removed `regex` property of `executables`. -- Removed `recursiveRegex` property of `executables`. -- Changed behaviour of `path` property of `executables`. - Now it can understand "VSCode patterns". ([Details](https://code.visualstudio.com/docs/editor/codebasics#_advanced-search-options)) - - These work for only path's inside the _workspace directory_. Paths outside of it can be used - with absolute path or with relative to _working directory_ (ex.: `../build/test.exe`), but - without patterns (and without file-watch). +- Changed behaviour of `path` property of `executables`. + Now it can understand "VSCode patterns". ([Details](https://code.visualstudio.com/docs/editor/codebasics#_advanced-search-options)) - These work for only path's inside the _workspace directory_. Paths outside of it can be used + with absolute path or with relative to _working directory_ (ex.: `../build/test.exe`), but + without patterns (and without file-watch). + - `*` to match one or more characters in a path segment - `?` to match on one character in a path segment - `**` to match any number of path segments, including none - `{}` to group conditions (e.g. {`**/*.html,**/*.txt`} matches all HTML and text files) - `[]` to declare a range of characters to match (e.g., `example.[0-9]` to match on example.0, example.1, …) -- File system is watched through the previously mentioned pattern (only inside the _workspace directory_), and +- File system is watched through the previously mentioned pattern (only inside the _workspace directory_), and newly created executables will be added autamtically, deleted ones will be removed and changed ones will be refresed. - Variable substitution has been changed. (See [README.md] for details.) +### Removed 🚫 + +- Removed `regex` property of `executables`. +- Removed `recursiveRegex` property of `executables`. + ## [1.2.0] - 2018-10-24 ### Added diff --git a/README.md b/README.md index 8f7c13b6..5951da21 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,10 @@ -__Warning__: Version 2 has been released: __API has been changed__. ---------------------------------------------------------------------- +## **Warning**: Version 2 has been released: **API has been changed**. -Update your settings! ([See changelog](CHANGELOG.md) or [configuration below](#Configuration).) +Update your settings! ([See changelog](CHANGELOG.md) or [configuration below](#Configuration).) --- -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) [![GitHub issues](https://img.shields.io/github/issues/matepek/vscode-catch2-test-adapter.svg)](https://github.com/matepek/vscode-catch2-test-adapter/issues) @@ -14,8 +12,6 @@ Catch2 Test Explorer for Visual Studio Code [![Visual Studio Marketplace](https://img.shields.io/vscode-marketplace/d/matepek.vscode-catch2-test-adapter.svg)](https://marketplace.visualstudio.com/items?itemName=matepek.vscode-catch2-test-adapter) [![Visual Studio Marketplace](https://img.shields.io/vscode-marketplace/v/matepek.vscode-catch2-test-adapter.svg)](https://marketplace.visualstudio.com/items?itemName=matepek.vscode-catch2-test-adapter) - - This extension allows you to run your [Catch2 tests](https://github.com/catchorg/Catch2) using the [Test Explorer for VS Code](https://marketplace.visualstudio.com/items?itemName=hbenl.vscode-test-explorer). @@ -28,44 +24,46 @@ This adapter doesn't support everything. | ------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `catch2TestExplorer.executables` | The location of your test executables (relative to the workspace folder or absolute path) and with a lot of other setting. Details: [below](#catch2TestExplorer.executables) | | `catch2TestExplorer.defaultEnv` | Default environment variables to be set when running the tests, if it isn't provided in 'executables'. (Resolves: ${workspaceFolder}) | -| `catch2TestExplorer.defaultCwd` | The working directory where the test is run (relative to the workspace folder or absolue path), if it isn't provided in 'executables'. (Resolves: ${workspaceFolder}) | +| `catch2TestExplorer.defaultCwd` | The working directory where the test is run (relative to the workspace folder or absolue path), if it isn't provided in "executables". (Resolves: ${workspaceFolder}) | | `catch2TestExplorer.defaultRngSeed` | Specify a seed for the Random Number Generator. For details see [Catch2 documentation](https://github.com/catchorg/Catch2/blob/master/docs/command-line.md#rng-seed) | | `catch2TestExplorer.defaultWatchTimeoutSec` | Test executables are being watched. In case of one compiles too much this variable can help with it. Unit: second. | | `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) | -| `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.) | +| `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.) | ### catch2TestExplorer.executables -This `catch2TestExplorer.executables` variable can be string, an array of strings, an array of objects or an array of strings and objects. +This variable can be string, an array of strings, an array of objects or an array of strings and objects. + +If it is an object it can contains the following properties: -| Property | | Description | -| ---------------- | ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| `name` | (optional) | The name of the test suite (file). Can contains variables. | -| `pattern` | (requierd) | A relative pattern (to workspace) or an absolute file-path. ([Details](https://code.visualstudio.com/docs/editor/codebasics#_advanced-search-options)). Example: `{build,out}/**/test[1-9]*` | -| `path` | (alias) | Alias of `pattern`. | -| `cwd` | (optional) | The current working directory for the test executable. If it isn't provided and `defaultCwd` does, then that will be used. Can contains variables. | -| `env` | (optional) | Environment variables for the test executable. If it isn't provided and `defaultEnv` does, then that will be used. Can contains variables. | +| Property | | Description | +| --------- | ---------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `name` | (optional) | The name of the test suite (file). Can contains variables. | +| `pattern` | (requierd) | A relative pattern (to workspace) or an absolute file-path. ([Details](https://code.visualstudio.com/docs/editor/codebasics#_advanced-search-options)). Example: `{build,out}/**/test[1-9]*` | +| `path` | (alias) | Alias of `pattern`. | +| `cwd` | (optional) | The current working directory for the test executable. If it isn't provided and `defaultCwd` does, then that will be used. Can contains variables. | +| `env` | (optional) | Environment variables for the test executable. If it isn't provided and `defaultEnv` does, then that will be used. Can contains variables. | Variables which can be used in `name`, `cwd` and `env` of `executables`: -| Variable | Description | -|----------------------- | --------------------------------------------------------------------------------- | -| `${absPath}` | Absolute path of the test executable | -| `${relPath}` | Relative path of the test executable to the workspace folder | -| `${absDirpath}` | Absolute path of the test executable's parent directory | -| `${relDirpath}` | Relative path of the test executable's parent directory to the workspace folder | -| `${filename}` | Filename ( = Path withouth directories, "d/a.b.c" => "a.b.c") | -| `${baseFilename}` | Filename without extension ("d/a.b.c" => "a.b") | -| `${extFilename}` | Filename extension. ("d/a.b.c" => ".c") | -| `${base2Filename}` | Filename without extension ("d/a.b.c" => "a") | -| `${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" => "") | -| `${workspaceDirectoy}` | (You can only guess one.) | -| `${workspaceFolder}` | Alias of `${workspaceDirectoy}` | +| Variable | Description | +| ----------------------- | ------------------------------------------------------------------------------- | +| `${absPath}` | Absolute path of the test executable | +| `${relPath}` | Relative path of the test executable to the workspace folder | +| `${absDirpath}` | Absolute path of the test executable's parent directory | +| `${relDirpath}` | Relative path of the test executable's parent directory to the workspace folder | +| `${filename}` | Filename ( = Path withouth directories, "d/a.b.c" => "a.b.c") | +| `${baseFilename}` | Filename without extension ("d/a.b.c" => "a.b") | +| `${extFilename}` | Filename extension. ("d/a.b.c" => ".c") | +| `${base2Filename}` | Filename without extension ("d/a.b.c" => "a") | +| `${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.) | +| `${workspaceFolder}` | Alias of `${workspaceDirectory}` | Examples: diff --git a/package.json b/package.json index 2cbeb746..29e812af 100644 --- a/package.json +++ b/package.json @@ -87,7 +87,7 @@ "path": "{build,Build,BUILD,out,Out,OUT}/**/*{test,Test,TEST}*", "cwd": "${absDirname}", "env": { - "ExampleENV1": "You can use variables here too, like ${absName}" + "C2TESTEXECUTABLEPATH": "${absName}" } } ], @@ -108,7 +108,7 @@ "scope": "resource" }, "catch2TestExplorer.defaultRngSeed": { - "description": "Specify a seed for the Random Number Generator. For details see: https://github.com/catchorg/Catch2/blob/master/docs/command-line.md#rng-seed", + "description": "Specify a seed for the Random Number Generator. (ex.: '', '\"time\"') For details see: https://github.com/catchorg/Catch2/blob/master/docs/command-line.md#rng-seed", "type": [ "string", "number", @@ -118,7 +118,7 @@ "scope": "resource" }, "catch2TestExplorer.defaultWatchTimeoutSec": { - "description": "Test executables are being watched. In case of one compiles too much this variable can help with it.", + "description": "Test executables are being watched (only inside the workspace directory). In case of one compiles too much this variable can help with it.", "type": [ "number" ], @@ -128,23 +128,24 @@ "catch2TestExplorer.workerMaxNumber": { "description": "The variable maximize the number of the parallel test execution.", "type": "number", - "default": 4, + "default": 1, "scope": "resource" }, "catch2TestExplorer.enableSourceDecoration": { - "description": "Sets the source code decorations: Errored lines will be highlited.", + "description": "Sets the source code decorations: Errored lines will be highlighted.", "type": "boolean", "default": true, "scope": "resource" }, "catch2TestExplorer.debugConfigTemplate": { - "description": "Set the necessary debug configuraitons and the debug button will work.", + "description": "Set the necessary debug configuraitons and then the debug button will work.", "type": [ "object", "null" ], "default": { "type": "cppdbg", + "MIMode": "lldb", "program": "${exec}", "args": "${args}", "cwd": "${cwd}", diff --git a/src/C2ExecutableInfo.ts b/src/C2ExecutableInfo.ts index 135dd725..a3290496 100644 --- a/src/C2ExecutableInfo.ts +++ b/src/C2ExecutableInfo.ts @@ -47,13 +47,8 @@ export class C2ExecutableInfo implements vscode.Disposable { if (!isAbsolute || (isAbsolute && isPartOfWs)) { let relativePattern: vscode.RelativePattern; - if (isAbsolute) { - relativePattern = new vscode.RelativePattern( - this._adapter.workspaceFolder, relativeToWs); - } else { - relativePattern = new vscode.RelativePattern( - this._adapter.workspaceFolder, this.pattern); - } + relativePattern = new vscode.RelativePattern( + this._adapter.workspaceFolder, relativeToWs); fileUris = await vscode.workspace.findFiles(relativePattern, undefined, 1000); try { @@ -95,9 +90,9 @@ export class C2ExecutableInfo implements vscode.Disposable { let resolvedCwd = this.cwd; let resolvedEnv: {[prop: string]: string} = this.env; try { - const relPath = path.relative(wsUri.path, file.path); + const relPath = path.relative(wsUri.fsPath, file.fsPath); - const filename = path.basename(file.path); + const filename = path.basename(file.fsPath); const extFilename = path.extname(filename); const baseFilename = path.basename(filename, extFilename); const ext2Filename = path.extname(baseFilename); @@ -109,7 +104,7 @@ export class C2ExecutableInfo implements vscode.Disposable { ...this._adapter.variableToValue, ['${absPath}', file.path], ['${relPath}', relPath], - ['${absDirpath}', path.dirname(file.path)], + ['${absDirpath}', path.dirname(file.fsPath)], ['${relDirpath}', path.dirname(relPath)], ['${filename}', filename], ['${extFilename}', extFilename], diff --git a/src/C2TestAdapter.ts b/src/C2TestAdapter.ts index 208a0738..e7e9ebca 100644 --- a/src/C2TestAdapter.ts +++ b/src/C2TestAdapter.ts @@ -337,10 +337,6 @@ export class C2TestAdapter implements TestAdapter, vscode.Disposable { const configExecs:|undefined|string|string[]|{[prop: string]: any}| {[prop: string]: any}[] = config.get('executables'); - const fullPath = (p: string): string => { - return path.isAbsolute(p) ? p : this.resolveRelPath(p); - }; - const createFromObject = (obj: {[prop: string]: any}): C2ExecutableInfo => { const name: string = obj.hasOwnProperty('name') ? obj.name : '${relName} (${relDirname}/)'; @@ -366,19 +362,16 @@ export class C2TestAdapter implements TestAdapter, vscode.Disposable { if (typeof configExecs === 'string') { if (configExecs.length == 0) return []; executables.push(new C2ExecutableInfo( - this, allTests, configExecs, - fullPath(resolveVariables(configExecs, this.variableToValue)), - globalWorkingDirectory, {})); + this, allTests, configExecs, configExecs, globalWorkingDirectory, + {})); } else if (Array.isArray(configExecs)) { for (var i = 0; i < configExecs.length; ++i) { const configExe = configExecs[i]; if (typeof configExe == 'string') { const configExecsName = String(configExe); if (configExecsName.length > 0) { - const resolvedName = - resolveVariables(configExecsName, this.variableToValue); executables.push(new C2ExecutableInfo( - this, allTests, resolvedName, fullPath(resolvedName), + this, allTests, configExecsName, configExecsName, globalWorkingDirectory, {})); } } else { diff --git a/src/test/C2TestAdapter.test.ts b/src/test/C2TestAdapter.test.ts index d477e7c5..39f8f298 100644 --- a/src/test/C2TestAdapter.test.ts +++ b/src/test/C2TestAdapter.test.ts @@ -37,527 +37,748 @@ const sinonSandbox = sinon.createSandbox(); /// -describe( - 'C2TestAdapter', - function() { - this.enableTimeouts(false); // TODO - - let testsEvents: (TestLoadStartedEvent|TestLoadFinishedEvent)[] = []; - let testStatesEvents: (TestRunStartedEvent|TestRunFinishedEvent| - TestSuiteEvent|TestEvent)[] = []; - - function getConfig() { - return vscode.workspace.getConfiguration( - 'catch2TestExplorer', workspaceFolderUri) - }; - - async function updateConfig(key: string, value: any) { - let count = testsEvents.length; - await getConfig().update(key, value); - // cleanup - while (testsEvents.length < count--) testsEvents.pop(); - } - - let adapter: C2TestAdapter|undefined; - let testsEventsConnection: vscode.Disposable|undefined; - let testStatesEventsConnection: vscode.Disposable|undefined; - - let spawnStub: sinon.SinonStub; - let vsfsWatchStub: sinon.SinonStub; - let c2fsStatStub: sinon.SinonStub; - let vsFindFilesStub: sinon.SinonStub; - - function resetConfig(): Thenable { - const packageJson = fse.readJSONSync( - path.join(workspaceFolderUri.path, '../..', 'package.json')); - const properties: {[prop: string]: any}[] = - packageJson['contributes']['configuration']['properties']; - let t: Thenable = Promise.resolve(); - Object.keys(properties).forEach(key => { - assert.ok(key.startsWith('catch2TestExplorer.')); - const k = key.replace('catch2TestExplorer.', '') - t = t.then(function() { - return getConfig().update(k, undefined); - }); +describe('C2TestAdapter', function() { + this.enableTimeouts(false); // TODO + + let testsEvents: (TestLoadStartedEvent|TestLoadFinishedEvent)[] = []; + let testStatesEvents: (TestRunStartedEvent|TestRunFinishedEvent| + TestSuiteEvent|TestEvent)[] = []; + + function getConfig() { + return vscode.workspace.getConfiguration( + 'catch2TestExplorer', workspaceFolderUri) + }; + + async function updateConfig(key: string, value: any) { + let count = testsEvents.length; + await getConfig().update(key, value); + // cleanup + while (testsEvents.length < count--) testsEvents.pop(); + } + + let adapter: C2TestAdapter|undefined; + let testsEventsConnection: vscode.Disposable|undefined; + let testStatesEventsConnection: vscode.Disposable|undefined; + + let spawnStub: sinon.SinonStub; + let vsfsWatchStub: sinon.SinonStub; + let c2fsStatStub: sinon.SinonStub; + let vsFindFilesStub: sinon.SinonStub; + + function resetConfig(): Thenable { + const packageJson = fse.readJSONSync( + path.join(workspaceFolderUri.path, '../..', 'package.json')); + const properties: {[prop: string]: any}[] = + packageJson['contributes']['configuration']['properties']; + let t: Thenable = Promise.resolve(); + Object.keys(properties).forEach(key => { + assert.ok(key.startsWith('catch2TestExplorer.')); + const k = key.replace('catch2TestExplorer.', '') + t = t.then(function() { + return getConfig().update(k, undefined); + }); + }); + return t; + } + + function testStatesEvI(o: any) { + const i = testStatesEvents.findIndex( + (v: TestRunStartedEvent|TestRunFinishedEvent|TestSuiteEvent| + TestEvent) => { + if (o.type == v.type) + if (o.type == 'suite' || o.type == 'test') + return o.state === (v).state && + o[o.type] === (v)[v.type]; + return deepStrictEqual(o, v); + }); + assert.notEqual( + i, -1, + 'testStatesEvI failed to find: ' + inspect(o) + '\n\nin\n\n' + + inspect(testStatesEvents)); + assert.deepStrictEqual(testStatesEvents[i], o); + return i; + }; + + function createAdapterAndSubscribe() { + adapter = new C2TestAdapter(workspaceFolder, logger); + + testsEventsConnection = + adapter.tests((e: TestLoadStartedEvent|TestLoadFinishedEvent) => { + testsEvents.push(e); }); - return t; - } - - function testStatesEvI(o: any) { - const i = testStatesEvents.findIndex( - (v: TestRunStartedEvent|TestRunFinishedEvent|TestSuiteEvent| - TestEvent) => { - if (o.type == v.type) - if (o.type == 'suite' || o.type == 'test') - return o.state === (v).state && - o[o.type] === (v)[v.type]; - return deepStrictEqual(o, v); - }); - assert.notEqual( - i, -1, - 'testStatesEvI failed to find: ' + inspect(o) + '\n\nin\n\n' + - inspect(testStatesEvents)); - assert.deepStrictEqual(testStatesEvents[i], o); - return i; - }; - - function createAdapterAndSubscribe() { - adapter = new C2TestAdapter(workspaceFolder, logger); - - testsEventsConnection = - adapter.tests((e: TestLoadStartedEvent|TestLoadFinishedEvent) => { - testsEvents.push(e); - }); - - testStatesEvents = []; - testStatesEventsConnection = adapter.testStates( - (e: TestRunStartedEvent|TestRunFinishedEvent|TestSuiteEvent| - TestEvent) => { - testStatesEvents.push(e); - }); - - return adapter!; - } - - async function waitFor( - test: Mocha.Context, condition: Function, - timeout: number = 1000): Promise { - const start = Date.now(); - let c = await condition(); - while (!(c = await condition()) && - ((Date.now() - start) < timeout || !test.enableTimeouts())) - await promisify(setTimeout)(10); - assert.ok(c); - } - - async function doAndWaitForReloadEvent( - test: Mocha.Context, action: Function, - timeout: number = 1000): Promise { - const origCount = testsEvents.length; - await action(); - await waitFor(test, () => { - return testsEvents.length == origCount + 2; - }, timeout); - assert.equal(testsEvents.length, origCount + 2); - const e = testsEvents[testsEvents.length - 1]!; - assert.equal(e.type, 'finished'); - assert.ok(e.suite != undefined); - return e.suite!; - } - function disposeAdapterAndSubscribers(check: boolean = true) { - adapter && adapter.dispose(); - testsEventsConnection && testsEventsConnection.dispose(); - testStatesEventsConnection && testStatesEventsConnection.dispose(); - testStatesEvents = []; - if (check) { - for (let i = 0; i < testsEvents.length; i++) { - assert.deepStrictEqual( - {type: 'started'}, testsEvents[i], - inspect({index: i, testsEvents: testsEvents})); - i++; - assert.ok( - i < testsEvents.length, - inspect({index: i, testsEvents: testsEvents})); - assert.equal( - testsEvents[i].type, 'finished', - inspect({index: i, testsEvents: testsEvents})); - assert.ok( - (testsEvents[i]).suite, - inspect({index: i, testsEvents: testsEvents})); - } - } - testsEvents = []; - } + testStatesEvents = []; + testStatesEventsConnection = adapter.testStates( + (e: TestRunStartedEvent|TestRunFinishedEvent|TestSuiteEvent| + TestEvent) => { + testStatesEvents.push(e); + }); - function stubsResetToMyDefault() { - spawnStub.reset(); - spawnStub.callThrough(); - vsfsWatchStub.reset(); - vsfsWatchStub.callThrough(); - c2fsStatStub.reset(); - c2fsStatStub.callThrough(); - vsFindFilesStub.reset(); - vsFindFilesStub.callThrough(); + return adapter!; + } + + async function waitFor( + test: Mocha.Context, condition: Function, + timeout: number = 1000): Promise { + const start = Date.now(); + let c = await condition(); + while (!(c = await condition()) && + ((Date.now() - start) < timeout || !test.enableTimeouts())) + await promisify(setTimeout)(10); + assert.ok(c); + } + + async function doAndWaitForReloadEvent( + test: Mocha.Context, action: Function, + timeout: number = 1000): Promise { + const origCount = testsEvents.length; + await action(); + await waitFor(test, () => { + return testsEvents.length == origCount + 2; + }, timeout); + assert.equal(testsEvents.length, origCount + 2); + const e = testsEvents[testsEvents.length - 1]!; + assert.equal(e.type, 'finished'); + assert.ok(e.suite != undefined); + return e.suite!; + } + + function disposeAdapterAndSubscribers(check: boolean = true) { + adapter && adapter.dispose(); + testsEventsConnection && testsEventsConnection.dispose(); + testStatesEventsConnection && testStatesEventsConnection.dispose(); + testStatesEvents = []; + if (check) { + for (let i = 0; i < testsEvents.length; i++) { + assert.deepStrictEqual( + {type: 'started'}, testsEvents[i], + inspect({index: i, testsEvents: testsEvents})); + i++; + assert.ok( + i < testsEvents.length, + inspect({index: i, testsEvents: testsEvents})); + assert.equal( + testsEvents[i].type, 'finished', + inspect({index: i, testsEvents: testsEvents})); + assert.ok( + (testsEvents[i]).suite, + inspect({index: i, testsEvents: testsEvents})); } + } + testsEvents = []; + } + + function stubsResetToMyDefault() { + spawnStub.reset(); + spawnStub.callThrough(); + vsfsWatchStub.reset(); + vsfsWatchStub.callThrough(); + c2fsStatStub.reset(); + c2fsStatStub.callThrough(); + vsFindFilesStub.reset(); + vsFindFilesStub.callThrough(); + } + + before(function() { + fse.removeSync(dotVscodePath); + adapter = undefined; + + spawnStub = sinonSandbox.stub(child_process, 'spawn').named('spawnStub'); + vsfsWatchStub = + sinonSandbox.stub(vscode.workspace, 'createFileSystemWatcher') + .named('vscode.createFileSystemWatcher'); + c2fsStatStub = sinonSandbox.stub(fs, 'stat').named('fsStat'); + vsFindFilesStub = sinonSandbox.stub(vscode.workspace, 'findFiles') + .named('vsFindFilesStub'); + + stubsResetToMyDefault(); + + // reset config can cause problem with fse.removeSync(dotVscodePath); + return resetConfig(); + }); + + after(function() { + disposeAdapterAndSubscribers(); + sinonSandbox.restore(); + }); + + describe('detect config change', function() { + this.slow(200); + + let adapter: C2TestAdapter; + + before(function() { + adapter = createAdapterAndSubscribe(); + assert.deepStrictEqual(testsEvents, []); + }) - before(function() { - fse.removeSync(dotVscodePath); - adapter = undefined; + after(function() { + disposeAdapterAndSubscribers(); + return resetConfig(); + }) - spawnStub = - sinonSandbox.stub(child_process, 'spawn').named('spawnStub'); - vsfsWatchStub = - sinonSandbox.stub(vscode.workspace, 'createFileSystemWatcher') - .named('vscode.createFileSystemWatcher'); - c2fsStatStub = sinonSandbox.stub(fs, 'stat').named('fsStat'); - vsFindFilesStub = sinonSandbox.stub(vscode.workspace, 'findFiles') - .named('vsFindFilesStub'); + it('defaultEnv', function() { + return doAndWaitForReloadEvent(this, () => { + return updateConfig('defaultEnv', {'APPLE': 'apple'}); + }); + }) - stubsResetToMyDefault(); + it('defaultCwd', function() { + return doAndWaitForReloadEvent(this, () => { + return updateConfig('defaultCwd', 'apple/peach'); + }); + }) - // reset config can cause problem with fse.removeSync(dotVscodePath); - return resetConfig(); + it('enableSourceDecoration', function() { + return updateConfig('enableSourceDecoration', false).then(function() { + assert.ok(!adapter.getIsEnabledSourceDecoration()); }); + }) - after(function() { - disposeAdapterAndSubscribers(); - sinonSandbox.restore(); + it('defaultRngSeed', function() { + return updateConfig('defaultRngSeed', 987).then(function() { + assert.equal(adapter.getRngSeed(), 987); }); + }) + }) + + it('load with empty config', async function() { + this.slow(500); + const adapter = createAdapterAndSubscribe(); + await adapter.load(); + assert.equal(testsEvents.length, 2); + assert.equal(testsEvents[0].type, 'started'); + assert.equal(testsEvents[1].type, 'finished'); + const suite = (testsEvents[1]).suite; + assert.notEqual(suite, undefined); + assert.equal(suite!.children.length, 0); + disposeAdapterAndSubscribers(); + }) + + context('example1', function() { + const watchers: Map = new Map(); + + function handleCreateWatcherCb( + p: vscode.RelativePattern, ignoreCreateEvents: boolean, + ignoreChangeEvents: boolean, ignoreDeleteEvents: boolean) { + const pp = path.join(p.base, p.pattern); + const e = new FileSystemWatcherStub( + vscode.Uri.file(pp), ignoreCreateEvents, ignoreChangeEvents, + ignoreDeleteEvents); + watchers.set(pp, e); + return e; + } + + function handleStatExistsFile( + path: string, + cb: (err: NodeJS.ErrnoException|null, stats: fs.Stats|undefined) => + void) { + cb(null, { + isFile() { + return true; + }, + isDirectory() { + return false; + } + }); + } + + function handleStatNotExists( + path: string, + cb: (err: NodeJS.ErrnoException|null|any, stats: fs.Stats|undefined) => + void) { + cb({ + code: 'ENOENT', + errno: -2, + message: 'ENOENT', + path: path, + syscall: 'stat' + }, + undefined); + } + + function matchRelativePattern(p: string) { + return sinon.match((actual: vscode.RelativePattern) => { + const required = new vscode.RelativePattern( + workspaceFolder, path.relative(workspaceFolderUri.path, p)); + return required.base == actual.base && + required.pattern == actual.pattern; + }); + } - describe('detect config change', function() { - this.slow(150); + before(function() { + for (let suite of example1.outputs) { + for (let scenario of suite[1]) { + spawnStub.withArgs(suite[0], scenario[0]).callsFake(function() { + return new ChildProcessStub(scenario[1]); + }); + } - let adapter: C2TestAdapter; + c2fsStatStub.withArgs(suite[0]).callsFake(handleStatExistsFile); - before(function() { - adapter = createAdapterAndSubscribe(); - assert.deepStrictEqual(testsEvents, []); - }) + vsfsWatchStub.withArgs(matchRelativePattern(suite[0])) + .callsFake(handleCreateWatcherCb); + } - after(function() { - disposeAdapterAndSubscribers(); - return resetConfig(); - }) + const dirContent: Map = new Map(); + for (let p of example1.outputs) { + const parent = path.dirname(p[0]); + let children: vscode.Uri[] = []; + if (dirContent.has(parent)) + children = dirContent.get(parent)!; + else { + dirContent.set(parent, children); + } + children.push(vscode.Uri.file(p[0])); + } - it('defaultEnv', function() { - return doAndWaitForReloadEvent(this, () => { - return updateConfig('defaultEnv', {'APPLE': 'apple'}); - }); - }) + dirContent.forEach((v: vscode.Uri[], k: string) => { + assert.equal(workspaceFolderUri.path, k); + vsFindFilesStub.withArgs(matchRelativePattern(k)).returns(v); + for (const p of v) { + vsFindFilesStub.withArgs(matchRelativePattern(p.path)).returns([p]); + } + }); + }) - it('defaultCwd', function() { - return doAndWaitForReloadEvent(this, () => { - return updateConfig('defaultCwd', 'apple/peach'); - }); - }) + after(function() { + stubsResetToMyDefault(); + }) - it('enableSourceDecoration', function() { - return updateConfig('enableSourceDecoration', false).then(function() { - assert.ok(!adapter.getIsEnabledSourceDecoration()); - }); - }) + afterEach(function() { + watchers.clear(); + }) - it('defaultRngSeed', function() { - return updateConfig('defaultRngSeed', 987).then(function() { - assert.equal(adapter.getRngSeed(), 987); - }); - }) + describe('load', function() { + const uniqueIdC = new Set(); + let adapter: TestAdapter; + + let root: TestSuiteInfo; + let suite1: TestSuiteInfo|any; + let s1t1: TestInfo|any; + let s1t2: TestInfo|any; + let suite2: TestSuiteInfo|any; + let s2t1: TestInfo|any; + let s2t2: TestInfo|any; + let s2t3: TestInfo|any; + + before(function(){ + return updateConfig("workerMaxNumber", 4); }) - it('load with empty config', async function() { - this.slow(400); - const adapter = createAdapterAndSubscribe(); + after(function(){ + return updateConfig("workerMaxNumber", undefined); + }) + + beforeEach(async function() { + adapter = createAdapterAndSubscribe(); await adapter.load(); - assert.equal(testsEvents.length, 2); - assert.equal(testsEvents[0].type, 'started'); + + assert.equal(testsEvents.length, 2, inspect(testsEvents)); assert.equal(testsEvents[1].type, 'finished'); - const suite = (testsEvents[1]).suite; - assert.notEqual(suite, undefined); - assert.equal(suite!.children.length, 0); + assert.ok((testsEvents[1]).suite); + root = (testsEvents[1]).suite!; + testsEvents.pop(); + testsEvents.pop(); + + suite1 = undefined; + s1t1 = undefined; + s1t2 = undefined; + suite2 = undefined; + s2t1 = undefined; + s2t2 = undefined; + s2t3 = undefined; + + example1.assertWithoutChildren(root, uniqueIdC); + assert.deepStrictEqual(testStatesEvents, []); + }); + + afterEach(function() { + uniqueIdC.clear(); disposeAdapterAndSubscribers(); - }) + }); - context('example1', function() { - const watchers: Map = new Map(); - - function handleCreateWatcherCb( - p: vscode.RelativePattern, ignoreCreateEvents: boolean, - ignoreChangeEvents: boolean, ignoreDeleteEvents: boolean) { - const pp = path.join(p.base, p.pattern); - const e = new FileSystemWatcherStub( - vscode.Uri.file(pp), ignoreCreateEvents, ignoreChangeEvents, - ignoreDeleteEvents); - watchers.set(pp, e); - return e; - } + context('executables="execPath1"', function() { + before(function() { + return updateConfig('executables', 'execPath1'); + }); - function handleStatExistsFile( - path: string, - cb: (err: NodeJS.ErrnoException|null, stats: fs.Stats|undefined) => - void) { - cb(null, { - isFile() { - return true; - }, - isDirectory() { - return false; - } - }); - } + after(function() { + return updateConfig('executables', undefined); + }); - function handleStatNotExists( - path: string, - cb: ( - err: NodeJS.ErrnoException|null|any, - stats: fs.Stats|undefined) => void) { - cb({ - code: 'ENOENT', - errno: -2, - message: 'ENOENT', - path: path, - syscall: 'stat' - }, - undefined); - } + beforeEach(async function() { + assert.deepStrictEqual( + getConfig().get('executables'), 'execPath1'); + assert.equal(root.children.length, 1); + assert.equal(root.children[0].type, 'suite'); + suite1 = root.children[0]; + example1.suite1.assert( + 'execPath1', ['s1t1', 's1t2'], suite1, uniqueIdC); + assert.equal(suite1.children.length, 2); + assert.equal(suite1.children[0].type, 'test'); + s1t1 = suite1.children[0]; + assert.equal(suite1.children[1].type, 'test'); + s1t2 = suite1.children[1]; + }); - function matchRelativePattern(p: string) { - return sinon.match((actual: vscode.RelativePattern) => { - const required = new vscode.RelativePattern( - workspaceFolder, path.relative(workspaceFolderUri.path, p)); - return required.base == actual.base && - required.pattern == actual.pattern; - }); - } + it('should run with not existing test id', async function() { + await adapter.run(['not existing id']); - before(function() { - for (let suite of example1.outputs) { - for (let scenario of suite[1]) { - spawnStub.withArgs(suite[0], scenario[0]).callsFake(function() { - return new ChildProcessStub(scenario[1]); - }); - } + assert.deepStrictEqual(testStatesEvents, [ + {type: 'started', tests: ['not existing id']}, + {type: 'finished'}, + ]); + }); - c2fsStatStub.withArgs(suite[0]).callsFake(handleStatExistsFile); + it('should run s1t1 with success', async function() { + assert.equal(getConfig().get('executables'), 'execPath1'); + await adapter.run([s1t1.id]); + const expected = [ + {type: 'started', tests: [s1t1.id]}, + {type: 'suite', state: 'running', suite: suite1}, + {type: 'test', state: 'running', test: s1t1}, + { + type: 'test', + state: 'passed', + test: s1t1, + decorations: undefined, + message: 'Duration: 0.000112 second(s)\n' + }, + {type: 'suite', state: 'completed', suite: suite1}, + {type: 'finished'}, + ]; + assert.deepStrictEqual(testStatesEvents, expected); - vsfsWatchStub.withArgs(matchRelativePattern(suite[0])) - .callsFake(handleCreateWatcherCb); - } + await adapter.run([s1t1.id]); + assert.deepStrictEqual(testStatesEvents, [...expected, ...expected]); + }); - const dirContent: Map = new Map(); - for (let p of example1.outputs) { - const parent = path.dirname(p[0]); - let children: vscode.Uri[] = []; - if (dirContent.has(parent)) - children = dirContent.get(parent)!; - else { - dirContent.set(parent, children); - } - children.push(vscode.Uri.file(p[0])); - } + it('should run suite1', async function() { + await adapter.run([suite1.id]); + const expected = [ + {type: 'started', tests: [suite1.id]}, + {type: 'suite', state: 'running', suite: suite1}, + {type: 'test', state: 'running', test: s1t1}, + { + type: 'test', + state: 'passed', + test: s1t1, + decorations: undefined, + message: 'Duration: 0.000132 second(s)\n' + }, + {type: 'test', state: 'running', test: s1t2}, + { + type: 'test', + state: 'failed', + test: s1t2, + decorations: [{line: 14, message: 'Expanded: false'}], + message: + 'Duration: 0.000204 second(s)\n>>> s1t2(line: 13) REQUIRE (line: 15) \n Original:\n std::false_type::value\n Expanded:\n false\n<<<\n' + }, + {type: 'suite', state: 'completed', suite: suite1}, + {type: 'finished'}, + ]; + assert.deepStrictEqual(testStatesEvents, expected); - dirContent.forEach((v: vscode.Uri[], k: string) => { - assert.equal(workspaceFolderUri.path, k); - vsFindFilesStub.withArgs(matchRelativePattern(k)).returns(v); - for (const p of v) { - vsFindFilesStub.withArgs(matchRelativePattern(p.path)).returns([ - p - ]); - } - }); - }) + await adapter.run([suite1.id]); + assert.deepStrictEqual(testStatesEvents, [...expected, ...expected]); + }); - after(function() { - stubsResetToMyDefault(); - }) + it('should run all', async function() { + await adapter.run([root.id]); + const expected = [ + {type: 'started', tests: [root.id]}, + {type: 'suite', state: 'running', suite: suite1}, + {type: 'test', state: 'running', test: s1t1}, + { + type: 'test', + state: 'passed', + test: s1t1, + decorations: undefined, + message: 'Duration: 0.000132 second(s)\n' + }, + {type: 'test', state: 'running', test: s1t2}, + { + type: 'test', + state: 'failed', + test: s1t2, + decorations: [{line: 14, message: 'Expanded: false'}], + message: + 'Duration: 0.000204 second(s)\n>>> s1t2(line: 13) REQUIRE (line: 15) \n Original:\n std::false_type::value\n Expanded:\n false\n<<<\n' + }, + {type: 'suite', state: 'completed', suite: suite1}, + {type: 'finished'}, + ]; + assert.deepStrictEqual(testStatesEvents, expected); - afterEach(function() { - watchers.clear(); - }) + await adapter.run([root.id]); + assert.deepStrictEqual(testStatesEvents, [...expected, ...expected]); + }); - describe('load', function() { - const uniqueIdC = new Set(); - let adapter: TestAdapter; + it('cancels without any problem', async function() { + adapter.cancel(); + assert.deepStrictEqual(testsEvents, []); + assert.deepStrictEqual(testStatesEvents, []); - let root: TestSuiteInfo; - let suite1: TestSuiteInfo|any; - let s1t1: TestInfo|any; - let s1t2: TestInfo|any; - let suite2: TestSuiteInfo|any; - let s2t1: TestInfo|any; - let s2t2: TestInfo|any; - let s2t3: TestInfo|any; + adapter.cancel(); + assert.deepStrictEqual(testsEvents, []); + assert.deepStrictEqual(testStatesEvents, []); + + await adapter.run([s1t1.id]); + const expected = [ + {type: 'started', tests: [s1t1.id]}, + {type: 'suite', state: 'running', suite: suite1}, + {type: 'test', state: 'running', test: s1t1}, + { + type: 'test', + state: 'passed', + test: s1t1, + decorations: undefined, + message: 'Duration: 0.000112 second(s)\n' + }, + {type: 'suite', state: 'completed', suite: suite1}, + {type: 'finished'}, + ]; + assert.deepStrictEqual(testStatesEvents, expected); - beforeEach(async function() { - adapter = createAdapterAndSubscribe(); - await adapter.load(); - - assert.equal(testsEvents.length, 2, inspect(testsEvents)); - assert.equal(testsEvents[1].type, 'finished'); - assert.ok((testsEvents[1]).suite); - root = (testsEvents[1]).suite!; - testsEvents.pop(); - testsEvents.pop(); - - suite1 = undefined; - s1t1 = undefined; - s1t2 = undefined; - suite2 = undefined; - s2t1 = undefined; - s2t2 = undefined; - s2t3 = undefined; - - example1.assertWithoutChildren(root, uniqueIdC); - assert.deepStrictEqual(testStatesEvents, []); - }); + adapter.cancel(); + assert.deepStrictEqual(testsEvents, []); + assert.deepStrictEqual(testStatesEvents, expected); + }); - afterEach(function() { - uniqueIdC.clear(); - disposeAdapterAndSubscribers(); - }); + context('with config: defaultRngSeed=2', function() { + before(function() { + return updateConfig('defaultRngSeed', 2); + }) - context('executables="execPath1"', function() { - before(function() { - return updateConfig('executables', 'execPath1'); - }); + after(function() { + return updateConfig('defaultRngSeed', undefined); + }) - after(function() { - return updateConfig('executables', undefined); - }); + it('should run s1t1 with success', async function() { + await adapter.run([s1t1.id]); + const expected = [ + {type: 'started', tests: [s1t1.id]}, + {type: 'suite', state: 'running', suite: suite1}, + {type: 'test', state: 'running', test: s1t1}, + { + type: 'test', + state: 'passed', + test: s1t1, + decorations: undefined, + message: + 'Randomness seeded to: 2\nDuration: 0.000327 second(s)\n' + }, + {type: 'suite', state: 'completed', suite: suite1}, + {type: 'finished'}, + ]; + assert.deepStrictEqual(testStatesEvents, expected); - beforeEach(async function() { - assert.deepStrictEqual( - getConfig().get('executables'), 'execPath1'); - assert.equal(root.children.length, 1); - assert.equal(root.children[0].type, 'suite'); - suite1 = root.children[0]; - example1.suite1.assert( - 'execPath1', ['s1t1', 's1t2'], suite1, uniqueIdC); - assert.equal(suite1.children.length, 2); - assert.equal(suite1.children[0].type, 'test'); - s1t1 = suite1.children[0]; - assert.equal(suite1.children[1].type, 'test'); - s1t2 = suite1.children[1]; - }); + await adapter.run([s1t1.id]); + assert.deepStrictEqual( + testStatesEvents, [...expected, ...expected]); + }) + }) + }) - it('should run with not existing test id', async function() { - await adapter.run(['not existing id']); + context('suite1 and suite2 are used', function() { + beforeEach(function() { + assert.equal(root.children.length, 2); + + assert.equal(root.children[0].type, 'suite'); + assert.equal(root.children[1].type, 'suite'); + assert.equal(example1.suite1.outputs.length, 4 + 2 * 2); + assert.equal(example1.suite2.outputs.length, 4 + 2 * 3); + suite1 = root.children[0]; + suite2 = root.children[1]; + if (suite2.children.length == 2) { + suite1 = root.children[1]; + suite2 = root.children[0]; + } - assert.deepStrictEqual(testStatesEvents, [ - {type: 'started', tests: ['not existing id']}, - {type: 'finished'}, - ]); - }); + assert.equal(suite1.children.length, 2); + assert.equal(suite1.children[0].type, 'test'); + s1t1 = suite1.children[0]; + assert.equal(suite1.children[1].type, 'test'); + s1t2 = suite1.children[1]; + + assert.equal(suite2.children.length, 3); + assert.equal(suite2.children[0].type, 'test'); + s2t1 = suite2.children[0]; + assert.equal(suite2.children[1].type, 'test'); + s2t2 = suite2.children[1]; + assert.equal(suite2.children[2].type, 'test'); + s2t3 = suite2.children[2]; + }) - it('should run s1t1 with success', async function() { - assert.equal(getConfig().get('executables'), 'execPath1'); - await adapter.run([s1t1.id]); - const expected = [ - {type: 'started', tests: [s1t1.id]}, - {type: 'suite', state: 'running', suite: suite1}, - {type: 'test', state: 'running', test: s1t1}, - { + const testsForAdapterWithSuite1AndSuite2: Mocha.Test[] = [ + new Mocha.Test( + 'test variables are fine, suite1 and suite1 are loaded', + function() { + assert.equal(root.children.length, 2); + assert.ok(suite1 != undefined); + assert.ok(s1t1 != undefined); + assert.ok(s1t2 != undefined); + assert.ok(suite2 != undefined); + assert.ok(s2t1 != undefined); + assert.ok(s2t2 != undefined); + assert.ok(s2t3 != undefined); + }), + new Mocha.Test( + 'should run all', + async function() { + assert.equal(root.children.length, 2); + await adapter.run([root.id]); + + const running = {type: 'started', tests: [root.id]}; + + const s1running = { + type: 'suite', + state: 'running', + suite: suite1 + }; + const s1finished = { + type: 'suite', + state: 'completed', + suite: suite1 + }; + assert.ok(testStatesEvI(running) < testStatesEvI(s1running)); + assert.ok(testStatesEvI(s1running) < testStatesEvI(s1finished)); + + const s2running = { + type: 'suite', + state: 'running', + suite: suite2 + }; + const s2finished = { + type: 'suite', + state: 'completed', + suite: suite2 + }; + assert.ok(testStatesEvI(running) < testStatesEvI(s1running)); + assert.ok(testStatesEvI(s2running) < testStatesEvI(s2finished)); + + const s1t1running = { type: 'test', - state: 'passed', - test: s1t1, - decorations: undefined, - message: 'Duration: 0.000112 second(s)\n' - }, - {type: 'suite', state: 'completed', suite: suite1}, - {type: 'finished'}, - ]; - assert.deepStrictEqual(testStatesEvents, expected); - - await adapter.run([s1t1.id]); - assert.deepStrictEqual( - testStatesEvents, [...expected, ...expected]); - }); + state: 'running', + test: s1t1 + }; + assert.ok( + testStatesEvI(s1running) < testStatesEvI(s1t1running)); - it('should run suite1', async function() { - await adapter.run([suite1.id]); - const expected = [ - {type: 'started', tests: [suite1.id]}, - {type: 'suite', state: 'running', suite: suite1}, - {type: 'test', state: 'running', test: s1t1}, - { + const s1t1finished = { type: 'test', state: 'passed', test: s1t1, decorations: undefined, message: 'Duration: 0.000132 second(s)\n' - }, - {type: 'test', state: 'running', test: s1t2}, - { - type: 'test', - state: 'failed', - test: s1t2, - decorations: [{line: 14, message: 'Expanded: false'}], - message: - 'Duration: 0.000204 second(s)\n>>> s1t2(line: 13) REQUIRE (line: 15) \n Original:\n std::false_type::value\n Expanded:\n false\n<<<\n' - }, - {type: 'suite', state: 'completed', suite: suite1}, - {type: 'finished'}, - ]; - assert.deepStrictEqual(testStatesEvents, expected); + }; + assert.ok( + testStatesEvI(s1t1running) < testStatesEvI(s1t1finished)); + assert.ok( + testStatesEvI(s1t1finished) < testStatesEvI(s1finished)); - await adapter.run([suite1.id]); - assert.deepStrictEqual( - testStatesEvents, [...expected, ...expected]); - }); - - it('should run all', async function() { - await adapter.run([root.id]); - const expected = [ - {type: 'started', tests: [root.id]}, - {type: 'suite', state: 'running', suite: suite1}, - {type: 'test', state: 'running', test: s1t1}, - { + const s1t2running = { type: 'test', - state: 'passed', - test: s1t1, - decorations: undefined, - message: 'Duration: 0.000132 second(s)\n' - }, - {type: 'test', state: 'running', test: s1t2}, - { + state: 'running', + test: s1t2 + }; + assert.ok( + testStatesEvI(s1running) < testStatesEvI(s1t2running)); + + const s1t2finished = { type: 'test', state: 'failed', test: s1t2, decorations: [{line: 14, message: 'Expanded: false'}], message: 'Duration: 0.000204 second(s)\n>>> s1t2(line: 13) REQUIRE (line: 15) \n Original:\n std::false_type::value\n Expanded:\n false\n<<<\n' - }, - {type: 'suite', state: 'completed', suite: suite1}, - {type: 'finished'}, - ]; - assert.deepStrictEqual(testStatesEvents, expected); - - await adapter.run([root.id]); - assert.deepStrictEqual( - testStatesEvents, [...expected, ...expected]); - }); - - it('cancels without any problem', async function() { - adapter.cancel(); - assert.deepStrictEqual(testsEvents, []); - assert.deepStrictEqual(testStatesEvents, []); + }; + assert.ok( + testStatesEvI(s1t2running) < testStatesEvI(s1t2finished)); + assert.ok( + testStatesEvI(s1t2finished) < testStatesEvI(s1finished)); - adapter.cancel(); - assert.deepStrictEqual(testsEvents, []); - assert.deepStrictEqual(testStatesEvents, []); + const s2t1running = { + type: 'test', + state: 'running', + test: s2t1 + }; + assert.ok( + testStatesEvI(s2running) < testStatesEvI(s2t1running)); - await adapter.run([s1t1.id]); - const expected = [ - {type: 'started', tests: [s1t1.id]}, - {type: 'suite', state: 'running', suite: suite1}, - {type: 'test', state: 'running', test: s1t1}, - { + const s2t1finished = { type: 'test', state: 'passed', - test: s1t1, + test: s2t1, decorations: undefined, - message: 'Duration: 0.000112 second(s)\n' - }, - {type: 'suite', state: 'completed', suite: suite1}, - {type: 'finished'}, - ]; - assert.deepStrictEqual(testStatesEvents, expected); - - adapter.cancel(); - assert.deepStrictEqual(testsEvents, []); - assert.deepStrictEqual(testStatesEvents, expected); - }); + message: 'Duration: 0.00037 second(s)\n' + }; + assert.ok( + testStatesEvI(s2t1running) < testStatesEvI(s2t1finished)); + assert.ok( + testStatesEvI(s2t1finished) < testStatesEvI(s2finished)); + + const s2t2running = { + type: 'test', + state: 'running', + test: s2t2 + }; + assert.ok( + testStatesEvI(s2running) < testStatesEvI(s2t2running)); - context('with config: defaultRngSeed=2', function() { - before(function() { - return updateConfig('defaultRngSeed', 2); - }) + const s2t2finished = { + type: 'test', + state: 'skipped', + test: s2t2 + }; + assert.ok( + testStatesEvI(s2t2running) < testStatesEvI(s2t2finished)); + assert.ok( + testStatesEvI(s2t2finished) < testStatesEvI(s2finished)); + + const s2t3running = { + type: 'test', + state: 'running', + test: s2t3 + }; + assert.ok( + testStatesEvI(s2running) < testStatesEvI(s2t3running)); - after(function() { - return updateConfig('defaultRngSeed', undefined); - }) + const s2t3finished = { + type: 'test', + state: 'failed', + test: s2t3, + decorations: [{line: 20, message: 'Expanded: false'}], + message: + 'Duration: 0.000178 second(s)\n>>> s2t3(line: 19) REQUIRE (line: 21) \n Original:\n std::false_type::value\n Expanded:\n false\n<<<\n' + }; + assert.ok( + testStatesEvI(s2t3running) < testStatesEvI(s2t3finished)); + assert.ok( + testStatesEvI(s2t3finished) < testStatesEvI(s2finished)); + + const finished = {type: 'finished'}; + assert.ok(testStatesEvI(s1finished) < testStatesEvI(finished)); + assert.ok(testStatesEvI(s2finished) < testStatesEvI(finished)); + + assert.equal( + testStatesEvents.length, 16, inspect(testStatesEvents)); + }), + new Mocha.Test( + 'should run with not existing test id', + async function() { + await adapter.run(['not existing id']); - it('should run s1t1 with success', async function() { + assert.deepStrictEqual(testStatesEvents, [ + {type: 'started', tests: ['not existing id']}, + {type: 'finished'}, + ]); + }), + new Mocha.Test( + 'should run s1t1', + async function() { await adapter.run([s1t1.id]); const expected = [ {type: 'started', tests: [s1t1.id]}, @@ -568,8 +789,7 @@ describe( state: 'passed', test: s1t1, decorations: undefined, - message: - 'Randomness seeded to: 2\nDuration: 0.000327 second(s)\n' + message: 'Duration: 0.000112 second(s)\n' }, {type: 'suite', state: 'completed', suite: suite1}, {type: 'finished'}, @@ -579,982 +799,723 @@ describe( await adapter.run([s1t1.id]); assert.deepStrictEqual( testStatesEvents, [...expected, ...expected]); - }) - }) - }) - - context('suite1 and suite2 are used', function() { - beforeEach(function() { - assert.equal(root.children.length, 2); - - assert.equal(root.children[0].type, 'suite'); - assert.equal(root.children[1].type, 'suite'); - assert.equal(example1.suite1.outputs.length, 4 + 2 * 2); - assert.equal(example1.suite2.outputs.length, 4 + 2 * 3); - suite1 = root.children[0]; - suite2 = root.children[1]; - if (suite2.children.length == 2) { - suite1 = root.children[1]; - suite2 = root.children[0]; - } - - assert.equal(suite1.children.length, 2); - assert.equal(suite1.children[0].type, 'test'); - s1t1 = suite1.children[0]; - assert.equal(suite1.children[1].type, 'test'); - s1t2 = suite1.children[1]; - - assert.equal(suite2.children.length, 3); - assert.equal(suite2.children[0].type, 'test'); - s2t1 = suite2.children[0]; - assert.equal(suite2.children[1].type, 'test'); - s2t2 = suite2.children[1]; - assert.equal(suite2.children[2].type, 'test'); - s2t3 = suite2.children[2]; - }) - - const testsForAdapterWithSuite1AndSuite2: Mocha.Test[] = [ - new Mocha.Test( - 'test variables are fine, suite1 and suite1 are loaded', - function() { - assert.equal(root.children.length, 2); - assert.ok(suite1 != undefined); - assert.ok(s1t1 != undefined); - assert.ok(s1t2 != undefined); - assert.ok(suite2 != undefined); - assert.ok(s2t1 != undefined); - assert.ok(s2t2 != undefined); - assert.ok(s2t3 != undefined); - }), - new Mocha.Test( - 'should run all', - async function() { - assert.equal(root.children.length, 2); - await adapter.run([root.id]); - - const running = {type: 'started', tests: [root.id]}; - - const s1running = { - type: 'suite', - state: 'running', - suite: suite1 - }; - const s1finished = { - type: 'suite', - state: 'completed', - suite: suite1 - }; - assert.ok( - testStatesEvI(running) < testStatesEvI(s1running)); - assert.ok( - testStatesEvI(s1running) < testStatesEvI(s1finished)); - - const s2running = { - type: 'suite', - state: 'running', - suite: suite2 - }; - const s2finished = { - type: 'suite', - state: 'completed', - suite: suite2 - }; - assert.ok( - testStatesEvI(running) < testStatesEvI(s1running)); - assert.ok( - testStatesEvI(s2running) < testStatesEvI(s2finished)); - - const s1t1running = { - type: 'test', - state: 'running', - test: s1t1 - }; - assert.ok( - testStatesEvI(s1running) < testStatesEvI(s1t1running)); - - const s1t1finished = { - type: 'test', - state: 'passed', - test: s1t1, - decorations: undefined, - message: 'Duration: 0.000132 second(s)\n' - }; - assert.ok( - testStatesEvI(s1t1running) < - testStatesEvI(s1t1finished)); - assert.ok( - testStatesEvI(s1t1finished) < - testStatesEvI(s1finished)); - - const s1t2running = { - type: 'test', - state: 'running', - test: s1t2 - }; - assert.ok( - testStatesEvI(s1running) < testStatesEvI(s1t2running)); - - const s1t2finished = { - type: 'test', - state: 'failed', - test: s1t2, - decorations: [{line: 14, message: 'Expanded: false'}], - message: - 'Duration: 0.000204 second(s)\n>>> s1t2(line: 13) REQUIRE (line: 15) \n Original:\n std::false_type::value\n Expanded:\n false\n<<<\n' - }; - assert.ok( - testStatesEvI(s1t2running) < - testStatesEvI(s1t2finished)); - assert.ok( - testStatesEvI(s1t2finished) < - testStatesEvI(s1finished)); - - const s2t1running = { - type: 'test', - state: 'running', - test: s2t1 - }; - assert.ok( - testStatesEvI(s2running) < testStatesEvI(s2t1running)); - - const s2t1finished = { - type: 'test', - state: 'passed', - test: s2t1, - decorations: undefined, - message: 'Duration: 0.00037 second(s)\n' - }; - assert.ok( - testStatesEvI(s2t1running) < - testStatesEvI(s2t1finished)); - assert.ok( - testStatesEvI(s2t1finished) < - testStatesEvI(s2finished)); - - const s2t2running = { - type: 'test', - state: 'running', - test: s2t2 - }; - assert.ok( - testStatesEvI(s2running) < testStatesEvI(s2t2running)); - - const s2t2finished = { - type: 'test', - state: 'skipped', - test: s2t2 - }; - assert.ok( - testStatesEvI(s2t2running) < - testStatesEvI(s2t2finished)); - assert.ok( - testStatesEvI(s2t2finished) < - testStatesEvI(s2finished)); - - const s2t3running = { - type: 'test', - state: 'running', - test: s2t3 - }; - assert.ok( - testStatesEvI(s2running) < testStatesEvI(s2t3running)); - - const s2t3finished = { - type: 'test', - state: 'failed', - test: s2t3, - decorations: [{line: 20, message: 'Expanded: false'}], - message: - 'Duration: 0.000178 second(s)\n>>> s2t3(line: 19) REQUIRE (line: 21) \n Original:\n std::false_type::value\n Expanded:\n false\n<<<\n' - }; - assert.ok( - testStatesEvI(s2t3running) < - testStatesEvI(s2t3finished)); - assert.ok( - testStatesEvI(s2t3finished) < - testStatesEvI(s2finished)); - - const finished = {type: 'finished'}; - assert.ok( - testStatesEvI(s1finished) < testStatesEvI(finished)); - assert.ok( - testStatesEvI(s2finished) < testStatesEvI(finished)); - - assert.equal( - testStatesEvents.length, 16, inspect(testStatesEvents)); - }), - new Mocha.Test( - 'should run with not existing test id', - async function() { - await adapter.run(['not existing id']); - - assert.deepStrictEqual(testStatesEvents, [ - {type: 'started', tests: ['not existing id']}, - {type: 'finished'}, - ]); - }), - new Mocha.Test( - 'should run s1t1', - async function() { - await adapter.run([s1t1.id]); - const expected = [ - {type: 'started', tests: [s1t1.id]}, - {type: 'suite', state: 'running', suite: suite1}, - {type: 'test', state: 'running', test: s1t1}, - { - type: 'test', - state: 'passed', - test: s1t1, - decorations: undefined, - message: 'Duration: 0.000112 second(s)\n' - }, - {type: 'suite', state: 'completed', suite: suite1}, - {type: 'finished'}, - ]; - assert.deepStrictEqual(testStatesEvents, expected); - - await adapter.run([s1t1.id]); - assert.deepStrictEqual( - testStatesEvents, [...expected, ...expected]); - }), - new Mocha.Test( - 'should run skipped s2t2', - async function() { - await adapter.run([s2t2.id]); - const expected = [ - {type: 'started', tests: [s2t2.id]}, - {type: 'suite', state: 'running', suite: suite2}, - {type: 'test', state: 'running', test: s2t2}, - { - type: 'test', - state: 'passed', - test: s2t2, - decorations: undefined, - message: 'Duration: 0.001294 second(s)\n' - }, - {type: 'suite', state: 'completed', suite: suite2}, - {type: 'finished'}, - ]; - assert.deepStrictEqual(testStatesEvents, expected); - - await adapter.run([s2t2.id]); - assert.deepStrictEqual( - testStatesEvents, [...expected, ...expected]); - }), - new Mocha.Test( - 'should run failing test s2t3', - async function() { - await adapter.run([s2t3.id]); - const expected = [ - {type: 'started', tests: [s2t3.id]}, - {type: 'suite', state: 'running', suite: suite2}, - {type: 'test', state: 'running', test: s2t3}, - { - type: 'test', - state: 'failed', - test: s2t3, - decorations: [{line: 20, message: 'Expanded: false'}], - message: - 'Duration: 0.000596 second(s)\n>>> s2t3(line: 19) REQUIRE (line: 21) \n Original:\n std::false_type::value\n Expanded:\n false\n<<<\n' - }, - {type: 'suite', state: 'completed', suite: suite2}, - {type: 'finished'}, - ]; - assert.deepStrictEqual(testStatesEvents, expected); - - await adapter.run([s2t3.id]); - assert.deepStrictEqual( - testStatesEvents, [...expected, ...expected]); - }), - new Mocha.Test( - 'should run failing test s2t3 with chunks', - async function() { - const withArgs = spawnStub.withArgs( - example1.suite2.execPath, - example1.suite2.t3.outputs[0][0]); - withArgs.onCall(withArgs.callCount) - .returns(new ChildProcessStub( - example1.suite2.t3.outputs[0][1])); - - await adapter.run([s2t3.id]); - const expected = [ - {type: 'started', tests: [s2t3.id]}, - {type: 'suite', state: 'running', suite: suite2}, - {type: 'test', state: 'running', test: s2t3}, - { - type: 'test', - state: 'failed', - test: s2t3, - decorations: [{line: 20, message: 'Expanded: false'}], - message: - 'Duration: 0.000596 second(s)\n>>> s2t3(line: 19) REQUIRE (line: 21) \n Original:\n std::false_type::value\n Expanded:\n false\n<<<\n' - }, - {type: 'suite', state: 'completed', suite: suite2}, - {type: 'finished'}, - ]; - assert.deepStrictEqual(testStatesEvents, expected); - - await adapter.run([s2t3.id]); - assert.deepStrictEqual( - testStatesEvents, [...expected, ...expected]); - }), - new Mocha.Test( - 'should run suite1', - async function() { - await adapter.run([suite1.id]); - const expected = [ - {type: 'started', tests: [suite1.id]}, - {type: 'suite', state: 'running', suite: suite1}, - {type: 'test', state: 'running', test: s1t1}, - { - type: 'test', - state: 'passed', - test: s1t1, - decorations: undefined, - message: 'Duration: 0.000132 second(s)\n' - }, - {type: 'test', state: 'running', test: s1t2}, - { - type: 'test', - state: 'failed', - test: s1t2, - decorations: [{line: 14, message: 'Expanded: false'}], - message: - 'Duration: 0.000204 second(s)\n>>> s1t2(line: 13) REQUIRE (line: 15) \n Original:\n std::false_type::value\n Expanded:\n false\n<<<\n' - }, - {type: 'suite', state: 'completed', suite: suite1}, - {type: 'finished'}, - ]; - assert.deepStrictEqual(testStatesEvents, expected); - - await adapter.run([suite1.id]); - assert.deepStrictEqual( - testStatesEvents, [...expected, ...expected]); - }), - new Mocha.Test( - 'should run with wrong xml', - async function() { - const m = example1.suite1.t1.outputs[0][1].match( - ']+>'); - assert.notEqual(m, undefined); - assert.notEqual(m!.input, undefined); - assert.notEqual(m!.index, undefined); - const part = m!.input!.substr(0, m!.index! + m![0].length); - const withArgs = spawnStub.withArgs( - example1.suite1.execPath, - example1.suite1.t1.outputs[0][0]); - withArgs.onCall(withArgs.callCount) - .returns(new ChildProcessStub(part)); - - await adapter.run([s1t1.id]); - - const expected = [ - {type: 'started', tests: [s1t1.id]}, - {type: 'suite', state: 'running', suite: suite1}, - {type: 'test', state: 'running', test: s1t1}, - { - type: 'test', - state: 'failed', - test: s1t1, - message: 'Unexpected test error. (Is Catch2 crashed?)\n' - }, - {type: 'suite', state: 'completed', suite: suite1}, - {type: 'finished'}, - ]; - assert.deepStrictEqual(testStatesEvents, expected); - - // this tests the sinon stubs too - await adapter.run([s1t1.id]); - assert.deepStrictEqual(testStatesEvents, [ - ...expected, - {type: 'started', tests: [s1t1.id]}, - {type: 'suite', state: 'running', suite: suite1}, - {type: 'test', state: 'running', test: s1t1}, - { - type: 'test', - state: 'passed', - test: s1t1, - decorations: undefined, - message: 'Duration: 0.000112 second(s)\n' - }, - {type: 'suite', state: 'completed', suite: suite1}, - {type: 'finished'}, - ]); - }), - new Mocha.Test( - 'should cancel without error', - function() { - adapter.cancel(); - }), - new Mocha.Test( - 'cancel', - async function() { - let spyKill1: sinon.SinonSpy; - let spyKill2: sinon.SinonSpy; - { - const spawnEvent = - new ChildProcessStub(example1.suite1.outputs[2][1]); - spyKill1 = sinon.spy(spawnEvent, 'kill'); - const withArgs = spawnStub.withArgs( - example1.suite1.execPath, - example1.suite1.outputs[2][0]); - withArgs.onCall(withArgs.callCount).returns(spawnEvent); - } - { - const spawnEvent = - new ChildProcessStub(example1.suite2.outputs[2][1]); - spyKill2 = sinon.spy(spawnEvent, 'kill'); - const withArgs = spawnStub.withArgs( - example1.suite2.execPath, - example1.suite2.outputs[2][0]); - withArgs.onCall(withArgs.callCount).returns(spawnEvent); - } - const run = adapter.run([root.id]); - adapter.cancel(); - await run; - - assert.equal(spyKill1.callCount, 1); - assert.equal(spyKill2.callCount, 1); - - const running = {type: 'started', tests: [root.id]}; - - const s1running = { - type: 'suite', - state: 'running', - suite: suite1 - }; - const s1finished = { - type: 'suite', - state: 'completed', - suite: suite1 - }; - assert.ok( - testStatesEvI(running) < testStatesEvI(s1running)); - assert.ok( - testStatesEvI(s1running) < testStatesEvI(s1finished)); - - const s2running = { - type: 'suite', - state: 'running', - suite: suite2 - }; - const s2finished = { - type: 'suite', - state: 'completed', - suite: suite2 - }; - assert.ok( - testStatesEvI(running) < testStatesEvI(s1running)); - assert.ok( - testStatesEvI(s2running) < testStatesEvI(s2finished)); - - const s2t2running = { - type: 'test', - state: 'running', - test: s2t2 - }; - assert.ok( - testStatesEvI(s2running) < testStatesEvI(s2t2running)); - - const s2t2finished = { - type: 'test', - state: 'skipped', - test: s2t2 - }; - assert.ok( - testStatesEvI(s2t2running) < - testStatesEvI(s2t2finished)); - assert.ok( - testStatesEvI(s2t2finished) < - testStatesEvI(s2finished)); - - const finished = {type: 'finished'}; - assert.ok( - testStatesEvI(s1finished) < testStatesEvI(finished)); - assert.ok( - testStatesEvI(s2finished) < testStatesEvI(finished)); + }), + new Mocha.Test( + 'should run skipped s2t2', + async function() { + await adapter.run([s2t2.id]); + const expected = [ + {type: 'started', tests: [s2t2.id]}, + {type: 'suite', state: 'running', suite: suite2}, + {type: 'test', state: 'running', test: s2t2}, + { + type: 'test', + state: 'passed', + test: s2t2, + decorations: undefined, + message: 'Duration: 0.001294 second(s)\n' + }, + {type: 'suite', state: 'completed', suite: suite2}, + {type: 'finished'}, + ]; + assert.deepStrictEqual(testStatesEvents, expected); - assert.equal( - testStatesEvents.length, 8, inspect(testStatesEvents)); - }), - new Mocha.Test( - 'cancel after run finished', - function() { - let spyKill1: sinon.SinonSpy; - let spyKill2: sinon.SinonSpy; - { - const spawnEvent = - new ChildProcessStub(example1.suite1.outputs[2][1]); - spyKill1 = sinon.spy(spawnEvent, 'kill'); - const withArgs = spawnStub.withArgs( - example1.suite1.execPath, - example1.suite1.outputs[2][0]); - withArgs.onCall(withArgs.callCount).returns(spawnEvent); - } - { - const spawnEvent = - new ChildProcessStub(example1.suite2.outputs[2][1]); - spyKill2 = sinon.spy(spawnEvent, 'kill'); - const withArgs = spawnStub.withArgs( - example1.suite2.execPath, - example1.suite2.outputs[2][0]); - withArgs.onCall(withArgs.callCount).returns(spawnEvent); - } - const run = adapter.run([root.id]); - return run.then(function() { - adapter.cancel(); - assert.equal(spyKill1.callCount, 0); - assert.equal(spyKill2.callCount, 0); - }); - }), - ]; + await adapter.run([s2t2.id]); + assert.deepStrictEqual( + testStatesEvents, [...expected, ...expected]); + }), + new Mocha.Test( + 'should run failing test s2t3', + async function() { + await adapter.run([s2t3.id]); + const expected = [ + {type: 'started', tests: [s2t3.id]}, + {type: 'suite', state: 'running', suite: suite2}, + {type: 'test', state: 'running', test: s2t3}, + { + type: 'test', + state: 'failed', + test: s2t3, + decorations: [{line: 20, message: 'Expanded: false'}], + message: + 'Duration: 0.000596 second(s)\n>>> s2t3(line: 19) REQUIRE (line: 21) \n Original:\n std::false_type::value\n Expanded:\n false\n<<<\n' + }, + {type: 'suite', state: 'completed', suite: suite2}, + {type: 'finished'}, + ]; + assert.deepStrictEqual(testStatesEvents, expected); - context( - 'executables=["execPath1", "${workspaceFolder}/execPath2"]', - function() { - before(function() { - return updateConfig( - 'executables', - ['execPath1', '${workspaceFolder}/execPath2']); - }); + await adapter.run([s2t3.id]); + assert.deepStrictEqual( + testStatesEvents, [...expected, ...expected]); + }), + new Mocha.Test( + 'should run failing test s2t3 with chunks', + async function() { + const withArgs = spawnStub.withArgs( + example1.suite2.execPath, example1.suite2.t3.outputs[0][0]); + withArgs.onCall(withArgs.callCount) + .returns( + new ChildProcessStub(example1.suite2.t3.outputs[0][1])); - after(function() { - return updateConfig('executables', undefined); - }); + await adapter.run([s2t3.id]); + const expected = [ + {type: 'started', tests: [s2t3.id]}, + {type: 'suite', state: 'running', suite: suite2}, + {type: 'test', state: 'running', test: s2t3}, + { + type: 'test', + state: 'failed', + test: s2t3, + decorations: [{line: 20, message: 'Expanded: false'}], + message: + 'Duration: 0.000596 second(s)\n>>> s2t3(line: 19) REQUIRE (line: 21) \n Original:\n std::false_type::value\n Expanded:\n false\n<<<\n' + }, + {type: 'suite', state: 'completed', suite: suite2}, + {type: 'finished'}, + ]; + assert.deepStrictEqual(testStatesEvents, expected); - let suite1Watcher: FileSystemWatcherStub; - - beforeEach(async function() { - assert.equal(watchers.size, 2); - assert.ok(watchers.has(example1.suite1.execPath)); - suite1Watcher = watchers.get(example1.suite1.execPath)!; - - example1.suite1.assert( - 'execPath1', ['s1t1', 's1t2'], suite1, uniqueIdC); - - example1.suite2.assert( - path.join(workspaceFolderUri.path, 'execPath2'), - ['s2t1', 's2t2 [.]', 's2t3'], suite2, uniqueIdC); - }) - - for (let t of testsForAdapterWithSuite1AndSuite2) this - .addTest(t.clone()); - - it('reload because of fswatcher event: touch(changed)', - async function() { - this.slow(200); - const newRoot = - await doAndWaitForReloadEvent(this, async () => { - suite1Watcher.sendChange(); - }); - assert.deepStrictEqual(newRoot, root); - assert.deepStrictEqual(testsEvents, [ - {type: 'started'}, - {type: 'finished', suite: root}, - ]); - }) - - it('reload because of fswatcher event: double touch(changed)', - async function() { - this.slow(200); - const oldRoot = root; - suite1Watcher.sendChange(); - suite1Watcher.sendChange(); - await waitFor(this, async () => { - return testsEvents.length >= 4; - }); - assert.ok( - deepStrictEqual( - testsEvents, - [ - {type: 'started'}, - {type: 'finished', suite: oldRoot}, - {type: 'started'}, - {type: 'finished', suite: oldRoot}, - ]) || - deepStrictEqual(testsEvents, [ - {type: 'started'}, - {type: 'started'}, - {type: 'finished', suite: oldRoot}, - {type: 'finished', suite: oldRoot}, - ])); - testsEvents.pop(); - testsEvents.pop(); - testsEvents.pop(); - testsEvents.pop(); - }) - - it('reload because of fswatcher event: double touch(changed) with delay', - async function() { - this.slow(200); - const oldRoot = root; - suite1Watcher.sendChange(); - setTimeout(() => { - suite1Watcher.sendChange(); - }, 20); - await waitFor(this, async () => { - return testsEvents.length >= 4; - }); - assert.ok( - deepStrictEqual( - testsEvents, - [ - {type: 'started'}, - {type: 'finished', suite: oldRoot}, - {type: 'started'}, - {type: 'finished', suite: oldRoot}, - ]) || - deepStrictEqual(testsEvents, [ - {type: 'started'}, - {type: 'started'}, - {type: 'finished', suite: oldRoot}, - {type: 'finished', suite: oldRoot}, - ])); - testsEvents.pop(); - testsEvents.pop(); - testsEvents.pop(); - testsEvents.pop(); - }) - - it('reload because of fswatcher event: touch(delete,create)', - async function() { - this.slow(200); - const newRoot = - await doAndWaitForReloadEvent(this, async () => { - suite1Watcher.sendDelete(); - suite1Watcher.sendCreate(); - }); - assert.deepStrictEqual(newRoot, root); - assert.deepStrictEqual(testsEvents, [ - {type: 'started'}, - {type: 'finished', suite: root}, - ]); - }) - - it('reload because of fswatcher event: double touch(delete,create)', - async function() { - this.slow(200); - const oldRoot = root; - suite1Watcher.sendChange(); - suite1Watcher.sendChange(); - await waitFor(this, async () => { - return testsEvents.length >= 4; - }); - assert.ok( - deepStrictEqual( - testsEvents, - [ - {type: 'started'}, - {type: 'finished', suite: oldRoot}, - {type: 'started'}, - {type: 'finished', suite: oldRoot}, - ]) || - deepStrictEqual(testsEvents, [ - {type: 'started'}, - {type: 'started'}, - {type: 'finished', suite: oldRoot}, - {type: 'finished', suite: oldRoot}, - ])); - testsEvents.pop(); - testsEvents.pop(); - testsEvents.pop(); - testsEvents.pop(); - }) - - it('reload because of fswatcher event: double touch(delete,create) with delay', - async function() { - this.slow(200); - const oldRoot = root; - suite1Watcher.sendChange(); - setTimeout(() => { - suite1Watcher.sendChange(); - }, 20); - await waitFor(this, async () => { - return testsEvents.length >= 4; - }); - assert.ok( - deepStrictEqual( - testsEvents, - [ - {type: 'started'}, - {type: 'finished', suite: oldRoot}, - {type: 'started'}, - {type: 'finished', suite: oldRoot}, - ]) || - deepStrictEqual(testsEvents, [ - {type: 'started'}, - {type: 'started'}, - {type: 'finished', suite: oldRoot}, - {type: 'finished', suite: oldRoot}, - ])); - testsEvents.pop(); - testsEvents.pop(); - testsEvents.pop(); - testsEvents.pop(); - }) - - it('reload because of fswatcher event: test added', - async function(this: Mocha.Context) { - this.slow(200); - const testListOutput = - example1.suite1.outputs[1][1].split('\n'); - assert.equal(testListOutput.length, 10); - testListOutput.splice( - 1, 0, ' s1t0', ' suite1.cpp:6', ' tag1'); - const withArgs = spawnStub.withArgs( - example1.suite1.execPath, - example1.suite1.outputs[1][0]); - withArgs.onCall(withArgs.callCount) - .returns( - new ChildProcessStub(testListOutput.join('\n'))); - - const oldRootChildren = [...root.children]; - const oldSuite1Children = [...suite1.children]; - const oldSuite2Children = [...suite2.children]; - - const newRoot = - await doAndWaitForReloadEvent(this, async () => { - suite1Watcher.sendDelete(); - suite1Watcher.sendCreate(); - }); - - assert.equal(newRoot, root); - assert.equal( - root.children.length, oldRootChildren.length); - for (let i = 0; i < oldRootChildren.length; i++) { - assert.equal(root.children[i], oldRootChildren[i]); - } - - assert.equal( - suite1.children.length, - oldSuite1Children.length + 1); - for (let i = 0; i < suite1.children.length; i++) { - assert.equal( - suite1.children[i + 1], oldSuite1Children[i]); - } - const newTest = suite1.children[0]; - assert.ok(!uniqueIdC.has(newTest.id)); - assert.equal(newTest.label, 's1t0'); - - assert.equal( - suite2.children.length, oldSuite2Children.length); - for (let i = 0; i < suite2.children.length; i++) { - assert.equal(suite2.children[i], oldSuite2Children[i]); - } - }) - it('reload because of fswatcher event: test deleted', - async function(this: Mocha.Context) { - this.slow(200); - const testListOutput = - example1.suite1.outputs[1][1].split('\n'); - assert.equal(testListOutput.length, 10); - testListOutput.splice(1, 3); - const withArgs = spawnStub.withArgs( - example1.suite1.execPath, - example1.suite1.outputs[1][0]); - withArgs.onCall(withArgs.callCount) - .returns( - new ChildProcessStub(testListOutput.join('\n'))); - - const oldRootChildren = [...root.children]; - const oldSuite1Children = [...suite1.children]; - const oldSuite2Children = [...suite2.children]; - - const newRoot = - await doAndWaitForReloadEvent(this, async () => { - suite1Watcher.sendDelete(); - suite1Watcher.sendCreate(); - }); - - assert.equal(newRoot, root); - assert.equal( - root.children.length, oldRootChildren.length); - for (let i = 0; i < oldRootChildren.length; i++) { - assert.equal(root.children[i], oldRootChildren[i]); - } - - assert.equal( - suite1.children.length + 1, - oldSuite1Children.length); - for (let i = 0; i < suite1.children.length; i++) { - assert.equal( - suite1.children[i], oldSuite1Children[i + 1]); - } - - assert.equal( - suite2.children.length, oldSuite2Children.length); - for (let i = 0; i < suite2.children.length; i++) { - assert.equal(suite2.children[i], oldSuite2Children[i]); - } - }) - }) - - context('executables=[{}] and env={...}', function() { - before(async function() { - await updateConfig( - 'executables', [{ - name: '${relDirpath}/${filename} (${absDirpath})', - path: 'execPath{1,2}', - cwd: '${workspaceFolder}/cwd', - env: { - 'C2LOCALTESTENV': 'c2localtestenv', - 'C2OVERRIDETESTENV': 'c2overridetestenv-l', - } - }]); - - vsfsWatchStub - .withArgs(matchRelativePattern( - path.join(workspaceFolderUri.path, 'execPath{1,2}'))) - .callsFake(handleCreateWatcherCb); - - vsFindFilesStub - .withArgs(matchRelativePattern( - path.join(workspaceFolderUri.path, 'execPath{1,2}'))) - .returns([ - vscode.Uri.file(example1.suite1.execPath), - vscode.Uri.file(example1.suite2.execPath), - ]); - await updateConfig('defaultEnv', { - 'C2GLOBALTESTENV': 'c2globaltestenv', - 'C2OVERRIDETESTENV': 'c2overridetestenv-g', - }); - }); + await adapter.run([s2t3.id]); + assert.deepStrictEqual( + testStatesEvents, [...expected, ...expected]); + }), + new Mocha.Test( + 'should run suite1', + async function() { + await adapter.run([suite1.id]); + const expected = [ + {type: 'started', tests: [suite1.id]}, + {type: 'suite', state: 'running', suite: suite1}, + {type: 'test', state: 'running', test: s1t1}, + { + type: 'test', + state: 'passed', + test: s1t1, + decorations: undefined, + message: 'Duration: 0.000132 second(s)\n' + }, + {type: 'test', state: 'running', test: s1t2}, + { + type: 'test', + state: 'failed', + test: s1t2, + decorations: [{line: 14, message: 'Expanded: false'}], + message: + 'Duration: 0.000204 second(s)\n>>> s1t2(line: 13) REQUIRE (line: 15) \n Original:\n std::false_type::value\n Expanded:\n false\n<<<\n' + }, + {type: 'suite', state: 'completed', suite: suite1}, + {type: 'finished'}, + ]; + assert.deepStrictEqual(testStatesEvents, expected); - after(async function() { - await updateConfig('executables', undefined); - await updateConfig('defaultEnv', undefined); - }); + await adapter.run([suite1.id]); + assert.deepStrictEqual( + testStatesEvents, [...expected, ...expected]); + }), + new Mocha.Test( + 'should run with wrong xml', + async function() { + const m = + example1.suite1.t1.outputs[0][1].match(']+>'); + assert.notEqual(m, undefined); + assert.notEqual(m!.input, undefined); + assert.notEqual(m!.index, undefined); + const part = m!.input!.substr(0, m!.index! + m![0].length); + const withArgs = spawnStub.withArgs( + example1.suite1.execPath, example1.suite1.t1.outputs[0][0]); + withArgs.onCall(withArgs.callCount) + .returns(new ChildProcessStub(part)); - beforeEach(async function() { - example1.suite1.assert( - './execPath1 (' + workspaceFolderUri.path + ')', - ['s1t1', 's1t2'], suite1, uniqueIdC); + await adapter.run([s1t1.id]); - example1.suite2.assert( - './execPath2 (' + workspaceFolderUri.path + ')', - ['s2t1', 's2t2 [.]', 's2t3'], suite2, uniqueIdC); - }) + const expected = [ + {type: 'started', tests: [s1t1.id]}, + {type: 'suite', state: 'running', suite: suite1}, + {type: 'test', state: 'running', test: s1t1}, + { + type: 'test', + state: 'failed', + test: s1t1, + message: 'Unexpected test error. (Is Catch2 crashed?)\n' + }, + {type: 'suite', state: 'completed', suite: suite1}, + {type: 'finished'}, + ]; + assert.deepStrictEqual(testStatesEvents, expected); - for (let t of testsForAdapterWithSuite1AndSuite2) this.addTest( - t.clone()); + // this tests the sinon stubs too + await adapter.run([s1t1.id]); + assert.deepStrictEqual(testStatesEvents, [ + ...expected, + {type: 'started', tests: [s1t1.id]}, + {type: 'suite', state: 'running', suite: suite1}, + {type: 'test', state: 'running', test: s1t1}, + { + type: 'test', + state: 'passed', + test: s1t1, + decorations: undefined, + message: 'Duration: 0.000112 second(s)\n' + }, + {type: 'suite', state: 'completed', suite: suite1}, + {type: 'finished'}, + ]); + }), + new Mocha.Test( + 'should cancel without error', + function() { + adapter.cancel(); + }), + new Mocha.Test( + 'cancel', + async function() { + let spyKill1: sinon.SinonSpy; + let spyKill2: sinon.SinonSpy; + { + const spawnEvent = + new ChildProcessStub(example1.suite1.outputs[2][1]); + spyKill1 = sinon.spy(spawnEvent, 'kill'); + const withArgs = spawnStub.withArgs( + example1.suite1.execPath, example1.suite1.outputs[2][0]); + withArgs.onCall(withArgs.callCount).returns(spawnEvent); + } + { + const spawnEvent = + new ChildProcessStub(example1.suite2.outputs[2][1]); + spyKill2 = sinon.spy(spawnEvent, 'kill'); + const withArgs = spawnStub.withArgs( + example1.suite2.execPath, example1.suite2.outputs[2][0]); + withArgs.onCall(withArgs.callCount).returns(spawnEvent); + } + const run = adapter.run([root.id]); + adapter.cancel(); + await run; + + assert.equal(spyKill1.callCount, 1); + assert.equal(spyKill2.callCount, 1); + + const running = {type: 'started', tests: [root.id]}; + + const s1running = { + type: 'suite', + state: 'running', + suite: suite1 + }; + const s1finished = { + type: 'suite', + state: 'completed', + suite: suite1 + }; + assert.ok(testStatesEvI(running) < testStatesEvI(s1running)); + assert.ok(testStatesEvI(s1running) < testStatesEvI(s1finished)); + + const s2running = { + type: 'suite', + state: 'running', + suite: suite2 + }; + const s2finished = { + type: 'suite', + state: 'completed', + suite: suite2 + }; + assert.ok(testStatesEvI(running) < testStatesEvI(s1running)); + assert.ok(testStatesEvI(s2running) < testStatesEvI(s2finished)); + + const s2t2running = { + type: 'test', + state: 'running', + test: s2t2 + }; + assert.ok( + testStatesEvI(s2running) < testStatesEvI(s2t2running)); - it('should get execution options', async function() { + const s2t2finished = { + type: 'test', + state: 'skipped', + test: s2t2 + }; + assert.ok( + testStatesEvI(s2t2running) < testStatesEvI(s2t2finished)); + assert.ok( + testStatesEvI(s2t2finished) < testStatesEvI(s2finished)); + + const finished = {type: 'finished'}; + assert.ok(testStatesEvI(s1finished) < testStatesEvI(finished)); + assert.ok(testStatesEvI(s2finished) < testStatesEvI(finished)); + + assert.equal( + testStatesEvents.length, 8, inspect(testStatesEvents)); + }), + new Mocha.Test( + 'cancel after run finished', + function() { + let spyKill1: sinon.SinonSpy; + let spyKill2: sinon.SinonSpy; { + const spawnEvent = + new ChildProcessStub(example1.suite1.outputs[2][1]); + spyKill1 = sinon.spy(spawnEvent, 'kill'); const withArgs = spawnStub.withArgs( example1.suite1.execPath, example1.suite1.outputs[2][0]); - withArgs.onCall(withArgs.callCount) - .callsFake((p: string, args: string[], ops: any) => { - assert.equal( - ops.cwd, path.join(workspaceFolderUri.path, 'cwd')); - assert.equal(ops.env.C2LOCALTESTENV, 'c2localtestenv'); - assert.ok(!ops.env.hasOwnProperty('C2GLOBALTESTENV')); - assert.equal( - ops.env.C2OVERRIDETESTENV, 'c2overridetestenv-l'); - return new ChildProcessStub( - example1.suite1.outputs[2][1]); - }); - - const cc = withArgs.callCount; - await adapter.run([suite1.id]); - assert.equal(withArgs.callCount, cc + 1) + withArgs.onCall(withArgs.callCount).returns(spawnEvent); } { + const spawnEvent = + new ChildProcessStub(example1.suite2.outputs[2][1]); + spyKill2 = sinon.spy(spawnEvent, 'kill'); const withArgs = spawnStub.withArgs( example1.suite2.execPath, example1.suite2.outputs[2][0]); - withArgs.onCall(withArgs.callCount) - .callsFake((p: string, args: string[], ops: any) => { - assert.equal( - ops.cwd, path.join(workspaceFolderUri.path, 'cwd')); - assert.equal(ops.env.C2LOCALTESTENV, 'c2localtestenv'); - assert.ok(!ops.env.hasOwnProperty('C2GLOBALTESTENV')); - assert.equal( - ops.env.C2OVERRIDETESTENV, 'c2overridetestenv-l'); - return new ChildProcessStub( - example1.suite2.outputs[2][1]); - }); - const cc = withArgs.callCount; - await adapter.run([suite2.id]); - assert.equal(withArgs.callCount, cc + 1) + withArgs.onCall(withArgs.callCount).returns(spawnEvent); } - }) - }) + const run = adapter.run([root.id]); + return run.then(function() { + adapter.cancel(); + assert.equal(spyKill1.callCount, 0); + assert.equal(spyKill2.callCount, 0); + }); + }), + ]; + + context('executables=["execPath1", "./execPath2"]', function() { + before(function() { + return updateConfig('executables', ['execPath1', './execPath2']); + }); + + after(function() { + return updateConfig('executables', undefined); + }); + + let suite1Watcher: FileSystemWatcherStub; + + beforeEach(async function() { + assert.equal(watchers.size, 2); + assert.ok(watchers.has(example1.suite1.execPath)); + suite1Watcher = watchers.get(example1.suite1.execPath)!; + + example1.suite1.assert( + 'execPath1', ['s1t1', 's1t2'], suite1, uniqueIdC); + + example1.suite2.assert( + './execPath2', ['s2t1', 's2t2 [.]', 's2t3'], suite2, uniqueIdC); }) - context( - 'executables=["execPath1", "execPath2", "execPath3"]', - async function() { - before(function() { - return updateConfig( - 'executables', ['execPath1', 'execPath2', 'execPath3']); - }); + for (let t of testsForAdapterWithSuite1AndSuite2) this.addTest( + t.clone()); + + it('reload because of fswatcher event: touch(changed)', + async function() { + this.slow(200); + const newRoot = await doAndWaitForReloadEvent(this, async () => { + suite1Watcher.sendChange(); + }); + assert.deepStrictEqual(newRoot, root); + assert.deepStrictEqual(testsEvents, [ + {type: 'started'}, + {type: 'finished', suite: root}, + ]); + }) + + it('reload because of fswatcher event: double touch(changed)', + async function() { + this.slow(200); + const oldRoot = root; + suite1Watcher.sendChange(); + suite1Watcher.sendChange(); + await waitFor(this, async () => { + return testsEvents.length >= 4; + }); + assert.ok( + deepStrictEqual( + testsEvents, + [ + {type: 'started'}, + {type: 'finished', suite: oldRoot}, + {type: 'started'}, + {type: 'finished', suite: oldRoot}, + ]) || + deepStrictEqual(testsEvents, [ + {type: 'started'}, + {type: 'started'}, + {type: 'finished', suite: oldRoot}, + {type: 'finished', suite: oldRoot}, + ])); + testsEvents.pop(); + testsEvents.pop(); + testsEvents.pop(); + testsEvents.pop(); + }) + + it('reload because of fswatcher event: double touch(changed) with delay', + async function() { + this.slow(200); + const oldRoot = root; + suite1Watcher.sendChange(); + setTimeout(() => { + suite1Watcher.sendChange(); + }, 20); + await waitFor(this, async () => { + return testsEvents.length >= 4; + }); + assert.ok( + deepStrictEqual( + testsEvents, + [ + {type: 'started'}, + {type: 'finished', suite: oldRoot}, + {type: 'started'}, + {type: 'finished', suite: oldRoot}, + ]) || + deepStrictEqual(testsEvents, [ + {type: 'started'}, + {type: 'started'}, + {type: 'finished', suite: oldRoot}, + {type: 'finished', suite: oldRoot}, + ])); + testsEvents.pop(); + testsEvents.pop(); + testsEvents.pop(); + testsEvents.pop(); + }) + + it('reload because of fswatcher event: touch(delete,create)', + async function() { + this.slow(200); + const newRoot = await doAndWaitForReloadEvent(this, async () => { + suite1Watcher.sendDelete(); + suite1Watcher.sendCreate(); + }); + assert.deepStrictEqual(newRoot, root); + assert.deepStrictEqual(testsEvents, [ + {type: 'started'}, + {type: 'finished', suite: root}, + ]); + }) + + it('reload because of fswatcher event: double touch(delete,create)', + async function() { + this.slow(200); + const oldRoot = root; + suite1Watcher.sendChange(); + suite1Watcher.sendChange(); + await waitFor(this, async () => { + return testsEvents.length >= 4; + }); + assert.ok( + deepStrictEqual( + testsEvents, + [ + {type: 'started'}, + {type: 'finished', suite: oldRoot}, + {type: 'started'}, + {type: 'finished', suite: oldRoot}, + ]) || + deepStrictEqual(testsEvents, [ + {type: 'started'}, + {type: 'started'}, + {type: 'finished', suite: oldRoot}, + {type: 'finished', suite: oldRoot}, + ])); + testsEvents.pop(); + testsEvents.pop(); + testsEvents.pop(); + testsEvents.pop(); + }) + + it('reload because of fswatcher event: double touch(delete,create) with delay', + async function() { + this.slow(200); + const oldRoot = root; + suite1Watcher.sendChange(); + setTimeout(() => { + suite1Watcher.sendChange(); + }, 20); + await waitFor(this, async () => { + return testsEvents.length >= 4; + }); + assert.ok( + deepStrictEqual( + testsEvents, + [ + {type: 'started'}, + {type: 'finished', suite: oldRoot}, + {type: 'started'}, + {type: 'finished', suite: oldRoot}, + ]) || + deepStrictEqual(testsEvents, [ + {type: 'started'}, + {type: 'started'}, + {type: 'finished', suite: oldRoot}, + {type: 'finished', suite: oldRoot}, + ])); + testsEvents.pop(); + testsEvents.pop(); + testsEvents.pop(); + testsEvents.pop(); + }) + + it('reload because of fswatcher event: test added', + async function(this: Mocha.Context) { + this.slow(200); + const testListOutput = example1.suite1.outputs[1][1].split('\n'); + assert.equal(testListOutput.length, 10); + testListOutput.splice( + 1, 0, ' s1t0', ' suite1.cpp:6', ' tag1'); + const withArgs = spawnStub.withArgs( + example1.suite1.execPath, example1.suite1.outputs[1][0]); + withArgs.onCall(withArgs.callCount) + .returns(new ChildProcessStub(testListOutput.join('\n'))); + + const oldRootChildren = [...root.children]; + const oldSuite1Children = [...suite1.children]; + const oldSuite2Children = [...suite2.children]; + + const newRoot = await doAndWaitForReloadEvent(this, async () => { + suite1Watcher.sendDelete(); + suite1Watcher.sendCreate(); + }); + + assert.equal(newRoot, root); + assert.equal(root.children.length, oldRootChildren.length); + for (let i = 0; i < oldRootChildren.length; i++) { + assert.equal(root.children[i], oldRootChildren[i]); + } + + assert.equal( + suite1.children.length, oldSuite1Children.length + 1); + for (let i = 0; i < suite1.children.length; i++) { + assert.equal(suite1.children[i + 1], oldSuite1Children[i]); + } + const newTest = suite1.children[0]; + assert.ok(!uniqueIdC.has(newTest.id)); + assert.equal(newTest.label, 's1t0'); + + assert.equal(suite2.children.length, oldSuite2Children.length); + for (let i = 0; i < suite2.children.length; i++) { + assert.equal(suite2.children[i], oldSuite2Children[i]); + } + }) + it('reload because of fswatcher event: test deleted', + async function(this: Mocha.Context) { + this.slow(200); + const testListOutput = example1.suite1.outputs[1][1].split('\n'); + assert.equal(testListOutput.length, 10); + testListOutput.splice(1, 3); + const withArgs = spawnStub.withArgs( + example1.suite1.execPath, example1.suite1.outputs[1][0]); + withArgs.onCall(withArgs.callCount) + .returns(new ChildProcessStub(testListOutput.join('\n'))); + + const oldRootChildren = [...root.children]; + const oldSuite1Children = [...suite1.children]; + const oldSuite2Children = [...suite2.children]; + + const newRoot = await doAndWaitForReloadEvent(this, async () => { + suite1Watcher.sendDelete(); + suite1Watcher.sendCreate(); + }); + + assert.equal(newRoot, root); + assert.equal(root.children.length, oldRootChildren.length); + for (let i = 0; i < oldRootChildren.length; i++) { + assert.equal(root.children[i], oldRootChildren[i]); + } + + assert.equal( + suite1.children.length + 1, oldSuite1Children.length); + for (let i = 0; i < suite1.children.length; i++) { + assert.equal(suite1.children[i], oldSuite1Children[i + 1]); + } + + assert.equal(suite2.children.length, oldSuite2Children.length); + for (let i = 0; i < suite2.children.length; i++) { + assert.equal(suite2.children[i], oldSuite2Children[i]); + } + }) + }) - after(function() { - return updateConfig('executables', undefined); - }); + context('executables=[{}] and env={...}', function() { + before(async function() { + await updateConfig( + 'executables', [{ + name: '${relDirpath}/${filename} (${absDirpath})', + path: 'execPath{1,2}', + cwd: '${workspaceFolder}/cwd', + env: { + 'C2LOCALTESTENV': 'c2localtestenv', + 'C2OVERRIDETESTENV': 'c2overridetestenv-l', + } + }]); - it('run suite3 one-by-one', async function() { - this.slow(300); - assert.equal(root.children.length, 3); - assert.equal(root.children[0].type, 'suite'); - const suite3 = root.children[2]; - assert.equal(suite3.children.length, 33); + vsfsWatchStub + .withArgs(matchRelativePattern( + path.join(workspaceFolderUri.path, 'execPath{1,2}'))) + .callsFake(handleCreateWatcherCb); - spawnStub.withArgs(example1.suite3.execPath).throwsArg(1); + vsFindFilesStub + .withArgs(matchRelativePattern( + path.join(workspaceFolderUri.path, 'execPath{1,2}'))) + .returns([ + vscode.Uri.file(example1.suite1.execPath), + vscode.Uri.file(example1.suite2.execPath), + ]); + await updateConfig('defaultEnv', { + 'C2GLOBALTESTENV': 'c2globaltestenv', + 'C2OVERRIDETESTENV': 'c2overridetestenv-g', + }); + }); - const runAndCheckEvents = async (test: TestInfo) => { - assert.equal(testStatesEvents.length, 0); + after(async function() { + await updateConfig('executables', undefined); + await updateConfig('defaultEnv', undefined); + }); - await adapter.run([test.id]); + beforeEach(async function() { + example1.suite1.assert( + './execPath1 (' + workspaceFolderUri.path + ')', + ['s1t1', 's1t2'], suite1, uniqueIdC); - assert.equal(testStatesEvents.length, 6, inspect(test)); + example1.suite2.assert( + './execPath2 (' + workspaceFolderUri.path + ')', + ['s2t1', 's2t2 [.]', 's2t3'], suite2, uniqueIdC); + }) - assert.deepStrictEqual( - {type: 'started', tests: [test.id]}, - testStatesEvents[0]); - assert.deepStrictEqual( - {type: 'suite', state: 'running', suite: suite3}, - testStatesEvents[1]); + for (let t of testsForAdapterWithSuite1AndSuite2) this.addTest( + t.clone()); - assert.equal(testStatesEvents[2].type, 'test'); + it('should get execution options', async function() { + { + const withArgs = spawnStub.withArgs( + example1.suite1.execPath, example1.suite1.outputs[2][0]); + withArgs.onCall(withArgs.callCount) + .callsFake((p: string, args: string[], ops: any) => { assert.equal( - (testStatesEvents[2]).state, 'running'); - assert.equal((testStatesEvents[2]).test, test); - - assert.equal(testStatesEvents[3].type, 'test'); - assert.ok( - (testStatesEvents[3]).state == 'passed' || - (testStatesEvents[3]).state == 'skipped' || - (testStatesEvents[3]).state == 'failed'); - assert.equal((testStatesEvents[3]).test, test); - - assert.deepStrictEqual( - {type: 'suite', state: 'completed', suite: suite3}, - testStatesEvents[4]); - assert.deepStrictEqual( - {type: 'finished'}, testStatesEvents[5]); - - while (testStatesEvents.length) testStatesEvents.pop(); - }; - - for (let test of suite3.children) { - assert.equal(test.type, 'test'); - await runAndCheckEvents(test); - } - }) - }) + ops.cwd, path.join(workspaceFolderUri.path, 'cwd')); + assert.equal(ops.env.C2LOCALTESTENV, 'c2localtestenv'); + assert.ok(!ops.env.hasOwnProperty('C2GLOBALTESTENV')); + assert.equal( + ops.env.C2OVERRIDETESTENV, 'c2overridetestenv-l'); + return new ChildProcessStub(example1.suite1.outputs[2][1]); + }); + + const cc = withArgs.callCount; + await adapter.run([suite1.id]); + assert.equal(withArgs.callCount, cc + 1) + } + { + const withArgs = spawnStub.withArgs( + example1.suite2.execPath, example1.suite2.outputs[2][0]); + withArgs.onCall(withArgs.callCount) + .callsFake((p: string, args: string[], ops: any) => { + assert.equal( + ops.cwd, path.join(workspaceFolderUri.path, 'cwd')); + assert.equal(ops.env.C2LOCALTESTENV, 'c2localtestenv'); + assert.ok(!ops.env.hasOwnProperty('C2GLOBALTESTENV')); + assert.equal( + ops.env.C2OVERRIDETESTENV, 'c2overridetestenv-l'); + return new ChildProcessStub(example1.suite2.outputs[2][1]); + }); + const cc = withArgs.callCount; + await adapter.run([suite2.id]); + assert.equal(withArgs.callCount, cc + 1) + } + }) }) + }) + + context( + 'executables=["execPath1", "execPath2", "execPath3"]', + async function() { + before(function() { + return updateConfig( + 'executables', ['execPath1', 'execPath2', 'execPath3']); + }); + + after(function() { + return updateConfig('executables', undefined); + }); + + it('run suite3 one-by-one', async function() { + this.slow(300); + assert.equal(root.children.length, 3); + assert.equal(root.children[0].type, 'suite'); + const suite3 = root.children[2]; + assert.equal(suite3.children.length, 33); + + spawnStub.withArgs(example1.suite3.execPath).throwsArg(1); - specify('load executables=', async function() { + const runAndCheckEvents = async (test: TestInfo) => { + assert.equal(testStatesEvents.length, 0); + + await adapter.run([test.id]); + + assert.equal(testStatesEvents.length, 6, inspect(test)); + + assert.deepStrictEqual( + {type: 'started', tests: [test.id]}, testStatesEvents[0]); + assert.deepStrictEqual( + {type: 'suite', state: 'running', suite: suite3}, + testStatesEvents[1]); + + assert.equal(testStatesEvents[2].type, 'test'); + assert.equal((testStatesEvents[2]).state, 'running'); + assert.equal((testStatesEvents[2]).test, test); + + assert.equal(testStatesEvents[3].type, 'test'); + assert.ok( + (testStatesEvents[3]).state == 'passed' || + (testStatesEvents[3]).state == 'skipped' || + (testStatesEvents[3]).state == 'failed'); + assert.equal((testStatesEvents[3]).test, test); + + assert.deepStrictEqual( + {type: 'suite', state: 'completed', suite: suite3}, + testStatesEvents[4]); + assert.deepStrictEqual({type: 'finished'}, testStatesEvents[5]); + + while (testStatesEvents.length) testStatesEvents.pop(); + }; + + for (let test of suite3.children) { + assert.equal(test.type, 'test'); + await runAndCheckEvents(test); + } + }) + }) + }) + + specify('load executables=', async function() { + this.slow(300); + await updateConfig('executables', example1.suite1.execPath); + adapter = createAdapterAndSubscribe(); + + await adapter.load(); + assert.equal(testsEvents.length, 2); + assert.equal( + (testsEvents[testsEvents.length - 1]) + .suite!.children.length, + 1); + testsEvents.pop(); + testsEvents.pop(); + + disposeAdapterAndSubscribers(); + await updateConfig('executables', undefined); + }) + + specify( + 'load executables=["execPath1", "execPath2"] with error', + async function() { this.slow(300); - await updateConfig('executables', example1.suite1.execPath); + await updateConfig('executables', ['execPath1', 'execPath2']) adapter = createAdapterAndSubscribe(); + const withArgs = spawnStub.withArgs( + example1.suite2.execPath, example1.suite2.outputs[1][0]); + withArgs.onCall(withArgs.callCount).throws( + 'dummy error for testing (should be handled)'); + await adapter.load(); - assert.equal(testsEvents.length, 2); - assert.equal( - (testsEvents[testsEvents.length - 1]) - .suite!.children.length, - 1); testsEvents.pop(); testsEvents.pop(); @@ -1562,190 +1523,275 @@ describe( await updateConfig('executables', undefined); }) - specify( - 'load executables=["execPath1", "execPath2"] with error', - async function() { - this.slow(300); - await updateConfig('executables', ['execPath1', 'execPath2']) - adapter = createAdapterAndSubscribe(); + specify( + 'load executables=["execPath1", "execPath2Copy"]; delete; sleep 3; create', + async function() { + const watchTimeout = 6; + await updateConfig('defaultWatchTimeoutSec', watchTimeout); + this.timeout(watchTimeout * 1000 + 2500 /* because of 'delay' */); + this.slow(watchTimeout * 1000 + 2500 /* because of 'delay' */); + const execPath2CopyPath = + path.join(workspaceFolderUri.path, 'execPath2Copy'); + + for (let scenario of example1.suite2.outputs) { + spawnStub.withArgs(execPath2CopyPath, scenario[0]) + .callsFake(function() { + return new ChildProcessStub(scenario[1]); + }); + } - const withArgs = spawnStub.withArgs( - example1.suite2.execPath, example1.suite2.outputs[1][0]); - withArgs.onCall(withArgs.callCount).throws( - 'dummy error for testing (should be handled)'); + c2fsStatStub.withArgs(execPath2CopyPath) + .callsFake(handleStatExistsFile); - await adapter.load(); - testsEvents.pop(); - testsEvents.pop(); + vsfsWatchStub.withArgs(matchRelativePattern(execPath2CopyPath)) + .callsFake(handleCreateWatcherCb); - disposeAdapterAndSubscribers(); - await updateConfig('executables', undefined); - }) + vsFindFilesStub.withArgs(matchRelativePattern(execPath2CopyPath)) + .returns([vscode.Uri.file(execPath2CopyPath)]); - specify( - 'load executables=["execPath1", "execPath2Copy"]; delete; sleep 3; create', - async function(this: Mocha.Context) { - const watchTimeout = 6; - await updateConfig('defaultWatchTimeoutSec', watchTimeout); - this.timeout(watchTimeout * 1000 + 2500 /* because of 'delay' */); - this.slow(watchTimeout * 1000 + 2500 /* because of 'delay' */); - const execPath2CopyPath = - path.join(workspaceFolderUri.path, 'execPath2Copy'); - - for (let scenario of example1.suite2.outputs) { - spawnStub.withArgs(execPath2CopyPath, scenario[0]) - .callsFake(function() { - return new ChildProcessStub(scenario[1]); - }); - } + await updateConfig('executables', ['execPath1', 'execPath2Copy']) + adapter = createAdapterAndSubscribe(); + + await adapter.load(); + + assert.equal( + (testsEvents[testsEvents.length - 1]) + .suite!.children.length, + 2); + testsEvents.pop(); + testsEvents.pop(); + assert.ok(watchers.has(execPath2CopyPath)); + const watcher = watchers.get(execPath2CopyPath)!; + + let start: number = 0; + const newRoot = await doAndWaitForReloadEvent(this, async () => { + c2fsStatStub.withArgs(execPath2CopyPath) + .callsFake(handleStatNotExists); + start = Date.now(); + watcher.sendDelete(); + setTimeout(() => { + assert.equal(testsEvents.length, 0); + }, 1500); + setTimeout(() => { c2fsStatStub.withArgs(execPath2CopyPath) .callsFake(handleStatExistsFile); + watcher.sendCreate(); + }, 3000); + }, 40000); + const elapsed = Date.now() - start; - vsfsWatchStub.withArgs(matchRelativePattern(execPath2CopyPath)) - .callsFake(handleCreateWatcherCb); - - vsFindFilesStub.withArgs(matchRelativePattern(execPath2CopyPath)) - .returns([vscode.Uri.file(execPath2CopyPath)]); - - await updateConfig('executables', ['execPath1', 'execPath2Copy']) - adapter = createAdapterAndSubscribe(); - - await adapter.load(); - - assert.equal( - (testsEvents[testsEvents.length - 1]) - .suite!.children.length, - 2); - testsEvents.pop(); - testsEvents.pop(); - - assert.ok(watchers.has(execPath2CopyPath)); - const watcher = watchers.get(execPath2CopyPath)!; - - let start: number = 0; - const newRoot = await doAndWaitForReloadEvent(this, async () => { - c2fsStatStub.withArgs(execPath2CopyPath) - .callsFake(handleStatNotExists); - start = Date.now(); - watcher.sendDelete(); - setTimeout(() => { - assert.equal(testsEvents.length, 0); - }, 1500); - setTimeout(() => { - c2fsStatStub.withArgs(execPath2CopyPath) - .callsFake(handleStatExistsFile); - watcher.sendCreate(); - }, 3000); - }, 40000); - const elapsed = Date.now() - start; - - assert.equal(testsEvents.length, 2); - testsEvents.pop(); - testsEvents.pop(); - for (let scenario of example1.suite2.outputs) { - spawnStub.withArgs(execPath2CopyPath, scenario[0]) - .callsFake(() => { - throw Error('restore'); - }); - } - c2fsStatStub.withArgs(execPath2CopyPath).callsFake(() => { + assert.equal(testsEvents.length, 2); + testsEvents.pop(); + testsEvents.pop(); + for (let scenario of example1.suite2.outputs) { + spawnStub.withArgs(execPath2CopyPath, scenario[0]).callsFake(() => { + throw Error('restore'); + }); + } + c2fsStatStub.withArgs(execPath2CopyPath).callsFake(() => { + throw Error('restore'); + }); + vsfsWatchStub.withArgs(matchRelativePattern(execPath2CopyPath)) + .callsFake(() => { throw Error('restore'); }); - vsfsWatchStub.withArgs(matchRelativePattern(execPath2CopyPath)) - .callsFake(() => { - throw Error('restore'); - }); - vsFindFilesStub.withArgs(matchRelativePattern(execPath2CopyPath)) - .callsFake(() => { - throw Error('restore'); - }); - disposeAdapterAndSubscribers(); - await updateConfig('executables', undefined); - await updateConfig('defaultWatchTimeoutSec', undefined); + vsFindFilesStub.withArgs(matchRelativePattern(execPath2CopyPath)) + .callsFake(() => { + throw Error('restore'); + }); + disposeAdapterAndSubscribers(); + await updateConfig('executables', undefined); + await updateConfig('defaultWatchTimeoutSec', undefined); - assert.equal(newRoot.children.length, 2); - assert.ok(3000 < elapsed, inspect(elapsed)); - assert.ok(elapsed < watchTimeout * 1000 + 2400, inspect(elapsed)); - }) + assert.equal(newRoot.children.length, 2); + assert.ok(3000 < elapsed, inspect(elapsed)); + assert.ok(elapsed < watchTimeout * 1000 + 2400, inspect(elapsed)); + }) - specify( - 'load executables=["execPath1", "execPath2Copy"]; delete second', - async function() { - const watchTimeout = 5; - await updateConfig('defaultWatchTimeoutSec', watchTimeout); - this.timeout(watchTimeout * 1000 + 2500 /* because of 'delay' */); - this.slow(watchTimeout + 2500 /* because of 'delay' */); - const execPath2CopyPath = - path.join(workspaceFolderUri.path, 'execPath2Copy'); - - for (let scenario of example1.suite2.outputs) { - spawnStub.withArgs(execPath2CopyPath, scenario[0]) - .callsFake(function() { - return new ChildProcessStub(scenario[1]); - }); - } + specify( + 'load executables=["execPath1", "execPath2Copy"]; delete second', + async function() { + const watchTimeout = 5; + await updateConfig('defaultWatchTimeoutSec', watchTimeout); + this.timeout(watchTimeout * 1000 + 2500 /* because of 'delay' */); + this.slow(watchTimeout* 1000 + 2500 /* because of 'delay' */); + const execPath2CopyPath = + path.join(workspaceFolderUri.path, 'execPath2Copy'); + + for (let scenario of example1.suite2.outputs) { + spawnStub.withArgs(execPath2CopyPath, scenario[0]) + .callsFake(function() { + return new ChildProcessStub(scenario[1]); + }); + } - c2fsStatStub.withArgs(execPath2CopyPath) - .callsFake(handleStatExistsFile); + c2fsStatStub.withArgs(execPath2CopyPath) + .callsFake(handleStatExistsFile); - vsfsWatchStub.withArgs(matchRelativePattern(execPath2CopyPath)) - .callsFake(handleCreateWatcherCb); - - vsFindFilesStub.withArgs(matchRelativePattern(execPath2CopyPath)) - .returns([vscode.Uri.file(execPath2CopyPath)]); - - await updateConfig('executables', ['execPath1', 'execPath2Copy']) - adapter = createAdapterAndSubscribe(); - - await adapter.load(); - - assert.equal( - (testsEvents[testsEvents.length - 1]) - .suite!.children.length, - 2); - testsEvents.pop(); - testsEvents.pop(); - - assert.ok(watchers.has(execPath2CopyPath)); - const watcher = watchers.get(execPath2CopyPath)!; - - let start: number = 0; - const newRoot = await doAndWaitForReloadEvent(this, async () => { - c2fsStatStub.withArgs(execPath2CopyPath) - .callsFake(handleStatNotExists); - start = Date.now(); - watcher.sendDelete(); - }, 40000); - const elapsed = Date.now() - start; - testsEvents.pop(); - testsEvents.pop(); - for (let scenario of example1.suite2.outputs) { - spawnStub.withArgs(execPath2CopyPath, scenario[0]) - .callsFake(() => { - throw Error('restore'); - }); - } - c2fsStatStub.withArgs(execPath2CopyPath).callsFake(() => { + vsfsWatchStub.withArgs(matchRelativePattern(execPath2CopyPath)) + .callsFake(handleCreateWatcherCb); + + vsFindFilesStub.withArgs(matchRelativePattern(execPath2CopyPath)) + .returns([vscode.Uri.file(execPath2CopyPath)]); + + await updateConfig('executables', ['execPath1', 'execPath2Copy']) + adapter = createAdapterAndSubscribe(); + + await adapter.load(); + + assert.equal( + (testsEvents[testsEvents.length - 1]) + .suite!.children.length, + 2); + testsEvents.pop(); + testsEvents.pop(); + + assert.ok(watchers.has(execPath2CopyPath)); + const watcher = watchers.get(execPath2CopyPath)!; + + let start: number = 0; + const newRoot = await doAndWaitForReloadEvent(this, async () => { + c2fsStatStub.withArgs(execPath2CopyPath) + .callsFake(handleStatNotExists); + start = Date.now(); + watcher.sendDelete(); + }, 40000); + const elapsed = Date.now() - start; + testsEvents.pop(); + testsEvents.pop(); + for (let scenario of example1.suite2.outputs) { + spawnStub.withArgs(execPath2CopyPath, scenario[0]).callsFake(() => { + throw Error('restore'); + }); + } + c2fsStatStub.withArgs(execPath2CopyPath).callsFake(() => { + throw Error('restore'); + }); + vsfsWatchStub.withArgs(matchRelativePattern(execPath2CopyPath)) + .callsFake(() => { throw Error('restore'); }); - vsfsWatchStub.withArgs(matchRelativePattern(execPath2CopyPath)) - .callsFake(() => { - throw Error('restore'); - }); - vsFindFilesStub.withArgs(matchRelativePattern(execPath2CopyPath)) - .callsFake(() => { - throw Error('restore'); - }); - disposeAdapterAndSubscribers(); - await updateConfig('executables', undefined); - await updateConfig('defaultWatchTimeoutSec', undefined); + vsFindFilesStub.withArgs(matchRelativePattern(execPath2CopyPath)) + .callsFake(() => { + throw Error('restore'); + }); + disposeAdapterAndSubscribers(); + await updateConfig('executables', undefined); + await updateConfig('defaultWatchTimeoutSec', undefined); - assert.equal(newRoot.children.length, 1); - assert.ok(watchTimeout * 1000 < elapsed, inspect(elapsed)); - assert.ok(elapsed < watchTimeout * 1000 + 2400, inspect(elapsed)); - }) - }) + assert.equal(newRoot.children.length, 1); + assert.ok(watchTimeout * 1000 < elapsed, inspect(elapsed)); + assert.ok(elapsed < watchTimeout * 1000 + 2400, inspect(elapsed)); + }) + + specify('wrong executables format', async function() { + this.slow(300); + await updateConfig('executables', {name: ''}); + + adapter = createAdapterAndSubscribe(); + + await adapter.load(); + + const root = + (testsEvents[testsEvents.length - 1]).suite!; + assert.equal(root.children.length, 0); + testsEvents.pop(); + testsEvents.pop(); + + disposeAdapterAndSubscribers(); + await updateConfig('executables', undefined); }) - // TODO tests outside working directory + specify('variable substitution with executables={...}', async function() { + this.slow(300); + const wsPath = workspaceFolderUri.fsPath + const execPath2CopyRelPath = 'foo/bar/base.second.first'; + const execPath2CopyPath = path.join(wsPath, execPath2CopyRelPath); + + const envArray: [string, string][] = [ + ['${absPath}', execPath2CopyPath], + ['${relPath}', execPath2CopyRelPath], + ['${absDirpath}', path.join(wsPath, 'foo/bar')], + ['${relDirpath}', 'foo/bar'], + ['${filename}', 'base.second.first'], + ['${baseFilename}', 'base.second'], + ['${extFilename}', '.first'], + ['${base2Filename}', 'base'], + ['${ext2Filename}', '.second'], + ['${base3Filename}', 'base'], + ['${ext3Filename}', ''], + ['${workspaceDirectory}', wsPath], + ['${workspaceFolder}', wsPath], + ]; + const envsStr = envArray.map(v => {return v[0]}).join(' , '); + const expectStr = envArray.map(v => {return v[1]}).join(' , '); + + await updateConfig('executables', { + name: envsStr, + pattern: execPath2CopyRelPath, + cwd: envsStr, + env: {C2TESTVARS: envsStr} + }); + + for (let scenario of example1.suite2.outputs) { + spawnStub.withArgs(execPath2CopyPath, scenario[0]) + .callsFake(function() { + return new ChildProcessStub(scenario[1]); + }); + } + spawnStub.withArgs(execPath2CopyPath, example1.suite2.t1.outputs[0][0]) + .callsFake(function(p: string, args: string[], ops: any) { + assert.equal(ops.cwd, expectStr); + assert.ok(ops.env.hasOwnProperty('C2TESTVARS')); + assert.equal(ops.env.C2TESTVARS, expectStr); + return new ChildProcessStub(example1.suite2.t1.outputs[0][1]); + }); + + c2fsStatStub.withArgs(execPath2CopyPath).callsFake(handleStatExistsFile); + + vsfsWatchStub.withArgs(matchRelativePattern(execPath2CopyPath)) + .callsFake(handleCreateWatcherCb); + + vsFindFilesStub.withArgs(matchRelativePattern(execPath2CopyPath)) + .returns([vscode.Uri.file(execPath2CopyPath)]); + + adapter = createAdapterAndSubscribe(); + + await adapter.load(); + + const root = + (testsEvents[testsEvents.length - 1]).suite!; + testsEvents.pop(); + testsEvents.pop(); + + assert.equal(root.children.length, 1); + assert.equal(root.children[0].type, 'suite'); + const suite = root.children[0]; + assert.equal(suite.label, expectStr); + + assert.equal(suite.children.length, 3); + + await adapter.run([suite.children[0].id]); + + for (let scenario of example1.suite2.outputs) { + spawnStub.withArgs(execPath2CopyPath, scenario[0]).callsFake(() => { + throw Error('restore'); + }); + } + c2fsStatStub.withArgs(execPath2CopyPath).callsFake(() => { + throw Error('restore'); + }); + vsfsWatchStub.withArgs(matchRelativePattern(execPath2CopyPath)) + .callsFake(() => { + throw Error('restore'); + }); + vsFindFilesStub.withArgs(matchRelativePattern(execPath2CopyPath)) + .callsFake(() => { + throw Error('restore'); + }); + disposeAdapterAndSubscribers(); + await updateConfig('executables', undefined); + }) + }) +}) From cca3a2e179dc1c569fbedfe7cd5047d1a90a224f Mon Sep 17 00:00:00 2001 From: Mate Pek Date: Sat, 27 Oct 2018 16:18:02 +0200 Subject: [PATCH 12/49] added appveyor.yml --- README.md | 1 + appveyor.yml | 15 +++++++++++++++ 2 files changed, 16 insertions(+) create mode 100644 appveyor.yml diff --git a/README.md b/README.md index 5951da21..a525d5ce 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,7 @@ Update your settings! ([See changelog](CHANGELOG.md) or [configuration below](#C ## 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) [![GitHub issues](https://img.shields.io/github/issues/matepek/vscode-catch2-test-adapter.svg)](https://github.com/matepek/vscode-catch2-test-adapter/issues) [![GitHub license](https://img.shields.io/github/license/matepek/vscode-catch2-test-adapter.svg)](https://github.com/matepek/vscode-catch2-test-adapter/blob/master/LICENSE) [![Visual Studio Marketplace](https://img.shields.io/vscode-marketplace/d/matepek.vscode-catch2-test-adapter.svg)](https://marketplace.visualstudio.com/items?itemName=matepek.vscode-catch2-test-adapter) diff --git a/appveyor.yml b/appveyor.yml new file mode 100644 index 00000000..32236b71 --- /dev/null +++ b/appveyor.yml @@ -0,0 +1,15 @@ +environment: + nodejs_version: "10" + +install: + - ps: Install-Product node $env:nodejs_version + - node --version + - npm --version + - npm install + +test_script: + - npm run compile + - npm test --silent + +# Don't actually build. +build: off From 0df1e309a94739133b13d9a3de84fe5b2fda2157 Mon Sep 17 00:00:00 2001 From: Mate Pek Date: Sat, 27 Oct 2018 16:38:07 +0200 Subject: [PATCH 13/49] nodejs 8,11 --- .travis.yml | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/.travis.yml b/.travis.yml index f1e53a23..43d977b5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,8 @@ sudo: false language: node_js node_js: - - "10" + - "8" + - "11" os: - osx @@ -9,9 +10,9 @@ os: before_install: - if [ $TRAVIS_OS_NAME == "linux" ]; then - export CXX="g++-4.9" CC="gcc-4.9" DISPLAY=:99.0; - sh -e /etc/init.d/xvfb start; - sleep 3; + export CXX="g++-4.9" CC="gcc-4.9" DISPLAY=:99.0; + sh -e /etc/init.d/xvfb start; + sleep 3; fi install: @@ -19,4 +20,4 @@ install: - npm run compile script: - - npm test --silent \ No newline at end of file + - npm test --silent From 337954678810c97d8efbe50f3c23a94ead541af8 Mon Sep 17 00:00:00 2001 From: Mate Pek Date: Sat, 27 Oct 2018 17:01:34 +0200 Subject: [PATCH 14/49] Catch2 header is added to repo --- src/C2ExecutableInfo.ts | 11 +- src/test/cpp/catch.hpp | 14026 ++++++++++++++++++++++++++++++++ src/test/{ => cpp}/suite1.cpp | 0 src/test/{ => cpp}/suite2.cpp | 0 src/test/{ => cpp}/suite3.cpp | 0 5 files changed, 14036 insertions(+), 1 deletion(-) create mode 100644 src/test/cpp/catch.hpp rename src/test/{ => cpp}/suite1.cpp (100%) rename src/test/{ => cpp}/suite2.cpp (100%) rename src/test/{ => cpp}/suite3.cpp (100%) diff --git a/src/C2ExecutableInfo.ts b/src/C2ExecutableInfo.ts index a3290496..33eb0e4b 100644 --- a/src/C2ExecutableInfo.ts +++ b/src/C2ExecutableInfo.ts @@ -129,6 +129,9 @@ export class C2ExecutableInfo implements vscode.Disposable { return suite; } + private readonly _lastEventArrivedAt: + Map = new Map(); + private _handleEverything(uri: vscode.Uri) { let suite = this._executables.get(uri.fsPath); @@ -140,7 +143,13 @@ export class C2ExecutableInfo implements vscode.Disposable { const x = (exists: boolean, startTime: number, timeout: number, delay: number): Promise => { - if ((Date.now() - startTime) > timeout) { + let lastEventArrivedAt = this._lastEventArrivedAt.get(uri.fsPath); + if (lastEventArrivedAt === undefined) { + lastEventArrivedAt = Date.now(); + this._lastEventArrivedAt.set(uri.fsPath, lastEventArrivedAt); + this._adapter.log.error('assert in ' + __filename); + } + if ((Date.now() - lastEventArrivedAt!) > timeout) { this._executables.delete(uri.fsPath); this._adapter.testsEmitter.fire({type: 'started'}); this._allTests.removeChild(suite!); diff --git a/src/test/cpp/catch.hpp b/src/test/cpp/catch.hpp new file mode 100644 index 00000000..30c664fe --- /dev/null +++ b/src/test/cpp/catch.hpp @@ -0,0 +1,14026 @@ +/* + * Catch v2.4.1 + * Generated: 2018-10-14 08:01:41.816440 + * ---------------------------------------------------------- + * This file has been merged from multiple headers. Please don't edit it directly + * Copyright (c) 2018 Two Blue Cubes Ltd. All rights reserved. + * + * Distributed under the Boost Software License, Version 1.0. (See accompanying + * file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + */ +#ifndef TWOBLUECUBES_SINGLE_INCLUDE_CATCH_HPP_INCLUDED +#define TWOBLUECUBES_SINGLE_INCLUDE_CATCH_HPP_INCLUDED +// start catch.hpp + + +#define CATCH_VERSION_MAJOR 2 +#define CATCH_VERSION_MINOR 4 +#define CATCH_VERSION_PATCH 1 + +#ifdef __clang__ +# pragma clang system_header +#elif defined __GNUC__ +# pragma GCC system_header +#endif + +// start catch_suppress_warnings.h + +#ifdef __clang__ +# ifdef __ICC // icpc defines the __clang__ macro +# pragma warning(push) +# pragma warning(disable: 161 1682) +# else // __ICC +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wpadded" +# pragma clang diagnostic ignored "-Wswitch-enum" +# pragma clang diagnostic ignored "-Wcovered-switch-default" +# endif +#elif defined __GNUC__ + // GCC likes to warn on REQUIREs, and we cannot suppress them + // locally because g++'s support for _Pragma is lacking in older, + // still supported, versions +# pragma GCC diagnostic ignored "-Wparentheses" +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wunused-variable" +# pragma GCC diagnostic ignored "-Wpadded" +#endif +// end catch_suppress_warnings.h +#if defined(CATCH_CONFIG_MAIN) || defined(CATCH_CONFIG_RUNNER) +# define CATCH_IMPL +# define CATCH_CONFIG_ALL_PARTS +#endif + +// In the impl file, we want to have access to all parts of the headers +// Can also be used to sanely support PCHs +#if defined(CATCH_CONFIG_ALL_PARTS) +# define CATCH_CONFIG_EXTERNAL_INTERFACES +# if defined(CATCH_CONFIG_DISABLE_MATCHERS) +# undef CATCH_CONFIG_DISABLE_MATCHERS +# endif +# if !defined(CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER) +# define CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER +# endif +#endif + +#if !defined(CATCH_CONFIG_IMPL_ONLY) +// start catch_platform.h + +#ifdef __APPLE__ +# include +# if TARGET_OS_OSX == 1 +# define CATCH_PLATFORM_MAC +# elif TARGET_OS_IPHONE == 1 +# define CATCH_PLATFORM_IPHONE +# endif + +#elif defined(linux) || defined(__linux) || defined(__linux__) +# define CATCH_PLATFORM_LINUX + +#elif defined(WIN32) || defined(__WIN32__) || defined(_WIN32) || defined(_MSC_VER) || defined(__MINGW32__) +# define CATCH_PLATFORM_WINDOWS +#endif + +// end catch_platform.h + +#ifdef CATCH_IMPL +# ifndef CLARA_CONFIG_MAIN +# define CLARA_CONFIG_MAIN_NOT_DEFINED +# define CLARA_CONFIG_MAIN +# endif +#endif + +// start catch_user_interfaces.h + +namespace Catch { + unsigned int rngSeed(); +} + +// end catch_user_interfaces.h +// start catch_tag_alias_autoregistrar.h + +// start catch_common.h + +// start catch_compiler_capabilities.h + +// Detect a number of compiler features - by compiler +// The following features are defined: +// +// CATCH_CONFIG_COUNTER : is the __COUNTER__ macro supported? +// CATCH_CONFIG_WINDOWS_SEH : is Windows SEH supported? +// CATCH_CONFIG_POSIX_SIGNALS : are POSIX signals supported? +// CATCH_CONFIG_DISABLE_EXCEPTIONS : Are exceptions enabled? +// **************** +// Note to maintainers: if new toggles are added please document them +// in configuration.md, too +// **************** + +// In general each macro has a _NO_ form +// (e.g. CATCH_CONFIG_NO_POSIX_SIGNALS) which disables the feature. +// Many features, at point of detection, define an _INTERNAL_ macro, so they +// can be combined, en-mass, with the _NO_ forms later. + +#ifdef __cplusplus + +# if (__cplusplus >= 201402L) || (defined(_MSVC_LANG) && _MSVC_LANG >= 201402L) +# define CATCH_CPP14_OR_GREATER +# endif + +# if (__cplusplus >= 201703L) || (defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) +# define CATCH_CPP17_OR_GREATER +# endif + +#endif + +#if defined(CATCH_CPP17_OR_GREATER) +# define CATCH_INTERNAL_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS +#endif + +#ifdef __clang__ + +# define CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ + _Pragma( "clang diagnostic push" ) \ + _Pragma( "clang diagnostic ignored \"-Wexit-time-destructors\"" ) \ + _Pragma( "clang diagnostic ignored \"-Wglobal-constructors\"") +# define CATCH_INTERNAL_UNSUPPRESS_GLOBALS_WARNINGS \ + _Pragma( "clang diagnostic pop" ) + +# define CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS \ + _Pragma( "clang diagnostic push" ) \ + _Pragma( "clang diagnostic ignored \"-Wparentheses\"" ) +# define CATCH_INTERNAL_UNSUPPRESS_PARENTHESES_WARNINGS \ + _Pragma( "clang diagnostic pop" ) + +# define CATCH_INTERNAL_SUPPRESS_UNUSED_WARNINGS \ + _Pragma( "clang diagnostic push" ) \ + _Pragma( "clang diagnostic ignored \"-Wunused-variable\"" ) +# define CATCH_INTERNAL_UNSUPPRESS_UNUSED_WARNINGS \ + _Pragma( "clang diagnostic pop" ) + +#endif // __clang__ + +//////////////////////////////////////////////////////////////////////////////// +// Assume that non-Windows platforms support posix signals by default +#if !defined(CATCH_PLATFORM_WINDOWS) + #define CATCH_INTERNAL_CONFIG_POSIX_SIGNALS +#endif + +//////////////////////////////////////////////////////////////////////////////// +// We know some environments not to support full POSIX signals +#if defined(__CYGWIN__) || defined(__QNX__) || defined(__EMSCRIPTEN__) || defined(__DJGPP__) + #define CATCH_INTERNAL_CONFIG_NO_POSIX_SIGNALS +#endif + +#ifdef __OS400__ +# define CATCH_INTERNAL_CONFIG_NO_POSIX_SIGNALS +# define CATCH_CONFIG_COLOUR_NONE +#endif + +//////////////////////////////////////////////////////////////////////////////// +// Android somehow still does not support std::to_string +#if defined(__ANDROID__) +# define CATCH_INTERNAL_CONFIG_NO_CPP11_TO_STRING +#endif + +//////////////////////////////////////////////////////////////////////////////// +// Not all Windows environments support SEH properly +#if defined(__MINGW32__) +# define CATCH_INTERNAL_CONFIG_NO_WINDOWS_SEH +#endif + +//////////////////////////////////////////////////////////////////////////////// +// PS4 +#if defined(__ORBIS__) +# define CATCH_INTERNAL_CONFIG_NO_NEW_CAPTURE +#endif + +//////////////////////////////////////////////////////////////////////////////// +// Cygwin +#ifdef __CYGWIN__ + +// Required for some versions of Cygwin to declare gettimeofday +// see: http://stackoverflow.com/questions/36901803/gettimeofday-not-declared-in-this-scope-cygwin +# define _BSD_SOURCE +// some versions of cygwin (most) do not support std::to_string. Use the libstd check. +// https://gcc.gnu.org/onlinedocs/gcc-4.8.2/libstdc++/api/a01053_source.html line 2812-2813 +# if !((__cplusplus >= 201103L) && defined(_GLIBCXX_USE_C99) \ + && !defined(_GLIBCXX_HAVE_BROKEN_VSWPRINTF)) + +# define CATCH_INTERNAL_CONFIG_NO_CPP11_TO_STRING + +# endif +#endif // __CYGWIN__ + +//////////////////////////////////////////////////////////////////////////////// +// Visual C++ +#ifdef _MSC_VER + +# if _MSC_VER >= 1900 // Visual Studio 2015 or newer +# define CATCH_INTERNAL_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS +# endif + +// Universal Windows platform does not support SEH +// Or console colours (or console at all...) +# if defined(WINAPI_FAMILY) && (WINAPI_FAMILY == WINAPI_FAMILY_APP) +# define CATCH_CONFIG_COLOUR_NONE +# else +# define CATCH_INTERNAL_CONFIG_WINDOWS_SEH +# endif + +#endif // _MSC_VER + +//////////////////////////////////////////////////////////////////////////////// +// Check if we are compiled with -fno-exceptions or equivalent +#if defined(__EXCEPTIONS) || defined(__cpp_exceptions) || defined(_CPPUNWIND) +# define CATCH_INTERNAL_CONFIG_EXCEPTIONS_ENABLED +#endif + +//////////////////////////////////////////////////////////////////////////////// +// DJGPP +#ifdef __DJGPP__ +# define CATCH_INTERNAL_CONFIG_NO_WCHAR +#endif // __DJGPP__ + +//////////////////////////////////////////////////////////////////////////////// + +// Use of __COUNTER__ is suppressed during code analysis in +// CLion/AppCode 2017.2.x and former, because __COUNTER__ is not properly +// handled by it. +// Otherwise all supported compilers support COUNTER macro, +// but user still might want to turn it off +#if ( !defined(__JETBRAINS_IDE__) || __JETBRAINS_IDE__ >= 20170300L ) + #define CATCH_INTERNAL_CONFIG_COUNTER +#endif + +//////////////////////////////////////////////////////////////////////////////// +// Check if string_view is available and usable +// The check is split apart to work around v140 (VS2015) preprocessor issue... +#if defined(__has_include) +#if __has_include() && defined(CATCH_CPP17_OR_GREATER) +# define CATCH_INTERNAL_CONFIG_CPP17_STRING_VIEW +#endif +#endif + +//////////////////////////////////////////////////////////////////////////////// +// Check if variant is available and usable +#if defined(__has_include) +# if __has_include() && defined(CATCH_CPP17_OR_GREATER) +# if defined(__clang__) && (__clang_major__ < 8) + // work around clang bug with libstdc++ https://bugs.llvm.org/show_bug.cgi?id=31852 + // fix should be in clang 8, workaround in libstdc++ 8.2 +# include +# if defined(__GLIBCXX__) && defined(_GLIBCXX_RELEASE) && (_GLIBCXX_RELEASE < 9) +# define CATCH_CONFIG_NO_CPP17_VARIANT +# else +# define CATCH_INTERNAL_CONFIG_CPP17_VARIANT +# endif // defined(__GLIBCXX__) && defined(_GLIBCXX_RELEASE) && (_GLIBCXX_RELEASE < 9) +# endif // defined(__clang__) && (__clang_major__ < 8) +# endif // __has_include() && defined(CATCH_CPP17_OR_GREATER) +#endif // __has_include + +#if defined(CATCH_INTERNAL_CONFIG_COUNTER) && !defined(CATCH_CONFIG_NO_COUNTER) && !defined(CATCH_CONFIG_COUNTER) +# define CATCH_CONFIG_COUNTER +#endif +#if defined(CATCH_INTERNAL_CONFIG_WINDOWS_SEH) && !defined(CATCH_CONFIG_NO_WINDOWS_SEH) && !defined(CATCH_CONFIG_WINDOWS_SEH) && !defined(CATCH_INTERNAL_CONFIG_NO_WINDOWS_SEH) +# define CATCH_CONFIG_WINDOWS_SEH +#endif +// This is set by default, because we assume that unix compilers are posix-signal-compatible by default. +#if defined(CATCH_INTERNAL_CONFIG_POSIX_SIGNALS) && !defined(CATCH_INTERNAL_CONFIG_NO_POSIX_SIGNALS) && !defined(CATCH_CONFIG_NO_POSIX_SIGNALS) && !defined(CATCH_CONFIG_POSIX_SIGNALS) +# define CATCH_CONFIG_POSIX_SIGNALS +#endif +// This is set by default, because we assume that compilers with no wchar_t support are just rare exceptions. +#if !defined(CATCH_INTERNAL_CONFIG_NO_WCHAR) && !defined(CATCH_CONFIG_NO_WCHAR) && !defined(CATCH_CONFIG_WCHAR) +# define CATCH_CONFIG_WCHAR +#endif + +#if !defined(CATCH_INTERNAL_CONFIG_NO_CPP11_TO_STRING) && !defined(CATCH_CONFIG_NO_CPP11_TO_STRING) && !defined(CATCH_CONFIG_CPP11_TO_STRING) +# define CATCH_CONFIG_CPP11_TO_STRING +#endif + +#if defined(CATCH_INTERNAL_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS) && !defined(CATCH_CONFIG_NO_CPP17_UNCAUGHT_EXCEPTIONS) && !defined(CATCH_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS) +# define CATCH_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS +#endif + +#if defined(CATCH_INTERNAL_CONFIG_CPP17_STRING_VIEW) && !defined(CATCH_CONFIG_NO_CPP17_STRING_VIEW) && !defined(CATCH_CONFIG_CPP17_STRING_VIEW) +# define CATCH_CONFIG_CPP17_STRING_VIEW +#endif + +#if defined(CATCH_INTERNAL_CONFIG_CPP17_VARIANT) && !defined(CATCH_CONFIG_NO_CPP17_VARIANT) && !defined(CATCH_CONFIG_CPP17_VARIANT) +# define CATCH_CONFIG_CPP17_VARIANT +#endif + +#if defined(CATCH_CONFIG_EXPERIMENTAL_REDIRECT) +# define CATCH_INTERNAL_CONFIG_NEW_CAPTURE +#endif + +#if defined(CATCH_INTERNAL_CONFIG_NEW_CAPTURE) && !defined(CATCH_INTERNAL_CONFIG_NO_NEW_CAPTURE) && !defined(CATCH_CONFIG_NO_NEW_CAPTURE) && !defined(CATCH_CONFIG_NEW_CAPTURE) +# define CATCH_CONFIG_NEW_CAPTURE +#endif + +#if !defined(CATCH_INTERNAL_CONFIG_EXCEPTIONS_ENABLED) && !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) +# define CATCH_CONFIG_DISABLE_EXCEPTIONS +#endif + +#if !defined(CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS) +# define CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS +# define CATCH_INTERNAL_UNSUPPRESS_PARENTHESES_WARNINGS +#endif +#if !defined(CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS) +# define CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS +# define CATCH_INTERNAL_UNSUPPRESS_GLOBALS_WARNINGS +#endif +#if !defined(CATCH_INTERNAL_SUPPRESS_UNUSED_WARNINGS) +# define CATCH_INTERNAL_SUPPRESS_UNUSED_WARNINGS +# define CATCH_INTERNAL_UNSUPPRESS_UNUSED_WARNINGS +#endif + +#if defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) +#define CATCH_TRY if ((true)) +#define CATCH_CATCH_ALL if ((false)) +#define CATCH_CATCH_ANON(type) if ((false)) +#else +#define CATCH_TRY try +#define CATCH_CATCH_ALL catch (...) +#define CATCH_CATCH_ANON(type) catch (type) +#endif + +// end catch_compiler_capabilities.h +#define INTERNAL_CATCH_UNIQUE_NAME_LINE2( name, line ) name##line +#define INTERNAL_CATCH_UNIQUE_NAME_LINE( name, line ) INTERNAL_CATCH_UNIQUE_NAME_LINE2( name, line ) +#ifdef CATCH_CONFIG_COUNTER +# define INTERNAL_CATCH_UNIQUE_NAME( name ) INTERNAL_CATCH_UNIQUE_NAME_LINE( name, __COUNTER__ ) +#else +# define INTERNAL_CATCH_UNIQUE_NAME( name ) INTERNAL_CATCH_UNIQUE_NAME_LINE( name, __LINE__ ) +#endif + +#include +#include +#include + +// We need a dummy global operator<< so we can bring it into Catch namespace later +struct Catch_global_namespace_dummy {}; +std::ostream& operator<<(std::ostream&, Catch_global_namespace_dummy); + +namespace Catch { + + struct CaseSensitive { enum Choice { + Yes, + No + }; }; + + class NonCopyable { + NonCopyable( NonCopyable const& ) = delete; + NonCopyable( NonCopyable && ) = delete; + NonCopyable& operator = ( NonCopyable const& ) = delete; + NonCopyable& operator = ( NonCopyable && ) = delete; + + protected: + NonCopyable(); + virtual ~NonCopyable(); + }; + + struct SourceLineInfo { + + SourceLineInfo() = delete; + SourceLineInfo( char const* _file, std::size_t _line ) noexcept + : file( _file ), + line( _line ) + {} + + SourceLineInfo( SourceLineInfo const& other ) = default; + SourceLineInfo( SourceLineInfo && ) = default; + SourceLineInfo& operator = ( SourceLineInfo const& ) = default; + SourceLineInfo& operator = ( SourceLineInfo && ) = default; + + bool empty() const noexcept; + bool operator == ( SourceLineInfo const& other ) const noexcept; + bool operator < ( SourceLineInfo const& other ) const noexcept; + + char const* file; + std::size_t line; + }; + + std::ostream& operator << ( std::ostream& os, SourceLineInfo const& info ); + + // Bring in operator<< from global namespace into Catch namespace + // This is necessary because the overload of operator<< above makes + // lookup stop at namespace Catch + using ::operator<<; + + // Use this in variadic streaming macros to allow + // >> +StreamEndStop + // as well as + // >> stuff +StreamEndStop + struct StreamEndStop { + std::string operator+() const; + }; + template + T const& operator + ( T const& value, StreamEndStop ) { + return value; + } +} + +#define CATCH_INTERNAL_LINEINFO \ + ::Catch::SourceLineInfo( __FILE__, static_cast( __LINE__ ) ) + +// end catch_common.h +namespace Catch { + + struct RegistrarForTagAliases { + RegistrarForTagAliases( char const* alias, char const* tag, SourceLineInfo const& lineInfo ); + }; + +} // end namespace Catch + +#define CATCH_REGISTER_TAG_ALIAS( alias, spec ) \ + CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ + namespace{ Catch::RegistrarForTagAliases INTERNAL_CATCH_UNIQUE_NAME( AutoRegisterTagAlias )( alias, spec, CATCH_INTERNAL_LINEINFO ); } \ + CATCH_INTERNAL_UNSUPPRESS_GLOBALS_WARNINGS + +// end catch_tag_alias_autoregistrar.h +// start catch_test_registry.h + +// start catch_interfaces_testcase.h + +#include +#include + +namespace Catch { + + class TestSpec; + + struct ITestInvoker { + virtual void invoke () const = 0; + virtual ~ITestInvoker(); + }; + + using ITestCasePtr = std::shared_ptr; + + class TestCase; + struct IConfig; + + struct ITestCaseRegistry { + virtual ~ITestCaseRegistry(); + virtual std::vector const& getAllTests() const = 0; + virtual std::vector const& getAllTestsSorted( IConfig const& config ) const = 0; + }; + + bool matchTest( TestCase const& testCase, TestSpec const& testSpec, IConfig const& config ); + std::vector filterTests( std::vector const& testCases, TestSpec const& testSpec, IConfig const& config ); + std::vector const& getAllTestCasesSorted( IConfig const& config ); + +} + +// end catch_interfaces_testcase.h +// start catch_stringref.h + +#include +#include +#include + +namespace Catch { + + class StringData; + + /// A non-owning string class (similar to the forthcoming std::string_view) + /// Note that, because a StringRef may be a substring of another string, + /// it may not be null terminated. c_str() must return a null terminated + /// string, however, and so the StringRef will internally take ownership + /// (taking a copy), if necessary. In theory this ownership is not externally + /// visible - but it does mean (substring) StringRefs should not be shared between + /// threads. + class StringRef { + public: + using size_type = std::size_t; + + private: + friend struct StringRefTestAccess; + + char const* m_start; + size_type m_size; + + char* m_data = nullptr; + + void takeOwnership(); + + static constexpr char const* const s_empty = ""; + + public: // construction/ assignment + StringRef() noexcept + : StringRef( s_empty, 0 ) + {} + + StringRef( StringRef const& other ) noexcept + : m_start( other.m_start ), + m_size( other.m_size ) + {} + + StringRef( StringRef&& other ) noexcept + : m_start( other.m_start ), + m_size( other.m_size ), + m_data( other.m_data ) + { + other.m_data = nullptr; + } + + StringRef( char const* rawChars ) noexcept; + + StringRef( char const* rawChars, size_type size ) noexcept + : m_start( rawChars ), + m_size( size ) + {} + + StringRef( std::string const& stdString ) noexcept + : m_start( stdString.c_str() ), + m_size( stdString.size() ) + {} + + ~StringRef() noexcept { + delete[] m_data; + } + + auto operator = ( StringRef const &other ) noexcept -> StringRef& { + delete[] m_data; + m_data = nullptr; + m_start = other.m_start; + m_size = other.m_size; + return *this; + } + + operator std::string() const; + + void swap( StringRef& other ) noexcept; + + public: // operators + auto operator == ( StringRef const& other ) const noexcept -> bool; + auto operator != ( StringRef const& other ) const noexcept -> bool; + + auto operator[] ( size_type index ) const noexcept -> char; + + public: // named queries + auto empty() const noexcept -> bool { + return m_size == 0; + } + auto size() const noexcept -> size_type { + return m_size; + } + + auto numberOfCharacters() const noexcept -> size_type; + auto c_str() const -> char const*; + + public: // substrings and searches + auto substr( size_type start, size_type size ) const noexcept -> StringRef; + + // Returns the current start pointer. + // Note that the pointer can change when if the StringRef is a substring + auto currentData() const noexcept -> char const*; + + private: // ownership queries - may not be consistent between calls + auto isOwned() const noexcept -> bool; + auto isSubstring() const noexcept -> bool; + }; + + auto operator + ( StringRef const& lhs, StringRef const& rhs ) -> std::string; + auto operator + ( StringRef const& lhs, char const* rhs ) -> std::string; + auto operator + ( char const* lhs, StringRef const& rhs ) -> std::string; + + auto operator += ( std::string& lhs, StringRef const& sr ) -> std::string&; + auto operator << ( std::ostream& os, StringRef const& sr ) -> std::ostream&; + + inline auto operator "" _sr( char const* rawChars, std::size_t size ) noexcept -> StringRef { + return StringRef( rawChars, size ); + } + +} // namespace Catch + +inline auto operator "" _catch_sr( char const* rawChars, std::size_t size ) noexcept -> Catch::StringRef { + return Catch::StringRef( rawChars, size ); +} + +// end catch_stringref.h +namespace Catch { + +template +class TestInvokerAsMethod : public ITestInvoker { + void (C::*m_testAsMethod)(); +public: + TestInvokerAsMethod( void (C::*testAsMethod)() ) noexcept : m_testAsMethod( testAsMethod ) {} + + void invoke() const override { + C obj; + (obj.*m_testAsMethod)(); + } +}; + +auto makeTestInvoker( void(*testAsFunction)() ) noexcept -> ITestInvoker*; + +template +auto makeTestInvoker( void (C::*testAsMethod)() ) noexcept -> ITestInvoker* { + return new(std::nothrow) TestInvokerAsMethod( testAsMethod ); +} + +struct NameAndTags { + NameAndTags( StringRef const& name_ = StringRef(), StringRef const& tags_ = StringRef() ) noexcept; + StringRef name; + StringRef tags; +}; + +struct AutoReg : NonCopyable { + AutoReg( ITestInvoker* invoker, SourceLineInfo const& lineInfo, StringRef const& classOrMethod, NameAndTags const& nameAndTags ) noexcept; + ~AutoReg(); +}; + +} // end namespace Catch + +#define INTERNAL_CATCH_EXPAND1(param) INTERNAL_CATCH_EXPAND2(param) +#define INTERNAL_CATCH_EXPAND2(...) INTERNAL_CATCH_NO## __VA_ARGS__ +#define INTERNAL_CATCH_DEF(...) INTERNAL_CATCH_DEF __VA_ARGS__ +#define INTERNAL_CATCH_NOINTERNAL_CATCH_DEF + +#if defined(CATCH_CONFIG_DISABLE) + #define INTERNAL_CATCH_TESTCASE_NO_REGISTRATION( TestName, ... ) \ + static void TestName() + #define INTERNAL_CATCH_TESTCASE_METHOD_NO_REGISTRATION( TestName, ClassName, ... ) \ + namespace{ \ + struct TestName : INTERNAL_CATCH_EXPAND1(INTERNAL_CATCH_DEF ClassName) { \ + void test(); \ + }; \ + } \ + void TestName::test() + +#endif + + /////////////////////////////////////////////////////////////////////////////// + #define INTERNAL_CATCH_TESTCASE2( TestName, ... ) \ + static void TestName(); \ + CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ + namespace{ Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( Catch::makeTestInvoker( &TestName ), CATCH_INTERNAL_LINEINFO, Catch::StringRef(), Catch::NameAndTags{ __VA_ARGS__ } ); } /* NOLINT */ \ + CATCH_INTERNAL_UNSUPPRESS_GLOBALS_WARNINGS \ + static void TestName() + #define INTERNAL_CATCH_TESTCASE( ... ) \ + INTERNAL_CATCH_TESTCASE2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ), __VA_ARGS__ ) + + /////////////////////////////////////////////////////////////////////////////// + #define INTERNAL_CATCH_METHOD_AS_TEST_CASE( QualifiedMethod, ... ) \ + CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ + namespace{ Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( Catch::makeTestInvoker( &QualifiedMethod ), CATCH_INTERNAL_LINEINFO, "&" #QualifiedMethod, Catch::NameAndTags{ __VA_ARGS__ } ); } /* NOLINT */ \ + CATCH_INTERNAL_UNSUPPRESS_GLOBALS_WARNINGS + + /////////////////////////////////////////////////////////////////////////////// + #define INTERNAL_CATCH_TEST_CASE_METHOD2( TestName, ClassName, ... )\ + CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ + namespace{ \ + struct TestName : INTERNAL_CATCH_EXPAND1(INTERNAL_CATCH_DEF ClassName) { \ + void test(); \ + }; \ + Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar ) ( Catch::makeTestInvoker( &TestName::test ), CATCH_INTERNAL_LINEINFO, #ClassName, Catch::NameAndTags{ __VA_ARGS__ } ); /* NOLINT */ \ + } \ + CATCH_INTERNAL_UNSUPPRESS_GLOBALS_WARNINGS \ + void TestName::test() + #define INTERNAL_CATCH_TEST_CASE_METHOD( ClassName, ... ) \ + INTERNAL_CATCH_TEST_CASE_METHOD2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ), ClassName, __VA_ARGS__ ) + + /////////////////////////////////////////////////////////////////////////////// + #define INTERNAL_CATCH_REGISTER_TESTCASE( Function, ... ) \ + CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ + Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( Catch::makeTestInvoker( Function ), CATCH_INTERNAL_LINEINFO, Catch::StringRef(), Catch::NameAndTags{ __VA_ARGS__ } ); /* NOLINT */ \ + CATCH_INTERNAL_UNSUPPRESS_GLOBALS_WARNINGS + +// end catch_test_registry.h +// start catch_capture.hpp + +// start catch_assertionhandler.h + +// start catch_assertioninfo.h + +// start catch_result_type.h + +namespace Catch { + + // ResultWas::OfType enum + struct ResultWas { enum OfType { + Unknown = -1, + Ok = 0, + Info = 1, + Warning = 2, + + FailureBit = 0x10, + + ExpressionFailed = FailureBit | 1, + ExplicitFailure = FailureBit | 2, + + Exception = 0x100 | FailureBit, + + ThrewException = Exception | 1, + DidntThrowException = Exception | 2, + + FatalErrorCondition = 0x200 | FailureBit + + }; }; + + bool isOk( ResultWas::OfType resultType ); + bool isJustInfo( int flags ); + + // ResultDisposition::Flags enum + struct ResultDisposition { enum Flags { + Normal = 0x01, + + ContinueOnFailure = 0x02, // Failures fail test, but execution continues + FalseTest = 0x04, // Prefix expression with ! + SuppressFail = 0x08 // Failures are reported but do not fail the test + }; }; + + ResultDisposition::Flags operator | ( ResultDisposition::Flags lhs, ResultDisposition::Flags rhs ); + + bool shouldContinueOnFailure( int flags ); + inline bool isFalseTest( int flags ) { return ( flags & ResultDisposition::FalseTest ) != 0; } + bool shouldSuppressFailure( int flags ); + +} // end namespace Catch + +// end catch_result_type.h +namespace Catch { + + struct AssertionInfo + { + StringRef macroName; + SourceLineInfo lineInfo; + StringRef capturedExpression; + ResultDisposition::Flags resultDisposition; + + // We want to delete this constructor but a compiler bug in 4.8 means + // the struct is then treated as non-aggregate + //AssertionInfo() = delete; + }; + +} // end namespace Catch + +// end catch_assertioninfo.h +// start catch_decomposer.h + +// start catch_tostring.h + +#include +#include +#include +#include +// start catch_stream.h + +#include +#include +#include + +namespace Catch { + + std::ostream& cout(); + std::ostream& cerr(); + std::ostream& clog(); + + class StringRef; + + struct IStream { + virtual ~IStream(); + virtual std::ostream& stream() const = 0; + }; + + auto makeStream( StringRef const &filename ) -> IStream const*; + + class ReusableStringStream { + std::size_t m_index; + std::ostream* m_oss; + public: + ReusableStringStream(); + ~ReusableStringStream(); + + auto str() const -> std::string; + + template + auto operator << ( T const& value ) -> ReusableStringStream& { + *m_oss << value; + return *this; + } + auto get() -> std::ostream& { return *m_oss; } + }; +} + +// end catch_stream.h + +#ifdef CATCH_CONFIG_CPP17_STRING_VIEW +#include +#endif + +#ifdef __OBJC__ +// start catch_objc_arc.hpp + +#import + +#ifdef __has_feature +#define CATCH_ARC_ENABLED __has_feature(objc_arc) +#else +#define CATCH_ARC_ENABLED 0 +#endif + +void arcSafeRelease( NSObject* obj ); +id performOptionalSelector( id obj, SEL sel ); + +#if !CATCH_ARC_ENABLED +inline void arcSafeRelease( NSObject* obj ) { + [obj release]; +} +inline id performOptionalSelector( id obj, SEL sel ) { + if( [obj respondsToSelector: sel] ) + return [obj performSelector: sel]; + return nil; +} +#define CATCH_UNSAFE_UNRETAINED +#define CATCH_ARC_STRONG +#else +inline void arcSafeRelease( NSObject* ){} +inline id performOptionalSelector( id obj, SEL sel ) { +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Warc-performSelector-leaks" +#endif + if( [obj respondsToSelector: sel] ) + return [obj performSelector: sel]; +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + return nil; +} +#define CATCH_UNSAFE_UNRETAINED __unsafe_unretained +#define CATCH_ARC_STRONG __strong +#endif + +// end catch_objc_arc.hpp +#endif + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable:4180) // We attempt to stream a function (address) by const&, which MSVC complains about but is harmless +#endif + +namespace Catch { + namespace Detail { + + extern const std::string unprintableString; + + std::string rawMemoryToString( const void *object, std::size_t size ); + + template + std::string rawMemoryToString( const T& object ) { + return rawMemoryToString( &object, sizeof(object) ); + } + + template + class IsStreamInsertable { + template + static auto test(int) + -> decltype(std::declval() << std::declval(), std::true_type()); + + template + static auto test(...)->std::false_type; + + public: + static const bool value = decltype(test(0))::value; + }; + + template + std::string convertUnknownEnumToString( E e ); + + template + typename std::enable_if< + !std::is_enum::value && !std::is_base_of::value, + std::string>::type convertUnstreamable( T const& ) { + return Detail::unprintableString; + } + template + typename std::enable_if< + !std::is_enum::value && std::is_base_of::value, + std::string>::type convertUnstreamable(T const& ex) { + return ex.what(); + } + + template + typename std::enable_if< + std::is_enum::value + , std::string>::type convertUnstreamable( T const& value ) { + return convertUnknownEnumToString( value ); + } + +#if defined(_MANAGED) + //! Convert a CLR string to a utf8 std::string + template + std::string clrReferenceToString( T^ ref ) { + if (ref == nullptr) + return std::string("null"); + auto bytes = System::Text::Encoding::UTF8->GetBytes(ref->ToString()); + cli::pin_ptr p = &bytes[0]; + return std::string(reinterpret_cast(p), bytes->Length); + } +#endif + + } // namespace Detail + + // If we decide for C++14, change these to enable_if_ts + template + struct StringMaker { + template + static + typename std::enable_if<::Catch::Detail::IsStreamInsertable::value, std::string>::type + convert(const Fake& value) { + ReusableStringStream rss; + // NB: call using the function-like syntax to avoid ambiguity with + // user-defined templated operator<< under clang. + rss.operator<<(value); + return rss.str(); + } + + template + static + typename std::enable_if::value, std::string>::type + convert( const Fake& value ) { +#if !defined(CATCH_CONFIG_FALLBACK_STRINGIFIER) + return Detail::convertUnstreamable(value); +#else + return CATCH_CONFIG_FALLBACK_STRINGIFIER(value); +#endif + } + }; + + namespace Detail { + + // This function dispatches all stringification requests inside of Catch. + // Should be preferably called fully qualified, like ::Catch::Detail::stringify + template + std::string stringify(const T& e) { + return ::Catch::StringMaker::type>::type>::convert(e); + } + + template + std::string convertUnknownEnumToString( E e ) { + return ::Catch::Detail::stringify(static_cast::type>(e)); + } + +#if defined(_MANAGED) + template + std::string stringify( T^ e ) { + return ::Catch::StringMaker::convert(e); + } +#endif + + } // namespace Detail + + // Some predefined specializations + + template<> + struct StringMaker { + static std::string convert(const std::string& str); + }; + +#ifdef CATCH_CONFIG_CPP17_STRING_VIEW + template<> + struct StringMaker { + static std::string convert(std::string_view str); + }; +#endif + + template<> + struct StringMaker { + static std::string convert(char const * str); + }; + template<> + struct StringMaker { + static std::string convert(char * str); + }; + +#ifdef CATCH_CONFIG_WCHAR + template<> + struct StringMaker { + static std::string convert(const std::wstring& wstr); + }; + +# ifdef CATCH_CONFIG_CPP17_STRING_VIEW + template<> + struct StringMaker { + static std::string convert(std::wstring_view str); + }; +# endif + + template<> + struct StringMaker { + static std::string convert(wchar_t const * str); + }; + template<> + struct StringMaker { + static std::string convert(wchar_t * str); + }; +#endif + + // TBD: Should we use `strnlen` to ensure that we don't go out of the buffer, + // while keeping string semantics? + template + struct StringMaker { + static std::string convert(char const* str) { + return ::Catch::Detail::stringify(std::string{ str }); + } + }; + template + struct StringMaker { + static std::string convert(signed char const* str) { + return ::Catch::Detail::stringify(std::string{ reinterpret_cast(str) }); + } + }; + template + struct StringMaker { + static std::string convert(unsigned char const* str) { + return ::Catch::Detail::stringify(std::string{ reinterpret_cast(str) }); + } + }; + + template<> + struct StringMaker { + static std::string convert(int value); + }; + template<> + struct StringMaker { + static std::string convert(long value); + }; + template<> + struct StringMaker { + static std::string convert(long long value); + }; + template<> + struct StringMaker { + static std::string convert(unsigned int value); + }; + template<> + struct StringMaker { + static std::string convert(unsigned long value); + }; + template<> + struct StringMaker { + static std::string convert(unsigned long long value); + }; + + template<> + struct StringMaker { + static std::string convert(bool b); + }; + + template<> + struct StringMaker { + static std::string convert(char c); + }; + template<> + struct StringMaker { + static std::string convert(signed char c); + }; + template<> + struct StringMaker { + static std::string convert(unsigned char c); + }; + + template<> + struct StringMaker { + static std::string convert(std::nullptr_t); + }; + + template<> + struct StringMaker { + static std::string convert(float value); + }; + template<> + struct StringMaker { + static std::string convert(double value); + }; + + template + struct StringMaker { + template + static std::string convert(U* p) { + if (p) { + return ::Catch::Detail::rawMemoryToString(p); + } else { + return "nullptr"; + } + } + }; + + template + struct StringMaker { + static std::string convert(R C::* p) { + if (p) { + return ::Catch::Detail::rawMemoryToString(p); + } else { + return "nullptr"; + } + } + }; + +#if defined(_MANAGED) + template + struct StringMaker { + static std::string convert( T^ ref ) { + return ::Catch::Detail::clrReferenceToString(ref); + } + }; +#endif + + namespace Detail { + template + std::string rangeToString(InputIterator first, InputIterator last) { + ReusableStringStream rss; + rss << "{ "; + if (first != last) { + rss << ::Catch::Detail::stringify(*first); + for (++first; first != last; ++first) + rss << ", " << ::Catch::Detail::stringify(*first); + } + rss << " }"; + return rss.str(); + } + } + +#ifdef __OBJC__ + template<> + struct StringMaker { + static std::string convert(NSString * nsstring) { + if (!nsstring) + return "nil"; + return std::string("@") + [nsstring UTF8String]; + } + }; + template<> + struct StringMaker { + static std::string convert(NSObject* nsObject) { + return ::Catch::Detail::stringify([nsObject description]); + } + + }; + namespace Detail { + inline std::string stringify( NSString* nsstring ) { + return StringMaker::convert( nsstring ); + } + + } // namespace Detail +#endif // __OBJC__ + +} // namespace Catch + +////////////////////////////////////////////////////// +// Separate std-lib types stringification, so it can be selectively enabled +// This means that we do not bring in + +#if defined(CATCH_CONFIG_ENABLE_ALL_STRINGMAKERS) +# define CATCH_CONFIG_ENABLE_PAIR_STRINGMAKER +# define CATCH_CONFIG_ENABLE_TUPLE_STRINGMAKER +# define CATCH_CONFIG_ENABLE_VARIANT_STRINGMAKER +# define CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER +#endif + +// Separate std::pair specialization +#if defined(CATCH_CONFIG_ENABLE_PAIR_STRINGMAKER) +#include +namespace Catch { + template + struct StringMaker > { + static std::string convert(const std::pair& pair) { + ReusableStringStream rss; + rss << "{ " + << ::Catch::Detail::stringify(pair.first) + << ", " + << ::Catch::Detail::stringify(pair.second) + << " }"; + return rss.str(); + } + }; +} +#endif // CATCH_CONFIG_ENABLE_PAIR_STRINGMAKER + +// Separate std::tuple specialization +#if defined(CATCH_CONFIG_ENABLE_TUPLE_STRINGMAKER) +#include +namespace Catch { + namespace Detail { + template< + typename Tuple, + std::size_t N = 0, + bool = (N < std::tuple_size::value) + > + struct TupleElementPrinter { + static void print(const Tuple& tuple, std::ostream& os) { + os << (N ? ", " : " ") + << ::Catch::Detail::stringify(std::get(tuple)); + TupleElementPrinter::print(tuple, os); + } + }; + + template< + typename Tuple, + std::size_t N + > + struct TupleElementPrinter { + static void print(const Tuple&, std::ostream&) {} + }; + + } + + template + struct StringMaker> { + static std::string convert(const std::tuple& tuple) { + ReusableStringStream rss; + rss << '{'; + Detail::TupleElementPrinter>::print(tuple, rss.get()); + rss << " }"; + return rss.str(); + } + }; +} +#endif // CATCH_CONFIG_ENABLE_TUPLE_STRINGMAKER + +#if defined(CATCH_CONFIG_ENABLE_VARIANT_STRINGMAKER) && defined(CATCH_CONFIG_CPP17_VARIANT) +#include +namespace Catch { + template<> + struct StringMaker { + static std::string convert(const std::monostate&) { + return "{ }"; + } + }; + + template + struct StringMaker> { + static std::string convert(const std::variant& variant) { + if (variant.valueless_by_exception()) { + return "{valueless variant}"; + } else { + return std::visit( + [](const auto& value) { + return ::Catch::Detail::stringify(value); + }, + variant + ); + } + } + }; +} +#endif // CATCH_CONFIG_ENABLE_VARIANT_STRINGMAKER + +namespace Catch { + struct not_this_one {}; // Tag type for detecting which begin/ end are being selected + + // Import begin/ end from std here so they are considered alongside the fallback (...) overloads in this namespace + using std::begin; + using std::end; + + not_this_one begin( ... ); + not_this_one end( ... ); + + template + struct is_range { + static const bool value = + !std::is_same())), not_this_one>::value && + !std::is_same())), not_this_one>::value; + }; + +#if defined(_MANAGED) // Managed types are never ranges + template + struct is_range { + static const bool value = false; + }; +#endif + + template + std::string rangeToString( Range const& range ) { + return ::Catch::Detail::rangeToString( begin( range ), end( range ) ); + } + + // Handle vector specially + template + std::string rangeToString( std::vector const& v ) { + ReusableStringStream rss; + rss << "{ "; + bool first = true; + for( bool b : v ) { + if( first ) + first = false; + else + rss << ", "; + rss << ::Catch::Detail::stringify( b ); + } + rss << " }"; + return rss.str(); + } + + template + struct StringMaker::value && !::Catch::Detail::IsStreamInsertable::value>::type> { + static std::string convert( R const& range ) { + return rangeToString( range ); + } + }; + + template + struct StringMaker { + static std::string convert(T const(&arr)[SZ]) { + return rangeToString(arr); + } + }; + +} // namespace Catch + +// Separate std::chrono::duration specialization +#if defined(CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER) +#include +#include +#include + +namespace Catch { + +template +struct ratio_string { + static std::string symbol(); +}; + +template +std::string ratio_string::symbol() { + Catch::ReusableStringStream rss; + rss << '[' << Ratio::num << '/' + << Ratio::den << ']'; + return rss.str(); +} +template <> +struct ratio_string { + static std::string symbol(); +}; +template <> +struct ratio_string { + static std::string symbol(); +}; +template <> +struct ratio_string { + static std::string symbol(); +}; +template <> +struct ratio_string { + static std::string symbol(); +}; +template <> +struct ratio_string { + static std::string symbol(); +}; +template <> +struct ratio_string { + static std::string symbol(); +}; + + //////////// + // std::chrono::duration specializations + template + struct StringMaker> { + static std::string convert(std::chrono::duration const& duration) { + ReusableStringStream rss; + rss << duration.count() << ' ' << ratio_string::symbol() << 's'; + return rss.str(); + } + }; + template + struct StringMaker>> { + static std::string convert(std::chrono::duration> const& duration) { + ReusableStringStream rss; + rss << duration.count() << " s"; + return rss.str(); + } + }; + template + struct StringMaker>> { + static std::string convert(std::chrono::duration> const& duration) { + ReusableStringStream rss; + rss << duration.count() << " m"; + return rss.str(); + } + }; + template + struct StringMaker>> { + static std::string convert(std::chrono::duration> const& duration) { + ReusableStringStream rss; + rss << duration.count() << " h"; + return rss.str(); + } + }; + + //////////// + // std::chrono::time_point specialization + // Generic time_point cannot be specialized, only std::chrono::time_point + template + struct StringMaker> { + static std::string convert(std::chrono::time_point const& time_point) { + return ::Catch::Detail::stringify(time_point.time_since_epoch()) + " since epoch"; + } + }; + // std::chrono::time_point specialization + template + struct StringMaker> { + static std::string convert(std::chrono::time_point const& time_point) { + auto converted = std::chrono::system_clock::to_time_t(time_point); + +#ifdef _MSC_VER + std::tm timeInfo = {}; + gmtime_s(&timeInfo, &converted); +#else + std::tm* timeInfo = std::gmtime(&converted); +#endif + + auto const timeStampSize = sizeof("2017-01-16T17:06:45Z"); + char timeStamp[timeStampSize]; + const char * const fmt = "%Y-%m-%dT%H:%M:%SZ"; + +#ifdef _MSC_VER + std::strftime(timeStamp, timeStampSize, fmt, &timeInfo); +#else + std::strftime(timeStamp, timeStampSize, fmt, timeInfo); +#endif + return std::string(timeStamp); + } + }; +} +#endif // CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +// end catch_tostring.h +#include + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable:4389) // '==' : signed/unsigned mismatch +#pragma warning(disable:4018) // more "signed/unsigned mismatch" +#pragma warning(disable:4312) // Converting int to T* using reinterpret_cast (issue on x64 platform) +#pragma warning(disable:4180) // qualifier applied to function type has no meaning +#endif + +namespace Catch { + + struct ITransientExpression { + auto isBinaryExpression() const -> bool { return m_isBinaryExpression; } + auto getResult() const -> bool { return m_result; } + virtual void streamReconstructedExpression( std::ostream &os ) const = 0; + + ITransientExpression( bool isBinaryExpression, bool result ) + : m_isBinaryExpression( isBinaryExpression ), + m_result( result ) + {} + + // We don't actually need a virtual destructor, but many static analysers + // complain if it's not here :-( + virtual ~ITransientExpression(); + + bool m_isBinaryExpression; + bool m_result; + + }; + + void formatReconstructedExpression( std::ostream &os, std::string const& lhs, StringRef op, std::string const& rhs ); + + template + class BinaryExpr : public ITransientExpression { + LhsT m_lhs; + StringRef m_op; + RhsT m_rhs; + + void streamReconstructedExpression( std::ostream &os ) const override { + formatReconstructedExpression + ( os, Catch::Detail::stringify( m_lhs ), m_op, Catch::Detail::stringify( m_rhs ) ); + } + + public: + BinaryExpr( bool comparisonResult, LhsT lhs, StringRef op, RhsT rhs ) + : ITransientExpression{ true, comparisonResult }, + m_lhs( lhs ), + m_op( op ), + m_rhs( rhs ) + {} + }; + + template + class UnaryExpr : public ITransientExpression { + LhsT m_lhs; + + void streamReconstructedExpression( std::ostream &os ) const override { + os << Catch::Detail::stringify( m_lhs ); + } + + public: + explicit UnaryExpr( LhsT lhs ) + : ITransientExpression{ false, lhs ? true : false }, + m_lhs( lhs ) + {} + }; + + // Specialised comparison functions to handle equality comparisons between ints and pointers (NULL deduces as an int) + template + auto compareEqual( LhsT const& lhs, RhsT const& rhs ) -> bool { return static_cast(lhs == rhs); } + template + auto compareEqual( T* const& lhs, int rhs ) -> bool { return lhs == reinterpret_cast( rhs ); } + template + auto compareEqual( T* const& lhs, long rhs ) -> bool { return lhs == reinterpret_cast( rhs ); } + template + auto compareEqual( int lhs, T* const& rhs ) -> bool { return reinterpret_cast( lhs ) == rhs; } + template + auto compareEqual( long lhs, T* const& rhs ) -> bool { return reinterpret_cast( lhs ) == rhs; } + + template + auto compareNotEqual( LhsT const& lhs, RhsT&& rhs ) -> bool { return static_cast(lhs != rhs); } + template + auto compareNotEqual( T* const& lhs, int rhs ) -> bool { return lhs != reinterpret_cast( rhs ); } + template + auto compareNotEqual( T* const& lhs, long rhs ) -> bool { return lhs != reinterpret_cast( rhs ); } + template + auto compareNotEqual( int lhs, T* const& rhs ) -> bool { return reinterpret_cast( lhs ) != rhs; } + template + auto compareNotEqual( long lhs, T* const& rhs ) -> bool { return reinterpret_cast( lhs ) != rhs; } + + template + class ExprLhs { + LhsT m_lhs; + public: + explicit ExprLhs( LhsT lhs ) : m_lhs( lhs ) {} + + template + auto operator == ( RhsT const& rhs ) -> BinaryExpr const { + return { compareEqual( m_lhs, rhs ), m_lhs, "==", rhs }; + } + auto operator == ( bool rhs ) -> BinaryExpr const { + return { m_lhs == rhs, m_lhs, "==", rhs }; + } + + template + auto operator != ( RhsT const& rhs ) -> BinaryExpr const { + return { compareNotEqual( m_lhs, rhs ), m_lhs, "!=", rhs }; + } + auto operator != ( bool rhs ) -> BinaryExpr const { + return { m_lhs != rhs, m_lhs, "!=", rhs }; + } + + template + auto operator > ( RhsT const& rhs ) -> BinaryExpr const { + return { static_cast(m_lhs > rhs), m_lhs, ">", rhs }; + } + template + auto operator < ( RhsT const& rhs ) -> BinaryExpr const { + return { static_cast(m_lhs < rhs), m_lhs, "<", rhs }; + } + template + auto operator >= ( RhsT const& rhs ) -> BinaryExpr const { + return { static_cast(m_lhs >= rhs), m_lhs, ">=", rhs }; + } + template + auto operator <= ( RhsT const& rhs ) -> BinaryExpr const { + return { static_cast(m_lhs <= rhs), m_lhs, "<=", rhs }; + } + + auto makeUnaryExpr() const -> UnaryExpr { + return UnaryExpr{ m_lhs }; + } + }; + + void handleExpression( ITransientExpression const& expr ); + + template + void handleExpression( ExprLhs const& expr ) { + handleExpression( expr.makeUnaryExpr() ); + } + + struct Decomposer { + template + auto operator <= ( T const& lhs ) -> ExprLhs { + return ExprLhs{ lhs }; + } + + auto operator <=( bool value ) -> ExprLhs { + return ExprLhs{ value }; + } + }; + +} // end namespace Catch + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +// end catch_decomposer.h +// start catch_interfaces_capture.h + +#include + +namespace Catch { + + class AssertionResult; + struct AssertionInfo; + struct SectionInfo; + struct SectionEndInfo; + struct MessageInfo; + struct Counts; + struct BenchmarkInfo; + struct BenchmarkStats; + struct AssertionReaction; + struct SourceLineInfo; + + struct ITransientExpression; + struct IGeneratorTracker; + + struct IResultCapture { + + virtual ~IResultCapture(); + + virtual bool sectionStarted( SectionInfo const& sectionInfo, + Counts& assertions ) = 0; + virtual void sectionEnded( SectionEndInfo const& endInfo ) = 0; + virtual void sectionEndedEarly( SectionEndInfo const& endInfo ) = 0; + + virtual auto acquireGeneratorTracker( SourceLineInfo const& lineInfo ) -> IGeneratorTracker& = 0; + + virtual void benchmarkStarting( BenchmarkInfo const& info ) = 0; + virtual void benchmarkEnded( BenchmarkStats const& stats ) = 0; + + virtual void pushScopedMessage( MessageInfo const& message ) = 0; + virtual void popScopedMessage( MessageInfo const& message ) = 0; + + virtual void handleFatalErrorCondition( StringRef message ) = 0; + + virtual void handleExpr + ( AssertionInfo const& info, + ITransientExpression const& expr, + AssertionReaction& reaction ) = 0; + virtual void handleMessage + ( AssertionInfo const& info, + ResultWas::OfType resultType, + StringRef const& message, + AssertionReaction& reaction ) = 0; + virtual void handleUnexpectedExceptionNotThrown + ( AssertionInfo const& info, + AssertionReaction& reaction ) = 0; + virtual void handleUnexpectedInflightException + ( AssertionInfo const& info, + std::string const& message, + AssertionReaction& reaction ) = 0; + virtual void handleIncomplete + ( AssertionInfo const& info ) = 0; + virtual void handleNonExpr + ( AssertionInfo const &info, + ResultWas::OfType resultType, + AssertionReaction &reaction ) = 0; + + virtual bool lastAssertionPassed() = 0; + virtual void assertionPassed() = 0; + + // Deprecated, do not use: + virtual std::string getCurrentTestName() const = 0; + virtual const AssertionResult* getLastResult() const = 0; + virtual void exceptionEarlyReported() = 0; + }; + + IResultCapture& getResultCapture(); +} + +// end catch_interfaces_capture.h +namespace Catch { + + struct TestFailureException{}; + struct AssertionResultData; + struct IResultCapture; + class RunContext; + + class LazyExpression { + friend class AssertionHandler; + friend struct AssertionStats; + friend class RunContext; + + ITransientExpression const* m_transientExpression = nullptr; + bool m_isNegated; + public: + LazyExpression( bool isNegated ); + LazyExpression( LazyExpression const& other ); + LazyExpression& operator = ( LazyExpression const& ) = delete; + + explicit operator bool() const; + + friend auto operator << ( std::ostream& os, LazyExpression const& lazyExpr ) -> std::ostream&; + }; + + struct AssertionReaction { + bool shouldDebugBreak = false; + bool shouldThrow = false; + }; + + class AssertionHandler { + AssertionInfo m_assertionInfo; + AssertionReaction m_reaction; + bool m_completed = false; + IResultCapture& m_resultCapture; + + public: + AssertionHandler + ( StringRef const& macroName, + SourceLineInfo const& lineInfo, + StringRef capturedExpression, + ResultDisposition::Flags resultDisposition ); + ~AssertionHandler() { + if ( !m_completed ) { + m_resultCapture.handleIncomplete( m_assertionInfo ); + } + } + + template + void handleExpr( ExprLhs const& expr ) { + handleExpr( expr.makeUnaryExpr() ); + } + void handleExpr( ITransientExpression const& expr ); + + void handleMessage(ResultWas::OfType resultType, StringRef const& message); + + void handleExceptionThrownAsExpected(); + void handleUnexpectedExceptionNotThrown(); + void handleExceptionNotThrownAsExpected(); + void handleThrowingCallSkipped(); + void handleUnexpectedInflightException(); + + void complete(); + void setCompleted(); + + // query + auto allowThrows() const -> bool; + }; + + void handleExceptionMatchExpr( AssertionHandler& handler, std::string const& str, StringRef const& matcherString ); + +} // namespace Catch + +// end catch_assertionhandler.h +// start catch_message.h + +#include +#include + +namespace Catch { + + struct MessageInfo { + MessageInfo( StringRef const& _macroName, + SourceLineInfo const& _lineInfo, + ResultWas::OfType _type ); + + StringRef macroName; + std::string message; + SourceLineInfo lineInfo; + ResultWas::OfType type; + unsigned int sequence; + + bool operator == ( MessageInfo const& other ) const; + bool operator < ( MessageInfo const& other ) const; + private: + static unsigned int globalCount; + }; + + struct MessageStream { + + template + MessageStream& operator << ( T const& value ) { + m_stream << value; + return *this; + } + + ReusableStringStream m_stream; + }; + + struct MessageBuilder : MessageStream { + MessageBuilder( StringRef const& macroName, + SourceLineInfo const& lineInfo, + ResultWas::OfType type ); + + template + MessageBuilder& operator << ( T const& value ) { + m_stream << value; + return *this; + } + + MessageInfo m_info; + }; + + class ScopedMessage { + public: + explicit ScopedMessage( MessageBuilder const& builder ); + ~ScopedMessage(); + + MessageInfo m_info; + }; + + class Capturer { + std::vector m_messages; + IResultCapture& m_resultCapture = getResultCapture(); + size_t m_captured = 0; + public: + Capturer( StringRef macroName, SourceLineInfo const& lineInfo, ResultWas::OfType resultType, StringRef names ); + ~Capturer(); + + void captureValue( size_t index, StringRef value ); + + template + void captureValues( size_t index, T&& value ) { + captureValue( index, Catch::Detail::stringify( value ) ); + } + + template + void captureValues( size_t index, T&& value, Ts&&... values ) { + captureValues( index, value ); + captureValues( index+1, values... ); + } + }; + +} // end namespace Catch + +// end catch_message.h +#if !defined(CATCH_CONFIG_DISABLE) + +#if !defined(CATCH_CONFIG_DISABLE_STRINGIFICATION) + #define CATCH_INTERNAL_STRINGIFY(...) #__VA_ARGS__ +#else + #define CATCH_INTERNAL_STRINGIFY(...) "Disabled by CATCH_CONFIG_DISABLE_STRINGIFICATION" +#endif + +#if defined(CATCH_CONFIG_FAST_COMPILE) || defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) + +/////////////////////////////////////////////////////////////////////////////// +// Another way to speed-up compilation is to omit local try-catch for REQUIRE* +// macros. +#define INTERNAL_CATCH_TRY +#define INTERNAL_CATCH_CATCH( capturer ) + +#else // CATCH_CONFIG_FAST_COMPILE + +#define INTERNAL_CATCH_TRY try +#define INTERNAL_CATCH_CATCH( handler ) catch(...) { handler.handleUnexpectedInflightException(); } + +#endif + +#define INTERNAL_CATCH_REACT( handler ) handler.complete(); + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_TEST( macroName, resultDisposition, ... ) \ + do { \ + Catch::AssertionHandler catchAssertionHandler( macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(__VA_ARGS__), resultDisposition ); \ + INTERNAL_CATCH_TRY { \ + CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS \ + catchAssertionHandler.handleExpr( Catch::Decomposer() <= __VA_ARGS__ ); \ + CATCH_INTERNAL_UNSUPPRESS_PARENTHESES_WARNINGS \ + } INTERNAL_CATCH_CATCH( catchAssertionHandler ) \ + INTERNAL_CATCH_REACT( catchAssertionHandler ) \ + } while( (void)0, false && static_cast( !!(__VA_ARGS__) ) ) // the expression here is never evaluated at runtime but it forces the compiler to give it a look + // The double negation silences MSVC's C4800 warning, the static_cast forces short-circuit evaluation if the type has overloaded &&. + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_IF( macroName, resultDisposition, ... ) \ + INTERNAL_CATCH_TEST( macroName, resultDisposition, __VA_ARGS__ ); \ + if( Catch::getResultCapture().lastAssertionPassed() ) + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_ELSE( macroName, resultDisposition, ... ) \ + INTERNAL_CATCH_TEST( macroName, resultDisposition, __VA_ARGS__ ); \ + if( !Catch::getResultCapture().lastAssertionPassed() ) + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_NO_THROW( macroName, resultDisposition, ... ) \ + do { \ + Catch::AssertionHandler catchAssertionHandler( macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(__VA_ARGS__), resultDisposition ); \ + try { \ + static_cast(__VA_ARGS__); \ + catchAssertionHandler.handleExceptionNotThrownAsExpected(); \ + } \ + catch( ... ) { \ + catchAssertionHandler.handleUnexpectedInflightException(); \ + } \ + INTERNAL_CATCH_REACT( catchAssertionHandler ) \ + } while( false ) + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_THROWS( macroName, resultDisposition, ... ) \ + do { \ + Catch::AssertionHandler catchAssertionHandler( macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(__VA_ARGS__), resultDisposition); \ + if( catchAssertionHandler.allowThrows() ) \ + try { \ + static_cast(__VA_ARGS__); \ + catchAssertionHandler.handleUnexpectedExceptionNotThrown(); \ + } \ + catch( ... ) { \ + catchAssertionHandler.handleExceptionThrownAsExpected(); \ + } \ + else \ + catchAssertionHandler.handleThrowingCallSkipped(); \ + INTERNAL_CATCH_REACT( catchAssertionHandler ) \ + } while( false ) + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_THROWS_AS( macroName, exceptionType, resultDisposition, expr ) \ + do { \ + Catch::AssertionHandler catchAssertionHandler( macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(expr) ", " CATCH_INTERNAL_STRINGIFY(exceptionType), resultDisposition ); \ + if( catchAssertionHandler.allowThrows() ) \ + try { \ + static_cast(expr); \ + catchAssertionHandler.handleUnexpectedExceptionNotThrown(); \ + } \ + catch( exceptionType const& ) { \ + catchAssertionHandler.handleExceptionThrownAsExpected(); \ + } \ + catch( ... ) { \ + catchAssertionHandler.handleUnexpectedInflightException(); \ + } \ + else \ + catchAssertionHandler.handleThrowingCallSkipped(); \ + INTERNAL_CATCH_REACT( catchAssertionHandler ) \ + } while( false ) + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_MSG( macroName, messageType, resultDisposition, ... ) \ + do { \ + Catch::AssertionHandler catchAssertionHandler( macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, Catch::StringRef(), resultDisposition ); \ + catchAssertionHandler.handleMessage( messageType, ( Catch::MessageStream() << __VA_ARGS__ + ::Catch::StreamEndStop() ).m_stream.str() ); \ + INTERNAL_CATCH_REACT( catchAssertionHandler ) \ + } while( false ) + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_CAPTURE( varName, macroName, ... ) \ + auto varName = Catch::Capturer( macroName, CATCH_INTERNAL_LINEINFO, Catch::ResultWas::Info, #__VA_ARGS__ ); \ + varName.captureValues( 0, __VA_ARGS__ ) + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_INFO( macroName, log ) \ + Catch::ScopedMessage INTERNAL_CATCH_UNIQUE_NAME( scopedMessage )( Catch::MessageBuilder( macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, Catch::ResultWas::Info ) << log ); + +/////////////////////////////////////////////////////////////////////////////// +// Although this is matcher-based, it can be used with just a string +#define INTERNAL_CATCH_THROWS_STR_MATCHES( macroName, resultDisposition, matcher, ... ) \ + do { \ + Catch::AssertionHandler catchAssertionHandler( macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(__VA_ARGS__) ", " CATCH_INTERNAL_STRINGIFY(matcher), resultDisposition ); \ + if( catchAssertionHandler.allowThrows() ) \ + try { \ + static_cast(__VA_ARGS__); \ + catchAssertionHandler.handleUnexpectedExceptionNotThrown(); \ + } \ + catch( ... ) { \ + Catch::handleExceptionMatchExpr( catchAssertionHandler, matcher, #matcher##_catch_sr ); \ + } \ + else \ + catchAssertionHandler.handleThrowingCallSkipped(); \ + INTERNAL_CATCH_REACT( catchAssertionHandler ) \ + } while( false ) + +#endif // CATCH_CONFIG_DISABLE + +// end catch_capture.hpp +// start catch_section.h + +// start catch_section_info.h + +// start catch_totals.h + +#include + +namespace Catch { + + struct Counts { + Counts operator - ( Counts const& other ) const; + Counts& operator += ( Counts const& other ); + + std::size_t total() const; + bool allPassed() const; + bool allOk() const; + + std::size_t passed = 0; + std::size_t failed = 0; + std::size_t failedButOk = 0; + }; + + struct Totals { + + Totals operator - ( Totals const& other ) const; + Totals& operator += ( Totals const& other ); + + Totals delta( Totals const& prevTotals ) const; + + int error = 0; + Counts assertions; + Counts testCases; + }; +} + +// end catch_totals.h +#include + +namespace Catch { + + struct SectionInfo { + SectionInfo + ( SourceLineInfo const& _lineInfo, + std::string const& _name ); + + // Deprecated + SectionInfo + ( SourceLineInfo const& _lineInfo, + std::string const& _name, + std::string const& ) : SectionInfo( _lineInfo, _name ) {} + + std::string name; + std::string description; // !Deprecated: this will always be empty + SourceLineInfo lineInfo; + }; + + struct SectionEndInfo { + SectionInfo sectionInfo; + Counts prevAssertions; + double durationInSeconds; + }; + +} // end namespace Catch + +// end catch_section_info.h +// start catch_timer.h + +#include + +namespace Catch { + + auto getCurrentNanosecondsSinceEpoch() -> uint64_t; + auto getEstimatedClockResolution() -> uint64_t; + + class Timer { + uint64_t m_nanoseconds = 0; + public: + void start(); + auto getElapsedNanoseconds() const -> uint64_t; + auto getElapsedMicroseconds() const -> uint64_t; + auto getElapsedMilliseconds() const -> unsigned int; + auto getElapsedSeconds() const -> double; + }; + +} // namespace Catch + +// end catch_timer.h +#include + +namespace Catch { + + class Section : NonCopyable { + public: + Section( SectionInfo const& info ); + ~Section(); + + // This indicates whether the section should be executed or not + explicit operator bool() const; + + private: + SectionInfo m_info; + + std::string m_name; + Counts m_assertions; + bool m_sectionIncluded; + Timer m_timer; + }; + +} // end namespace Catch + +#define INTERNAL_CATCH_SECTION( ... ) \ + CATCH_INTERNAL_SUPPRESS_UNUSED_WARNINGS \ + if( Catch::Section const& INTERNAL_CATCH_UNIQUE_NAME( catch_internal_Section ) = Catch::SectionInfo( CATCH_INTERNAL_LINEINFO, __VA_ARGS__ ) ) \ + CATCH_INTERNAL_UNSUPPRESS_UNUSED_WARNINGS + +#define INTERNAL_CATCH_DYNAMIC_SECTION( ... ) \ + CATCH_INTERNAL_SUPPRESS_UNUSED_WARNINGS \ + if( Catch::Section const& INTERNAL_CATCH_UNIQUE_NAME( catch_internal_Section ) = Catch::SectionInfo( CATCH_INTERNAL_LINEINFO, (Catch::ReusableStringStream() << __VA_ARGS__).str() ) ) \ + CATCH_INTERNAL_UNSUPPRESS_UNUSED_WARNINGS + +// end catch_section.h +// start catch_benchmark.h + +#include +#include + +namespace Catch { + + class BenchmarkLooper { + + std::string m_name; + std::size_t m_count = 0; + std::size_t m_iterationsToRun = 1; + uint64_t m_resolution; + Timer m_timer; + + static auto getResolution() -> uint64_t; + public: + // Keep most of this inline as it's on the code path that is being timed + BenchmarkLooper( StringRef name ) + : m_name( name ), + m_resolution( getResolution() ) + { + reportStart(); + m_timer.start(); + } + + explicit operator bool() { + if( m_count < m_iterationsToRun ) + return true; + return needsMoreIterations(); + } + + void increment() { + ++m_count; + } + + void reportStart(); + auto needsMoreIterations() -> bool; + }; + +} // end namespace Catch + +#define BENCHMARK( name ) \ + for( Catch::BenchmarkLooper looper( name ); looper; looper.increment() ) + +// end catch_benchmark.h +// start catch_interfaces_exception.h + +// start catch_interfaces_registry_hub.h + +#include +#include + +namespace Catch { + + class TestCase; + struct ITestCaseRegistry; + struct IExceptionTranslatorRegistry; + struct IExceptionTranslator; + struct IReporterRegistry; + struct IReporterFactory; + struct ITagAliasRegistry; + class StartupExceptionRegistry; + + using IReporterFactoryPtr = std::shared_ptr; + + struct IRegistryHub { + virtual ~IRegistryHub(); + + virtual IReporterRegistry const& getReporterRegistry() const = 0; + virtual ITestCaseRegistry const& getTestCaseRegistry() const = 0; + virtual ITagAliasRegistry const& getTagAliasRegistry() const = 0; + + virtual IExceptionTranslatorRegistry const& getExceptionTranslatorRegistry() const = 0; + + virtual StartupExceptionRegistry const& getStartupExceptionRegistry() const = 0; + }; + + struct IMutableRegistryHub { + virtual ~IMutableRegistryHub(); + virtual void registerReporter( std::string const& name, IReporterFactoryPtr const& factory ) = 0; + virtual void registerListener( IReporterFactoryPtr const& factory ) = 0; + virtual void registerTest( TestCase const& testInfo ) = 0; + virtual void registerTranslator( const IExceptionTranslator* translator ) = 0; + virtual void registerTagAlias( std::string const& alias, std::string const& tag, SourceLineInfo const& lineInfo ) = 0; + virtual void registerStartupException() noexcept = 0; + }; + + IRegistryHub const& getRegistryHub(); + IMutableRegistryHub& getMutableRegistryHub(); + void cleanUp(); + std::string translateActiveException(); + +} + +// end catch_interfaces_registry_hub.h +#if defined(CATCH_CONFIG_DISABLE) + #define INTERNAL_CATCH_TRANSLATE_EXCEPTION_NO_REG( translatorName, signature) \ + static std::string translatorName( signature ) +#endif + +#include +#include +#include + +namespace Catch { + using exceptionTranslateFunction = std::string(*)(); + + struct IExceptionTranslator; + using ExceptionTranslators = std::vector>; + + struct IExceptionTranslator { + virtual ~IExceptionTranslator(); + virtual std::string translate( ExceptionTranslators::const_iterator it, ExceptionTranslators::const_iterator itEnd ) const = 0; + }; + + struct IExceptionTranslatorRegistry { + virtual ~IExceptionTranslatorRegistry(); + + virtual std::string translateActiveException() const = 0; + }; + + class ExceptionTranslatorRegistrar { + template + class ExceptionTranslator : public IExceptionTranslator { + public: + + ExceptionTranslator( std::string(*translateFunction)( T& ) ) + : m_translateFunction( translateFunction ) + {} + + std::string translate( ExceptionTranslators::const_iterator it, ExceptionTranslators::const_iterator itEnd ) const override { + try { + if( it == itEnd ) + std::rethrow_exception(std::current_exception()); + else + return (*it)->translate( it+1, itEnd ); + } + catch( T& ex ) { + return m_translateFunction( ex ); + } + } + + protected: + std::string(*m_translateFunction)( T& ); + }; + + public: + template + ExceptionTranslatorRegistrar( std::string(*translateFunction)( T& ) ) { + getMutableRegistryHub().registerTranslator + ( new ExceptionTranslator( translateFunction ) ); + } + }; +} + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_TRANSLATE_EXCEPTION2( translatorName, signature ) \ + static std::string translatorName( signature ); \ + CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ + namespace{ Catch::ExceptionTranslatorRegistrar INTERNAL_CATCH_UNIQUE_NAME( catch_internal_ExceptionRegistrar )( &translatorName ); } \ + CATCH_INTERNAL_UNSUPPRESS_GLOBALS_WARNINGS \ + static std::string translatorName( signature ) + +#define INTERNAL_CATCH_TRANSLATE_EXCEPTION( signature ) INTERNAL_CATCH_TRANSLATE_EXCEPTION2( INTERNAL_CATCH_UNIQUE_NAME( catch_internal_ExceptionTranslator ), signature ) + +// end catch_interfaces_exception.h +// start catch_approx.h + +#include + +namespace Catch { +namespace Detail { + + class Approx { + private: + bool equalityComparisonImpl(double other) const; + // Validates the new margin (margin >= 0) + // out-of-line to avoid including stdexcept in the header + void setMargin(double margin); + // Validates the new epsilon (0 < epsilon < 1) + // out-of-line to avoid including stdexcept in the header + void setEpsilon(double epsilon); + + public: + explicit Approx ( double value ); + + static Approx custom(); + + Approx operator-() const; + + template ::value>::type> + Approx operator()( T const& value ) { + Approx approx( static_cast(value) ); + approx.m_epsilon = m_epsilon; + approx.m_margin = m_margin; + approx.m_scale = m_scale; + return approx; + } + + template ::value>::type> + explicit Approx( T const& value ): Approx(static_cast(value)) + {} + + template ::value>::type> + friend bool operator == ( const T& lhs, Approx const& rhs ) { + auto lhs_v = static_cast(lhs); + return rhs.equalityComparisonImpl(lhs_v); + } + + template ::value>::type> + friend bool operator == ( Approx const& lhs, const T& rhs ) { + return operator==( rhs, lhs ); + } + + template ::value>::type> + friend bool operator != ( T const& lhs, Approx const& rhs ) { + return !operator==( lhs, rhs ); + } + + template ::value>::type> + friend bool operator != ( Approx const& lhs, T const& rhs ) { + return !operator==( rhs, lhs ); + } + + template ::value>::type> + friend bool operator <= ( T const& lhs, Approx const& rhs ) { + return static_cast(lhs) < rhs.m_value || lhs == rhs; + } + + template ::value>::type> + friend bool operator <= ( Approx const& lhs, T const& rhs ) { + return lhs.m_value < static_cast(rhs) || lhs == rhs; + } + + template ::value>::type> + friend bool operator >= ( T const& lhs, Approx const& rhs ) { + return static_cast(lhs) > rhs.m_value || lhs == rhs; + } + + template ::value>::type> + friend bool operator >= ( Approx const& lhs, T const& rhs ) { + return lhs.m_value > static_cast(rhs) || lhs == rhs; + } + + template ::value>::type> + Approx& epsilon( T const& newEpsilon ) { + double epsilonAsDouble = static_cast(newEpsilon); + setEpsilon(epsilonAsDouble); + return *this; + } + + template ::value>::type> + Approx& margin( T const& newMargin ) { + double marginAsDouble = static_cast(newMargin); + setMargin(marginAsDouble); + return *this; + } + + template ::value>::type> + Approx& scale( T const& newScale ) { + m_scale = static_cast(newScale); + return *this; + } + + std::string toString() const; + + private: + double m_epsilon; + double m_margin; + double m_scale; + double m_value; + }; +} // end namespace Detail + +namespace literals { + Detail::Approx operator "" _a(long double val); + Detail::Approx operator "" _a(unsigned long long val); +} // end namespace literals + +template<> +struct StringMaker { + static std::string convert(Catch::Detail::Approx const& value); +}; + +} // end namespace Catch + +// end catch_approx.h +// start catch_string_manip.h + +#include +#include + +namespace Catch { + + bool startsWith( std::string const& s, std::string const& prefix ); + bool startsWith( std::string const& s, char prefix ); + bool endsWith( std::string const& s, std::string const& suffix ); + bool endsWith( std::string const& s, char suffix ); + bool contains( std::string const& s, std::string const& infix ); + void toLowerInPlace( std::string& s ); + std::string toLower( std::string const& s ); + std::string trim( std::string const& str ); + bool replaceInPlace( std::string& str, std::string const& replaceThis, std::string const& withThis ); + + struct pluralise { + pluralise( std::size_t count, std::string const& label ); + + friend std::ostream& operator << ( std::ostream& os, pluralise const& pluraliser ); + + std::size_t m_count; + std::string m_label; + }; +} + +// end catch_string_manip.h +#ifndef CATCH_CONFIG_DISABLE_MATCHERS +// start catch_capture_matchers.h + +// start catch_matchers.h + +#include +#include + +namespace Catch { +namespace Matchers { + namespace Impl { + + template struct MatchAllOf; + template struct MatchAnyOf; + template struct MatchNotOf; + + class MatcherUntypedBase { + public: + MatcherUntypedBase() = default; + MatcherUntypedBase ( MatcherUntypedBase const& ) = default; + MatcherUntypedBase& operator = ( MatcherUntypedBase const& ) = delete; + std::string toString() const; + + protected: + virtual ~MatcherUntypedBase(); + virtual std::string describe() const = 0; + mutable std::string m_cachedToString; + }; + +#ifdef __clang__ +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wnon-virtual-dtor" +#endif + + template + struct MatcherMethod { + virtual bool match( ObjectT const& arg ) const = 0; + }; + template + struct MatcherMethod { + virtual bool match( PtrT* arg ) const = 0; + }; + +#ifdef __clang__ +# pragma clang diagnostic pop +#endif + + template + struct MatcherBase : MatcherUntypedBase, MatcherMethod { + + MatchAllOf operator && ( MatcherBase const& other ) const; + MatchAnyOf operator || ( MatcherBase const& other ) const; + MatchNotOf operator ! () const; + }; + + template + struct MatchAllOf : MatcherBase { + bool match( ArgT const& arg ) const override { + for( auto matcher : m_matchers ) { + if (!matcher->match(arg)) + return false; + } + return true; + } + std::string describe() const override { + std::string description; + description.reserve( 4 + m_matchers.size()*32 ); + description += "( "; + bool first = true; + for( auto matcher : m_matchers ) { + if( first ) + first = false; + else + description += " and "; + description += matcher->toString(); + } + description += " )"; + return description; + } + + MatchAllOf& operator && ( MatcherBase const& other ) { + m_matchers.push_back( &other ); + return *this; + } + + std::vector const*> m_matchers; + }; + template + struct MatchAnyOf : MatcherBase { + + bool match( ArgT const& arg ) const override { + for( auto matcher : m_matchers ) { + if (matcher->match(arg)) + return true; + } + return false; + } + std::string describe() const override { + std::string description; + description.reserve( 4 + m_matchers.size()*32 ); + description += "( "; + bool first = true; + for( auto matcher : m_matchers ) { + if( first ) + first = false; + else + description += " or "; + description += matcher->toString(); + } + description += " )"; + return description; + } + + MatchAnyOf& operator || ( MatcherBase const& other ) { + m_matchers.push_back( &other ); + return *this; + } + + std::vector const*> m_matchers; + }; + + template + struct MatchNotOf : MatcherBase { + + MatchNotOf( MatcherBase const& underlyingMatcher ) : m_underlyingMatcher( underlyingMatcher ) {} + + bool match( ArgT const& arg ) const override { + return !m_underlyingMatcher.match( arg ); + } + + std::string describe() const override { + return "not " + m_underlyingMatcher.toString(); + } + MatcherBase const& m_underlyingMatcher; + }; + + template + MatchAllOf MatcherBase::operator && ( MatcherBase const& other ) const { + return MatchAllOf() && *this && other; + } + template + MatchAnyOf MatcherBase::operator || ( MatcherBase const& other ) const { + return MatchAnyOf() || *this || other; + } + template + MatchNotOf MatcherBase::operator ! () const { + return MatchNotOf( *this ); + } + + } // namespace Impl + +} // namespace Matchers + +using namespace Matchers; +using Matchers::Impl::MatcherBase; + +} // namespace Catch + +// end catch_matchers.h +// start catch_matchers_floating.h + +#include +#include + +namespace Catch { +namespace Matchers { + + namespace Floating { + + enum class FloatingPointKind : uint8_t; + + struct WithinAbsMatcher : MatcherBase { + WithinAbsMatcher(double target, double margin); + bool match(double const& matchee) const override; + std::string describe() const override; + private: + double m_target; + double m_margin; + }; + + struct WithinUlpsMatcher : MatcherBase { + WithinUlpsMatcher(double target, int ulps, FloatingPointKind baseType); + bool match(double const& matchee) const override; + std::string describe() const override; + private: + double m_target; + int m_ulps; + FloatingPointKind m_type; + }; + + } // namespace Floating + + // The following functions create the actual matcher objects. + // This allows the types to be inferred + Floating::WithinUlpsMatcher WithinULP(double target, int maxUlpDiff); + Floating::WithinUlpsMatcher WithinULP(float target, int maxUlpDiff); + Floating::WithinAbsMatcher WithinAbs(double target, double margin); + +} // namespace Matchers +} // namespace Catch + +// end catch_matchers_floating.h +// start catch_matchers_generic.hpp + +#include +#include + +namespace Catch { +namespace Matchers { +namespace Generic { + +namespace Detail { + std::string finalizeDescription(const std::string& desc); +} + +template +class PredicateMatcher : public MatcherBase { + std::function m_predicate; + std::string m_description; +public: + + PredicateMatcher(std::function const& elem, std::string const& descr) + :m_predicate(std::move(elem)), + m_description(Detail::finalizeDescription(descr)) + {} + + bool match( T const& item ) const override { + return m_predicate(item); + } + + std::string describe() const override { + return m_description; + } +}; + +} // namespace Generic + + // The following functions create the actual matcher objects. + // The user has to explicitly specify type to the function, because + // infering std::function is hard (but possible) and + // requires a lot of TMP. + template + Generic::PredicateMatcher Predicate(std::function const& predicate, std::string const& description = "") { + return Generic::PredicateMatcher(predicate, description); + } + +} // namespace Matchers +} // namespace Catch + +// end catch_matchers_generic.hpp +// start catch_matchers_string.h + +#include + +namespace Catch { +namespace Matchers { + + namespace StdString { + + struct CasedString + { + CasedString( std::string const& str, CaseSensitive::Choice caseSensitivity ); + std::string adjustString( std::string const& str ) const; + std::string caseSensitivitySuffix() const; + + CaseSensitive::Choice m_caseSensitivity; + std::string m_str; + }; + + struct StringMatcherBase : MatcherBase { + StringMatcherBase( std::string const& operation, CasedString const& comparator ); + std::string describe() const override; + + CasedString m_comparator; + std::string m_operation; + }; + + struct EqualsMatcher : StringMatcherBase { + EqualsMatcher( CasedString const& comparator ); + bool match( std::string const& source ) const override; + }; + struct ContainsMatcher : StringMatcherBase { + ContainsMatcher( CasedString const& comparator ); + bool match( std::string const& source ) const override; + }; + struct StartsWithMatcher : StringMatcherBase { + StartsWithMatcher( CasedString const& comparator ); + bool match( std::string const& source ) const override; + }; + struct EndsWithMatcher : StringMatcherBase { + EndsWithMatcher( CasedString const& comparator ); + bool match( std::string const& source ) const override; + }; + + struct RegexMatcher : MatcherBase { + RegexMatcher( std::string regex, CaseSensitive::Choice caseSensitivity ); + bool match( std::string const& matchee ) const override; + std::string describe() const override; + + private: + std::string m_regex; + CaseSensitive::Choice m_caseSensitivity; + }; + + } // namespace StdString + + // The following functions create the actual matcher objects. + // This allows the types to be inferred + + StdString::EqualsMatcher Equals( std::string const& str, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes ); + StdString::ContainsMatcher Contains( std::string const& str, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes ); + StdString::EndsWithMatcher EndsWith( std::string const& str, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes ); + StdString::StartsWithMatcher StartsWith( std::string const& str, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes ); + StdString::RegexMatcher Matches( std::string const& regex, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes ); + +} // namespace Matchers +} // namespace Catch + +// end catch_matchers_string.h +// start catch_matchers_vector.h + +#include + +namespace Catch { +namespace Matchers { + + namespace Vector { + namespace Detail { + template + size_t count(InputIterator first, InputIterator last, T const& item) { + size_t cnt = 0; + for (; first != last; ++first) { + if (*first == item) { + ++cnt; + } + } + return cnt; + } + template + bool contains(InputIterator first, InputIterator last, T const& item) { + for (; first != last; ++first) { + if (*first == item) { + return true; + } + } + return false; + } + } + + template + struct ContainsElementMatcher : MatcherBase> { + + ContainsElementMatcher(T const &comparator) : m_comparator( comparator) {} + + bool match(std::vector const &v) const override { + for (auto const& el : v) { + if (el == m_comparator) { + return true; + } + } + return false; + } + + std::string describe() const override { + return "Contains: " + ::Catch::Detail::stringify( m_comparator ); + } + + T const& m_comparator; + }; + + template + struct ContainsMatcher : MatcherBase> { + + ContainsMatcher(std::vector const &comparator) : m_comparator( comparator ) {} + + bool match(std::vector const &v) const override { + // !TBD: see note in EqualsMatcher + if (m_comparator.size() > v.size()) + return false; + for (auto const& comparator : m_comparator) { + auto present = false; + for (const auto& el : v) { + if (el == comparator) { + present = true; + break; + } + } + if (!present) { + return false; + } + } + return true; + } + std::string describe() const override { + return "Contains: " + ::Catch::Detail::stringify( m_comparator ); + } + + std::vector const& m_comparator; + }; + + template + struct EqualsMatcher : MatcherBase> { + + EqualsMatcher(std::vector const &comparator) : m_comparator( comparator ) {} + + bool match(std::vector const &v) const override { + // !TBD: This currently works if all elements can be compared using != + // - a more general approach would be via a compare template that defaults + // to using !=. but could be specialised for, e.g. std::vector etc + // - then just call that directly + if (m_comparator.size() != v.size()) + return false; + for (std::size_t i = 0; i < v.size(); ++i) + if (m_comparator[i] != v[i]) + return false; + return true; + } + std::string describe() const override { + return "Equals: " + ::Catch::Detail::stringify( m_comparator ); + } + std::vector const& m_comparator; + }; + + template + struct UnorderedEqualsMatcher : MatcherBase> { + UnorderedEqualsMatcher(std::vector const& target) : m_target(target) {} + bool match(std::vector const& vec) const override { + // Note: This is a reimplementation of std::is_permutation, + // because I don't want to include inside the common path + if (m_target.size() != vec.size()) { + return false; + } + auto lfirst = m_target.begin(), llast = m_target.end(); + auto rfirst = vec.begin(), rlast = vec.end(); + // Cut common prefix to optimize checking of permuted parts + while (lfirst != llast && *lfirst == *rfirst) { + ++lfirst; ++rfirst; + } + if (lfirst == llast) { + return true; + } + + for (auto mid = lfirst; mid != llast; ++mid) { + // Skip already counted items + if (Detail::contains(lfirst, mid, *mid)) { + continue; + } + size_t num_vec = Detail::count(rfirst, rlast, *mid); + if (num_vec == 0 || Detail::count(lfirst, llast, *mid) != num_vec) { + return false; + } + } + + return true; + } + + std::string describe() const override { + return "UnorderedEquals: " + ::Catch::Detail::stringify(m_target); + } + private: + std::vector const& m_target; + }; + + } // namespace Vector + + // The following functions create the actual matcher objects. + // This allows the types to be inferred + + template + Vector::ContainsMatcher Contains( std::vector const& comparator ) { + return Vector::ContainsMatcher( comparator ); + } + + template + Vector::ContainsElementMatcher VectorContains( T const& comparator ) { + return Vector::ContainsElementMatcher( comparator ); + } + + template + Vector::EqualsMatcher Equals( std::vector const& comparator ) { + return Vector::EqualsMatcher( comparator ); + } + + template + Vector::UnorderedEqualsMatcher UnorderedEquals(std::vector const& target) { + return Vector::UnorderedEqualsMatcher(target); + } + +} // namespace Matchers +} // namespace Catch + +// end catch_matchers_vector.h +namespace Catch { + + template + class MatchExpr : public ITransientExpression { + ArgT const& m_arg; + MatcherT m_matcher; + StringRef m_matcherString; + public: + MatchExpr( ArgT const& arg, MatcherT const& matcher, StringRef const& matcherString ) + : ITransientExpression{ true, matcher.match( arg ) }, + m_arg( arg ), + m_matcher( matcher ), + m_matcherString( matcherString ) + {} + + void streamReconstructedExpression( std::ostream &os ) const override { + auto matcherAsString = m_matcher.toString(); + os << Catch::Detail::stringify( m_arg ) << ' '; + if( matcherAsString == Detail::unprintableString ) + os << m_matcherString; + else + os << matcherAsString; + } + }; + + using StringMatcher = Matchers::Impl::MatcherBase; + + void handleExceptionMatchExpr( AssertionHandler& handler, StringMatcher const& matcher, StringRef const& matcherString ); + + template + auto makeMatchExpr( ArgT const& arg, MatcherT const& matcher, StringRef const& matcherString ) -> MatchExpr { + return MatchExpr( arg, matcher, matcherString ); + } + +} // namespace Catch + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CHECK_THAT( macroName, matcher, resultDisposition, arg ) \ + do { \ + Catch::AssertionHandler catchAssertionHandler( macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(arg) ", " CATCH_INTERNAL_STRINGIFY(matcher), resultDisposition ); \ + INTERNAL_CATCH_TRY { \ + catchAssertionHandler.handleExpr( Catch::makeMatchExpr( arg, matcher, #matcher##_catch_sr ) ); \ + } INTERNAL_CATCH_CATCH( catchAssertionHandler ) \ + INTERNAL_CATCH_REACT( catchAssertionHandler ) \ + } while( false ) + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_THROWS_MATCHES( macroName, exceptionType, resultDisposition, matcher, ... ) \ + do { \ + Catch::AssertionHandler catchAssertionHandler( macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(__VA_ARGS__) ", " CATCH_INTERNAL_STRINGIFY(exceptionType) ", " CATCH_INTERNAL_STRINGIFY(matcher), resultDisposition ); \ + if( catchAssertionHandler.allowThrows() ) \ + try { \ + static_cast(__VA_ARGS__ ); \ + catchAssertionHandler.handleUnexpectedExceptionNotThrown(); \ + } \ + catch( exceptionType const& ex ) { \ + catchAssertionHandler.handleExpr( Catch::makeMatchExpr( ex, matcher, #matcher##_catch_sr ) ); \ + } \ + catch( ... ) { \ + catchAssertionHandler.handleUnexpectedInflightException(); \ + } \ + else \ + catchAssertionHandler.handleThrowingCallSkipped(); \ + INTERNAL_CATCH_REACT( catchAssertionHandler ) \ + } while( false ) + +// end catch_capture_matchers.h +#endif +// start catch_generators.hpp + +// start catch_interfaces_generatortracker.h + + +#include + +namespace Catch { + + namespace Generators { + class GeneratorBase { + protected: + size_t m_size = 0; + + public: + GeneratorBase( size_t size ) : m_size( size ) {} + virtual ~GeneratorBase(); + auto size() const -> size_t { return m_size; } + }; + using GeneratorBasePtr = std::unique_ptr; + + } // namespace Generators + + struct IGeneratorTracker { + virtual ~IGeneratorTracker(); + virtual auto hasGenerator() const -> bool = 0; + virtual auto getGenerator() const -> Generators::GeneratorBasePtr const& = 0; + virtual void setGenerator( Generators::GeneratorBasePtr&& generator ) = 0; + virtual auto getIndex() const -> std::size_t = 0; + }; + +} // namespace Catch + +// end catch_interfaces_generatortracker.h +// start catch_enforce.h + +#include + +namespace Catch { +#if !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) + template + [[noreturn]] + void throw_exception(Ex const& e) { + throw e; + } +#else // ^^ Exceptions are enabled // Exceptions are disabled vv + [[noreturn]] + void throw_exception(std::exception const& e); +#endif +} // namespace Catch; + +#define CATCH_PREPARE_EXCEPTION( type, msg ) \ + type( ( Catch::ReusableStringStream() << msg ).str() ) +#define CATCH_INTERNAL_ERROR( msg ) \ + Catch::throw_exception(CATCH_PREPARE_EXCEPTION( std::logic_error, CATCH_INTERNAL_LINEINFO << ": Internal Catch error: " << msg)) +#define CATCH_ERROR( msg ) \ + Catch::throw_exception(CATCH_PREPARE_EXCEPTION( std::domain_error, msg )) +#define CATCH_RUNTIME_ERROR( msg ) \ + Catch::throw_exception(CATCH_PREPARE_EXCEPTION( std::runtime_error, msg )) +#define CATCH_ENFORCE( condition, msg ) \ + do{ if( !(condition) ) CATCH_ERROR( msg ); } while(false) + +// end catch_enforce.h +#include +#include +#include + +#include + +namespace Catch { +namespace Generators { + + // !TBD move this into its own location? + namespace pf{ + template + std::unique_ptr make_unique( Args&&... args ) { + return std::unique_ptr(new T(std::forward(args)...)); + } + } + + template + struct IGenerator { + virtual ~IGenerator() {} + virtual auto get( size_t index ) const -> T = 0; + }; + + template + class SingleValueGenerator : public IGenerator { + T m_value; + public: + SingleValueGenerator( T const& value ) : m_value( value ) {} + + auto get( size_t ) const -> T override { + return m_value; + } + }; + + template + class FixedValuesGenerator : public IGenerator { + std::vector m_values; + + public: + FixedValuesGenerator( std::initializer_list values ) : m_values( values ) {} + + auto get( size_t index ) const -> T override { + return m_values[index]; + } + }; + + template + class RangeGenerator : public IGenerator { + T const m_first; + T const m_last; + + public: + RangeGenerator( T const& first, T const& last ) : m_first( first ), m_last( last ) { + assert( m_last > m_first ); + } + + auto get( size_t index ) const -> T override { + // ToDo:: introduce a safe cast to catch potential overflows + return static_cast(m_first+index); + } + }; + + template + struct NullGenerator : IGenerator { + auto get( size_t ) const -> T override { + CATCH_INTERNAL_ERROR("A Null Generator is always empty"); + } + }; + + template + class Generator { + std::unique_ptr> m_generator; + size_t m_size; + + public: + Generator( size_t size, std::unique_ptr> generator ) + : m_generator( std::move( generator ) ), + m_size( size ) + {} + + auto size() const -> size_t { return m_size; } + auto operator[]( size_t index ) const -> T { + assert( index < m_size ); + return m_generator->get( index ); + } + }; + + std::vector randomiseIndices( size_t selectionSize, size_t sourceSize ); + + template + class GeneratorRandomiser : public IGenerator { + Generator m_baseGenerator; + + std::vector m_indices; + public: + GeneratorRandomiser( Generator&& baseGenerator, size_t numberOfItems ) + : m_baseGenerator( std::move( baseGenerator ) ), + m_indices( randomiseIndices( numberOfItems, m_baseGenerator.size() ) ) + {} + + auto get( size_t index ) const -> T override { + return m_baseGenerator[m_indices[index]]; + } + }; + + template + struct RequiresASpecialisationFor; + + template + auto all() -> Generator { return RequiresASpecialisationFor(); } + + template<> + auto all() -> Generator; + + template + auto range( T const& first, T const& last ) -> Generator { + return Generator( (last-first), pf::make_unique>( first, last ) ); + } + + template + auto random( T const& first, T const& last ) -> Generator { + auto gen = range( first, last ); + auto size = gen.size(); + + return Generator( size, pf::make_unique>( std::move( gen ), size ) ); + } + template + auto random( size_t size ) -> Generator { + return Generator( size, pf::make_unique>( all(), size ) ); + } + + template + auto values( std::initializer_list values ) -> Generator { + return Generator( values.size(), pf::make_unique>( values ) ); + } + template + auto value( T const& val ) -> Generator { + return Generator( 1, pf::make_unique>( val ) ); + } + + template + auto as() -> Generator { + return Generator( 0, pf::make_unique>() ); + } + + template + auto table( std::initializer_list>&& tuples ) -> Generator> { + return values>( std::forward>>( tuples ) ); + } + + template + struct Generators : GeneratorBase { + std::vector> m_generators; + + using type = T; + + Generators() : GeneratorBase( 0 ) {} + + void populate( T&& val ) { + m_size += 1; + m_generators.emplace_back( value( std::move( val ) ) ); + } + template + void populate( U&& val ) { + populate( T( std::move( val ) ) ); + } + void populate( Generator&& generator ) { + m_size += generator.size(); + m_generators.emplace_back( std::move( generator ) ); + } + + template + void populate( U&& valueOrGenerator, Gs... moreGenerators ) { + populate( std::forward( valueOrGenerator ) ); + populate( std::forward( moreGenerators )... ); + } + + auto operator[]( size_t index ) const -> T { + size_t sizes = 0; + for( auto const& gen : m_generators ) { + auto localIndex = index-sizes; + sizes += gen.size(); + if( index < sizes ) + return gen[localIndex]; + } + CATCH_INTERNAL_ERROR("Index '" << index << "' is out of range (" << sizes << ')'); + } + }; + + template + auto makeGenerators( Generator&& generator, Gs... moreGenerators ) -> Generators { + Generators generators; + generators.m_generators.reserve( 1+sizeof...(Gs) ); + generators.populate( std::move( generator ), std::forward( moreGenerators )... ); + return generators; + } + template + auto makeGenerators( Generator&& generator ) -> Generators { + Generators generators; + generators.populate( std::move( generator ) ); + return generators; + } + template + auto makeGenerators( T&& val, Gs... moreGenerators ) -> Generators { + return makeGenerators( value( std::forward( val ) ), std::forward( moreGenerators )... ); + } + template + auto makeGenerators( U&& val, Gs... moreGenerators ) -> Generators { + return makeGenerators( value( T( std::forward( val ) ) ), std::forward( moreGenerators )... ); + } + + auto acquireGeneratorTracker( SourceLineInfo const& lineInfo ) -> IGeneratorTracker&; + + template + // Note: The type after -> is weird, because VS2015 cannot parse + // the expression used in the typedef inside, when it is in + // return type. Yeah, ¯\_(ツ)_/¯ + auto generate( SourceLineInfo const& lineInfo, L const& generatorExpression ) -> decltype(std::declval()[0]) { + using UnderlyingType = typename decltype(generatorExpression())::type; + + IGeneratorTracker& tracker = acquireGeneratorTracker( lineInfo ); + if( !tracker.hasGenerator() ) + tracker.setGenerator( pf::make_unique>( generatorExpression() ) ); + + auto const& generator = static_cast const&>( *tracker.getGenerator() ); + return generator[tracker.getIndex()]; + } + +} // namespace Generators +} // namespace Catch + +#define GENERATE( ... ) \ + Catch::Generators::generate( CATCH_INTERNAL_LINEINFO, []{ using namespace Catch::Generators; return makeGenerators( __VA_ARGS__ ); } ) + +// end catch_generators.hpp + +// These files are included here so the single_include script doesn't put them +// in the conditionally compiled sections +// start catch_test_case_info.h + +#include +#include +#include + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wpadded" +#endif + +namespace Catch { + + struct ITestInvoker; + + struct TestCaseInfo { + enum SpecialProperties{ + None = 0, + IsHidden = 1 << 1, + ShouldFail = 1 << 2, + MayFail = 1 << 3, + Throws = 1 << 4, + NonPortable = 1 << 5, + Benchmark = 1 << 6 + }; + + TestCaseInfo( std::string const& _name, + std::string const& _className, + std::string const& _description, + std::vector const& _tags, + SourceLineInfo const& _lineInfo ); + + friend void setTags( TestCaseInfo& testCaseInfo, std::vector tags ); + + bool isHidden() const; + bool throws() const; + bool okToFail() const; + bool expectedToFail() const; + + std::string tagsAsString() const; + + std::string name; + std::string className; + std::string description; + std::vector tags; + std::vector lcaseTags; + SourceLineInfo lineInfo; + SpecialProperties properties; + }; + + class TestCase : public TestCaseInfo { + public: + + TestCase( ITestInvoker* testCase, TestCaseInfo&& info ); + + TestCase withName( std::string const& _newName ) const; + + void invoke() const; + + TestCaseInfo const& getTestCaseInfo() const; + + bool operator == ( TestCase const& other ) const; + bool operator < ( TestCase const& other ) const; + + private: + std::shared_ptr test; + }; + + TestCase makeTestCase( ITestInvoker* testCase, + std::string const& className, + NameAndTags const& nameAndTags, + SourceLineInfo const& lineInfo ); +} + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +// end catch_test_case_info.h +// start catch_interfaces_runner.h + +namespace Catch { + + struct IRunner { + virtual ~IRunner(); + virtual bool aborting() const = 0; + }; +} + +// end catch_interfaces_runner.h + +#ifdef __OBJC__ +// start catch_objc.hpp + +#import + +#include + +// NB. Any general catch headers included here must be included +// in catch.hpp first to make sure they are included by the single +// header for non obj-usage + +/////////////////////////////////////////////////////////////////////////////// +// This protocol is really only here for (self) documenting purposes, since +// all its methods are optional. +@protocol OcFixture + +@optional + +-(void) setUp; +-(void) tearDown; + +@end + +namespace Catch { + + class OcMethod : public ITestInvoker { + + public: + OcMethod( Class cls, SEL sel ) : m_cls( cls ), m_sel( sel ) {} + + virtual void invoke() const { + id obj = [[m_cls alloc] init]; + + performOptionalSelector( obj, @selector(setUp) ); + performOptionalSelector( obj, m_sel ); + performOptionalSelector( obj, @selector(tearDown) ); + + arcSafeRelease( obj ); + } + private: + virtual ~OcMethod() {} + + Class m_cls; + SEL m_sel; + }; + + namespace Detail{ + + inline std::string getAnnotation( Class cls, + std::string const& annotationName, + std::string const& testCaseName ) { + NSString* selStr = [[NSString alloc] initWithFormat:@"Catch_%s_%s", annotationName.c_str(), testCaseName.c_str()]; + SEL sel = NSSelectorFromString( selStr ); + arcSafeRelease( selStr ); + id value = performOptionalSelector( cls, sel ); + if( value ) + return [(NSString*)value UTF8String]; + return ""; + } + } + + inline std::size_t registerTestMethods() { + std::size_t noTestMethods = 0; + int noClasses = objc_getClassList( nullptr, 0 ); + + Class* classes = (CATCH_UNSAFE_UNRETAINED Class *)malloc( sizeof(Class) * noClasses); + objc_getClassList( classes, noClasses ); + + for( int c = 0; c < noClasses; c++ ) { + Class cls = classes[c]; + { + u_int count; + Method* methods = class_copyMethodList( cls, &count ); + for( u_int m = 0; m < count ; m++ ) { + SEL selector = method_getName(methods[m]); + std::string methodName = sel_getName(selector); + if( startsWith( methodName, "Catch_TestCase_" ) ) { + std::string testCaseName = methodName.substr( 15 ); + std::string name = Detail::getAnnotation( cls, "Name", testCaseName ); + std::string desc = Detail::getAnnotation( cls, "Description", testCaseName ); + const char* className = class_getName( cls ); + + getMutableRegistryHub().registerTest( makeTestCase( new OcMethod( cls, selector ), className, NameAndTags( name.c_str(), desc.c_str() ), SourceLineInfo("",0) ) ); + noTestMethods++; + } + } + free(methods); + } + } + return noTestMethods; + } + +#if !defined(CATCH_CONFIG_DISABLE_MATCHERS) + + namespace Matchers { + namespace Impl { + namespace NSStringMatchers { + + struct StringHolder : MatcherBase{ + StringHolder( NSString* substr ) : m_substr( [substr copy] ){} + StringHolder( StringHolder const& other ) : m_substr( [other.m_substr copy] ){} + StringHolder() { + arcSafeRelease( m_substr ); + } + + bool match( NSString* arg ) const override { + return false; + } + + NSString* CATCH_ARC_STRONG m_substr; + }; + + struct Equals : StringHolder { + Equals( NSString* substr ) : StringHolder( substr ){} + + bool match( NSString* str ) const override { + return (str != nil || m_substr == nil ) && + [str isEqualToString:m_substr]; + } + + std::string describe() const override { + return "equals string: " + Catch::Detail::stringify( m_substr ); + } + }; + + struct Contains : StringHolder { + Contains( NSString* substr ) : StringHolder( substr ){} + + bool match( NSString* str ) const { + return (str != nil || m_substr == nil ) && + [str rangeOfString:m_substr].location != NSNotFound; + } + + std::string describe() const override { + return "contains string: " + Catch::Detail::stringify( m_substr ); + } + }; + + struct StartsWith : StringHolder { + StartsWith( NSString* substr ) : StringHolder( substr ){} + + bool match( NSString* str ) const override { + return (str != nil || m_substr == nil ) && + [str rangeOfString:m_substr].location == 0; + } + + std::string describe() const override { + return "starts with: " + Catch::Detail::stringify( m_substr ); + } + }; + struct EndsWith : StringHolder { + EndsWith( NSString* substr ) : StringHolder( substr ){} + + bool match( NSString* str ) const override { + return (str != nil || m_substr == nil ) && + [str rangeOfString:m_substr].location == [str length] - [m_substr length]; + } + + std::string describe() const override { + return "ends with: " + Catch::Detail::stringify( m_substr ); + } + }; + + } // namespace NSStringMatchers + } // namespace Impl + + inline Impl::NSStringMatchers::Equals + Equals( NSString* substr ){ return Impl::NSStringMatchers::Equals( substr ); } + + inline Impl::NSStringMatchers::Contains + Contains( NSString* substr ){ return Impl::NSStringMatchers::Contains( substr ); } + + inline Impl::NSStringMatchers::StartsWith + StartsWith( NSString* substr ){ return Impl::NSStringMatchers::StartsWith( substr ); } + + inline Impl::NSStringMatchers::EndsWith + EndsWith( NSString* substr ){ return Impl::NSStringMatchers::EndsWith( substr ); } + + } // namespace Matchers + + using namespace Matchers; + +#endif // CATCH_CONFIG_DISABLE_MATCHERS + +} // namespace Catch + +/////////////////////////////////////////////////////////////////////////////// +#define OC_MAKE_UNIQUE_NAME( root, uniqueSuffix ) root##uniqueSuffix +#define OC_TEST_CASE2( name, desc, uniqueSuffix ) \ ++(NSString*) OC_MAKE_UNIQUE_NAME( Catch_Name_test_, uniqueSuffix ) \ +{ \ +return @ name; \ +} \ ++(NSString*) OC_MAKE_UNIQUE_NAME( Catch_Description_test_, uniqueSuffix ) \ +{ \ +return @ desc; \ +} \ +-(void) OC_MAKE_UNIQUE_NAME( Catch_TestCase_test_, uniqueSuffix ) + +#define OC_TEST_CASE( name, desc ) OC_TEST_CASE2( name, desc, __LINE__ ) + +// end catch_objc.hpp +#endif + +#ifdef CATCH_CONFIG_EXTERNAL_INTERFACES +// start catch_external_interfaces.h + +// start catch_reporter_bases.hpp + +// start catch_interfaces_reporter.h + +// start catch_config.hpp + +// start catch_test_spec_parser.h + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wpadded" +#endif + +// start catch_test_spec.h + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wpadded" +#endif + +// start catch_wildcard_pattern.h + +namespace Catch +{ + class WildcardPattern { + enum WildcardPosition { + NoWildcard = 0, + WildcardAtStart = 1, + WildcardAtEnd = 2, + WildcardAtBothEnds = WildcardAtStart | WildcardAtEnd + }; + + public: + + WildcardPattern( std::string const& pattern, CaseSensitive::Choice caseSensitivity ); + virtual ~WildcardPattern() = default; + virtual bool matches( std::string const& str ) const; + + private: + std::string adjustCase( std::string const& str ) const; + CaseSensitive::Choice m_caseSensitivity; + WildcardPosition m_wildcard = NoWildcard; + std::string m_pattern; + }; +} + +// end catch_wildcard_pattern.h +#include +#include +#include + +namespace Catch { + + class TestSpec { + struct Pattern { + virtual ~Pattern(); + virtual bool matches( TestCaseInfo const& testCase ) const = 0; + }; + using PatternPtr = std::shared_ptr; + + class NamePattern : public Pattern { + public: + NamePattern( std::string const& name ); + virtual ~NamePattern(); + virtual bool matches( TestCaseInfo const& testCase ) const override; + private: + WildcardPattern m_wildcardPattern; + }; + + class TagPattern : public Pattern { + public: + TagPattern( std::string const& tag ); + virtual ~TagPattern(); + virtual bool matches( TestCaseInfo const& testCase ) const override; + private: + std::string m_tag; + }; + + class ExcludedPattern : public Pattern { + public: + ExcludedPattern( PatternPtr const& underlyingPattern ); + virtual ~ExcludedPattern(); + virtual bool matches( TestCaseInfo const& testCase ) const override; + private: + PatternPtr m_underlyingPattern; + }; + + struct Filter { + std::vector m_patterns; + + bool matches( TestCaseInfo const& testCase ) const; + }; + + public: + bool hasFilters() const; + bool matches( TestCaseInfo const& testCase ) const; + + private: + std::vector m_filters; + + friend class TestSpecParser; + }; +} + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +// end catch_test_spec.h +// start catch_interfaces_tag_alias_registry.h + +#include + +namespace Catch { + + struct TagAlias; + + struct ITagAliasRegistry { + virtual ~ITagAliasRegistry(); + // Nullptr if not present + virtual TagAlias const* find( std::string const& alias ) const = 0; + virtual std::string expandAliases( std::string const& unexpandedTestSpec ) const = 0; + + static ITagAliasRegistry const& get(); + }; + +} // end namespace Catch + +// end catch_interfaces_tag_alias_registry.h +namespace Catch { + + class TestSpecParser { + enum Mode{ None, Name, QuotedName, Tag, EscapedName }; + Mode m_mode = None; + bool m_exclusion = false; + std::size_t m_start = std::string::npos, m_pos = 0; + std::string m_arg; + std::vector m_escapeChars; + TestSpec::Filter m_currentFilter; + TestSpec m_testSpec; + ITagAliasRegistry const* m_tagAliases = nullptr; + + public: + TestSpecParser( ITagAliasRegistry const& tagAliases ); + + TestSpecParser& parse( std::string const& arg ); + TestSpec testSpec(); + + private: + void visitChar( char c ); + void startNewMode( Mode mode, std::size_t start ); + void escape(); + std::string subString() const; + + template + void addPattern() { + std::string token = subString(); + for( std::size_t i = 0; i < m_escapeChars.size(); ++i ) + token = token.substr( 0, m_escapeChars[i]-m_start-i ) + token.substr( m_escapeChars[i]-m_start-i+1 ); + m_escapeChars.clear(); + if( startsWith( token, "exclude:" ) ) { + m_exclusion = true; + token = token.substr( 8 ); + } + if( !token.empty() ) { + TestSpec::PatternPtr pattern = std::make_shared( token ); + if( m_exclusion ) + pattern = std::make_shared( pattern ); + m_currentFilter.m_patterns.push_back( pattern ); + } + m_exclusion = false; + m_mode = None; + } + + void addFilter(); + }; + TestSpec parseTestSpec( std::string const& arg ); + +} // namespace Catch + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +// end catch_test_spec_parser.h +// start catch_interfaces_config.h + +#include +#include +#include +#include + +namespace Catch { + + enum class Verbosity { + Quiet = 0, + Normal, + High + }; + + struct WarnAbout { enum What { + Nothing = 0x00, + NoAssertions = 0x01, + NoTests = 0x02 + }; }; + + struct ShowDurations { enum OrNot { + DefaultForReporter, + Always, + Never + }; }; + struct RunTests { enum InWhatOrder { + InDeclarationOrder, + InLexicographicalOrder, + InRandomOrder + }; }; + struct UseColour { enum YesOrNo { + Auto, + Yes, + No + }; }; + struct WaitForKeypress { enum When { + Never, + BeforeStart = 1, + BeforeExit = 2, + BeforeStartAndExit = BeforeStart | BeforeExit + }; }; + + class TestSpec; + + struct IConfig : NonCopyable { + + virtual ~IConfig(); + + virtual bool allowThrows() const = 0; + virtual std::ostream& stream() const = 0; + virtual std::string name() const = 0; + virtual bool includeSuccessfulResults() const = 0; + virtual bool shouldDebugBreak() const = 0; + virtual bool warnAboutMissingAssertions() const = 0; + virtual bool warnAboutNoTests() const = 0; + virtual int abortAfter() const = 0; + virtual bool showInvisibles() const = 0; + virtual ShowDurations::OrNot showDurations() const = 0; + virtual TestSpec const& testSpec() const = 0; + virtual bool hasTestFilters() const = 0; + virtual RunTests::InWhatOrder runOrder() const = 0; + virtual unsigned int rngSeed() const = 0; + virtual int benchmarkResolutionMultiple() const = 0; + virtual UseColour::YesOrNo useColour() const = 0; + virtual std::vector const& getSectionsToRun() const = 0; + virtual Verbosity verbosity() const = 0; + }; + + using IConfigPtr = std::shared_ptr; +} + +// end catch_interfaces_config.h +// Libstdc++ doesn't like incomplete classes for unique_ptr + +#include +#include +#include + +#ifndef CATCH_CONFIG_CONSOLE_WIDTH +#define CATCH_CONFIG_CONSOLE_WIDTH 80 +#endif + +namespace Catch { + + struct IStream; + + struct ConfigData { + bool listTests = false; + bool listTags = false; + bool listReporters = false; + bool listTestNamesOnly = false; + + bool showSuccessfulTests = false; + bool shouldDebugBreak = false; + bool noThrow = false; + bool showHelp = false; + bool showInvisibles = false; + bool filenamesAsTags = false; + bool libIdentify = false; + + int abortAfter = -1; + unsigned int rngSeed = 0; + int benchmarkResolutionMultiple = 100; + + Verbosity verbosity = Verbosity::Normal; + WarnAbout::What warnings = WarnAbout::Nothing; + ShowDurations::OrNot showDurations = ShowDurations::DefaultForReporter; + RunTests::InWhatOrder runOrder = RunTests::InDeclarationOrder; + UseColour::YesOrNo useColour = UseColour::Auto; + WaitForKeypress::When waitForKeypress = WaitForKeypress::Never; + + std::string outputFilename; + std::string name; + std::string processName; +#ifndef CATCH_CONFIG_DEFAULT_REPORTER +#define CATCH_CONFIG_DEFAULT_REPORTER "console" +#endif + std::string reporterName = CATCH_CONFIG_DEFAULT_REPORTER; +#undef CATCH_CONFIG_DEFAULT_REPORTER + + std::vector testsOrTags; + std::vector sectionsToRun; + }; + + class Config : public IConfig { + public: + + Config() = default; + Config( ConfigData const& data ); + virtual ~Config() = default; + + std::string const& getFilename() const; + + bool listTests() const; + bool listTestNamesOnly() const; + bool listTags() const; + bool listReporters() const; + + std::string getProcessName() const; + std::string const& getReporterName() const; + + std::vector const& getTestsOrTags() const; + std::vector const& getSectionsToRun() const override; + + virtual TestSpec const& testSpec() const override; + bool hasTestFilters() const override; + + bool showHelp() const; + + // IConfig interface + bool allowThrows() const override; + std::ostream& stream() const override; + std::string name() const override; + bool includeSuccessfulResults() const override; + bool warnAboutMissingAssertions() const override; + bool warnAboutNoTests() const override; + ShowDurations::OrNot showDurations() const override; + RunTests::InWhatOrder runOrder() const override; + unsigned int rngSeed() const override; + int benchmarkResolutionMultiple() const override; + UseColour::YesOrNo useColour() const override; + bool shouldDebugBreak() const override; + int abortAfter() const override; + bool showInvisibles() const override; + Verbosity verbosity() const override; + + private: + + IStream const* openStream(); + ConfigData m_data; + + std::unique_ptr m_stream; + TestSpec m_testSpec; + bool m_hasTestFilters = false; + }; + +} // end namespace Catch + +// end catch_config.hpp +// start catch_assertionresult.h + +#include + +namespace Catch { + + struct AssertionResultData + { + AssertionResultData() = delete; + + AssertionResultData( ResultWas::OfType _resultType, LazyExpression const& _lazyExpression ); + + std::string message; + mutable std::string reconstructedExpression; + LazyExpression lazyExpression; + ResultWas::OfType resultType; + + std::string reconstructExpression() const; + }; + + class AssertionResult { + public: + AssertionResult() = delete; + AssertionResult( AssertionInfo const& info, AssertionResultData const& data ); + + bool isOk() const; + bool succeeded() const; + ResultWas::OfType getResultType() const; + bool hasExpression() const; + bool hasMessage() const; + std::string getExpression() const; + std::string getExpressionInMacro() const; + bool hasExpandedExpression() const; + std::string getExpandedExpression() const; + std::string getMessage() const; + SourceLineInfo getSourceInfo() const; + StringRef getTestMacroName() const; + + //protected: + AssertionInfo m_info; + AssertionResultData m_resultData; + }; + +} // end namespace Catch + +// end catch_assertionresult.h +// start catch_option.hpp + +namespace Catch { + + // An optional type + template + class Option { + public: + Option() : nullableValue( nullptr ) {} + Option( T const& _value ) + : nullableValue( new( storage ) T( _value ) ) + {} + Option( Option const& _other ) + : nullableValue( _other ? new( storage ) T( *_other ) : nullptr ) + {} + + ~Option() { + reset(); + } + + Option& operator= ( Option const& _other ) { + if( &_other != this ) { + reset(); + if( _other ) + nullableValue = new( storage ) T( *_other ); + } + return *this; + } + Option& operator = ( T const& _value ) { + reset(); + nullableValue = new( storage ) T( _value ); + return *this; + } + + void reset() { + if( nullableValue ) + nullableValue->~T(); + nullableValue = nullptr; + } + + T& operator*() { return *nullableValue; } + T const& operator*() const { return *nullableValue; } + T* operator->() { return nullableValue; } + const T* operator->() const { return nullableValue; } + + T valueOr( T const& defaultValue ) const { + return nullableValue ? *nullableValue : defaultValue; + } + + bool some() const { return nullableValue != nullptr; } + bool none() const { return nullableValue == nullptr; } + + bool operator !() const { return nullableValue == nullptr; } + explicit operator bool() const { + return some(); + } + + private: + T *nullableValue; + alignas(alignof(T)) char storage[sizeof(T)]; + }; + +} // end namespace Catch + +// end catch_option.hpp +#include +#include +#include +#include +#include + +namespace Catch { + + struct ReporterConfig { + explicit ReporterConfig( IConfigPtr const& _fullConfig ); + + ReporterConfig( IConfigPtr const& _fullConfig, std::ostream& _stream ); + + std::ostream& stream() const; + IConfigPtr fullConfig() const; + + private: + std::ostream* m_stream; + IConfigPtr m_fullConfig; + }; + + struct ReporterPreferences { + bool shouldRedirectStdOut = false; + bool shouldReportAllAssertions = false; + }; + + template + struct LazyStat : Option { + LazyStat& operator=( T const& _value ) { + Option::operator=( _value ); + used = false; + return *this; + } + void reset() { + Option::reset(); + used = false; + } + bool used = false; + }; + + struct TestRunInfo { + TestRunInfo( std::string const& _name ); + std::string name; + }; + struct GroupInfo { + GroupInfo( std::string const& _name, + std::size_t _groupIndex, + std::size_t _groupsCount ); + + std::string name; + std::size_t groupIndex; + std::size_t groupsCounts; + }; + + struct AssertionStats { + AssertionStats( AssertionResult const& _assertionResult, + std::vector const& _infoMessages, + Totals const& _totals ); + + AssertionStats( AssertionStats const& ) = default; + AssertionStats( AssertionStats && ) = default; + AssertionStats& operator = ( AssertionStats const& ) = default; + AssertionStats& operator = ( AssertionStats && ) = default; + virtual ~AssertionStats(); + + AssertionResult assertionResult; + std::vector infoMessages; + Totals totals; + }; + + struct SectionStats { + SectionStats( SectionInfo const& _sectionInfo, + Counts const& _assertions, + double _durationInSeconds, + bool _missingAssertions ); + SectionStats( SectionStats const& ) = default; + SectionStats( SectionStats && ) = default; + SectionStats& operator = ( SectionStats const& ) = default; + SectionStats& operator = ( SectionStats && ) = default; + virtual ~SectionStats(); + + SectionInfo sectionInfo; + Counts assertions; + double durationInSeconds; + bool missingAssertions; + }; + + struct TestCaseStats { + TestCaseStats( TestCaseInfo const& _testInfo, + Totals const& _totals, + std::string const& _stdOut, + std::string const& _stdErr, + bool _aborting ); + + TestCaseStats( TestCaseStats const& ) = default; + TestCaseStats( TestCaseStats && ) = default; + TestCaseStats& operator = ( TestCaseStats const& ) = default; + TestCaseStats& operator = ( TestCaseStats && ) = default; + virtual ~TestCaseStats(); + + TestCaseInfo testInfo; + Totals totals; + std::string stdOut; + std::string stdErr; + bool aborting; + }; + + struct TestGroupStats { + TestGroupStats( GroupInfo const& _groupInfo, + Totals const& _totals, + bool _aborting ); + TestGroupStats( GroupInfo const& _groupInfo ); + + TestGroupStats( TestGroupStats const& ) = default; + TestGroupStats( TestGroupStats && ) = default; + TestGroupStats& operator = ( TestGroupStats const& ) = default; + TestGroupStats& operator = ( TestGroupStats && ) = default; + virtual ~TestGroupStats(); + + GroupInfo groupInfo; + Totals totals; + bool aborting; + }; + + struct TestRunStats { + TestRunStats( TestRunInfo const& _runInfo, + Totals const& _totals, + bool _aborting ); + + TestRunStats( TestRunStats const& ) = default; + TestRunStats( TestRunStats && ) = default; + TestRunStats& operator = ( TestRunStats const& ) = default; + TestRunStats& operator = ( TestRunStats && ) = default; + virtual ~TestRunStats(); + + TestRunInfo runInfo; + Totals totals; + bool aborting; + }; + + struct BenchmarkInfo { + std::string name; + }; + struct BenchmarkStats { + BenchmarkInfo info; + std::size_t iterations; + uint64_t elapsedTimeInNanoseconds; + }; + + struct IStreamingReporter { + virtual ~IStreamingReporter() = default; + + // Implementing class must also provide the following static methods: + // static std::string getDescription(); + // static std::set getSupportedVerbosities() + + virtual ReporterPreferences getPreferences() const = 0; + + virtual void noMatchingTestCases( std::string const& spec ) = 0; + + virtual void testRunStarting( TestRunInfo const& testRunInfo ) = 0; + virtual void testGroupStarting( GroupInfo const& groupInfo ) = 0; + + virtual void testCaseStarting( TestCaseInfo const& testInfo ) = 0; + virtual void sectionStarting( SectionInfo const& sectionInfo ) = 0; + + // *** experimental *** + virtual void benchmarkStarting( BenchmarkInfo const& ) {} + + virtual void assertionStarting( AssertionInfo const& assertionInfo ) = 0; + + // The return value indicates if the messages buffer should be cleared: + virtual bool assertionEnded( AssertionStats const& assertionStats ) = 0; + + // *** experimental *** + virtual void benchmarkEnded( BenchmarkStats const& ) {} + + virtual void sectionEnded( SectionStats const& sectionStats ) = 0; + virtual void testCaseEnded( TestCaseStats const& testCaseStats ) = 0; + virtual void testGroupEnded( TestGroupStats const& testGroupStats ) = 0; + virtual void testRunEnded( TestRunStats const& testRunStats ) = 0; + + virtual void skipTest( TestCaseInfo const& testInfo ) = 0; + + // Default empty implementation provided + virtual void fatalErrorEncountered( StringRef name ); + + virtual bool isMulti() const; + }; + using IStreamingReporterPtr = std::unique_ptr; + + struct IReporterFactory { + virtual ~IReporterFactory(); + virtual IStreamingReporterPtr create( ReporterConfig const& config ) const = 0; + virtual std::string getDescription() const = 0; + }; + using IReporterFactoryPtr = std::shared_ptr; + + struct IReporterRegistry { + using FactoryMap = std::map; + using Listeners = std::vector; + + virtual ~IReporterRegistry(); + virtual IStreamingReporterPtr create( std::string const& name, IConfigPtr const& config ) const = 0; + virtual FactoryMap const& getFactories() const = 0; + virtual Listeners const& getListeners() const = 0; + }; + +} // end namespace Catch + +// end catch_interfaces_reporter.h +#include +#include +#include +#include +#include +#include +#include + +namespace Catch { + void prepareExpandedExpression(AssertionResult& result); + + // Returns double formatted as %.3f (format expected on output) + std::string getFormattedDuration( double duration ); + + template + struct StreamingReporterBase : IStreamingReporter { + + StreamingReporterBase( ReporterConfig const& _config ) + : m_config( _config.fullConfig() ), + stream( _config.stream() ) + { + m_reporterPrefs.shouldRedirectStdOut = false; + if( !DerivedT::getSupportedVerbosities().count( m_config->verbosity() ) ) + CATCH_ERROR( "Verbosity level not supported by this reporter" ); + } + + ReporterPreferences getPreferences() const override { + return m_reporterPrefs; + } + + static std::set getSupportedVerbosities() { + return { Verbosity::Normal }; + } + + ~StreamingReporterBase() override = default; + + void noMatchingTestCases(std::string const&) override {} + + void testRunStarting(TestRunInfo const& _testRunInfo) override { + currentTestRunInfo = _testRunInfo; + } + void testGroupStarting(GroupInfo const& _groupInfo) override { + currentGroupInfo = _groupInfo; + } + + void testCaseStarting(TestCaseInfo const& _testInfo) override { + currentTestCaseInfo = _testInfo; + } + void sectionStarting(SectionInfo const& _sectionInfo) override { + m_sectionStack.push_back(_sectionInfo); + } + + void sectionEnded(SectionStats const& /* _sectionStats */) override { + m_sectionStack.pop_back(); + } + void testCaseEnded(TestCaseStats const& /* _testCaseStats */) override { + currentTestCaseInfo.reset(); + } + void testGroupEnded(TestGroupStats const& /* _testGroupStats */) override { + currentGroupInfo.reset(); + } + void testRunEnded(TestRunStats const& /* _testRunStats */) override { + currentTestCaseInfo.reset(); + currentGroupInfo.reset(); + currentTestRunInfo.reset(); + } + + void skipTest(TestCaseInfo const&) override { + // Don't do anything with this by default. + // It can optionally be overridden in the derived class. + } + + IConfigPtr m_config; + std::ostream& stream; + + LazyStat currentTestRunInfo; + LazyStat currentGroupInfo; + LazyStat currentTestCaseInfo; + + std::vector m_sectionStack; + ReporterPreferences m_reporterPrefs; + }; + + template + struct CumulativeReporterBase : IStreamingReporter { + template + struct Node { + explicit Node( T const& _value ) : value( _value ) {} + virtual ~Node() {} + + using ChildNodes = std::vector>; + T value; + ChildNodes children; + }; + struct SectionNode { + explicit SectionNode(SectionStats const& _stats) : stats(_stats) {} + virtual ~SectionNode() = default; + + bool operator == (SectionNode const& other) const { + return stats.sectionInfo.lineInfo == other.stats.sectionInfo.lineInfo; + } + bool operator == (std::shared_ptr const& other) const { + return operator==(*other); + } + + SectionStats stats; + using ChildSections = std::vector>; + using Assertions = std::vector; + ChildSections childSections; + Assertions assertions; + std::string stdOut; + std::string stdErr; + }; + + struct BySectionInfo { + BySectionInfo( SectionInfo const& other ) : m_other( other ) {} + BySectionInfo( BySectionInfo const& other ) : m_other( other.m_other ) {} + bool operator() (std::shared_ptr const& node) const { + return ((node->stats.sectionInfo.name == m_other.name) && + (node->stats.sectionInfo.lineInfo == m_other.lineInfo)); + } + void operator=(BySectionInfo const&) = delete; + + private: + SectionInfo const& m_other; + }; + + using TestCaseNode = Node; + using TestGroupNode = Node; + using TestRunNode = Node; + + CumulativeReporterBase( ReporterConfig const& _config ) + : m_config( _config.fullConfig() ), + stream( _config.stream() ) + { + m_reporterPrefs.shouldRedirectStdOut = false; + if( !DerivedT::getSupportedVerbosities().count( m_config->verbosity() ) ) + CATCH_ERROR( "Verbosity level not supported by this reporter" ); + } + ~CumulativeReporterBase() override = default; + + ReporterPreferences getPreferences() const override { + return m_reporterPrefs; + } + + static std::set getSupportedVerbosities() { + return { Verbosity::Normal }; + } + + void testRunStarting( TestRunInfo const& ) override {} + void testGroupStarting( GroupInfo const& ) override {} + + void testCaseStarting( TestCaseInfo const& ) override {} + + void sectionStarting( SectionInfo const& sectionInfo ) override { + SectionStats incompleteStats( sectionInfo, Counts(), 0, false ); + std::shared_ptr node; + if( m_sectionStack.empty() ) { + if( !m_rootSection ) + m_rootSection = std::make_shared( incompleteStats ); + node = m_rootSection; + } + else { + SectionNode& parentNode = *m_sectionStack.back(); + auto it = + std::find_if( parentNode.childSections.begin(), + parentNode.childSections.end(), + BySectionInfo( sectionInfo ) ); + if( it == parentNode.childSections.end() ) { + node = std::make_shared( incompleteStats ); + parentNode.childSections.push_back( node ); + } + else + node = *it; + } + m_sectionStack.push_back( node ); + m_deepestSection = std::move(node); + } + + void assertionStarting(AssertionInfo const&) override {} + + bool assertionEnded(AssertionStats const& assertionStats) override { + assert(!m_sectionStack.empty()); + // AssertionResult holds a pointer to a temporary DecomposedExpression, + // which getExpandedExpression() calls to build the expression string. + // Our section stack copy of the assertionResult will likely outlive the + // temporary, so it must be expanded or discarded now to avoid calling + // a destroyed object later. + prepareExpandedExpression(const_cast( assertionStats.assertionResult ) ); + SectionNode& sectionNode = *m_sectionStack.back(); + sectionNode.assertions.push_back(assertionStats); + return true; + } + void sectionEnded(SectionStats const& sectionStats) override { + assert(!m_sectionStack.empty()); + SectionNode& node = *m_sectionStack.back(); + node.stats = sectionStats; + m_sectionStack.pop_back(); + } + void testCaseEnded(TestCaseStats const& testCaseStats) override { + auto node = std::make_shared(testCaseStats); + assert(m_sectionStack.size() == 0); + node->children.push_back(m_rootSection); + m_testCases.push_back(node); + m_rootSection.reset(); + + assert(m_deepestSection); + m_deepestSection->stdOut = testCaseStats.stdOut; + m_deepestSection->stdErr = testCaseStats.stdErr; + } + void testGroupEnded(TestGroupStats const& testGroupStats) override { + auto node = std::make_shared(testGroupStats); + node->children.swap(m_testCases); + m_testGroups.push_back(node); + } + void testRunEnded(TestRunStats const& testRunStats) override { + auto node = std::make_shared(testRunStats); + node->children.swap(m_testGroups); + m_testRuns.push_back(node); + testRunEndedCumulative(); + } + virtual void testRunEndedCumulative() = 0; + + void skipTest(TestCaseInfo const&) override {} + + IConfigPtr m_config; + std::ostream& stream; + std::vector m_assertions; + std::vector>> m_sections; + std::vector> m_testCases; + std::vector> m_testGroups; + + std::vector> m_testRuns; + + std::shared_ptr m_rootSection; + std::shared_ptr m_deepestSection; + std::vector> m_sectionStack; + ReporterPreferences m_reporterPrefs; + }; + + template + char const* getLineOfChars() { + static char line[CATCH_CONFIG_CONSOLE_WIDTH] = {0}; + if( !*line ) { + std::memset( line, C, CATCH_CONFIG_CONSOLE_WIDTH-1 ); + line[CATCH_CONFIG_CONSOLE_WIDTH-1] = 0; + } + return line; + } + + struct TestEventListenerBase : StreamingReporterBase { + TestEventListenerBase( ReporterConfig const& _config ); + + void assertionStarting(AssertionInfo const&) override; + bool assertionEnded(AssertionStats const&) override; + }; + +} // end namespace Catch + +// end catch_reporter_bases.hpp +// start catch_console_colour.h + +namespace Catch { + + struct Colour { + enum Code { + None = 0, + + White, + Red, + Green, + Blue, + Cyan, + Yellow, + Grey, + + Bright = 0x10, + + BrightRed = Bright | Red, + BrightGreen = Bright | Green, + LightGrey = Bright | Grey, + BrightWhite = Bright | White, + BrightYellow = Bright | Yellow, + + // By intention + FileName = LightGrey, + Warning = BrightYellow, + ResultError = BrightRed, + ResultSuccess = BrightGreen, + ResultExpectedFailure = Warning, + + Error = BrightRed, + Success = Green, + + OriginalExpression = Cyan, + ReconstructedExpression = BrightYellow, + + SecondaryText = LightGrey, + Headers = White + }; + + // Use constructed object for RAII guard + Colour( Code _colourCode ); + Colour( Colour&& other ) noexcept; + Colour& operator=( Colour&& other ) noexcept; + ~Colour(); + + // Use static method for one-shot changes + static void use( Code _colourCode ); + + private: + bool m_moved = false; + }; + + std::ostream& operator << ( std::ostream& os, Colour const& ); + +} // end namespace Catch + +// end catch_console_colour.h +// start catch_reporter_registrars.hpp + + +namespace Catch { + + template + class ReporterRegistrar { + + class ReporterFactory : public IReporterFactory { + + virtual IStreamingReporterPtr create( ReporterConfig const& config ) const override { + return std::unique_ptr( new T( config ) ); + } + + virtual std::string getDescription() const override { + return T::getDescription(); + } + }; + + public: + + explicit ReporterRegistrar( std::string const& name ) { + getMutableRegistryHub().registerReporter( name, std::make_shared() ); + } + }; + + template + class ListenerRegistrar { + + class ListenerFactory : public IReporterFactory { + + virtual IStreamingReporterPtr create( ReporterConfig const& config ) const override { + return std::unique_ptr( new T( config ) ); + } + virtual std::string getDescription() const override { + return std::string(); + } + }; + + public: + + ListenerRegistrar() { + getMutableRegistryHub().registerListener( std::make_shared() ); + } + }; +} + +#if !defined(CATCH_CONFIG_DISABLE) + +#define CATCH_REGISTER_REPORTER( name, reporterType ) \ + CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ + namespace{ Catch::ReporterRegistrar catch_internal_RegistrarFor##reporterType( name ); } \ + CATCH_INTERNAL_UNSUPPRESS_GLOBALS_WARNINGS + +#define CATCH_REGISTER_LISTENER( listenerType ) \ + CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ + namespace{ Catch::ListenerRegistrar catch_internal_RegistrarFor##listenerType; } \ + CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS +#else // CATCH_CONFIG_DISABLE + +#define CATCH_REGISTER_REPORTER(name, reporterType) +#define CATCH_REGISTER_LISTENER(listenerType) + +#endif // CATCH_CONFIG_DISABLE + +// end catch_reporter_registrars.hpp +// Allow users to base their work off existing reporters +// start catch_reporter_compact.h + +namespace Catch { + + struct CompactReporter : StreamingReporterBase { + + using StreamingReporterBase::StreamingReporterBase; + + ~CompactReporter() override; + + static std::string getDescription(); + + ReporterPreferences getPreferences() const override; + + void noMatchingTestCases(std::string const& spec) override; + + void assertionStarting(AssertionInfo const&) override; + + bool assertionEnded(AssertionStats const& _assertionStats) override; + + void sectionEnded(SectionStats const& _sectionStats) override; + + void testRunEnded(TestRunStats const& _testRunStats) override; + + }; + +} // end namespace Catch + +// end catch_reporter_compact.h +// start catch_reporter_console.h + +#if defined(_MSC_VER) +#pragma warning(push) +#pragma warning(disable:4061) // Not all labels are EXPLICITLY handled in switch + // Note that 4062 (not all labels are handled + // and default is missing) is enabled +#endif + +namespace Catch { + // Fwd decls + struct SummaryColumn; + class TablePrinter; + + struct ConsoleReporter : StreamingReporterBase { + std::unique_ptr m_tablePrinter; + + ConsoleReporter(ReporterConfig const& config); + ~ConsoleReporter() override; + static std::string getDescription(); + + void noMatchingTestCases(std::string const& spec) override; + + void assertionStarting(AssertionInfo const&) override; + + bool assertionEnded(AssertionStats const& _assertionStats) override; + + void sectionStarting(SectionInfo const& _sectionInfo) override; + void sectionEnded(SectionStats const& _sectionStats) override; + + void benchmarkStarting(BenchmarkInfo const& info) override; + void benchmarkEnded(BenchmarkStats const& stats) override; + + void testCaseEnded(TestCaseStats const& _testCaseStats) override; + void testGroupEnded(TestGroupStats const& _testGroupStats) override; + void testRunEnded(TestRunStats const& _testRunStats) override; + + private: + + void lazyPrint(); + + void lazyPrintWithoutClosingBenchmarkTable(); + void lazyPrintRunInfo(); + void lazyPrintGroupInfo(); + void printTestCaseAndSectionHeader(); + + void printClosedHeader(std::string const& _name); + void printOpenHeader(std::string const& _name); + + // if string has a : in first line will set indent to follow it on + // subsequent lines + void printHeaderString(std::string const& _string, std::size_t indent = 0); + + void printTotals(Totals const& totals); + void printSummaryRow(std::string const& label, std::vector const& cols, std::size_t row); + + void printTotalsDivider(Totals const& totals); + void printSummaryDivider(); + + private: + bool m_headerPrinted = false; + }; + +} // end namespace Catch + +#if defined(_MSC_VER) +#pragma warning(pop) +#endif + +// end catch_reporter_console.h +// start catch_reporter_junit.h + +// start catch_xmlwriter.h + +#include + +namespace Catch { + + class XmlEncode { + public: + enum ForWhat { ForTextNodes, ForAttributes }; + + XmlEncode( std::string const& str, ForWhat forWhat = ForTextNodes ); + + void encodeTo( std::ostream& os ) const; + + friend std::ostream& operator << ( std::ostream& os, XmlEncode const& xmlEncode ); + + private: + std::string m_str; + ForWhat m_forWhat; + }; + + class XmlWriter { + public: + + class ScopedElement { + public: + ScopedElement( XmlWriter* writer ); + + ScopedElement( ScopedElement&& other ) noexcept; + ScopedElement& operator=( ScopedElement&& other ) noexcept; + + ~ScopedElement(); + + ScopedElement& writeText( std::string const& text, bool indent = true ); + + template + ScopedElement& writeAttribute( std::string const& name, T const& attribute ) { + m_writer->writeAttribute( name, attribute ); + return *this; + } + + private: + mutable XmlWriter* m_writer = nullptr; + }; + + XmlWriter( std::ostream& os = Catch::cout() ); + ~XmlWriter(); + + XmlWriter( XmlWriter const& ) = delete; + XmlWriter& operator=( XmlWriter const& ) = delete; + + XmlWriter& startElement( std::string const& name ); + + ScopedElement scopedElement( std::string const& name ); + + XmlWriter& endElement(); + + XmlWriter& writeAttribute( std::string const& name, std::string const& attribute ); + + XmlWriter& writeAttribute( std::string const& name, bool attribute ); + + template + XmlWriter& writeAttribute( std::string const& name, T const& attribute ) { + ReusableStringStream rss; + rss << attribute; + return writeAttribute( name, rss.str() ); + } + + XmlWriter& writeText( std::string const& text, bool indent = true ); + + XmlWriter& writeComment( std::string const& text ); + + void writeStylesheetRef( std::string const& url ); + + XmlWriter& writeBlankLine(); + + void ensureTagClosed(); + + private: + + void writeDeclaration(); + + void newlineIfNecessary(); + + bool m_tagIsOpen = false; + bool m_needsNewline = false; + std::vector m_tags; + std::string m_indent; + std::ostream& m_os; + }; + +} + +// end catch_xmlwriter.h +namespace Catch { + + class JunitReporter : public CumulativeReporterBase { + public: + JunitReporter(ReporterConfig const& _config); + + ~JunitReporter() override; + + static std::string getDescription(); + + void noMatchingTestCases(std::string const& /*spec*/) override; + + void testRunStarting(TestRunInfo const& runInfo) override; + + void testGroupStarting(GroupInfo const& groupInfo) override; + + void testCaseStarting(TestCaseInfo const& testCaseInfo) override; + bool assertionEnded(AssertionStats const& assertionStats) override; + + void testCaseEnded(TestCaseStats const& testCaseStats) override; + + void testGroupEnded(TestGroupStats const& testGroupStats) override; + + void testRunEndedCumulative() override; + + void writeGroup(TestGroupNode const& groupNode, double suiteTime); + + void writeTestCase(TestCaseNode const& testCaseNode); + + void writeSection(std::string const& className, + std::string const& rootName, + SectionNode const& sectionNode); + + void writeAssertions(SectionNode const& sectionNode); + void writeAssertion(AssertionStats const& stats); + + XmlWriter xml; + Timer suiteTimer; + std::string stdOutForSuite; + std::string stdErrForSuite; + unsigned int unexpectedExceptions = 0; + bool m_okToFail = false; + }; + +} // end namespace Catch + +// end catch_reporter_junit.h +// start catch_reporter_xml.h + +namespace Catch { + class XmlReporter : public StreamingReporterBase { + public: + XmlReporter(ReporterConfig const& _config); + + ~XmlReporter() override; + + static std::string getDescription(); + + virtual std::string getStylesheetRef() const; + + void writeSourceInfo(SourceLineInfo const& sourceInfo); + + public: // StreamingReporterBase + + void noMatchingTestCases(std::string const& s) override; + + void testRunStarting(TestRunInfo const& testInfo) override; + + void testGroupStarting(GroupInfo const& groupInfo) override; + + void testCaseStarting(TestCaseInfo const& testInfo) override; + + void sectionStarting(SectionInfo const& sectionInfo) override; + + void assertionStarting(AssertionInfo const&) override; + + bool assertionEnded(AssertionStats const& assertionStats) override; + + void sectionEnded(SectionStats const& sectionStats) override; + + void testCaseEnded(TestCaseStats const& testCaseStats) override; + + void testGroupEnded(TestGroupStats const& testGroupStats) override; + + void testRunEnded(TestRunStats const& testRunStats) override; + + private: + Timer m_testCaseTimer; + XmlWriter m_xml; + int m_sectionDepth = 0; + }; + +} // end namespace Catch + +// end catch_reporter_xml.h + +// end catch_external_interfaces.h +#endif + +#endif // ! CATCH_CONFIG_IMPL_ONLY + +#ifdef CATCH_IMPL +// start catch_impl.hpp + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wweak-vtables" +#endif + +// Keep these here for external reporters +// start catch_test_case_tracker.h + +#include +#include +#include + +namespace Catch { +namespace TestCaseTracking { + + struct NameAndLocation { + std::string name; + SourceLineInfo location; + + NameAndLocation( std::string const& _name, SourceLineInfo const& _location ); + }; + + struct ITracker; + + using ITrackerPtr = std::shared_ptr; + + struct ITracker { + virtual ~ITracker(); + + // static queries + virtual NameAndLocation const& nameAndLocation() const = 0; + + // dynamic queries + virtual bool isComplete() const = 0; // Successfully completed or failed + virtual bool isSuccessfullyCompleted() const = 0; + virtual bool isOpen() const = 0; // Started but not complete + virtual bool hasChildren() const = 0; + + virtual ITracker& parent() = 0; + + // actions + virtual void close() = 0; // Successfully complete + virtual void fail() = 0; + virtual void markAsNeedingAnotherRun() = 0; + + virtual void addChild( ITrackerPtr const& child ) = 0; + virtual ITrackerPtr findChild( NameAndLocation const& nameAndLocation ) = 0; + virtual void openChild() = 0; + + // Debug/ checking + virtual bool isSectionTracker() const = 0; + virtual bool isIndexTracker() const = 0; + }; + + class TrackerContext { + + enum RunState { + NotStarted, + Executing, + CompletedCycle + }; + + ITrackerPtr m_rootTracker; + ITracker* m_currentTracker = nullptr; + RunState m_runState = NotStarted; + + public: + + static TrackerContext& instance(); + + ITracker& startRun(); + void endRun(); + + void startCycle(); + void completeCycle(); + + bool completedCycle() const; + ITracker& currentTracker(); + void setCurrentTracker( ITracker* tracker ); + }; + + class TrackerBase : public ITracker { + protected: + enum CycleState { + NotStarted, + Executing, + ExecutingChildren, + NeedsAnotherRun, + CompletedSuccessfully, + Failed + }; + + using Children = std::vector; + NameAndLocation m_nameAndLocation; + TrackerContext& m_ctx; + ITracker* m_parent; + Children m_children; + CycleState m_runState = NotStarted; + + public: + TrackerBase( NameAndLocation const& nameAndLocation, TrackerContext& ctx, ITracker* parent ); + + NameAndLocation const& nameAndLocation() const override; + bool isComplete() const override; + bool isSuccessfullyCompleted() const override; + bool isOpen() const override; + bool hasChildren() const override; + + void addChild( ITrackerPtr const& child ) override; + + ITrackerPtr findChild( NameAndLocation const& nameAndLocation ) override; + ITracker& parent() override; + + void openChild() override; + + bool isSectionTracker() const override; + bool isIndexTracker() const override; + + void open(); + + void close() override; + void fail() override; + void markAsNeedingAnotherRun() override; + + private: + void moveToParent(); + void moveToThis(); + }; + + class SectionTracker : public TrackerBase { + std::vector m_filters; + public: + SectionTracker( NameAndLocation const& nameAndLocation, TrackerContext& ctx, ITracker* parent ); + + bool isSectionTracker() const override; + + static SectionTracker& acquire( TrackerContext& ctx, NameAndLocation const& nameAndLocation ); + + void tryOpen(); + + void addInitialFilters( std::vector const& filters ); + void addNextFilters( std::vector const& filters ); + }; + + class IndexTracker : public TrackerBase { + int m_size; + int m_index = -1; + public: + IndexTracker( NameAndLocation const& nameAndLocation, TrackerContext& ctx, ITracker* parent, int size ); + + bool isIndexTracker() const override; + void close() override; + + static IndexTracker& acquire( TrackerContext& ctx, NameAndLocation const& nameAndLocation, int size ); + + int index() const; + + void moveNext(); + }; + +} // namespace TestCaseTracking + +using TestCaseTracking::ITracker; +using TestCaseTracking::TrackerContext; +using TestCaseTracking::SectionTracker; +using TestCaseTracking::IndexTracker; + +} // namespace Catch + +// end catch_test_case_tracker.h + +// start catch_leak_detector.h + +namespace Catch { + + struct LeakDetector { + LeakDetector(); + }; + +} +// end catch_leak_detector.h +// Cpp files will be included in the single-header file here +// start catch_approx.cpp + +#include +#include + +namespace { + +// Performs equivalent check of std::fabs(lhs - rhs) <= margin +// But without the subtraction to allow for INFINITY in comparison +bool marginComparison(double lhs, double rhs, double margin) { + return (lhs + margin >= rhs) && (rhs + margin >= lhs); +} + +} + +namespace Catch { +namespace Detail { + + Approx::Approx ( double value ) + : m_epsilon( std::numeric_limits::epsilon()*100 ), + m_margin( 0.0 ), + m_scale( 0.0 ), + m_value( value ) + {} + + Approx Approx::custom() { + return Approx( 0 ); + } + + Approx Approx::operator-() const { + auto temp(*this); + temp.m_value = -temp.m_value; + return temp; + } + + std::string Approx::toString() const { + ReusableStringStream rss; + rss << "Approx( " << ::Catch::Detail::stringify( m_value ) << " )"; + return rss.str(); + } + + bool Approx::equalityComparisonImpl(const double other) const { + // First try with fixed margin, then compute margin based on epsilon, scale and Approx's value + // Thanks to Richard Harris for his help refining the scaled margin value + return marginComparison(m_value, other, m_margin) || marginComparison(m_value, other, m_epsilon * (m_scale + std::fabs(m_value))); + } + + void Approx::setMargin(double margin) { + CATCH_ENFORCE(margin >= 0, + "Invalid Approx::margin: " << margin << '.' + << " Approx::Margin has to be non-negative."); + m_margin = margin; + } + + void Approx::setEpsilon(double epsilon) { + CATCH_ENFORCE(epsilon >= 0 && epsilon <= 1.0, + "Invalid Approx::epsilon: " << epsilon << '.' + << " Approx::epsilon has to be in [0, 1]"); + m_epsilon = epsilon; + } + +} // end namespace Detail + +namespace literals { + Detail::Approx operator "" _a(long double val) { + return Detail::Approx(val); + } + Detail::Approx operator "" _a(unsigned long long val) { + return Detail::Approx(val); + } +} // end namespace literals + +std::string StringMaker::convert(Catch::Detail::Approx const& value) { + return value.toString(); +} + +} // end namespace Catch +// end catch_approx.cpp +// start catch_assertionhandler.cpp + +// start catch_context.h + +#include + +namespace Catch { + + struct IResultCapture; + struct IRunner; + struct IConfig; + struct IMutableContext; + + using IConfigPtr = std::shared_ptr; + + struct IContext + { + virtual ~IContext(); + + virtual IResultCapture* getResultCapture() = 0; + virtual IRunner* getRunner() = 0; + virtual IConfigPtr const& getConfig() const = 0; + }; + + struct IMutableContext : IContext + { + virtual ~IMutableContext(); + virtual void setResultCapture( IResultCapture* resultCapture ) = 0; + virtual void setRunner( IRunner* runner ) = 0; + virtual void setConfig( IConfigPtr const& config ) = 0; + + private: + static IMutableContext *currentContext; + friend IMutableContext& getCurrentMutableContext(); + friend void cleanUpContext(); + static void createContext(); + }; + + inline IMutableContext& getCurrentMutableContext() + { + if( !IMutableContext::currentContext ) + IMutableContext::createContext(); + return *IMutableContext::currentContext; + } + + inline IContext& getCurrentContext() + { + return getCurrentMutableContext(); + } + + void cleanUpContext(); +} + +// end catch_context.h +// start catch_debugger.h + +namespace Catch { + bool isDebuggerActive(); +} + +#ifdef CATCH_PLATFORM_MAC + + #define CATCH_TRAP() __asm__("int $3\n" : : ) /* NOLINT */ + +#elif defined(CATCH_PLATFORM_LINUX) + // If we can use inline assembler, do it because this allows us to break + // directly at the location of the failing check instead of breaking inside + // raise() called from it, i.e. one stack frame below. + #if defined(__GNUC__) && (defined(__i386) || defined(__x86_64)) + #define CATCH_TRAP() asm volatile ("int $3") /* NOLINT */ + #else // Fall back to the generic way. + #include + + #define CATCH_TRAP() raise(SIGTRAP) + #endif +#elif defined(_MSC_VER) + #define CATCH_TRAP() __debugbreak() +#elif defined(__MINGW32__) + extern "C" __declspec(dllimport) void __stdcall DebugBreak(); + #define CATCH_TRAP() DebugBreak() +#endif + +#ifdef CATCH_TRAP + #define CATCH_BREAK_INTO_DEBUGGER() if( Catch::isDebuggerActive() ) { CATCH_TRAP(); } +#else + namespace Catch { + inline void doNothing() {} + } + #define CATCH_BREAK_INTO_DEBUGGER() Catch::doNothing() +#endif + +// end catch_debugger.h +// start catch_run_context.h + +// start catch_fatal_condition.h + +// start catch_windows_h_proxy.h + + +#if defined(CATCH_PLATFORM_WINDOWS) + +#if !defined(NOMINMAX) && !defined(CATCH_CONFIG_NO_NOMINMAX) +# define CATCH_DEFINED_NOMINMAX +# define NOMINMAX +#endif +#if !defined(WIN32_LEAN_AND_MEAN) && !defined(CATCH_CONFIG_NO_WIN32_LEAN_AND_MEAN) +# define CATCH_DEFINED_WIN32_LEAN_AND_MEAN +# define WIN32_LEAN_AND_MEAN +#endif + +#ifdef __AFXDLL +#include +#else +#include +#endif + +#ifdef CATCH_DEFINED_NOMINMAX +# undef NOMINMAX +#endif +#ifdef CATCH_DEFINED_WIN32_LEAN_AND_MEAN +# undef WIN32_LEAN_AND_MEAN +#endif + +#endif // defined(CATCH_PLATFORM_WINDOWS) + +// end catch_windows_h_proxy.h +#if defined( CATCH_CONFIG_WINDOWS_SEH ) + +namespace Catch { + + struct FatalConditionHandler { + + static LONG CALLBACK handleVectoredException(PEXCEPTION_POINTERS ExceptionInfo); + FatalConditionHandler(); + static void reset(); + ~FatalConditionHandler(); + + private: + static bool isSet; + static ULONG guaranteeSize; + static PVOID exceptionHandlerHandle; + }; + +} // namespace Catch + +#elif defined ( CATCH_CONFIG_POSIX_SIGNALS ) + +#include + +namespace Catch { + + struct FatalConditionHandler { + + static bool isSet; + static struct sigaction oldSigActions[]; + static stack_t oldSigStack; + static char altStackMem[]; + + static void handleSignal( int sig ); + + FatalConditionHandler(); + ~FatalConditionHandler(); + static void reset(); + }; + +} // namespace Catch + +#else + +namespace Catch { + struct FatalConditionHandler { + void reset(); + }; +} + +#endif + +// end catch_fatal_condition.h +#include + +namespace Catch { + + struct IMutableContext; + + /////////////////////////////////////////////////////////////////////////// + + class RunContext : public IResultCapture, public IRunner { + + public: + RunContext( RunContext const& ) = delete; + RunContext& operator =( RunContext const& ) = delete; + + explicit RunContext( IConfigPtr const& _config, IStreamingReporterPtr&& reporter ); + + ~RunContext() override; + + void testGroupStarting( std::string const& testSpec, std::size_t groupIndex, std::size_t groupsCount ); + void testGroupEnded( std::string const& testSpec, Totals const& totals, std::size_t groupIndex, std::size_t groupsCount ); + + Totals runTest(TestCase const& testCase); + + IConfigPtr config() const; + IStreamingReporter& reporter() const; + + public: // IResultCapture + + // Assertion handlers + void handleExpr + ( AssertionInfo const& info, + ITransientExpression const& expr, + AssertionReaction& reaction ) override; + void handleMessage + ( AssertionInfo const& info, + ResultWas::OfType resultType, + StringRef const& message, + AssertionReaction& reaction ) override; + void handleUnexpectedExceptionNotThrown + ( AssertionInfo const& info, + AssertionReaction& reaction ) override; + void handleUnexpectedInflightException + ( AssertionInfo const& info, + std::string const& message, + AssertionReaction& reaction ) override; + void handleIncomplete + ( AssertionInfo const& info ) override; + void handleNonExpr + ( AssertionInfo const &info, + ResultWas::OfType resultType, + AssertionReaction &reaction ) override; + + bool sectionStarted( SectionInfo const& sectionInfo, Counts& assertions ) override; + + void sectionEnded( SectionEndInfo const& endInfo ) override; + void sectionEndedEarly( SectionEndInfo const& endInfo ) override; + + auto acquireGeneratorTracker( SourceLineInfo const& lineInfo ) -> IGeneratorTracker& override; + + void benchmarkStarting( BenchmarkInfo const& info ) override; + void benchmarkEnded( BenchmarkStats const& stats ) override; + + void pushScopedMessage( MessageInfo const& message ) override; + void popScopedMessage( MessageInfo const& message ) override; + + std::string getCurrentTestName() const override; + + const AssertionResult* getLastResult() const override; + + void exceptionEarlyReported() override; + + void handleFatalErrorCondition( StringRef message ) override; + + bool lastAssertionPassed() override; + + void assertionPassed() override; + + public: + // !TBD We need to do this another way! + bool aborting() const final; + + private: + + void runCurrentTest( std::string& redirectedCout, std::string& redirectedCerr ); + void invokeActiveTestCase(); + + void resetAssertionInfo(); + bool testForMissingAssertions( Counts& assertions ); + + void assertionEnded( AssertionResult const& result ); + void reportExpr + ( AssertionInfo const &info, + ResultWas::OfType resultType, + ITransientExpression const *expr, + bool negated ); + + void populateReaction( AssertionReaction& reaction ); + + private: + + void handleUnfinishedSections(); + + TestRunInfo m_runInfo; + IMutableContext& m_context; + TestCase const* m_activeTestCase = nullptr; + ITracker* m_testCaseTracker; + Option m_lastResult; + + IConfigPtr m_config; + Totals m_totals; + IStreamingReporterPtr m_reporter; + std::vector m_messages; + AssertionInfo m_lastAssertionInfo; + std::vector m_unfinishedSections; + std::vector m_activeSections; + TrackerContext m_trackerContext; + bool m_lastAssertionPassed = false; + bool m_shouldReportUnexpected = true; + bool m_includeSuccessfulResults; + }; + +} // end namespace Catch + +// end catch_run_context.h +namespace Catch { + + namespace { + auto operator <<( std::ostream& os, ITransientExpression const& expr ) -> std::ostream& { + expr.streamReconstructedExpression( os ); + return os; + } + } + + LazyExpression::LazyExpression( bool isNegated ) + : m_isNegated( isNegated ) + {} + + LazyExpression::LazyExpression( LazyExpression const& other ) : m_isNegated( other.m_isNegated ) {} + + LazyExpression::operator bool() const { + return m_transientExpression != nullptr; + } + + auto operator << ( std::ostream& os, LazyExpression const& lazyExpr ) -> std::ostream& { + if( lazyExpr.m_isNegated ) + os << "!"; + + if( lazyExpr ) { + if( lazyExpr.m_isNegated && lazyExpr.m_transientExpression->isBinaryExpression() ) + os << "(" << *lazyExpr.m_transientExpression << ")"; + else + os << *lazyExpr.m_transientExpression; + } + else { + os << "{** error - unchecked empty expression requested **}"; + } + return os; + } + + AssertionHandler::AssertionHandler + ( StringRef const& macroName, + SourceLineInfo const& lineInfo, + StringRef capturedExpression, + ResultDisposition::Flags resultDisposition ) + : m_assertionInfo{ macroName, lineInfo, capturedExpression, resultDisposition }, + m_resultCapture( getResultCapture() ) + {} + + void AssertionHandler::handleExpr( ITransientExpression const& expr ) { + m_resultCapture.handleExpr( m_assertionInfo, expr, m_reaction ); + } + void AssertionHandler::handleMessage(ResultWas::OfType resultType, StringRef const& message) { + m_resultCapture.handleMessage( m_assertionInfo, resultType, message, m_reaction ); + } + + auto AssertionHandler::allowThrows() const -> bool { + return getCurrentContext().getConfig()->allowThrows(); + } + + void AssertionHandler::complete() { + setCompleted(); + if( m_reaction.shouldDebugBreak ) { + + // If you find your debugger stopping you here then go one level up on the + // call-stack for the code that caused it (typically a failed assertion) + + // (To go back to the test and change execution, jump over the throw, next) + CATCH_BREAK_INTO_DEBUGGER(); + } + if (m_reaction.shouldThrow) { +#if !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) + throw Catch::TestFailureException(); +#else + CATCH_ERROR( "Test failure requires aborting test!" ); +#endif + } + } + void AssertionHandler::setCompleted() { + m_completed = true; + } + + void AssertionHandler::handleUnexpectedInflightException() { + m_resultCapture.handleUnexpectedInflightException( m_assertionInfo, Catch::translateActiveException(), m_reaction ); + } + + void AssertionHandler::handleExceptionThrownAsExpected() { + m_resultCapture.handleNonExpr(m_assertionInfo, ResultWas::Ok, m_reaction); + } + void AssertionHandler::handleExceptionNotThrownAsExpected() { + m_resultCapture.handleNonExpr(m_assertionInfo, ResultWas::Ok, m_reaction); + } + + void AssertionHandler::handleUnexpectedExceptionNotThrown() { + m_resultCapture.handleUnexpectedExceptionNotThrown( m_assertionInfo, m_reaction ); + } + + void AssertionHandler::handleThrowingCallSkipped() { + m_resultCapture.handleNonExpr(m_assertionInfo, ResultWas::Ok, m_reaction); + } + + // This is the overload that takes a string and infers the Equals matcher from it + // The more general overload, that takes any string matcher, is in catch_capture_matchers.cpp + void handleExceptionMatchExpr( AssertionHandler& handler, std::string const& str, StringRef const& matcherString ) { + handleExceptionMatchExpr( handler, Matchers::Equals( str ), matcherString ); + } + +} // namespace Catch +// end catch_assertionhandler.cpp +// start catch_assertionresult.cpp + +namespace Catch { + AssertionResultData::AssertionResultData(ResultWas::OfType _resultType, LazyExpression const & _lazyExpression): + lazyExpression(_lazyExpression), + resultType(_resultType) {} + + std::string AssertionResultData::reconstructExpression() const { + + if( reconstructedExpression.empty() ) { + if( lazyExpression ) { + ReusableStringStream rss; + rss << lazyExpression; + reconstructedExpression = rss.str(); + } + } + return reconstructedExpression; + } + + AssertionResult::AssertionResult( AssertionInfo const& info, AssertionResultData const& data ) + : m_info( info ), + m_resultData( data ) + {} + + // Result was a success + bool AssertionResult::succeeded() const { + return Catch::isOk( m_resultData.resultType ); + } + + // Result was a success, or failure is suppressed + bool AssertionResult::isOk() const { + return Catch::isOk( m_resultData.resultType ) || shouldSuppressFailure( m_info.resultDisposition ); + } + + ResultWas::OfType AssertionResult::getResultType() const { + return m_resultData.resultType; + } + + bool AssertionResult::hasExpression() const { + return m_info.capturedExpression[0] != 0; + } + + bool AssertionResult::hasMessage() const { + return !m_resultData.message.empty(); + } + + std::string AssertionResult::getExpression() const { + if( isFalseTest( m_info.resultDisposition ) ) + return "!(" + m_info.capturedExpression + ")"; + else + return m_info.capturedExpression; + } + + std::string AssertionResult::getExpressionInMacro() const { + std::string expr; + if( m_info.macroName[0] == 0 ) + expr = m_info.capturedExpression; + else { + expr.reserve( m_info.macroName.size() + m_info.capturedExpression.size() + 4 ); + expr += m_info.macroName; + expr += "( "; + expr += m_info.capturedExpression; + expr += " )"; + } + return expr; + } + + bool AssertionResult::hasExpandedExpression() const { + return hasExpression() && getExpandedExpression() != getExpression(); + } + + std::string AssertionResult::getExpandedExpression() const { + std::string expr = m_resultData.reconstructExpression(); + return expr.empty() + ? getExpression() + : expr; + } + + std::string AssertionResult::getMessage() const { + return m_resultData.message; + } + SourceLineInfo AssertionResult::getSourceInfo() const { + return m_info.lineInfo; + } + + StringRef AssertionResult::getTestMacroName() const { + return m_info.macroName; + } + +} // end namespace Catch +// end catch_assertionresult.cpp +// start catch_benchmark.cpp + +namespace Catch { + + auto BenchmarkLooper::getResolution() -> uint64_t { + return getEstimatedClockResolution() * getCurrentContext().getConfig()->benchmarkResolutionMultiple(); + } + + void BenchmarkLooper::reportStart() { + getResultCapture().benchmarkStarting( { m_name } ); + } + auto BenchmarkLooper::needsMoreIterations() -> bool { + auto elapsed = m_timer.getElapsedNanoseconds(); + + // Exponentially increasing iterations until we're confident in our timer resolution + if( elapsed < m_resolution ) { + m_iterationsToRun *= 10; + return true; + } + + getResultCapture().benchmarkEnded( { { m_name }, m_count, elapsed } ); + return false; + } + +} // end namespace Catch +// end catch_benchmark.cpp +// start catch_capture_matchers.cpp + +namespace Catch { + + using StringMatcher = Matchers::Impl::MatcherBase; + + // This is the general overload that takes a any string matcher + // There is another overload, in catch_assertionhandler.h/.cpp, that only takes a string and infers + // the Equals matcher (so the header does not mention matchers) + void handleExceptionMatchExpr( AssertionHandler& handler, StringMatcher const& matcher, StringRef const& matcherString ) { + std::string exceptionMessage = Catch::translateActiveException(); + MatchExpr expr( exceptionMessage, matcher, matcherString ); + handler.handleExpr( expr ); + } + +} // namespace Catch +// end catch_capture_matchers.cpp +// start catch_commandline.cpp + +// start catch_commandline.h + +// start catch_clara.h + +// Use Catch's value for console width (store Clara's off to the side, if present) +#ifdef CLARA_CONFIG_CONSOLE_WIDTH +#define CATCH_TEMP_CLARA_CONFIG_CONSOLE_WIDTH CATCH_CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH +#undef CATCH_CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH +#endif +#define CATCH_CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH CATCH_CONFIG_CONSOLE_WIDTH-1 + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wweak-vtables" +#pragma clang diagnostic ignored "-Wexit-time-destructors" +#pragma clang diagnostic ignored "-Wshadow" +#endif + +// start clara.hpp +// Copyright 2017 Two Blue Cubes Ltd. All rights reserved. +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// See https://github.com/philsquared/Clara for more details + +// Clara v1.1.4 + + +#ifndef CATCH_CLARA_CONFIG_CONSOLE_WIDTH +#define CATCH_CLARA_CONFIG_CONSOLE_WIDTH 80 +#endif + +#ifndef CATCH_CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH +#define CATCH_CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH CATCH_CLARA_CONFIG_CONSOLE_WIDTH +#endif + +#ifndef CLARA_CONFIG_OPTIONAL_TYPE +#ifdef __has_include +#if __has_include() && __cplusplus >= 201703L +#include +#define CLARA_CONFIG_OPTIONAL_TYPE std::optional +#endif +#endif +#endif + +// ----------- #included from clara_textflow.hpp ----------- + +// TextFlowCpp +// +// A single-header library for wrapping and laying out basic text, by Phil Nash +// +// This work is licensed under the BSD 2-Clause license. +// See the accompanying LICENSE file, or the one at https://opensource.org/licenses/BSD-2-Clause +// +// This project is hosted at https://github.com/philsquared/textflowcpp + + +#include +#include +#include +#include + +#ifndef CATCH_CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH +#define CATCH_CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH 80 +#endif + +namespace Catch { namespace clara { namespace TextFlow { + + inline auto isWhitespace( char c ) -> bool { + static std::string chars = " \t\n\r"; + return chars.find( c ) != std::string::npos; + } + inline auto isBreakableBefore( char c ) -> bool { + static std::string chars = "[({<|"; + return chars.find( c ) != std::string::npos; + } + inline auto isBreakableAfter( char c ) -> bool { + static std::string chars = "])}>.,:;*+-=&/\\"; + return chars.find( c ) != std::string::npos; + } + + class Columns; + + class Column { + std::vector m_strings; + size_t m_width = CATCH_CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH; + size_t m_indent = 0; + size_t m_initialIndent = std::string::npos; + + public: + class iterator { + friend Column; + + Column const& m_column; + size_t m_stringIndex = 0; + size_t m_pos = 0; + + size_t m_len = 0; + size_t m_end = 0; + bool m_suffix = false; + + iterator( Column const& column, size_t stringIndex ) + : m_column( column ), + m_stringIndex( stringIndex ) + {} + + auto line() const -> std::string const& { return m_column.m_strings[m_stringIndex]; } + + auto isBoundary( size_t at ) const -> bool { + assert( at > 0 ); + assert( at <= line().size() ); + + return at == line().size() || + ( isWhitespace( line()[at] ) && !isWhitespace( line()[at-1] ) ) || + isBreakableBefore( line()[at] ) || + isBreakableAfter( line()[at-1] ); + } + + void calcLength() { + assert( m_stringIndex < m_column.m_strings.size() ); + + m_suffix = false; + auto width = m_column.m_width-indent(); + m_end = m_pos; + while( m_end < line().size() && line()[m_end] != '\n' ) + ++m_end; + + if( m_end < m_pos + width ) { + m_len = m_end - m_pos; + } + else { + size_t len = width; + while (len > 0 && !isBoundary(m_pos + len)) + --len; + while (len > 0 && isWhitespace( line()[m_pos + len - 1] )) + --len; + + if (len > 0) { + m_len = len; + } else { + m_suffix = true; + m_len = width - 1; + } + } + } + + auto indent() const -> size_t { + auto initial = m_pos == 0 && m_stringIndex == 0 ? m_column.m_initialIndent : std::string::npos; + return initial == std::string::npos ? m_column.m_indent : initial; + } + + auto addIndentAndSuffix(std::string const &plain) const -> std::string { + return std::string( indent(), ' ' ) + (m_suffix ? plain + "-" : plain); + } + + public: + explicit iterator( Column const& column ) : m_column( column ) { + assert( m_column.m_width > m_column.m_indent ); + assert( m_column.m_initialIndent == std::string::npos || m_column.m_width > m_column.m_initialIndent ); + calcLength(); + if( m_len == 0 ) + m_stringIndex++; // Empty string + } + + auto operator *() const -> std::string { + assert( m_stringIndex < m_column.m_strings.size() ); + assert( m_pos <= m_end ); + if( m_pos + m_column.m_width < m_end ) + return addIndentAndSuffix(line().substr(m_pos, m_len)); + else + return addIndentAndSuffix(line().substr(m_pos, m_end - m_pos)); + } + + auto operator ++() -> iterator& { + m_pos += m_len; + if( m_pos < line().size() && line()[m_pos] == '\n' ) + m_pos += 1; + else + while( m_pos < line().size() && isWhitespace( line()[m_pos] ) ) + ++m_pos; + + if( m_pos == line().size() ) { + m_pos = 0; + ++m_stringIndex; + } + if( m_stringIndex < m_column.m_strings.size() ) + calcLength(); + return *this; + } + auto operator ++(int) -> iterator { + iterator prev( *this ); + operator++(); + return prev; + } + + auto operator ==( iterator const& other ) const -> bool { + return + m_pos == other.m_pos && + m_stringIndex == other.m_stringIndex && + &m_column == &other.m_column; + } + auto operator !=( iterator const& other ) const -> bool { + return !operator==( other ); + } + }; + using const_iterator = iterator; + + explicit Column( std::string const& text ) { m_strings.push_back( text ); } + + auto width( size_t newWidth ) -> Column& { + assert( newWidth > 0 ); + m_width = newWidth; + return *this; + } + auto indent( size_t newIndent ) -> Column& { + m_indent = newIndent; + return *this; + } + auto initialIndent( size_t newIndent ) -> Column& { + m_initialIndent = newIndent; + return *this; + } + + auto width() const -> size_t { return m_width; } + auto begin() const -> iterator { return iterator( *this ); } + auto end() const -> iterator { return { *this, m_strings.size() }; } + + inline friend std::ostream& operator << ( std::ostream& os, Column const& col ) { + bool first = true; + for( auto line : col ) { + if( first ) + first = false; + else + os << "\n"; + os << line; + } + return os; + } + + auto operator + ( Column const& other ) -> Columns; + + auto toString() const -> std::string { + std::ostringstream oss; + oss << *this; + return oss.str(); + } + }; + + class Spacer : public Column { + + public: + explicit Spacer( size_t spaceWidth ) : Column( "" ) { + width( spaceWidth ); + } + }; + + class Columns { + std::vector m_columns; + + public: + + class iterator { + friend Columns; + struct EndTag {}; + + std::vector const& m_columns; + std::vector m_iterators; + size_t m_activeIterators; + + iterator( Columns const& columns, EndTag ) + : m_columns( columns.m_columns ), + m_activeIterators( 0 ) + { + m_iterators.reserve( m_columns.size() ); + + for( auto const& col : m_columns ) + m_iterators.push_back( col.end() ); + } + + public: + explicit iterator( Columns const& columns ) + : m_columns( columns.m_columns ), + m_activeIterators( m_columns.size() ) + { + m_iterators.reserve( m_columns.size() ); + + for( auto const& col : m_columns ) + m_iterators.push_back( col.begin() ); + } + + auto operator ==( iterator const& other ) const -> bool { + return m_iterators == other.m_iterators; + } + auto operator !=( iterator const& other ) const -> bool { + return m_iterators != other.m_iterators; + } + auto operator *() const -> std::string { + std::string row, padding; + + for( size_t i = 0; i < m_columns.size(); ++i ) { + auto width = m_columns[i].width(); + if( m_iterators[i] != m_columns[i].end() ) { + std::string col = *m_iterators[i]; + row += padding + col; + if( col.size() < width ) + padding = std::string( width - col.size(), ' ' ); + else + padding = ""; + } + else { + padding += std::string( width, ' ' ); + } + } + return row; + } + auto operator ++() -> iterator& { + for( size_t i = 0; i < m_columns.size(); ++i ) { + if (m_iterators[i] != m_columns[i].end()) + ++m_iterators[i]; + } + return *this; + } + auto operator ++(int) -> iterator { + iterator prev( *this ); + operator++(); + return prev; + } + }; + using const_iterator = iterator; + + auto begin() const -> iterator { return iterator( *this ); } + auto end() const -> iterator { return { *this, iterator::EndTag() }; } + + auto operator += ( Column const& col ) -> Columns& { + m_columns.push_back( col ); + return *this; + } + auto operator + ( Column const& col ) -> Columns { + Columns combined = *this; + combined += col; + return combined; + } + + inline friend std::ostream& operator << ( std::ostream& os, Columns const& cols ) { + + bool first = true; + for( auto line : cols ) { + if( first ) + first = false; + else + os << "\n"; + os << line; + } + return os; + } + + auto toString() const -> std::string { + std::ostringstream oss; + oss << *this; + return oss.str(); + } + }; + + inline auto Column::operator + ( Column const& other ) -> Columns { + Columns cols; + cols += *this; + cols += other; + return cols; + } +}}} // namespace Catch::clara::TextFlow + +// ----------- end of #include from clara_textflow.hpp ----------- +// ........... back in clara.hpp + +#include +#include +#include + +#if !defined(CATCH_PLATFORM_WINDOWS) && ( defined(WIN32) || defined(__WIN32__) || defined(_WIN32) || defined(_MSC_VER) ) +#define CATCH_PLATFORM_WINDOWS +#endif + +namespace Catch { namespace clara { +namespace detail { + + // Traits for extracting arg and return type of lambdas (for single argument lambdas) + template + struct UnaryLambdaTraits : UnaryLambdaTraits {}; + + template + struct UnaryLambdaTraits { + static const bool isValid = false; + }; + + template + struct UnaryLambdaTraits { + static const bool isValid = true; + using ArgType = typename std::remove_const::type>::type; + using ReturnType = ReturnT; + }; + + class TokenStream; + + // Transport for raw args (copied from main args, or supplied via init list for testing) + class Args { + friend TokenStream; + std::string m_exeName; + std::vector m_args; + + public: + Args( int argc, char const* const* argv ) + : m_exeName(argv[0]), + m_args(argv + 1, argv + argc) {} + + Args( std::initializer_list args ) + : m_exeName( *args.begin() ), + m_args( args.begin()+1, args.end() ) + {} + + auto exeName() const -> std::string { + return m_exeName; + } + }; + + // Wraps a token coming from a token stream. These may not directly correspond to strings as a single string + // may encode an option + its argument if the : or = form is used + enum class TokenType { + Option, Argument + }; + struct Token { + TokenType type; + std::string token; + }; + + inline auto isOptPrefix( char c ) -> bool { + return c == '-' +#ifdef CATCH_PLATFORM_WINDOWS + || c == '/' +#endif + ; + } + + // Abstracts iterators into args as a stream of tokens, with option arguments uniformly handled + class TokenStream { + using Iterator = std::vector::const_iterator; + Iterator it; + Iterator itEnd; + std::vector m_tokenBuffer; + + void loadBuffer() { + m_tokenBuffer.resize( 0 ); + + // Skip any empty strings + while( it != itEnd && it->empty() ) + ++it; + + if( it != itEnd ) { + auto const &next = *it; + if( isOptPrefix( next[0] ) ) { + auto delimiterPos = next.find_first_of( " :=" ); + if( delimiterPos != std::string::npos ) { + m_tokenBuffer.push_back( { TokenType::Option, next.substr( 0, delimiterPos ) } ); + m_tokenBuffer.push_back( { TokenType::Argument, next.substr( delimiterPos + 1 ) } ); + } else { + if( next[1] != '-' && next.size() > 2 ) { + std::string opt = "- "; + for( size_t i = 1; i < next.size(); ++i ) { + opt[1] = next[i]; + m_tokenBuffer.push_back( { TokenType::Option, opt } ); + } + } else { + m_tokenBuffer.push_back( { TokenType::Option, next } ); + } + } + } else { + m_tokenBuffer.push_back( { TokenType::Argument, next } ); + } + } + } + + public: + explicit TokenStream( Args const &args ) : TokenStream( args.m_args.begin(), args.m_args.end() ) {} + + TokenStream( Iterator it, Iterator itEnd ) : it( it ), itEnd( itEnd ) { + loadBuffer(); + } + + explicit operator bool() const { + return !m_tokenBuffer.empty() || it != itEnd; + } + + auto count() const -> size_t { return m_tokenBuffer.size() + (itEnd - it); } + + auto operator*() const -> Token { + assert( !m_tokenBuffer.empty() ); + return m_tokenBuffer.front(); + } + + auto operator->() const -> Token const * { + assert( !m_tokenBuffer.empty() ); + return &m_tokenBuffer.front(); + } + + auto operator++() -> TokenStream & { + if( m_tokenBuffer.size() >= 2 ) { + m_tokenBuffer.erase( m_tokenBuffer.begin() ); + } else { + if( it != itEnd ) + ++it; + loadBuffer(); + } + return *this; + } + }; + + class ResultBase { + public: + enum Type { + Ok, LogicError, RuntimeError + }; + + protected: + ResultBase( Type type ) : m_type( type ) {} + virtual ~ResultBase() = default; + + virtual void enforceOk() const = 0; + + Type m_type; + }; + + template + class ResultValueBase : public ResultBase { + public: + auto value() const -> T const & { + enforceOk(); + return m_value; + } + + protected: + ResultValueBase( Type type ) : ResultBase( type ) {} + + ResultValueBase( ResultValueBase const &other ) : ResultBase( other ) { + if( m_type == ResultBase::Ok ) + new( &m_value ) T( other.m_value ); + } + + ResultValueBase( Type, T const &value ) : ResultBase( Ok ) { + new( &m_value ) T( value ); + } + + auto operator=( ResultValueBase const &other ) -> ResultValueBase & { + if( m_type == ResultBase::Ok ) + m_value.~T(); + ResultBase::operator=(other); + if( m_type == ResultBase::Ok ) + new( &m_value ) T( other.m_value ); + return *this; + } + + ~ResultValueBase() override { + if( m_type == Ok ) + m_value.~T(); + } + + union { + T m_value; + }; + }; + + template<> + class ResultValueBase : public ResultBase { + protected: + using ResultBase::ResultBase; + }; + + template + class BasicResult : public ResultValueBase { + public: + template + explicit BasicResult( BasicResult const &other ) + : ResultValueBase( other.type() ), + m_errorMessage( other.errorMessage() ) + { + assert( type() != ResultBase::Ok ); + } + + template + static auto ok( U const &value ) -> BasicResult { return { ResultBase::Ok, value }; } + static auto ok() -> BasicResult { return { ResultBase::Ok }; } + static auto logicError( std::string const &message ) -> BasicResult { return { ResultBase::LogicError, message }; } + static auto runtimeError( std::string const &message ) -> BasicResult { return { ResultBase::RuntimeError, message }; } + + explicit operator bool() const { return m_type == ResultBase::Ok; } + auto type() const -> ResultBase::Type { return m_type; } + auto errorMessage() const -> std::string { return m_errorMessage; } + + protected: + void enforceOk() const override { + + // Errors shouldn't reach this point, but if they do + // the actual error message will be in m_errorMessage + assert( m_type != ResultBase::LogicError ); + assert( m_type != ResultBase::RuntimeError ); + if( m_type != ResultBase::Ok ) + std::abort(); + } + + std::string m_errorMessage; // Only populated if resultType is an error + + BasicResult( ResultBase::Type type, std::string const &message ) + : ResultValueBase(type), + m_errorMessage(message) + { + assert( m_type != ResultBase::Ok ); + } + + using ResultValueBase::ResultValueBase; + using ResultBase::m_type; + }; + + enum class ParseResultType { + Matched, NoMatch, ShortCircuitAll, ShortCircuitSame + }; + + class ParseState { + public: + + ParseState( ParseResultType type, TokenStream const &remainingTokens ) + : m_type(type), + m_remainingTokens( remainingTokens ) + {} + + auto type() const -> ParseResultType { return m_type; } + auto remainingTokens() const -> TokenStream { return m_remainingTokens; } + + private: + ParseResultType m_type; + TokenStream m_remainingTokens; + }; + + using Result = BasicResult; + using ParserResult = BasicResult; + using InternalParseResult = BasicResult; + + struct HelpColumns { + std::string left; + std::string right; + }; + + template + inline auto convertInto( std::string const &source, T& target ) -> ParserResult { + std::stringstream ss; + ss << source; + ss >> target; + if( ss.fail() ) + return ParserResult::runtimeError( "Unable to convert '" + source + "' to destination type" ); + else + return ParserResult::ok( ParseResultType::Matched ); + } + inline auto convertInto( std::string const &source, std::string& target ) -> ParserResult { + target = source; + return ParserResult::ok( ParseResultType::Matched ); + } + inline auto convertInto( std::string const &source, bool &target ) -> ParserResult { + std::string srcLC = source; + std::transform( srcLC.begin(), srcLC.end(), srcLC.begin(), []( char c ) { return static_cast( ::tolower(c) ); } ); + if (srcLC == "y" || srcLC == "1" || srcLC == "true" || srcLC == "yes" || srcLC == "on") + target = true; + else if (srcLC == "n" || srcLC == "0" || srcLC == "false" || srcLC == "no" || srcLC == "off") + target = false; + else + return ParserResult::runtimeError( "Expected a boolean value but did not recognise: '" + source + "'" ); + return ParserResult::ok( ParseResultType::Matched ); + } +#ifdef CLARA_CONFIG_OPTIONAL_TYPE + template + inline auto convertInto( std::string const &source, CLARA_CONFIG_OPTIONAL_TYPE& target ) -> ParserResult { + T temp; + auto result = convertInto( source, temp ); + if( result ) + target = std::move(temp); + return result; + } +#endif // CLARA_CONFIG_OPTIONAL_TYPE + + struct NonCopyable { + NonCopyable() = default; + NonCopyable( NonCopyable const & ) = delete; + NonCopyable( NonCopyable && ) = delete; + NonCopyable &operator=( NonCopyable const & ) = delete; + NonCopyable &operator=( NonCopyable && ) = delete; + }; + + struct BoundRef : NonCopyable { + virtual ~BoundRef() = default; + virtual auto isContainer() const -> bool { return false; } + virtual auto isFlag() const -> bool { return false; } + }; + struct BoundValueRefBase : BoundRef { + virtual auto setValue( std::string const &arg ) -> ParserResult = 0; + }; + struct BoundFlagRefBase : BoundRef { + virtual auto setFlag( bool flag ) -> ParserResult = 0; + virtual auto isFlag() const -> bool { return true; } + }; + + template + struct BoundValueRef : BoundValueRefBase { + T &m_ref; + + explicit BoundValueRef( T &ref ) : m_ref( ref ) {} + + auto setValue( std::string const &arg ) -> ParserResult override { + return convertInto( arg, m_ref ); + } + }; + + template + struct BoundValueRef> : BoundValueRefBase { + std::vector &m_ref; + + explicit BoundValueRef( std::vector &ref ) : m_ref( ref ) {} + + auto isContainer() const -> bool override { return true; } + + auto setValue( std::string const &arg ) -> ParserResult override { + T temp; + auto result = convertInto( arg, temp ); + if( result ) + m_ref.push_back( temp ); + return result; + } + }; + + struct BoundFlagRef : BoundFlagRefBase { + bool &m_ref; + + explicit BoundFlagRef( bool &ref ) : m_ref( ref ) {} + + auto setFlag( bool flag ) -> ParserResult override { + m_ref = flag; + return ParserResult::ok( ParseResultType::Matched ); + } + }; + + template + struct LambdaInvoker { + static_assert( std::is_same::value, "Lambda must return void or clara::ParserResult" ); + + template + static auto invoke( L const &lambda, ArgType const &arg ) -> ParserResult { + return lambda( arg ); + } + }; + + template<> + struct LambdaInvoker { + template + static auto invoke( L const &lambda, ArgType const &arg ) -> ParserResult { + lambda( arg ); + return ParserResult::ok( ParseResultType::Matched ); + } + }; + + template + inline auto invokeLambda( L const &lambda, std::string const &arg ) -> ParserResult { + ArgType temp{}; + auto result = convertInto( arg, temp ); + return !result + ? result + : LambdaInvoker::ReturnType>::invoke( lambda, temp ); + } + + template + struct BoundLambda : BoundValueRefBase { + L m_lambda; + + static_assert( UnaryLambdaTraits::isValid, "Supplied lambda must take exactly one argument" ); + explicit BoundLambda( L const &lambda ) : m_lambda( lambda ) {} + + auto setValue( std::string const &arg ) -> ParserResult override { + return invokeLambda::ArgType>( m_lambda, arg ); + } + }; + + template + struct BoundFlagLambda : BoundFlagRefBase { + L m_lambda; + + static_assert( UnaryLambdaTraits::isValid, "Supplied lambda must take exactly one argument" ); + static_assert( std::is_same::ArgType, bool>::value, "flags must be boolean" ); + + explicit BoundFlagLambda( L const &lambda ) : m_lambda( lambda ) {} + + auto setFlag( bool flag ) -> ParserResult override { + return LambdaInvoker::ReturnType>::invoke( m_lambda, flag ); + } + }; + + enum class Optionality { Optional, Required }; + + struct Parser; + + class ParserBase { + public: + virtual ~ParserBase() = default; + virtual auto validate() const -> Result { return Result::ok(); } + virtual auto parse( std::string const& exeName, TokenStream const &tokens) const -> InternalParseResult = 0; + virtual auto cardinality() const -> size_t { return 1; } + + auto parse( Args const &args ) const -> InternalParseResult { + return parse( args.exeName(), TokenStream( args ) ); + } + }; + + template + class ComposableParserImpl : public ParserBase { + public: + template + auto operator|( T const &other ) const -> Parser; + + template + auto operator+( T const &other ) const -> Parser; + }; + + // Common code and state for Args and Opts + template + class ParserRefImpl : public ComposableParserImpl { + protected: + Optionality m_optionality = Optionality::Optional; + std::shared_ptr m_ref; + std::string m_hint; + std::string m_description; + + explicit ParserRefImpl( std::shared_ptr const &ref ) : m_ref( ref ) {} + + public: + template + ParserRefImpl( T &ref, std::string const &hint ) + : m_ref( std::make_shared>( ref ) ), + m_hint( hint ) + {} + + template + ParserRefImpl( LambdaT const &ref, std::string const &hint ) + : m_ref( std::make_shared>( ref ) ), + m_hint(hint) + {} + + auto operator()( std::string const &description ) -> DerivedT & { + m_description = description; + return static_cast( *this ); + } + + auto optional() -> DerivedT & { + m_optionality = Optionality::Optional; + return static_cast( *this ); + }; + + auto required() -> DerivedT & { + m_optionality = Optionality::Required; + return static_cast( *this ); + }; + + auto isOptional() const -> bool { + return m_optionality == Optionality::Optional; + } + + auto cardinality() const -> size_t override { + if( m_ref->isContainer() ) + return 0; + else + return 1; + } + + auto hint() const -> std::string { return m_hint; } + }; + + class ExeName : public ComposableParserImpl { + std::shared_ptr m_name; + std::shared_ptr m_ref; + + template + static auto makeRef(LambdaT const &lambda) -> std::shared_ptr { + return std::make_shared>( lambda) ; + } + + public: + ExeName() : m_name( std::make_shared( "" ) ) {} + + explicit ExeName( std::string &ref ) : ExeName() { + m_ref = std::make_shared>( ref ); + } + + template + explicit ExeName( LambdaT const& lambda ) : ExeName() { + m_ref = std::make_shared>( lambda ); + } + + // The exe name is not parsed out of the normal tokens, but is handled specially + auto parse( std::string const&, TokenStream const &tokens ) const -> InternalParseResult override { + return InternalParseResult::ok( ParseState( ParseResultType::NoMatch, tokens ) ); + } + + auto name() const -> std::string { return *m_name; } + auto set( std::string const& newName ) -> ParserResult { + + auto lastSlash = newName.find_last_of( "\\/" ); + auto filename = ( lastSlash == std::string::npos ) + ? newName + : newName.substr( lastSlash+1 ); + + *m_name = filename; + if( m_ref ) + return m_ref->setValue( filename ); + else + return ParserResult::ok( ParseResultType::Matched ); + } + }; + + class Arg : public ParserRefImpl { + public: + using ParserRefImpl::ParserRefImpl; + + auto parse( std::string const &, TokenStream const &tokens ) const -> InternalParseResult override { + auto validationResult = validate(); + if( !validationResult ) + return InternalParseResult( validationResult ); + + auto remainingTokens = tokens; + auto const &token = *remainingTokens; + if( token.type != TokenType::Argument ) + return InternalParseResult::ok( ParseState( ParseResultType::NoMatch, remainingTokens ) ); + + assert( !m_ref->isFlag() ); + auto valueRef = static_cast( m_ref.get() ); + + auto result = valueRef->setValue( remainingTokens->token ); + if( !result ) + return InternalParseResult( result ); + else + return InternalParseResult::ok( ParseState( ParseResultType::Matched, ++remainingTokens ) ); + } + }; + + inline auto normaliseOpt( std::string const &optName ) -> std::string { +#ifdef CATCH_PLATFORM_WINDOWS + if( optName[0] == '/' ) + return "-" + optName.substr( 1 ); + else +#endif + return optName; + } + + class Opt : public ParserRefImpl { + protected: + std::vector m_optNames; + + public: + template + explicit Opt( LambdaT const &ref ) : ParserRefImpl( std::make_shared>( ref ) ) {} + + explicit Opt( bool &ref ) : ParserRefImpl( std::make_shared( ref ) ) {} + + template + Opt( LambdaT const &ref, std::string const &hint ) : ParserRefImpl( ref, hint ) {} + + template + Opt( T &ref, std::string const &hint ) : ParserRefImpl( ref, hint ) {} + + auto operator[]( std::string const &optName ) -> Opt & { + m_optNames.push_back( optName ); + return *this; + } + + auto getHelpColumns() const -> std::vector { + std::ostringstream oss; + bool first = true; + for( auto const &opt : m_optNames ) { + if (first) + first = false; + else + oss << ", "; + oss << opt; + } + if( !m_hint.empty() ) + oss << " <" << m_hint << ">"; + return { { oss.str(), m_description } }; + } + + auto isMatch( std::string const &optToken ) const -> bool { + auto normalisedToken = normaliseOpt( optToken ); + for( auto const &name : m_optNames ) { + if( normaliseOpt( name ) == normalisedToken ) + return true; + } + return false; + } + + using ParserBase::parse; + + auto parse( std::string const&, TokenStream const &tokens ) const -> InternalParseResult override { + auto validationResult = validate(); + if( !validationResult ) + return InternalParseResult( validationResult ); + + auto remainingTokens = tokens; + if( remainingTokens && remainingTokens->type == TokenType::Option ) { + auto const &token = *remainingTokens; + if( isMatch(token.token ) ) { + if( m_ref->isFlag() ) { + auto flagRef = static_cast( m_ref.get() ); + auto result = flagRef->setFlag( true ); + if( !result ) + return InternalParseResult( result ); + if( result.value() == ParseResultType::ShortCircuitAll ) + return InternalParseResult::ok( ParseState( result.value(), remainingTokens ) ); + } else { + auto valueRef = static_cast( m_ref.get() ); + ++remainingTokens; + if( !remainingTokens ) + return InternalParseResult::runtimeError( "Expected argument following " + token.token ); + auto const &argToken = *remainingTokens; + if( argToken.type != TokenType::Argument ) + return InternalParseResult::runtimeError( "Expected argument following " + token.token ); + auto result = valueRef->setValue( argToken.token ); + if( !result ) + return InternalParseResult( result ); + if( result.value() == ParseResultType::ShortCircuitAll ) + return InternalParseResult::ok( ParseState( result.value(), remainingTokens ) ); + } + return InternalParseResult::ok( ParseState( ParseResultType::Matched, ++remainingTokens ) ); + } + } + return InternalParseResult::ok( ParseState( ParseResultType::NoMatch, remainingTokens ) ); + } + + auto validate() const -> Result override { + if( m_optNames.empty() ) + return Result::logicError( "No options supplied to Opt" ); + for( auto const &name : m_optNames ) { + if( name.empty() ) + return Result::logicError( "Option name cannot be empty" ); +#ifdef CATCH_PLATFORM_WINDOWS + if( name[0] != '-' && name[0] != '/' ) + return Result::logicError( "Option name must begin with '-' or '/'" ); +#else + if( name[0] != '-' ) + return Result::logicError( "Option name must begin with '-'" ); +#endif + } + return ParserRefImpl::validate(); + } + }; + + struct Help : Opt { + Help( bool &showHelpFlag ) + : Opt([&]( bool flag ) { + showHelpFlag = flag; + return ParserResult::ok( ParseResultType::ShortCircuitAll ); + }) + { + static_cast( *this ) + ("display usage information") + ["-?"]["-h"]["--help"] + .optional(); + } + }; + + struct Parser : ParserBase { + + mutable ExeName m_exeName; + std::vector m_options; + std::vector m_args; + + auto operator|=( ExeName const &exeName ) -> Parser & { + m_exeName = exeName; + return *this; + } + + auto operator|=( Arg const &arg ) -> Parser & { + m_args.push_back(arg); + return *this; + } + + auto operator|=( Opt const &opt ) -> Parser & { + m_options.push_back(opt); + return *this; + } + + auto operator|=( Parser const &other ) -> Parser & { + m_options.insert(m_options.end(), other.m_options.begin(), other.m_options.end()); + m_args.insert(m_args.end(), other.m_args.begin(), other.m_args.end()); + return *this; + } + + template + auto operator|( T const &other ) const -> Parser { + return Parser( *this ) |= other; + } + + // Forward deprecated interface with '+' instead of '|' + template + auto operator+=( T const &other ) -> Parser & { return operator|=( other ); } + template + auto operator+( T const &other ) const -> Parser { return operator|( other ); } + + auto getHelpColumns() const -> std::vector { + std::vector cols; + for (auto const &o : m_options) { + auto childCols = o.getHelpColumns(); + cols.insert( cols.end(), childCols.begin(), childCols.end() ); + } + return cols; + } + + void writeToStream( std::ostream &os ) const { + if (!m_exeName.name().empty()) { + os << "usage:\n" << " " << m_exeName.name() << " "; + bool required = true, first = true; + for( auto const &arg : m_args ) { + if (first) + first = false; + else + os << " "; + if( arg.isOptional() && required ) { + os << "["; + required = false; + } + os << "<" << arg.hint() << ">"; + if( arg.cardinality() == 0 ) + os << " ... "; + } + if( !required ) + os << "]"; + if( !m_options.empty() ) + os << " options"; + os << "\n\nwhere options are:" << std::endl; + } + + auto rows = getHelpColumns(); + size_t consoleWidth = CATCH_CLARA_CONFIG_CONSOLE_WIDTH; + size_t optWidth = 0; + for( auto const &cols : rows ) + optWidth = (std::max)(optWidth, cols.left.size() + 2); + + optWidth = (std::min)(optWidth, consoleWidth/2); + + for( auto const &cols : rows ) { + auto row = + TextFlow::Column( cols.left ).width( optWidth ).indent( 2 ) + + TextFlow::Spacer(4) + + TextFlow::Column( cols.right ).width( consoleWidth - 7 - optWidth ); + os << row << std::endl; + } + } + + friend auto operator<<( std::ostream &os, Parser const &parser ) -> std::ostream& { + parser.writeToStream( os ); + return os; + } + + auto validate() const -> Result override { + for( auto const &opt : m_options ) { + auto result = opt.validate(); + if( !result ) + return result; + } + for( auto const &arg : m_args ) { + auto result = arg.validate(); + if( !result ) + return result; + } + return Result::ok(); + } + + using ParserBase::parse; + + auto parse( std::string const& exeName, TokenStream const &tokens ) const -> InternalParseResult override { + + struct ParserInfo { + ParserBase const* parser = nullptr; + size_t count = 0; + }; + const size_t totalParsers = m_options.size() + m_args.size(); + assert( totalParsers < 512 ); + // ParserInfo parseInfos[totalParsers]; // <-- this is what we really want to do + ParserInfo parseInfos[512]; + + { + size_t i = 0; + for (auto const &opt : m_options) parseInfos[i++].parser = &opt; + for (auto const &arg : m_args) parseInfos[i++].parser = &arg; + } + + m_exeName.set( exeName ); + + auto result = InternalParseResult::ok( ParseState( ParseResultType::NoMatch, tokens ) ); + while( result.value().remainingTokens() ) { + bool tokenParsed = false; + + for( size_t i = 0; i < totalParsers; ++i ) { + auto& parseInfo = parseInfos[i]; + if( parseInfo.parser->cardinality() == 0 || parseInfo.count < parseInfo.parser->cardinality() ) { + result = parseInfo.parser->parse(exeName, result.value().remainingTokens()); + if (!result) + return result; + if (result.value().type() != ParseResultType::NoMatch) { + tokenParsed = true; + ++parseInfo.count; + break; + } + } + } + + if( result.value().type() == ParseResultType::ShortCircuitAll ) + return result; + if( !tokenParsed ) + return InternalParseResult::runtimeError( "Unrecognised token: " + result.value().remainingTokens()->token ); + } + // !TBD Check missing required options + return result; + } + }; + + template + template + auto ComposableParserImpl::operator|( T const &other ) const -> Parser { + return Parser() | static_cast( *this ) | other; + } +} // namespace detail + +// A Combined parser +using detail::Parser; + +// A parser for options +using detail::Opt; + +// A parser for arguments +using detail::Arg; + +// Wrapper for argc, argv from main() +using detail::Args; + +// Specifies the name of the executable +using detail::ExeName; + +// Convenience wrapper for option parser that specifies the help option +using detail::Help; + +// enum of result types from a parse +using detail::ParseResultType; + +// Result type for parser operation +using detail::ParserResult; + +}} // namespace Catch::clara + +// end clara.hpp +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +// Restore Clara's value for console width, if present +#ifdef CATCH_TEMP_CLARA_CONFIG_CONSOLE_WIDTH +#define CATCH_CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH CATCH_TEMP_CLARA_CONFIG_CONSOLE_WIDTH +#undef CATCH_TEMP_CLARA_CONFIG_CONSOLE_WIDTH +#endif + +// end catch_clara.h +namespace Catch { + + clara::Parser makeCommandLineParser( ConfigData& config ); + +} // end namespace Catch + +// end catch_commandline.h +#include +#include + +namespace Catch { + + clara::Parser makeCommandLineParser( ConfigData& config ) { + + using namespace clara; + + auto const setWarning = [&]( std::string const& warning ) { + auto warningSet = [&]() { + if( warning == "NoAssertions" ) + return WarnAbout::NoAssertions; + + if ( warning == "NoTests" ) + return WarnAbout::NoTests; + + return WarnAbout::Nothing; + }(); + + if (warningSet == WarnAbout::Nothing) + return ParserResult::runtimeError( "Unrecognised warning: '" + warning + "'" ); + config.warnings = static_cast( config.warnings | warningSet ); + return ParserResult::ok( ParseResultType::Matched ); + }; + auto const loadTestNamesFromFile = [&]( std::string const& filename ) { + std::ifstream f( filename.c_str() ); + if( !f.is_open() ) + return ParserResult::runtimeError( "Unable to load input file: '" + filename + "'" ); + + std::string line; + while( std::getline( f, line ) ) { + line = trim(line); + if( !line.empty() && !startsWith( line, '#' ) ) { + if( !startsWith( line, '"' ) ) + line = '"' + line + '"'; + config.testsOrTags.push_back( line + ',' ); + } + } + return ParserResult::ok( ParseResultType::Matched ); + }; + auto const setTestOrder = [&]( std::string const& order ) { + if( startsWith( "declared", order ) ) + config.runOrder = RunTests::InDeclarationOrder; + else if( startsWith( "lexical", order ) ) + config.runOrder = RunTests::InLexicographicalOrder; + else if( startsWith( "random", order ) ) + config.runOrder = RunTests::InRandomOrder; + else + return clara::ParserResult::runtimeError( "Unrecognised ordering: '" + order + "'" ); + return ParserResult::ok( ParseResultType::Matched ); + }; + auto const setRngSeed = [&]( std::string const& seed ) { + if( seed != "time" ) + return clara::detail::convertInto( seed, config.rngSeed ); + config.rngSeed = static_cast( std::time(nullptr) ); + return ParserResult::ok( ParseResultType::Matched ); + }; + auto const setColourUsage = [&]( std::string const& useColour ) { + auto mode = toLower( useColour ); + + if( mode == "yes" ) + config.useColour = UseColour::Yes; + else if( mode == "no" ) + config.useColour = UseColour::No; + else if( mode == "auto" ) + config.useColour = UseColour::Auto; + else + return ParserResult::runtimeError( "colour mode must be one of: auto, yes or no. '" + useColour + "' not recognised" ); + return ParserResult::ok( ParseResultType::Matched ); + }; + auto const setWaitForKeypress = [&]( std::string const& keypress ) { + auto keypressLc = toLower( keypress ); + if( keypressLc == "start" ) + config.waitForKeypress = WaitForKeypress::BeforeStart; + else if( keypressLc == "exit" ) + config.waitForKeypress = WaitForKeypress::BeforeExit; + else if( keypressLc == "both" ) + config.waitForKeypress = WaitForKeypress::BeforeStartAndExit; + else + return ParserResult::runtimeError( "keypress argument must be one of: start, exit or both. '" + keypress + "' not recognised" ); + return ParserResult::ok( ParseResultType::Matched ); + }; + auto const setVerbosity = [&]( std::string const& verbosity ) { + auto lcVerbosity = toLower( verbosity ); + if( lcVerbosity == "quiet" ) + config.verbosity = Verbosity::Quiet; + else if( lcVerbosity == "normal" ) + config.verbosity = Verbosity::Normal; + else if( lcVerbosity == "high" ) + config.verbosity = Verbosity::High; + else + return ParserResult::runtimeError( "Unrecognised verbosity, '" + verbosity + "'" ); + return ParserResult::ok( ParseResultType::Matched ); + }; + + auto cli + = ExeName( config.processName ) + | Help( config.showHelp ) + | Opt( config.listTests ) + ["-l"]["--list-tests"] + ( "list all/matching test cases" ) + | Opt( config.listTags ) + ["-t"]["--list-tags"] + ( "list all/matching tags" ) + | Opt( config.showSuccessfulTests ) + ["-s"]["--success"] + ( "include successful tests in output" ) + | Opt( config.shouldDebugBreak ) + ["-b"]["--break"] + ( "break into debugger on failure" ) + | Opt( config.noThrow ) + ["-e"]["--nothrow"] + ( "skip exception tests" ) + | Opt( config.showInvisibles ) + ["-i"]["--invisibles"] + ( "show invisibles (tabs, newlines)" ) + | Opt( config.outputFilename, "filename" ) + ["-o"]["--out"] + ( "output filename" ) + | Opt( config.reporterName, "name" ) + ["-r"]["--reporter"] + ( "reporter to use (defaults to console)" ) + | Opt( config.name, "name" ) + ["-n"]["--name"] + ( "suite name" ) + | Opt( [&]( bool ){ config.abortAfter = 1; } ) + ["-a"]["--abort"] + ( "abort at first failure" ) + | Opt( [&]( int x ){ config.abortAfter = x; }, "no. failures" ) + ["-x"]["--abortx"] + ( "abort after x failures" ) + | Opt( setWarning, "warning name" ) + ["-w"]["--warn"] + ( "enable warnings" ) + | Opt( [&]( bool flag ) { config.showDurations = flag ? ShowDurations::Always : ShowDurations::Never; }, "yes|no" ) + ["-d"]["--durations"] + ( "show test durations" ) + | Opt( loadTestNamesFromFile, "filename" ) + ["-f"]["--input-file"] + ( "load test names to run from a file" ) + | Opt( config.filenamesAsTags ) + ["-#"]["--filenames-as-tags"] + ( "adds a tag for the filename" ) + | Opt( config.sectionsToRun, "section name" ) + ["-c"]["--section"] + ( "specify section to run" ) + | Opt( setVerbosity, "quiet|normal|high" ) + ["-v"]["--verbosity"] + ( "set output verbosity" ) + | Opt( config.listTestNamesOnly ) + ["--list-test-names-only"] + ( "list all/matching test cases names only" ) + | Opt( config.listReporters ) + ["--list-reporters"] + ( "list all reporters" ) + | Opt( setTestOrder, "decl|lex|rand" ) + ["--order"] + ( "test case order (defaults to decl)" ) + | Opt( setRngSeed, "'time'|number" ) + ["--rng-seed"] + ( "set a specific seed for random numbers" ) + | Opt( setColourUsage, "yes|no" ) + ["--use-colour"] + ( "should output be colourised" ) + | Opt( config.libIdentify ) + ["--libidentify"] + ( "report name and version according to libidentify standard" ) + | Opt( setWaitForKeypress, "start|exit|both" ) + ["--wait-for-keypress"] + ( "waits for a keypress before exiting" ) + | Opt( config.benchmarkResolutionMultiple, "multiplier" ) + ["--benchmark-resolution-multiple"] + ( "multiple of clock resolution to run benchmarks" ) + + | Arg( config.testsOrTags, "test name|pattern|tags" ) + ( "which test or tests to use" ); + + return cli; + } + +} // end namespace Catch +// end catch_commandline.cpp +// start catch_common.cpp + +#include +#include + +namespace Catch { + + bool SourceLineInfo::empty() const noexcept { + return file[0] == '\0'; + } + bool SourceLineInfo::operator == ( SourceLineInfo const& other ) const noexcept { + return line == other.line && (file == other.file || std::strcmp(file, other.file) == 0); + } + bool SourceLineInfo::operator < ( SourceLineInfo const& other ) const noexcept { + // We can assume that the same file will usually have the same pointer. + // Thus, if the pointers are the same, there is no point in calling the strcmp + return line < other.line || ( line == other.line && file != other.file && (std::strcmp(file, other.file) < 0)); + } + + std::ostream& operator << ( std::ostream& os, SourceLineInfo const& info ) { +#ifndef __GNUG__ + os << info.file << '(' << info.line << ')'; +#else + os << info.file << ':' << info.line; +#endif + return os; + } + + std::string StreamEndStop::operator+() const { + return std::string(); + } + + NonCopyable::NonCopyable() = default; + NonCopyable::~NonCopyable() = default; + +} +// end catch_common.cpp +// start catch_config.cpp + +namespace Catch { + + Config::Config( ConfigData const& data ) + : m_data( data ), + m_stream( openStream() ) + { + TestSpecParser parser(ITagAliasRegistry::get()); + if (data.testsOrTags.empty()) { + parser.parse("~[.]"); // All not hidden tests + } + else { + m_hasTestFilters = true; + for( auto const& testOrTags : data.testsOrTags ) + parser.parse( testOrTags ); + } + m_testSpec = parser.testSpec(); + } + + std::string const& Config::getFilename() const { + return m_data.outputFilename ; + } + + bool Config::listTests() const { return m_data.listTests; } + bool Config::listTestNamesOnly() const { return m_data.listTestNamesOnly; } + bool Config::listTags() const { return m_data.listTags; } + bool Config::listReporters() const { return m_data.listReporters; } + + std::string Config::getProcessName() const { return m_data.processName; } + std::string const& Config::getReporterName() const { return m_data.reporterName; } + + std::vector const& Config::getTestsOrTags() const { return m_data.testsOrTags; } + std::vector const& Config::getSectionsToRun() const { return m_data.sectionsToRun; } + + TestSpec const& Config::testSpec() const { return m_testSpec; } + bool Config::hasTestFilters() const { return m_hasTestFilters; } + + bool Config::showHelp() const { return m_data.showHelp; } + + // IConfig interface + bool Config::allowThrows() const { return !m_data.noThrow; } + std::ostream& Config::stream() const { return m_stream->stream(); } + std::string Config::name() const { return m_data.name.empty() ? m_data.processName : m_data.name; } + bool Config::includeSuccessfulResults() const { return m_data.showSuccessfulTests; } + bool Config::warnAboutMissingAssertions() const { return !!(m_data.warnings & WarnAbout::NoAssertions); } + bool Config::warnAboutNoTests() const { return !!(m_data.warnings & WarnAbout::NoTests); } + ShowDurations::OrNot Config::showDurations() const { return m_data.showDurations; } + RunTests::InWhatOrder Config::runOrder() const { return m_data.runOrder; } + unsigned int Config::rngSeed() const { return m_data.rngSeed; } + int Config::benchmarkResolutionMultiple() const { return m_data.benchmarkResolutionMultiple; } + UseColour::YesOrNo Config::useColour() const { return m_data.useColour; } + bool Config::shouldDebugBreak() const { return m_data.shouldDebugBreak; } + int Config::abortAfter() const { return m_data.abortAfter; } + bool Config::showInvisibles() const { return m_data.showInvisibles; } + Verbosity Config::verbosity() const { return m_data.verbosity; } + + IStream const* Config::openStream() { + return Catch::makeStream(m_data.outputFilename); + } + +} // end namespace Catch +// end catch_config.cpp +// start catch_console_colour.cpp + +#if defined(__clang__) +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wexit-time-destructors" +#endif + +// start catch_errno_guard.h + +namespace Catch { + + class ErrnoGuard { + public: + ErrnoGuard(); + ~ErrnoGuard(); + private: + int m_oldErrno; + }; + +} + +// end catch_errno_guard.h +#include + +namespace Catch { + namespace { + + struct IColourImpl { + virtual ~IColourImpl() = default; + virtual void use( Colour::Code _colourCode ) = 0; + }; + + struct NoColourImpl : IColourImpl { + void use( Colour::Code ) {} + + static IColourImpl* instance() { + static NoColourImpl s_instance; + return &s_instance; + } + }; + + } // anon namespace +} // namespace Catch + +#if !defined( CATCH_CONFIG_COLOUR_NONE ) && !defined( CATCH_CONFIG_COLOUR_WINDOWS ) && !defined( CATCH_CONFIG_COLOUR_ANSI ) +# ifdef CATCH_PLATFORM_WINDOWS +# define CATCH_CONFIG_COLOUR_WINDOWS +# else +# define CATCH_CONFIG_COLOUR_ANSI +# endif +#endif + +#if defined ( CATCH_CONFIG_COLOUR_WINDOWS ) ///////////////////////////////////////// + +namespace Catch { +namespace { + + class Win32ColourImpl : public IColourImpl { + public: + Win32ColourImpl() : stdoutHandle( GetStdHandle(STD_OUTPUT_HANDLE) ) + { + CONSOLE_SCREEN_BUFFER_INFO csbiInfo; + GetConsoleScreenBufferInfo( stdoutHandle, &csbiInfo ); + originalForegroundAttributes = csbiInfo.wAttributes & ~( BACKGROUND_GREEN | BACKGROUND_RED | BACKGROUND_BLUE | BACKGROUND_INTENSITY ); + originalBackgroundAttributes = csbiInfo.wAttributes & ~( FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE | FOREGROUND_INTENSITY ); + } + + virtual void use( Colour::Code _colourCode ) override { + switch( _colourCode ) { + case Colour::None: return setTextAttribute( originalForegroundAttributes ); + case Colour::White: return setTextAttribute( FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE ); + case Colour::Red: return setTextAttribute( FOREGROUND_RED ); + case Colour::Green: return setTextAttribute( FOREGROUND_GREEN ); + case Colour::Blue: return setTextAttribute( FOREGROUND_BLUE ); + case Colour::Cyan: return setTextAttribute( FOREGROUND_BLUE | FOREGROUND_GREEN ); + case Colour::Yellow: return setTextAttribute( FOREGROUND_RED | FOREGROUND_GREEN ); + case Colour::Grey: return setTextAttribute( 0 ); + + case Colour::LightGrey: return setTextAttribute( FOREGROUND_INTENSITY ); + case Colour::BrightRed: return setTextAttribute( FOREGROUND_INTENSITY | FOREGROUND_RED ); + case Colour::BrightGreen: return setTextAttribute( FOREGROUND_INTENSITY | FOREGROUND_GREEN ); + case Colour::BrightWhite: return setTextAttribute( FOREGROUND_INTENSITY | FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE ); + case Colour::BrightYellow: return setTextAttribute( FOREGROUND_INTENSITY | FOREGROUND_RED | FOREGROUND_GREEN ); + + case Colour::Bright: CATCH_INTERNAL_ERROR( "not a colour" ); + + default: + CATCH_ERROR( "Unknown colour requested" ); + } + } + + private: + void setTextAttribute( WORD _textAttribute ) { + SetConsoleTextAttribute( stdoutHandle, _textAttribute | originalBackgroundAttributes ); + } + HANDLE stdoutHandle; + WORD originalForegroundAttributes; + WORD originalBackgroundAttributes; + }; + + IColourImpl* platformColourInstance() { + static Win32ColourImpl s_instance; + + IConfigPtr config = getCurrentContext().getConfig(); + UseColour::YesOrNo colourMode = config + ? config->useColour() + : UseColour::Auto; + if( colourMode == UseColour::Auto ) + colourMode = UseColour::Yes; + return colourMode == UseColour::Yes + ? &s_instance + : NoColourImpl::instance(); + } + +} // end anon namespace +} // end namespace Catch + +#elif defined( CATCH_CONFIG_COLOUR_ANSI ) ////////////////////////////////////// + +#include + +namespace Catch { +namespace { + + // use POSIX/ ANSI console terminal codes + // Thanks to Adam Strzelecki for original contribution + // (http://github.com/nanoant) + // https://github.com/philsquared/Catch/pull/131 + class PosixColourImpl : public IColourImpl { + public: + virtual void use( Colour::Code _colourCode ) override { + switch( _colourCode ) { + case Colour::None: + case Colour::White: return setColour( "[0m" ); + case Colour::Red: return setColour( "[0;31m" ); + case Colour::Green: return setColour( "[0;32m" ); + case Colour::Blue: return setColour( "[0;34m" ); + case Colour::Cyan: return setColour( "[0;36m" ); + case Colour::Yellow: return setColour( "[0;33m" ); + case Colour::Grey: return setColour( "[1;30m" ); + + case Colour::LightGrey: return setColour( "[0;37m" ); + case Colour::BrightRed: return setColour( "[1;31m" ); + case Colour::BrightGreen: return setColour( "[1;32m" ); + case Colour::BrightWhite: return setColour( "[1;37m" ); + case Colour::BrightYellow: return setColour( "[1;33m" ); + + case Colour::Bright: CATCH_INTERNAL_ERROR( "not a colour" ); + default: CATCH_INTERNAL_ERROR( "Unknown colour requested" ); + } + } + static IColourImpl* instance() { + static PosixColourImpl s_instance; + return &s_instance; + } + + private: + void setColour( const char* _escapeCode ) { + Catch::cout() << '\033' << _escapeCode; + } + }; + + bool useColourOnPlatform() { + return +#ifdef CATCH_PLATFORM_MAC + !isDebuggerActive() && +#endif +#if !(defined(__DJGPP__) && defined(__STRICT_ANSI__)) + isatty(STDOUT_FILENO) +#else + false +#endif + ; + } + IColourImpl* platformColourInstance() { + ErrnoGuard guard; + IConfigPtr config = getCurrentContext().getConfig(); + UseColour::YesOrNo colourMode = config + ? config->useColour() + : UseColour::Auto; + if( colourMode == UseColour::Auto ) + colourMode = useColourOnPlatform() + ? UseColour::Yes + : UseColour::No; + return colourMode == UseColour::Yes + ? PosixColourImpl::instance() + : NoColourImpl::instance(); + } + +} // end anon namespace +} // end namespace Catch + +#else // not Windows or ANSI /////////////////////////////////////////////// + +namespace Catch { + + static IColourImpl* platformColourInstance() { return NoColourImpl::instance(); } + +} // end namespace Catch + +#endif // Windows/ ANSI/ None + +namespace Catch { + + Colour::Colour( Code _colourCode ) { use( _colourCode ); } + Colour::Colour( Colour&& rhs ) noexcept { + m_moved = rhs.m_moved; + rhs.m_moved = true; + } + Colour& Colour::operator=( Colour&& rhs ) noexcept { + m_moved = rhs.m_moved; + rhs.m_moved = true; + return *this; + } + + Colour::~Colour(){ if( !m_moved ) use( None ); } + + void Colour::use( Code _colourCode ) { + static IColourImpl* impl = platformColourInstance(); + impl->use( _colourCode ); + } + + std::ostream& operator << ( std::ostream& os, Colour const& ) { + return os; + } + +} // end namespace Catch + +#if defined(__clang__) +# pragma clang diagnostic pop +#endif + +// end catch_console_colour.cpp +// start catch_context.cpp + +namespace Catch { + + class Context : public IMutableContext, NonCopyable { + + public: // IContext + virtual IResultCapture* getResultCapture() override { + return m_resultCapture; + } + virtual IRunner* getRunner() override { + return m_runner; + } + + virtual IConfigPtr const& getConfig() const override { + return m_config; + } + + virtual ~Context() override; + + public: // IMutableContext + virtual void setResultCapture( IResultCapture* resultCapture ) override { + m_resultCapture = resultCapture; + } + virtual void setRunner( IRunner* runner ) override { + m_runner = runner; + } + virtual void setConfig( IConfigPtr const& config ) override { + m_config = config; + } + + friend IMutableContext& getCurrentMutableContext(); + + private: + IConfigPtr m_config; + IRunner* m_runner = nullptr; + IResultCapture* m_resultCapture = nullptr; + }; + + IMutableContext *IMutableContext::currentContext = nullptr; + + void IMutableContext::createContext() + { + currentContext = new Context(); + } + + void cleanUpContext() { + delete IMutableContext::currentContext; + IMutableContext::currentContext = nullptr; + } + IContext::~IContext() = default; + IMutableContext::~IMutableContext() = default; + Context::~Context() = default; +} +// end catch_context.cpp +// start catch_debug_console.cpp + +// start catch_debug_console.h + +#include + +namespace Catch { + void writeToDebugConsole( std::string const& text ); +} + +// end catch_debug_console.h +#ifdef CATCH_PLATFORM_WINDOWS + + namespace Catch { + void writeToDebugConsole( std::string const& text ) { + ::OutputDebugStringA( text.c_str() ); + } + } + +#else + + namespace Catch { + void writeToDebugConsole( std::string const& text ) { + // !TBD: Need a version for Mac/ XCode and other IDEs + Catch::cout() << text; + } + } + +#endif // Platform +// end catch_debug_console.cpp +// start catch_debugger.cpp + +#ifdef CATCH_PLATFORM_MAC + +# include +# include +# include +# include +# include +# include +# include + +namespace Catch { + + // The following function is taken directly from the following technical note: + // http://developer.apple.com/library/mac/#qa/qa2004/qa1361.html + + // Returns true if the current process is being debugged (either + // running under the debugger or has a debugger attached post facto). + bool isDebuggerActive(){ + + int mib[4]; + struct kinfo_proc info; + std::size_t size; + + // Initialize the flags so that, if sysctl fails for some bizarre + // reason, we get a predictable result. + + info.kp_proc.p_flag = 0; + + // Initialize mib, which tells sysctl the info we want, in this case + // we're looking for information about a specific process ID. + + mib[0] = CTL_KERN; + mib[1] = KERN_PROC; + mib[2] = KERN_PROC_PID; + mib[3] = getpid(); + + // Call sysctl. + + size = sizeof(info); + if( sysctl(mib, sizeof(mib) / sizeof(*mib), &info, &size, nullptr, 0) != 0 ) { + Catch::cerr() << "\n** Call to sysctl failed - unable to determine if debugger is active **\n" << std::endl; + return false; + } + + // We're being debugged if the P_TRACED flag is set. + + return ( (info.kp_proc.p_flag & P_TRACED) != 0 ); + } + } // namespace Catch + +#elif defined(CATCH_PLATFORM_LINUX) + #include + #include + + namespace Catch{ + // The standard POSIX way of detecting a debugger is to attempt to + // ptrace() the process, but this needs to be done from a child and not + // this process itself to still allow attaching to this process later + // if wanted, so is rather heavy. Under Linux we have the PID of the + // "debugger" (which doesn't need to be gdb, of course, it could also + // be strace, for example) in /proc/$PID/status, so just get it from + // there instead. + bool isDebuggerActive(){ + // Libstdc++ has a bug, where std::ifstream sets errno to 0 + // This way our users can properly assert over errno values + ErrnoGuard guard; + std::ifstream in("/proc/self/status"); + for( std::string line; std::getline(in, line); ) { + static const int PREFIX_LEN = 11; + if( line.compare(0, PREFIX_LEN, "TracerPid:\t") == 0 ) { + // We're traced if the PID is not 0 and no other PID starts + // with 0 digit, so it's enough to check for just a single + // character. + return line.length() > PREFIX_LEN && line[PREFIX_LEN] != '0'; + } + } + + return false; + } + } // namespace Catch +#elif defined(_MSC_VER) + extern "C" __declspec(dllimport) int __stdcall IsDebuggerPresent(); + namespace Catch { + bool isDebuggerActive() { + return IsDebuggerPresent() != 0; + } + } +#elif defined(__MINGW32__) + extern "C" __declspec(dllimport) int __stdcall IsDebuggerPresent(); + namespace Catch { + bool isDebuggerActive() { + return IsDebuggerPresent() != 0; + } + } +#else + namespace Catch { + bool isDebuggerActive() { return false; } + } +#endif // Platform +// end catch_debugger.cpp +// start catch_decomposer.cpp + +namespace Catch { + + ITransientExpression::~ITransientExpression() = default; + + void formatReconstructedExpression( std::ostream &os, std::string const& lhs, StringRef op, std::string const& rhs ) { + if( lhs.size() + rhs.size() < 40 && + lhs.find('\n') == std::string::npos && + rhs.find('\n') == std::string::npos ) + os << lhs << " " << op << " " << rhs; + else + os << lhs << "\n" << op << "\n" << rhs; + } +} +// end catch_decomposer.cpp +// start catch_enforce.cpp + +namespace Catch { +#if defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) && !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS_CUSTOM_HANDLER) + [[noreturn]] + void throw_exception(std::exception const& e) { + Catch::cerr() << "Catch will terminate because it needed to throw an exception.\n" + << "The message was: " << e.what() << '\n'; + std::terminate(); + } +#endif +} // namespace Catch; +// end catch_enforce.cpp +// start catch_errno_guard.cpp + +#include + +namespace Catch { + ErrnoGuard::ErrnoGuard():m_oldErrno(errno){} + ErrnoGuard::~ErrnoGuard() { errno = m_oldErrno; } +} +// end catch_errno_guard.cpp +// start catch_exception_translator_registry.cpp + +// start catch_exception_translator_registry.h + +#include +#include +#include + +namespace Catch { + + class ExceptionTranslatorRegistry : public IExceptionTranslatorRegistry { + public: + ~ExceptionTranslatorRegistry(); + virtual void registerTranslator( const IExceptionTranslator* translator ); + virtual std::string translateActiveException() const override; + std::string tryTranslators() const; + + private: + std::vector> m_translators; + }; +} + +// end catch_exception_translator_registry.h +#ifdef __OBJC__ +#import "Foundation/Foundation.h" +#endif + +namespace Catch { + + ExceptionTranslatorRegistry::~ExceptionTranslatorRegistry() { + } + + void ExceptionTranslatorRegistry::registerTranslator( const IExceptionTranslator* translator ) { + m_translators.push_back( std::unique_ptr( translator ) ); + } + +#if !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) + std::string ExceptionTranslatorRegistry::translateActiveException() const { + try { +#ifdef __OBJC__ + // In Objective-C try objective-c exceptions first + @try { + return tryTranslators(); + } + @catch (NSException *exception) { + return Catch::Detail::stringify( [exception description] ); + } +#else + // Compiling a mixed mode project with MSVC means that CLR + // exceptions will be caught in (...) as well. However, these + // do not fill-in std::current_exception and thus lead to crash + // when attempting rethrow. + // /EHa switch also causes structured exceptions to be caught + // here, but they fill-in current_exception properly, so + // at worst the output should be a little weird, instead of + // causing a crash. + if (std::current_exception() == nullptr) { + return "Non C++ exception. Possibly a CLR exception."; + } + return tryTranslators(); +#endif + } + catch( TestFailureException& ) { + std::rethrow_exception(std::current_exception()); + } + catch( std::exception& ex ) { + return ex.what(); + } + catch( std::string& msg ) { + return msg; + } + catch( const char* msg ) { + return msg; + } + catch(...) { + return "Unknown exception"; + } + } + +#else // ^^ Exceptions are enabled // Exceptions are disabled vv + std::string ExceptionTranslatorRegistry::translateActiveException() const { + CATCH_INTERNAL_ERROR("Attempted to translate active exception under CATCH_CONFIG_DISABLE_EXCEPTIONS!"); + } +#endif + + std::string ExceptionTranslatorRegistry::tryTranslators() const { + if( m_translators.empty() ) + std::rethrow_exception(std::current_exception()); + else + return m_translators[0]->translate( m_translators.begin()+1, m_translators.end() ); + } +} +// end catch_exception_translator_registry.cpp +// start catch_fatal_condition.cpp + +#if defined(__GNUC__) +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wmissing-field-initializers" +#endif + +#if defined( CATCH_CONFIG_WINDOWS_SEH ) || defined( CATCH_CONFIG_POSIX_SIGNALS ) + +namespace { + // Report the error condition + void reportFatal( char const * const message ) { + Catch::getCurrentContext().getResultCapture()->handleFatalErrorCondition( message ); + } +} + +#endif // signals/SEH handling + +#if defined( CATCH_CONFIG_WINDOWS_SEH ) + +namespace Catch { + struct SignalDefs { DWORD id; const char* name; }; + + // There is no 1-1 mapping between signals and windows exceptions. + // Windows can easily distinguish between SO and SigSegV, + // but SigInt, SigTerm, etc are handled differently. + static SignalDefs signalDefs[] = { + { EXCEPTION_ILLEGAL_INSTRUCTION, "SIGILL - Illegal instruction signal" }, + { EXCEPTION_STACK_OVERFLOW, "SIGSEGV - Stack overflow" }, + { EXCEPTION_ACCESS_VIOLATION, "SIGSEGV - Segmentation violation signal" }, + { EXCEPTION_INT_DIVIDE_BY_ZERO, "Divide by zero error" }, + }; + + LONG CALLBACK FatalConditionHandler::handleVectoredException(PEXCEPTION_POINTERS ExceptionInfo) { + for (auto const& def : signalDefs) { + if (ExceptionInfo->ExceptionRecord->ExceptionCode == def.id) { + reportFatal(def.name); + } + } + // If its not an exception we care about, pass it along. + // This stops us from eating debugger breaks etc. + return EXCEPTION_CONTINUE_SEARCH; + } + + FatalConditionHandler::FatalConditionHandler() { + isSet = true; + // 32k seems enough for Catch to handle stack overflow, + // but the value was found experimentally, so there is no strong guarantee + guaranteeSize = 32 * 1024; + exceptionHandlerHandle = nullptr; + // Register as first handler in current chain + exceptionHandlerHandle = AddVectoredExceptionHandler(1, handleVectoredException); + // Pass in guarantee size to be filled + SetThreadStackGuarantee(&guaranteeSize); + } + + void FatalConditionHandler::reset() { + if (isSet) { + RemoveVectoredExceptionHandler(exceptionHandlerHandle); + SetThreadStackGuarantee(&guaranteeSize); + exceptionHandlerHandle = nullptr; + isSet = false; + } + } + + FatalConditionHandler::~FatalConditionHandler() { + reset(); + } + +bool FatalConditionHandler::isSet = false; +ULONG FatalConditionHandler::guaranteeSize = 0; +PVOID FatalConditionHandler::exceptionHandlerHandle = nullptr; + +} // namespace Catch + +#elif defined( CATCH_CONFIG_POSIX_SIGNALS ) + +namespace Catch { + + struct SignalDefs { + int id; + const char* name; + }; + + // 32kb for the alternate stack seems to be sufficient. However, this value + // is experimentally determined, so that's not guaranteed. + constexpr static std::size_t sigStackSize = 32768 >= MINSIGSTKSZ ? 32768 : MINSIGSTKSZ; + + static SignalDefs signalDefs[] = { + { SIGINT, "SIGINT - Terminal interrupt signal" }, + { SIGILL, "SIGILL - Illegal instruction signal" }, + { SIGFPE, "SIGFPE - Floating point error signal" }, + { SIGSEGV, "SIGSEGV - Segmentation violation signal" }, + { SIGTERM, "SIGTERM - Termination request signal" }, + { SIGABRT, "SIGABRT - Abort (abnormal termination) signal" } + }; + + void FatalConditionHandler::handleSignal( int sig ) { + char const * name = ""; + for (auto const& def : signalDefs) { + if (sig == def.id) { + name = def.name; + break; + } + } + reset(); + reportFatal(name); + raise( sig ); + } + + FatalConditionHandler::FatalConditionHandler() { + isSet = true; + stack_t sigStack; + sigStack.ss_sp = altStackMem; + sigStack.ss_size = sigStackSize; + sigStack.ss_flags = 0; + sigaltstack(&sigStack, &oldSigStack); + struct sigaction sa = { }; + + sa.sa_handler = handleSignal; + sa.sa_flags = SA_ONSTACK; + for (std::size_t i = 0; i < sizeof(signalDefs)/sizeof(SignalDefs); ++i) { + sigaction(signalDefs[i].id, &sa, &oldSigActions[i]); + } + } + + FatalConditionHandler::~FatalConditionHandler() { + reset(); + } + + void FatalConditionHandler::reset() { + if( isSet ) { + // Set signals back to previous values -- hopefully nobody overwrote them in the meantime + for( std::size_t i = 0; i < sizeof(signalDefs)/sizeof(SignalDefs); ++i ) { + sigaction(signalDefs[i].id, &oldSigActions[i], nullptr); + } + // Return the old stack + sigaltstack(&oldSigStack, nullptr); + isSet = false; + } + } + + bool FatalConditionHandler::isSet = false; + struct sigaction FatalConditionHandler::oldSigActions[sizeof(signalDefs)/sizeof(SignalDefs)] = {}; + stack_t FatalConditionHandler::oldSigStack = {}; + char FatalConditionHandler::altStackMem[sigStackSize] = {}; + +} // namespace Catch + +#else + +namespace Catch { + void FatalConditionHandler::reset() {} +} + +#endif // signals/SEH handling + +#if defined(__GNUC__) +# pragma GCC diagnostic pop +#endif +// end catch_fatal_condition.cpp +// start catch_generators.cpp + +// start catch_random_number_generator.h + +#include +#include + +namespace Catch { + + struct IConfig; + + std::mt19937& rng(); + void seedRng( IConfig const& config ); + unsigned int rngSeed(); + +} + +// end catch_random_number_generator.h +#include +#include + +namespace Catch { + +IGeneratorTracker::~IGeneratorTracker() {} + +namespace Generators { + + GeneratorBase::~GeneratorBase() {} + + std::vector randomiseIndices( size_t selectionSize, size_t sourceSize ) { + + assert( selectionSize <= sourceSize ); + std::vector indices; + indices.reserve( selectionSize ); + std::uniform_int_distribution uid( 0, sourceSize-1 ); + + std::set seen; + // !TBD: improve this algorithm + while( indices.size() < selectionSize ) { + auto index = uid( rng() ); + if( seen.insert( index ).second ) + indices.push_back( index ); + } + return indices; + } + + auto acquireGeneratorTracker( SourceLineInfo const& lineInfo ) -> IGeneratorTracker& { + return getResultCapture().acquireGeneratorTracker( lineInfo ); + } + + template<> + auto all() -> Generator { + return range( std::numeric_limits::min(), std::numeric_limits::max() ); + } + +} // namespace Generators +} // namespace Catch +// end catch_generators.cpp +// start catch_interfaces_capture.cpp + +namespace Catch { + IResultCapture::~IResultCapture() = default; +} +// end catch_interfaces_capture.cpp +// start catch_interfaces_config.cpp + +namespace Catch { + IConfig::~IConfig() = default; +} +// end catch_interfaces_config.cpp +// start catch_interfaces_exception.cpp + +namespace Catch { + IExceptionTranslator::~IExceptionTranslator() = default; + IExceptionTranslatorRegistry::~IExceptionTranslatorRegistry() = default; +} +// end catch_interfaces_exception.cpp +// start catch_interfaces_registry_hub.cpp + +namespace Catch { + IRegistryHub::~IRegistryHub() = default; + IMutableRegistryHub::~IMutableRegistryHub() = default; +} +// end catch_interfaces_registry_hub.cpp +// start catch_interfaces_reporter.cpp + +// start catch_reporter_listening.h + +namespace Catch { + + class ListeningReporter : public IStreamingReporter { + using Reporters = std::vector; + Reporters m_listeners; + IStreamingReporterPtr m_reporter = nullptr; + ReporterPreferences m_preferences; + + public: + ListeningReporter(); + + void addListener( IStreamingReporterPtr&& listener ); + void addReporter( IStreamingReporterPtr&& reporter ); + + public: // IStreamingReporter + + ReporterPreferences getPreferences() const override; + + void noMatchingTestCases( std::string const& spec ) override; + + static std::set getSupportedVerbosities(); + + void benchmarkStarting( BenchmarkInfo const& benchmarkInfo ) override; + void benchmarkEnded( BenchmarkStats const& benchmarkStats ) override; + + void testRunStarting( TestRunInfo const& testRunInfo ) override; + void testGroupStarting( GroupInfo const& groupInfo ) override; + void testCaseStarting( TestCaseInfo const& testInfo ) override; + void sectionStarting( SectionInfo const& sectionInfo ) override; + void assertionStarting( AssertionInfo const& assertionInfo ) override; + + // The return value indicates if the messages buffer should be cleared: + bool assertionEnded( AssertionStats const& assertionStats ) override; + void sectionEnded( SectionStats const& sectionStats ) override; + void testCaseEnded( TestCaseStats const& testCaseStats ) override; + void testGroupEnded( TestGroupStats const& testGroupStats ) override; + void testRunEnded( TestRunStats const& testRunStats ) override; + + void skipTest( TestCaseInfo const& testInfo ) override; + bool isMulti() const override; + + }; + +} // end namespace Catch + +// end catch_reporter_listening.h +namespace Catch { + + ReporterConfig::ReporterConfig( IConfigPtr const& _fullConfig ) + : m_stream( &_fullConfig->stream() ), m_fullConfig( _fullConfig ) {} + + ReporterConfig::ReporterConfig( IConfigPtr const& _fullConfig, std::ostream& _stream ) + : m_stream( &_stream ), m_fullConfig( _fullConfig ) {} + + std::ostream& ReporterConfig::stream() const { return *m_stream; } + IConfigPtr ReporterConfig::fullConfig() const { return m_fullConfig; } + + TestRunInfo::TestRunInfo( std::string const& _name ) : name( _name ) {} + + GroupInfo::GroupInfo( std::string const& _name, + std::size_t _groupIndex, + std::size_t _groupsCount ) + : name( _name ), + groupIndex( _groupIndex ), + groupsCounts( _groupsCount ) + {} + + AssertionStats::AssertionStats( AssertionResult const& _assertionResult, + std::vector const& _infoMessages, + Totals const& _totals ) + : assertionResult( _assertionResult ), + infoMessages( _infoMessages ), + totals( _totals ) + { + assertionResult.m_resultData.lazyExpression.m_transientExpression = _assertionResult.m_resultData.lazyExpression.m_transientExpression; + + if( assertionResult.hasMessage() ) { + // Copy message into messages list. + // !TBD This should have been done earlier, somewhere + MessageBuilder builder( assertionResult.getTestMacroName(), assertionResult.getSourceInfo(), assertionResult.getResultType() ); + builder << assertionResult.getMessage(); + builder.m_info.message = builder.m_stream.str(); + + infoMessages.push_back( builder.m_info ); + } + } + + AssertionStats::~AssertionStats() = default; + + SectionStats::SectionStats( SectionInfo const& _sectionInfo, + Counts const& _assertions, + double _durationInSeconds, + bool _missingAssertions ) + : sectionInfo( _sectionInfo ), + assertions( _assertions ), + durationInSeconds( _durationInSeconds ), + missingAssertions( _missingAssertions ) + {} + + SectionStats::~SectionStats() = default; + + TestCaseStats::TestCaseStats( TestCaseInfo const& _testInfo, + Totals const& _totals, + std::string const& _stdOut, + std::string const& _stdErr, + bool _aborting ) + : testInfo( _testInfo ), + totals( _totals ), + stdOut( _stdOut ), + stdErr( _stdErr ), + aborting( _aborting ) + {} + + TestCaseStats::~TestCaseStats() = default; + + TestGroupStats::TestGroupStats( GroupInfo const& _groupInfo, + Totals const& _totals, + bool _aborting ) + : groupInfo( _groupInfo ), + totals( _totals ), + aborting( _aborting ) + {} + + TestGroupStats::TestGroupStats( GroupInfo const& _groupInfo ) + : groupInfo( _groupInfo ), + aborting( false ) + {} + + TestGroupStats::~TestGroupStats() = default; + + TestRunStats::TestRunStats( TestRunInfo const& _runInfo, + Totals const& _totals, + bool _aborting ) + : runInfo( _runInfo ), + totals( _totals ), + aborting( _aborting ) + {} + + TestRunStats::~TestRunStats() = default; + + void IStreamingReporter::fatalErrorEncountered( StringRef ) {} + bool IStreamingReporter::isMulti() const { return false; } + + IReporterFactory::~IReporterFactory() = default; + IReporterRegistry::~IReporterRegistry() = default; + +} // end namespace Catch +// end catch_interfaces_reporter.cpp +// start catch_interfaces_runner.cpp + +namespace Catch { + IRunner::~IRunner() = default; +} +// end catch_interfaces_runner.cpp +// start catch_interfaces_testcase.cpp + +namespace Catch { + ITestInvoker::~ITestInvoker() = default; + ITestCaseRegistry::~ITestCaseRegistry() = default; +} +// end catch_interfaces_testcase.cpp +// start catch_leak_detector.cpp + +#ifdef CATCH_CONFIG_WINDOWS_CRTDBG +#include + +namespace Catch { + + LeakDetector::LeakDetector() { + int flag = _CrtSetDbgFlag(_CRTDBG_REPORT_FLAG); + flag |= _CRTDBG_LEAK_CHECK_DF; + flag |= _CRTDBG_ALLOC_MEM_DF; + _CrtSetDbgFlag(flag); + _CrtSetReportMode(_CRT_WARN, _CRTDBG_MODE_FILE | _CRTDBG_MODE_DEBUG); + _CrtSetReportFile(_CRT_WARN, _CRTDBG_FILE_STDERR); + // Change this to leaking allocation's number to break there + _CrtSetBreakAlloc(-1); + } +} + +#else + + Catch::LeakDetector::LeakDetector() {} + +#endif +// end catch_leak_detector.cpp +// start catch_list.cpp + +// start catch_list.h + +#include + +namespace Catch { + + std::size_t listTests( Config const& config ); + + std::size_t listTestsNamesOnly( Config const& config ); + + struct TagInfo { + void add( std::string const& spelling ); + std::string all() const; + + std::set spellings; + std::size_t count = 0; + }; + + std::size_t listTags( Config const& config ); + + std::size_t listReporters( Config const& /*config*/ ); + + Option list( Config const& config ); + +} // end namespace Catch + +// end catch_list.h +// start catch_text.h + +namespace Catch { + using namespace clara::TextFlow; +} + +// end catch_text.h +#include +#include +#include + +namespace Catch { + + std::size_t listTests( Config const& config ) { + TestSpec testSpec = config.testSpec(); + if( config.hasTestFilters() ) + Catch::cout() << "Matching test cases:\n"; + else { + Catch::cout() << "All available test cases:\n"; + } + + auto matchedTestCases = filterTests( getAllTestCasesSorted( config ), testSpec, config ); + for( auto const& testCaseInfo : matchedTestCases ) { + Colour::Code colour = testCaseInfo.isHidden() + ? Colour::SecondaryText + : Colour::None; + Colour colourGuard( colour ); + + Catch::cout() << Column( testCaseInfo.name ).initialIndent( 2 ).indent( 4 ) << "\n"; + if( config.verbosity() >= Verbosity::High ) { + Catch::cout() << Column( Catch::Detail::stringify( testCaseInfo.lineInfo ) ).indent(4) << std::endl; + std::string description = testCaseInfo.description; + if( description.empty() ) + description = "(NO DESCRIPTION)"; + Catch::cout() << Column( description ).indent(4) << std::endl; + } + if( !testCaseInfo.tags.empty() ) + Catch::cout() << Column( testCaseInfo.tagsAsString() ).indent( 6 ) << "\n"; + } + + if( !config.hasTestFilters() ) + Catch::cout() << pluralise( matchedTestCases.size(), "test case" ) << '\n' << std::endl; + else + Catch::cout() << pluralise( matchedTestCases.size(), "matching test case" ) << '\n' << std::endl; + return matchedTestCases.size(); + } + + std::size_t listTestsNamesOnly( Config const& config ) { + TestSpec testSpec = config.testSpec(); + std::size_t matchedTests = 0; + std::vector matchedTestCases = filterTests( getAllTestCasesSorted( config ), testSpec, config ); + for( auto const& testCaseInfo : matchedTestCases ) { + matchedTests++; + if( startsWith( testCaseInfo.name, '#' ) ) + Catch::cout() << '"' << testCaseInfo.name << '"'; + else + Catch::cout() << testCaseInfo.name; + if ( config.verbosity() >= Verbosity::High ) + Catch::cout() << "\t@" << testCaseInfo.lineInfo; + Catch::cout() << std::endl; + } + return matchedTests; + } + + void TagInfo::add( std::string const& spelling ) { + ++count; + spellings.insert( spelling ); + } + + std::string TagInfo::all() const { + std::string out; + for( auto const& spelling : spellings ) + out += "[" + spelling + "]"; + return out; + } + + std::size_t listTags( Config const& config ) { + TestSpec testSpec = config.testSpec(); + if( config.hasTestFilters() ) + Catch::cout() << "Tags for matching test cases:\n"; + else { + Catch::cout() << "All available tags:\n"; + } + + std::map tagCounts; + + std::vector matchedTestCases = filterTests( getAllTestCasesSorted( config ), testSpec, config ); + for( auto const& testCase : matchedTestCases ) { + for( auto const& tagName : testCase.getTestCaseInfo().tags ) { + std::string lcaseTagName = toLower( tagName ); + auto countIt = tagCounts.find( lcaseTagName ); + if( countIt == tagCounts.end() ) + countIt = tagCounts.insert( std::make_pair( lcaseTagName, TagInfo() ) ).first; + countIt->second.add( tagName ); + } + } + + for( auto const& tagCount : tagCounts ) { + ReusableStringStream rss; + rss << " " << std::setw(2) << tagCount.second.count << " "; + auto str = rss.str(); + auto wrapper = Column( tagCount.second.all() ) + .initialIndent( 0 ) + .indent( str.size() ) + .width( CATCH_CONFIG_CONSOLE_WIDTH-10 ); + Catch::cout() << str << wrapper << '\n'; + } + Catch::cout() << pluralise( tagCounts.size(), "tag" ) << '\n' << std::endl; + return tagCounts.size(); + } + + std::size_t listReporters( Config const& /*config*/ ) { + Catch::cout() << "Available reporters:\n"; + IReporterRegistry::FactoryMap const& factories = getRegistryHub().getReporterRegistry().getFactories(); + std::size_t maxNameLen = 0; + for( auto const& factoryKvp : factories ) + maxNameLen = (std::max)( maxNameLen, factoryKvp.first.size() ); + + for( auto const& factoryKvp : factories ) { + Catch::cout() + << Column( factoryKvp.first + ":" ) + .indent(2) + .width( 5+maxNameLen ) + + Column( factoryKvp.second->getDescription() ) + .initialIndent(0) + .indent(2) + .width( CATCH_CONFIG_CONSOLE_WIDTH - maxNameLen-8 ) + << "\n"; + } + Catch::cout() << std::endl; + return factories.size(); + } + + Option list( Config const& config ) { + Option listedCount; + if( config.listTests() ) + listedCount = listedCount.valueOr(0) + listTests( config ); + if( config.listTestNamesOnly() ) + listedCount = listedCount.valueOr(0) + listTestsNamesOnly( config ); + if( config.listTags() ) + listedCount = listedCount.valueOr(0) + listTags( config ); + if( config.listReporters() ) + listedCount = listedCount.valueOr(0) + listReporters( config ); + return listedCount; + } + +} // end namespace Catch +// end catch_list.cpp +// start catch_matchers.cpp + +namespace Catch { +namespace Matchers { + namespace Impl { + + std::string MatcherUntypedBase::toString() const { + if( m_cachedToString.empty() ) + m_cachedToString = describe(); + return m_cachedToString; + } + + MatcherUntypedBase::~MatcherUntypedBase() = default; + + } // namespace Impl +} // namespace Matchers + +using namespace Matchers; +using Matchers::Impl::MatcherBase; + +} // namespace Catch +// end catch_matchers.cpp +// start catch_matchers_floating.cpp + +// start catch_to_string.hpp + +#include + +namespace Catch { + template + std::string to_string(T const& t) { +#if defined(CATCH_CONFIG_CPP11_TO_STRING) + return std::to_string(t); +#else + ReusableStringStream rss; + rss << t; + return rss.str(); +#endif + } +} // end namespace Catch + +// end catch_to_string.hpp +#include +#include +#include + +namespace Catch { +namespace Matchers { +namespace Floating { +enum class FloatingPointKind : uint8_t { + Float, + Double +}; +} +} +} + +namespace { + +template +struct Converter; + +template <> +struct Converter { + static_assert(sizeof(float) == sizeof(int32_t), "Important ULP matcher assumption violated"); + Converter(float f) { + std::memcpy(&i, &f, sizeof(f)); + } + int32_t i; +}; + +template <> +struct Converter { + static_assert(sizeof(double) == sizeof(int64_t), "Important ULP matcher assumption violated"); + Converter(double d) { + std::memcpy(&i, &d, sizeof(d)); + } + int64_t i; +}; + +template +auto convert(T t) -> Converter { + return Converter(t); +} + +template +bool almostEqualUlps(FP lhs, FP rhs, int maxUlpDiff) { + // Comparison with NaN should always be false. + // This way we can rule it out before getting into the ugly details + if (std::isnan(lhs) || std::isnan(rhs)) { + return false; + } + + auto lc = convert(lhs); + auto rc = convert(rhs); + + if ((lc.i < 0) != (rc.i < 0)) { + // Potentially we can have +0 and -0 + return lhs == rhs; + } + + auto ulpDiff = std::abs(lc.i - rc.i); + return ulpDiff <= maxUlpDiff; +} + +} + +namespace Catch { +namespace Matchers { +namespace Floating { + WithinAbsMatcher::WithinAbsMatcher(double target, double margin) + :m_target{ target }, m_margin{ margin } { + CATCH_ENFORCE(margin >= 0, "Invalid margin: " << margin << '.' + << " Margin has to be non-negative."); + } + + // Performs equivalent check of std::fabs(lhs - rhs) <= margin + // But without the subtraction to allow for INFINITY in comparison + bool WithinAbsMatcher::match(double const& matchee) const { + return (matchee + m_margin >= m_target) && (m_target + m_margin >= matchee); + } + + std::string WithinAbsMatcher::describe() const { + return "is within " + ::Catch::Detail::stringify(m_margin) + " of " + ::Catch::Detail::stringify(m_target); + } + + WithinUlpsMatcher::WithinUlpsMatcher(double target, int ulps, FloatingPointKind baseType) + :m_target{ target }, m_ulps{ ulps }, m_type{ baseType } { + CATCH_ENFORCE(ulps >= 0, "Invalid ULP setting: " << ulps << '.' + << " ULPs have to be non-negative."); + } + +#if defined(__clang__) +#pragma clang diagnostic push +// Clang <3.5 reports on the default branch in the switch below +#pragma clang diagnostic ignored "-Wunreachable-code" +#endif + + bool WithinUlpsMatcher::match(double const& matchee) const { + switch (m_type) { + case FloatingPointKind::Float: + return almostEqualUlps(static_cast(matchee), static_cast(m_target), m_ulps); + case FloatingPointKind::Double: + return almostEqualUlps(matchee, m_target, m_ulps); + default: + CATCH_INTERNAL_ERROR( "Unknown FloatingPointKind value" ); + } + } + +#if defined(__clang__) +#pragma clang diagnostic pop +#endif + + std::string WithinUlpsMatcher::describe() const { + return "is within " + Catch::to_string(m_ulps) + " ULPs of " + ::Catch::Detail::stringify(m_target) + ((m_type == FloatingPointKind::Float)? "f" : ""); + } + +}// namespace Floating + +Floating::WithinUlpsMatcher WithinULP(double target, int maxUlpDiff) { + return Floating::WithinUlpsMatcher(target, maxUlpDiff, Floating::FloatingPointKind::Double); +} + +Floating::WithinUlpsMatcher WithinULP(float target, int maxUlpDiff) { + return Floating::WithinUlpsMatcher(target, maxUlpDiff, Floating::FloatingPointKind::Float); +} + +Floating::WithinAbsMatcher WithinAbs(double target, double margin) { + return Floating::WithinAbsMatcher(target, margin); +} + +} // namespace Matchers +} // namespace Catch + +// end catch_matchers_floating.cpp +// start catch_matchers_generic.cpp + +std::string Catch::Matchers::Generic::Detail::finalizeDescription(const std::string& desc) { + if (desc.empty()) { + return "matches undescribed predicate"; + } else { + return "matches predicate: \"" + desc + '"'; + } +} +// end catch_matchers_generic.cpp +// start catch_matchers_string.cpp + +#include + +namespace Catch { +namespace Matchers { + + namespace StdString { + + CasedString::CasedString( std::string const& str, CaseSensitive::Choice caseSensitivity ) + : m_caseSensitivity( caseSensitivity ), + m_str( adjustString( str ) ) + {} + std::string CasedString::adjustString( std::string const& str ) const { + return m_caseSensitivity == CaseSensitive::No + ? toLower( str ) + : str; + } + std::string CasedString::caseSensitivitySuffix() const { + return m_caseSensitivity == CaseSensitive::No + ? " (case insensitive)" + : std::string(); + } + + StringMatcherBase::StringMatcherBase( std::string const& operation, CasedString const& comparator ) + : m_comparator( comparator ), + m_operation( operation ) { + } + + std::string StringMatcherBase::describe() const { + std::string description; + description.reserve(5 + m_operation.size() + m_comparator.m_str.size() + + m_comparator.caseSensitivitySuffix().size()); + description += m_operation; + description += ": \""; + description += m_comparator.m_str; + description += "\""; + description += m_comparator.caseSensitivitySuffix(); + return description; + } + + EqualsMatcher::EqualsMatcher( CasedString const& comparator ) : StringMatcherBase( "equals", comparator ) {} + + bool EqualsMatcher::match( std::string const& source ) const { + return m_comparator.adjustString( source ) == m_comparator.m_str; + } + + ContainsMatcher::ContainsMatcher( CasedString const& comparator ) : StringMatcherBase( "contains", comparator ) {} + + bool ContainsMatcher::match( std::string const& source ) const { + return contains( m_comparator.adjustString( source ), m_comparator.m_str ); + } + + StartsWithMatcher::StartsWithMatcher( CasedString const& comparator ) : StringMatcherBase( "starts with", comparator ) {} + + bool StartsWithMatcher::match( std::string const& source ) const { + return startsWith( m_comparator.adjustString( source ), m_comparator.m_str ); + } + + EndsWithMatcher::EndsWithMatcher( CasedString const& comparator ) : StringMatcherBase( "ends with", comparator ) {} + + bool EndsWithMatcher::match( std::string const& source ) const { + return endsWith( m_comparator.adjustString( source ), m_comparator.m_str ); + } + + RegexMatcher::RegexMatcher(std::string regex, CaseSensitive::Choice caseSensitivity): m_regex(std::move(regex)), m_caseSensitivity(caseSensitivity) {} + + bool RegexMatcher::match(std::string const& matchee) const { + auto flags = std::regex::ECMAScript; // ECMAScript is the default syntax option anyway + if (m_caseSensitivity == CaseSensitive::Choice::No) { + flags |= std::regex::icase; + } + auto reg = std::regex(m_regex, flags); + return std::regex_match(matchee, reg); + } + + std::string RegexMatcher::describe() const { + return "matches " + ::Catch::Detail::stringify(m_regex) + ((m_caseSensitivity == CaseSensitive::Choice::Yes)? " case sensitively" : " case insensitively"); + } + + } // namespace StdString + + StdString::EqualsMatcher Equals( std::string const& str, CaseSensitive::Choice caseSensitivity ) { + return StdString::EqualsMatcher( StdString::CasedString( str, caseSensitivity) ); + } + StdString::ContainsMatcher Contains( std::string const& str, CaseSensitive::Choice caseSensitivity ) { + return StdString::ContainsMatcher( StdString::CasedString( str, caseSensitivity) ); + } + StdString::EndsWithMatcher EndsWith( std::string const& str, CaseSensitive::Choice caseSensitivity ) { + return StdString::EndsWithMatcher( StdString::CasedString( str, caseSensitivity) ); + } + StdString::StartsWithMatcher StartsWith( std::string const& str, CaseSensitive::Choice caseSensitivity ) { + return StdString::StartsWithMatcher( StdString::CasedString( str, caseSensitivity) ); + } + + StdString::RegexMatcher Matches(std::string const& regex, CaseSensitive::Choice caseSensitivity) { + return StdString::RegexMatcher(regex, caseSensitivity); + } + +} // namespace Matchers +} // namespace Catch +// end catch_matchers_string.cpp +// start catch_message.cpp + +// start catch_uncaught_exceptions.h + +namespace Catch { + bool uncaught_exceptions(); +} // end namespace Catch + +// end catch_uncaught_exceptions.h +#include + +namespace Catch { + + MessageInfo::MessageInfo( StringRef const& _macroName, + SourceLineInfo const& _lineInfo, + ResultWas::OfType _type ) + : macroName( _macroName ), + lineInfo( _lineInfo ), + type( _type ), + sequence( ++globalCount ) + {} + + bool MessageInfo::operator==( MessageInfo const& other ) const { + return sequence == other.sequence; + } + + bool MessageInfo::operator<( MessageInfo const& other ) const { + return sequence < other.sequence; + } + + // This may need protecting if threading support is added + unsigned int MessageInfo::globalCount = 0; + + //////////////////////////////////////////////////////////////////////////// + + Catch::MessageBuilder::MessageBuilder( StringRef const& macroName, + SourceLineInfo const& lineInfo, + ResultWas::OfType type ) + :m_info(macroName, lineInfo, type) {} + + //////////////////////////////////////////////////////////////////////////// + + ScopedMessage::ScopedMessage( MessageBuilder const& builder ) + : m_info( builder.m_info ) + { + m_info.message = builder.m_stream.str(); + getResultCapture().pushScopedMessage( m_info ); + } + + ScopedMessage::~ScopedMessage() { + if ( !uncaught_exceptions() ){ + getResultCapture().popScopedMessage(m_info); + } + } + + Capturer::Capturer( StringRef macroName, SourceLineInfo const& lineInfo, ResultWas::OfType resultType, StringRef names ) { + auto start = std::string::npos; + for( size_t pos = 0; pos <= names.size(); ++pos ) { + char c = names[pos]; + if( pos == names.size() || c == ' ' || c == '\t' || c == ',' || c == ']' ) { + if( start != std::string::npos ) { + m_messages.push_back( MessageInfo( macroName, lineInfo, resultType ) ); + m_messages.back().message = names.substr( start, pos-start) + " := "; + start = std::string::npos; + } + } + else if( c != '[' && c != ']' && start == std::string::npos ) + start = pos; + } + } + Capturer::~Capturer() { + if ( !uncaught_exceptions() ){ + assert( m_captured == m_messages.size() ); + for( size_t i = 0; i < m_captured; ++i ) + m_resultCapture.popScopedMessage( m_messages[i] ); + } + } + + void Capturer::captureValue( size_t index, StringRef value ) { + assert( index < m_messages.size() ); + m_messages[index].message += value; + m_resultCapture.pushScopedMessage( m_messages[index] ); + m_captured++; + } + +} // end namespace Catch +// end catch_message.cpp +// start catch_output_redirect.cpp + +// start catch_output_redirect.h +#ifndef TWOBLUECUBES_CATCH_OUTPUT_REDIRECT_H +#define TWOBLUECUBES_CATCH_OUTPUT_REDIRECT_H + +#include +#include +#include + +namespace Catch { + + class RedirectedStream { + std::ostream& m_originalStream; + std::ostream& m_redirectionStream; + std::streambuf* m_prevBuf; + + public: + RedirectedStream( std::ostream& originalStream, std::ostream& redirectionStream ); + ~RedirectedStream(); + }; + + class RedirectedStdOut { + ReusableStringStream m_rss; + RedirectedStream m_cout; + public: + RedirectedStdOut(); + auto str() const -> std::string; + }; + + // StdErr has two constituent streams in C++, std::cerr and std::clog + // This means that we need to redirect 2 streams into 1 to keep proper + // order of writes + class RedirectedStdErr { + ReusableStringStream m_rss; + RedirectedStream m_cerr; + RedirectedStream m_clog; + public: + RedirectedStdErr(); + auto str() const -> std::string; + }; + +#if defined(CATCH_CONFIG_NEW_CAPTURE) + + // Windows's implementation of std::tmpfile is terrible (it tries + // to create a file inside system folder, thus requiring elevated + // privileges for the binary), so we have to use tmpnam(_s) and + // create the file ourselves there. + class TempFile { + public: + TempFile(TempFile const&) = delete; + TempFile& operator=(TempFile const&) = delete; + TempFile(TempFile&&) = delete; + TempFile& operator=(TempFile&&) = delete; + + TempFile(); + ~TempFile(); + + std::FILE* getFile(); + std::string getContents(); + + private: + std::FILE* m_file = nullptr; + #if defined(_MSC_VER) + char m_buffer[L_tmpnam] = { 0 }; + #endif + }; + + class OutputRedirect { + public: + OutputRedirect(OutputRedirect const&) = delete; + OutputRedirect& operator=(OutputRedirect const&) = delete; + OutputRedirect(OutputRedirect&&) = delete; + OutputRedirect& operator=(OutputRedirect&&) = delete; + + OutputRedirect(std::string& stdout_dest, std::string& stderr_dest); + ~OutputRedirect(); + + private: + int m_originalStdout = -1; + int m_originalStderr = -1; + TempFile m_stdoutFile; + TempFile m_stderrFile; + std::string& m_stdoutDest; + std::string& m_stderrDest; + }; + +#endif + +} // end namespace Catch + +#endif // TWOBLUECUBES_CATCH_OUTPUT_REDIRECT_H +// end catch_output_redirect.h +#include +#include +#include +#include +#include + +#if defined(CATCH_CONFIG_NEW_CAPTURE) + #if defined(_MSC_VER) + #include //_dup and _dup2 + #define dup _dup + #define dup2 _dup2 + #define fileno _fileno + #else + #include // dup and dup2 + #endif +#endif + +namespace Catch { + + RedirectedStream::RedirectedStream( std::ostream& originalStream, std::ostream& redirectionStream ) + : m_originalStream( originalStream ), + m_redirectionStream( redirectionStream ), + m_prevBuf( m_originalStream.rdbuf() ) + { + m_originalStream.rdbuf( m_redirectionStream.rdbuf() ); + } + + RedirectedStream::~RedirectedStream() { + m_originalStream.rdbuf( m_prevBuf ); + } + + RedirectedStdOut::RedirectedStdOut() : m_cout( Catch::cout(), m_rss.get() ) {} + auto RedirectedStdOut::str() const -> std::string { return m_rss.str(); } + + RedirectedStdErr::RedirectedStdErr() + : m_cerr( Catch::cerr(), m_rss.get() ), + m_clog( Catch::clog(), m_rss.get() ) + {} + auto RedirectedStdErr::str() const -> std::string { return m_rss.str(); } + +#if defined(CATCH_CONFIG_NEW_CAPTURE) + +#if defined(_MSC_VER) + TempFile::TempFile() { + if (tmpnam_s(m_buffer)) { + CATCH_RUNTIME_ERROR("Could not get a temp filename"); + } + if (fopen_s(&m_file, m_buffer, "w")) { + char buffer[100]; + if (strerror_s(buffer, errno)) { + CATCH_RUNTIME_ERROR("Could not translate errno to a string"); + } + CATCH_RUNTIME_ERROR("Coul dnot open the temp file: '" << m_buffer << "' because: " << buffer); + } + } +#else + TempFile::TempFile() { + m_file = std::tmpfile(); + if (!m_file) { + CATCH_RUNTIME_ERROR("Could not create a temp file."); + } + } + +#endif + + TempFile::~TempFile() { + // TBD: What to do about errors here? + std::fclose(m_file); + // We manually create the file on Windows only, on Linux + // it will be autodeleted +#if defined(_MSC_VER) + std::remove(m_buffer); +#endif + } + + FILE* TempFile::getFile() { + return m_file; + } + + std::string TempFile::getContents() { + std::stringstream sstr; + char buffer[100] = {}; + std::rewind(m_file); + while (std::fgets(buffer, sizeof(buffer), m_file)) { + sstr << buffer; + } + return sstr.str(); + } + + OutputRedirect::OutputRedirect(std::string& stdout_dest, std::string& stderr_dest) : + m_originalStdout(dup(1)), + m_originalStderr(dup(2)), + m_stdoutDest(stdout_dest), + m_stderrDest(stderr_dest) { + dup2(fileno(m_stdoutFile.getFile()), 1); + dup2(fileno(m_stderrFile.getFile()), 2); + } + + OutputRedirect::~OutputRedirect() { + Catch::cout() << std::flush; + fflush(stdout); + // Since we support overriding these streams, we flush cerr + // even though std::cerr is unbuffered + Catch::cerr() << std::flush; + Catch::clog() << std::flush; + fflush(stderr); + + dup2(m_originalStdout, 1); + dup2(m_originalStderr, 2); + + m_stdoutDest += m_stdoutFile.getContents(); + m_stderrDest += m_stderrFile.getContents(); + } + +#endif // CATCH_CONFIG_NEW_CAPTURE + +} // namespace Catch + +#if defined(CATCH_CONFIG_NEW_CAPTURE) + #if defined(_MSC_VER) + #undef dup + #undef dup2 + #undef fileno + #endif +#endif +// end catch_output_redirect.cpp +// start catch_random_number_generator.cpp + +namespace Catch { + + std::mt19937& rng() { + static std::mt19937 s_rng; + return s_rng; + } + + void seedRng( IConfig const& config ) { + if( config.rngSeed() != 0 ) { + std::srand( config.rngSeed() ); + rng().seed( config.rngSeed() ); + } + } + + unsigned int rngSeed() { + return getCurrentContext().getConfig()->rngSeed(); + } +} +// end catch_random_number_generator.cpp +// start catch_registry_hub.cpp + +// start catch_test_case_registry_impl.h + +#include +#include +#include +#include + +namespace Catch { + + class TestCase; + struct IConfig; + + std::vector sortTests( IConfig const& config, std::vector const& unsortedTestCases ); + bool matchTest( TestCase const& testCase, TestSpec const& testSpec, IConfig const& config ); + + void enforceNoDuplicateTestCases( std::vector const& functions ); + + std::vector filterTests( std::vector const& testCases, TestSpec const& testSpec, IConfig const& config ); + std::vector const& getAllTestCasesSorted( IConfig const& config ); + + class TestRegistry : public ITestCaseRegistry { + public: + virtual ~TestRegistry() = default; + + virtual void registerTest( TestCase const& testCase ); + + std::vector const& getAllTests() const override; + std::vector const& getAllTestsSorted( IConfig const& config ) const override; + + private: + std::vector m_functions; + mutable RunTests::InWhatOrder m_currentSortOrder = RunTests::InDeclarationOrder; + mutable std::vector m_sortedFunctions; + std::size_t m_unnamedCount = 0; + std::ios_base::Init m_ostreamInit; // Forces cout/ cerr to be initialised + }; + + /////////////////////////////////////////////////////////////////////////// + + class TestInvokerAsFunction : public ITestInvoker { + void(*m_testAsFunction)(); + public: + TestInvokerAsFunction( void(*testAsFunction)() ) noexcept; + + void invoke() const override; + }; + + std::string extractClassName( StringRef const& classOrQualifiedMethodName ); + + /////////////////////////////////////////////////////////////////////////// + +} // end namespace Catch + +// end catch_test_case_registry_impl.h +// start catch_reporter_registry.h + +#include + +namespace Catch { + + class ReporterRegistry : public IReporterRegistry { + + public: + + ~ReporterRegistry() override; + + IStreamingReporterPtr create( std::string const& name, IConfigPtr const& config ) const override; + + void registerReporter( std::string const& name, IReporterFactoryPtr const& factory ); + void registerListener( IReporterFactoryPtr const& factory ); + + FactoryMap const& getFactories() const override; + Listeners const& getListeners() const override; + + private: + FactoryMap m_factories; + Listeners m_listeners; + }; +} + +// end catch_reporter_registry.h +// start catch_tag_alias_registry.h + +// start catch_tag_alias.h + +#include + +namespace Catch { + + struct TagAlias { + TagAlias(std::string const& _tag, SourceLineInfo _lineInfo); + + std::string tag; + SourceLineInfo lineInfo; + }; + +} // end namespace Catch + +// end catch_tag_alias.h +#include + +namespace Catch { + + class TagAliasRegistry : public ITagAliasRegistry { + public: + ~TagAliasRegistry() override; + TagAlias const* find( std::string const& alias ) const override; + std::string expandAliases( std::string const& unexpandedTestSpec ) const override; + void add( std::string const& alias, std::string const& tag, SourceLineInfo const& lineInfo ); + + private: + std::map m_registry; + }; + +} // end namespace Catch + +// end catch_tag_alias_registry.h +// start catch_startup_exception_registry.h + +#include +#include + +namespace Catch { + + class StartupExceptionRegistry { + public: + void add(std::exception_ptr const& exception) noexcept; + std::vector const& getExceptions() const noexcept; + private: + std::vector m_exceptions; + }; + +} // end namespace Catch + +// end catch_startup_exception_registry.h +// start catch_singletons.hpp + +namespace Catch { + + struct ISingleton { + virtual ~ISingleton(); + }; + + void addSingleton( ISingleton* singleton ); + void cleanupSingletons(); + + template + class Singleton : SingletonImplT, public ISingleton { + + static auto getInternal() -> Singleton* { + static Singleton* s_instance = nullptr; + if( !s_instance ) { + s_instance = new Singleton; + addSingleton( s_instance ); + } + return s_instance; + } + + public: + static auto get() -> InterfaceT const& { + return *getInternal(); + } + static auto getMutable() -> MutableInterfaceT& { + return *getInternal(); + } + }; + +} // namespace Catch + +// end catch_singletons.hpp +namespace Catch { + + namespace { + + class RegistryHub : public IRegistryHub, public IMutableRegistryHub, + private NonCopyable { + + public: // IRegistryHub + RegistryHub() = default; + IReporterRegistry const& getReporterRegistry() const override { + return m_reporterRegistry; + } + ITestCaseRegistry const& getTestCaseRegistry() const override { + return m_testCaseRegistry; + } + IExceptionTranslatorRegistry const& getExceptionTranslatorRegistry() const override { + return m_exceptionTranslatorRegistry; + } + ITagAliasRegistry const& getTagAliasRegistry() const override { + return m_tagAliasRegistry; + } + StartupExceptionRegistry const& getStartupExceptionRegistry() const override { + return m_exceptionRegistry; + } + + public: // IMutableRegistryHub + void registerReporter( std::string const& name, IReporterFactoryPtr const& factory ) override { + m_reporterRegistry.registerReporter( name, factory ); + } + void registerListener( IReporterFactoryPtr const& factory ) override { + m_reporterRegistry.registerListener( factory ); + } + void registerTest( TestCase const& testInfo ) override { + m_testCaseRegistry.registerTest( testInfo ); + } + void registerTranslator( const IExceptionTranslator* translator ) override { + m_exceptionTranslatorRegistry.registerTranslator( translator ); + } + void registerTagAlias( std::string const& alias, std::string const& tag, SourceLineInfo const& lineInfo ) override { + m_tagAliasRegistry.add( alias, tag, lineInfo ); + } + void registerStartupException() noexcept override { + m_exceptionRegistry.add(std::current_exception()); + } + + private: + TestRegistry m_testCaseRegistry; + ReporterRegistry m_reporterRegistry; + ExceptionTranslatorRegistry m_exceptionTranslatorRegistry; + TagAliasRegistry m_tagAliasRegistry; + StartupExceptionRegistry m_exceptionRegistry; + }; + } + + using RegistryHubSingleton = Singleton; + + IRegistryHub const& getRegistryHub() { + return RegistryHubSingleton::get(); + } + IMutableRegistryHub& getMutableRegistryHub() { + return RegistryHubSingleton::getMutable(); + } + void cleanUp() { + cleanupSingletons(); + cleanUpContext(); + } + std::string translateActiveException() { + return getRegistryHub().getExceptionTranslatorRegistry().translateActiveException(); + } + +} // end namespace Catch +// end catch_registry_hub.cpp +// start catch_reporter_registry.cpp + +namespace Catch { + + ReporterRegistry::~ReporterRegistry() = default; + + IStreamingReporterPtr ReporterRegistry::create( std::string const& name, IConfigPtr const& config ) const { + auto it = m_factories.find( name ); + if( it == m_factories.end() ) + return nullptr; + return it->second->create( ReporterConfig( config ) ); + } + + void ReporterRegistry::registerReporter( std::string const& name, IReporterFactoryPtr const& factory ) { + m_factories.emplace(name, factory); + } + void ReporterRegistry::registerListener( IReporterFactoryPtr const& factory ) { + m_listeners.push_back( factory ); + } + + IReporterRegistry::FactoryMap const& ReporterRegistry::getFactories() const { + return m_factories; + } + IReporterRegistry::Listeners const& ReporterRegistry::getListeners() const { + return m_listeners; + } + +} +// end catch_reporter_registry.cpp +// start catch_result_type.cpp + +namespace Catch { + + bool isOk( ResultWas::OfType resultType ) { + return ( resultType & ResultWas::FailureBit ) == 0; + } + bool isJustInfo( int flags ) { + return flags == ResultWas::Info; + } + + ResultDisposition::Flags operator | ( ResultDisposition::Flags lhs, ResultDisposition::Flags rhs ) { + return static_cast( static_cast( lhs ) | static_cast( rhs ) ); + } + + bool shouldContinueOnFailure( int flags ) { return ( flags & ResultDisposition::ContinueOnFailure ) != 0; } + bool shouldSuppressFailure( int flags ) { return ( flags & ResultDisposition::SuppressFail ) != 0; } + +} // end namespace Catch +// end catch_result_type.cpp +// start catch_run_context.cpp + +#include +#include +#include + +namespace Catch { + + namespace Generators { + struct GeneratorTracker : TestCaseTracking::TrackerBase, IGeneratorTracker { + size_t m_index = static_cast( -1 ); + GeneratorBasePtr m_generator; + + GeneratorTracker( TestCaseTracking::NameAndLocation const& nameAndLocation, TrackerContext& ctx, ITracker* parent ) + : TrackerBase( nameAndLocation, ctx, parent ) + {} + ~GeneratorTracker(); + + static GeneratorTracker& acquire( TrackerContext& ctx, TestCaseTracking::NameAndLocation const& nameAndLocation ) { + std::shared_ptr tracker; + + ITracker& currentTracker = ctx.currentTracker(); + if( TestCaseTracking::ITrackerPtr childTracker = currentTracker.findChild( nameAndLocation ) ) { + assert( childTracker ); + assert( childTracker->isIndexTracker() ); + tracker = std::static_pointer_cast( childTracker ); + } + else { + tracker = std::make_shared( nameAndLocation, ctx, ¤tTracker ); + currentTracker.addChild( tracker ); + } + + if( !ctx.completedCycle() && !tracker->isComplete() ) { + if( tracker->m_runState != ExecutingChildren && tracker->m_runState != NeedsAnotherRun ) + tracker->moveNext(); + tracker->open(); + } + + return *tracker; + } + + void moveNext() { + m_index++; + m_children.clear(); + } + + // TrackerBase interface + bool isIndexTracker() const override { return true; } + auto hasGenerator() const -> bool override { + return !!m_generator; + } + void close() override { + TrackerBase::close(); + if( m_runState == CompletedSuccessfully && m_index < m_generator->size()-1 ) + m_runState = Executing; + } + + // IGeneratorTracker interface + auto getGenerator() const -> GeneratorBasePtr const& override { + return m_generator; + } + void setGenerator( GeneratorBasePtr&& generator ) override { + m_generator = std::move( generator ); + } + auto getIndex() const -> size_t override { + return m_index; + } + }; + GeneratorTracker::~GeneratorTracker() {} + } + + RunContext::RunContext(IConfigPtr const& _config, IStreamingReporterPtr&& reporter) + : m_runInfo(_config->name()), + m_context(getCurrentMutableContext()), + m_config(_config), + m_reporter(std::move(reporter)), + m_lastAssertionInfo{ StringRef(), SourceLineInfo("",0), StringRef(), ResultDisposition::Normal }, + m_includeSuccessfulResults( m_config->includeSuccessfulResults() || m_reporter->getPreferences().shouldReportAllAssertions ) + { + m_context.setRunner(this); + m_context.setConfig(m_config); + m_context.setResultCapture(this); + m_reporter->testRunStarting(m_runInfo); + } + + RunContext::~RunContext() { + m_reporter->testRunEnded(TestRunStats(m_runInfo, m_totals, aborting())); + } + + void RunContext::testGroupStarting(std::string const& testSpec, std::size_t groupIndex, std::size_t groupsCount) { + m_reporter->testGroupStarting(GroupInfo(testSpec, groupIndex, groupsCount)); + } + + void RunContext::testGroupEnded(std::string const& testSpec, Totals const& totals, std::size_t groupIndex, std::size_t groupsCount) { + m_reporter->testGroupEnded(TestGroupStats(GroupInfo(testSpec, groupIndex, groupsCount), totals, aborting())); + } + + Totals RunContext::runTest(TestCase const& testCase) { + Totals prevTotals = m_totals; + + std::string redirectedCout; + std::string redirectedCerr; + + auto const& testInfo = testCase.getTestCaseInfo(); + + m_reporter->testCaseStarting(testInfo); + + m_activeTestCase = &testCase; + + ITracker& rootTracker = m_trackerContext.startRun(); + assert(rootTracker.isSectionTracker()); + static_cast(rootTracker).addInitialFilters(m_config->getSectionsToRun()); + do { + m_trackerContext.startCycle(); + m_testCaseTracker = &SectionTracker::acquire(m_trackerContext, TestCaseTracking::NameAndLocation(testInfo.name, testInfo.lineInfo)); + runCurrentTest(redirectedCout, redirectedCerr); + } while (!m_testCaseTracker->isSuccessfullyCompleted() && !aborting()); + + Totals deltaTotals = m_totals.delta(prevTotals); + if (testInfo.expectedToFail() && deltaTotals.testCases.passed > 0) { + deltaTotals.assertions.failed++; + deltaTotals.testCases.passed--; + deltaTotals.testCases.failed++; + } + m_totals.testCases += deltaTotals.testCases; + m_reporter->testCaseEnded(TestCaseStats(testInfo, + deltaTotals, + redirectedCout, + redirectedCerr, + aborting())); + + m_activeTestCase = nullptr; + m_testCaseTracker = nullptr; + + return deltaTotals; + } + + IConfigPtr RunContext::config() const { + return m_config; + } + + IStreamingReporter& RunContext::reporter() const { + return *m_reporter; + } + + void RunContext::assertionEnded(AssertionResult const & result) { + if (result.getResultType() == ResultWas::Ok) { + m_totals.assertions.passed++; + m_lastAssertionPassed = true; + } else if (!result.isOk()) { + m_lastAssertionPassed = false; + if( m_activeTestCase->getTestCaseInfo().okToFail() ) + m_totals.assertions.failedButOk++; + else + m_totals.assertions.failed++; + } + else { + m_lastAssertionPassed = true; + } + + // We have no use for the return value (whether messages should be cleared), because messages were made scoped + // and should be let to clear themselves out. + static_cast(m_reporter->assertionEnded(AssertionStats(result, m_messages, m_totals))); + + // Reset working state + resetAssertionInfo(); + m_lastResult = result; + } + void RunContext::resetAssertionInfo() { + m_lastAssertionInfo.macroName = StringRef(); + m_lastAssertionInfo.capturedExpression = "{Unknown expression after the reported line}"_sr; + } + + bool RunContext::sectionStarted(SectionInfo const & sectionInfo, Counts & assertions) { + ITracker& sectionTracker = SectionTracker::acquire(m_trackerContext, TestCaseTracking::NameAndLocation(sectionInfo.name, sectionInfo.lineInfo)); + if (!sectionTracker.isOpen()) + return false; + m_activeSections.push_back(§ionTracker); + + m_lastAssertionInfo.lineInfo = sectionInfo.lineInfo; + + m_reporter->sectionStarting(sectionInfo); + + assertions = m_totals.assertions; + + return true; + } + auto RunContext::acquireGeneratorTracker( SourceLineInfo const& lineInfo ) -> IGeneratorTracker& { + using namespace Generators; + GeneratorTracker& tracker = GeneratorTracker::acquire( m_trackerContext, TestCaseTracking::NameAndLocation( "generator", lineInfo ) ); + assert( tracker.isOpen() ); + m_lastAssertionInfo.lineInfo = lineInfo; + return tracker; + } + + bool RunContext::testForMissingAssertions(Counts& assertions) { + if (assertions.total() != 0) + return false; + if (!m_config->warnAboutMissingAssertions()) + return false; + if (m_trackerContext.currentTracker().hasChildren()) + return false; + m_totals.assertions.failed++; + assertions.failed++; + return true; + } + + void RunContext::sectionEnded(SectionEndInfo const & endInfo) { + Counts assertions = m_totals.assertions - endInfo.prevAssertions; + bool missingAssertions = testForMissingAssertions(assertions); + + if (!m_activeSections.empty()) { + m_activeSections.back()->close(); + m_activeSections.pop_back(); + } + + m_reporter->sectionEnded(SectionStats(endInfo.sectionInfo, assertions, endInfo.durationInSeconds, missingAssertions)); + m_messages.clear(); + } + + void RunContext::sectionEndedEarly(SectionEndInfo const & endInfo) { + if (m_unfinishedSections.empty()) + m_activeSections.back()->fail(); + else + m_activeSections.back()->close(); + m_activeSections.pop_back(); + + m_unfinishedSections.push_back(endInfo); + } + void RunContext::benchmarkStarting( BenchmarkInfo const& info ) { + m_reporter->benchmarkStarting( info ); + } + void RunContext::benchmarkEnded( BenchmarkStats const& stats ) { + m_reporter->benchmarkEnded( stats ); + } + + void RunContext::pushScopedMessage(MessageInfo const & message) { + m_messages.push_back(message); + } + + void RunContext::popScopedMessage(MessageInfo const & message) { + m_messages.erase(std::remove(m_messages.begin(), m_messages.end(), message), m_messages.end()); + } + + std::string RunContext::getCurrentTestName() const { + return m_activeTestCase + ? m_activeTestCase->getTestCaseInfo().name + : std::string(); + } + + const AssertionResult * RunContext::getLastResult() const { + return &(*m_lastResult); + } + + void RunContext::exceptionEarlyReported() { + m_shouldReportUnexpected = false; + } + + void RunContext::handleFatalErrorCondition( StringRef message ) { + // First notify reporter that bad things happened + m_reporter->fatalErrorEncountered(message); + + // Don't rebuild the result -- the stringification itself can cause more fatal errors + // Instead, fake a result data. + AssertionResultData tempResult( ResultWas::FatalErrorCondition, { false } ); + tempResult.message = message; + AssertionResult result(m_lastAssertionInfo, tempResult); + + assertionEnded(result); + + handleUnfinishedSections(); + + // Recreate section for test case (as we will lose the one that was in scope) + auto const& testCaseInfo = m_activeTestCase->getTestCaseInfo(); + SectionInfo testCaseSection(testCaseInfo.lineInfo, testCaseInfo.name); + + Counts assertions; + assertions.failed = 1; + SectionStats testCaseSectionStats(testCaseSection, assertions, 0, false); + m_reporter->sectionEnded(testCaseSectionStats); + + auto const& testInfo = m_activeTestCase->getTestCaseInfo(); + + Totals deltaTotals; + deltaTotals.testCases.failed = 1; + deltaTotals.assertions.failed = 1; + m_reporter->testCaseEnded(TestCaseStats(testInfo, + deltaTotals, + std::string(), + std::string(), + false)); + m_totals.testCases.failed++; + testGroupEnded(std::string(), m_totals, 1, 1); + m_reporter->testRunEnded(TestRunStats(m_runInfo, m_totals, false)); + } + + bool RunContext::lastAssertionPassed() { + return m_lastAssertionPassed; + } + + void RunContext::assertionPassed() { + m_lastAssertionPassed = true; + ++m_totals.assertions.passed; + resetAssertionInfo(); + } + + bool RunContext::aborting() const { + return m_totals.assertions.failed >= static_cast(m_config->abortAfter()); + } + + void RunContext::runCurrentTest(std::string & redirectedCout, std::string & redirectedCerr) { + auto const& testCaseInfo = m_activeTestCase->getTestCaseInfo(); + SectionInfo testCaseSection(testCaseInfo.lineInfo, testCaseInfo.name); + m_reporter->sectionStarting(testCaseSection); + Counts prevAssertions = m_totals.assertions; + double duration = 0; + m_shouldReportUnexpected = true; + m_lastAssertionInfo = { "TEST_CASE"_sr, testCaseInfo.lineInfo, StringRef(), ResultDisposition::Normal }; + + seedRng(*m_config); + + Timer timer; + CATCH_TRY { + if (m_reporter->getPreferences().shouldRedirectStdOut) { +#if !defined(CATCH_CONFIG_EXPERIMENTAL_REDIRECT) + RedirectedStdOut redirectedStdOut; + RedirectedStdErr redirectedStdErr; + + timer.start(); + invokeActiveTestCase(); + redirectedCout += redirectedStdOut.str(); + redirectedCerr += redirectedStdErr.str(); +#else + OutputRedirect r(redirectedCout, redirectedCerr); + timer.start(); + invokeActiveTestCase(); +#endif + } else { + timer.start(); + invokeActiveTestCase(); + } + duration = timer.getElapsedSeconds(); + } CATCH_CATCH_ANON (TestFailureException&) { + // This just means the test was aborted due to failure + } CATCH_CATCH_ALL { + // Under CATCH_CONFIG_FAST_COMPILE, unexpected exceptions under REQUIRE assertions + // are reported without translation at the point of origin. + if( m_shouldReportUnexpected ) { + AssertionReaction dummyReaction; + handleUnexpectedInflightException( m_lastAssertionInfo, translateActiveException(), dummyReaction ); + } + } + Counts assertions = m_totals.assertions - prevAssertions; + bool missingAssertions = testForMissingAssertions(assertions); + + m_testCaseTracker->close(); + handleUnfinishedSections(); + m_messages.clear(); + + SectionStats testCaseSectionStats(testCaseSection, assertions, duration, missingAssertions); + m_reporter->sectionEnded(testCaseSectionStats); + } + + void RunContext::invokeActiveTestCase() { + FatalConditionHandler fatalConditionHandler; // Handle signals + m_activeTestCase->invoke(); + fatalConditionHandler.reset(); + } + + void RunContext::handleUnfinishedSections() { + // If sections ended prematurely due to an exception we stored their + // infos here so we can tear them down outside the unwind process. + for (auto it = m_unfinishedSections.rbegin(), + itEnd = m_unfinishedSections.rend(); + it != itEnd; + ++it) + sectionEnded(*it); + m_unfinishedSections.clear(); + } + + void RunContext::handleExpr( + AssertionInfo const& info, + ITransientExpression const& expr, + AssertionReaction& reaction + ) { + m_reporter->assertionStarting( info ); + + bool negated = isFalseTest( info.resultDisposition ); + bool result = expr.getResult() != negated; + + if( result ) { + if (!m_includeSuccessfulResults) { + assertionPassed(); + } + else { + reportExpr(info, ResultWas::Ok, &expr, negated); + } + } + else { + reportExpr(info, ResultWas::ExpressionFailed, &expr, negated ); + populateReaction( reaction ); + } + } + void RunContext::reportExpr( + AssertionInfo const &info, + ResultWas::OfType resultType, + ITransientExpression const *expr, + bool negated ) { + + m_lastAssertionInfo = info; + AssertionResultData data( resultType, LazyExpression( negated ) ); + + AssertionResult assertionResult{ info, data }; + assertionResult.m_resultData.lazyExpression.m_transientExpression = expr; + + assertionEnded( assertionResult ); + } + + void RunContext::handleMessage( + AssertionInfo const& info, + ResultWas::OfType resultType, + StringRef const& message, + AssertionReaction& reaction + ) { + m_reporter->assertionStarting( info ); + + m_lastAssertionInfo = info; + + AssertionResultData data( resultType, LazyExpression( false ) ); + data.message = message; + AssertionResult assertionResult{ m_lastAssertionInfo, data }; + assertionEnded( assertionResult ); + if( !assertionResult.isOk() ) + populateReaction( reaction ); + } + void RunContext::handleUnexpectedExceptionNotThrown( + AssertionInfo const& info, + AssertionReaction& reaction + ) { + handleNonExpr(info, Catch::ResultWas::DidntThrowException, reaction); + } + + void RunContext::handleUnexpectedInflightException( + AssertionInfo const& info, + std::string const& message, + AssertionReaction& reaction + ) { + m_lastAssertionInfo = info; + + AssertionResultData data( ResultWas::ThrewException, LazyExpression( false ) ); + data.message = message; + AssertionResult assertionResult{ info, data }; + assertionEnded( assertionResult ); + populateReaction( reaction ); + } + + void RunContext::populateReaction( AssertionReaction& reaction ) { + reaction.shouldDebugBreak = m_config->shouldDebugBreak(); + reaction.shouldThrow = aborting() || (m_lastAssertionInfo.resultDisposition & ResultDisposition::Normal); + } + + void RunContext::handleIncomplete( + AssertionInfo const& info + ) { + m_lastAssertionInfo = info; + + AssertionResultData data( ResultWas::ThrewException, LazyExpression( false ) ); + data.message = "Exception translation was disabled by CATCH_CONFIG_FAST_COMPILE"; + AssertionResult assertionResult{ info, data }; + assertionEnded( assertionResult ); + } + void RunContext::handleNonExpr( + AssertionInfo const &info, + ResultWas::OfType resultType, + AssertionReaction &reaction + ) { + m_lastAssertionInfo = info; + + AssertionResultData data( resultType, LazyExpression( false ) ); + AssertionResult assertionResult{ info, data }; + assertionEnded( assertionResult ); + + if( !assertionResult.isOk() ) + populateReaction( reaction ); + } + + IResultCapture& getResultCapture() { + if (auto* capture = getCurrentContext().getResultCapture()) + return *capture; + else + CATCH_INTERNAL_ERROR("No result capture instance"); + } +} +// end catch_run_context.cpp +// start catch_section.cpp + +namespace Catch { + + Section::Section( SectionInfo const& info ) + : m_info( info ), + m_sectionIncluded( getResultCapture().sectionStarted( m_info, m_assertions ) ) + { + m_timer.start(); + } + + Section::~Section() { + if( m_sectionIncluded ) { + SectionEndInfo endInfo{ m_info, m_assertions, m_timer.getElapsedSeconds() }; + if( uncaught_exceptions() ) + getResultCapture().sectionEndedEarly( endInfo ); + else + getResultCapture().sectionEnded( endInfo ); + } + } + + // This indicates whether the section should be executed or not + Section::operator bool() const { + return m_sectionIncluded; + } + +} // end namespace Catch +// end catch_section.cpp +// start catch_section_info.cpp + +namespace Catch { + + SectionInfo::SectionInfo + ( SourceLineInfo const& _lineInfo, + std::string const& _name ) + : name( _name ), + lineInfo( _lineInfo ) + {} + +} // end namespace Catch +// end catch_section_info.cpp +// start catch_session.cpp + +// start catch_session.h + +#include + +namespace Catch { + + class Session : NonCopyable { + public: + + Session(); + ~Session() override; + + void showHelp() const; + void libIdentify(); + + int applyCommandLine( int argc, char const * const * argv ); + #if defined(CATCH_CONFIG_WCHAR) && defined(WIN32) && defined(UNICODE) + int applyCommandLine( int argc, wchar_t const * const * argv ); + #endif + + void useConfigData( ConfigData const& configData ); + + template + int run(int argc, CharT const * const argv[]) { + if (m_startupExceptions) + return 1; + int returnCode = applyCommandLine(argc, argv); + if (returnCode == 0) + returnCode = run(); + return returnCode; + } + + int run(); + + clara::Parser const& cli() const; + void cli( clara::Parser const& newParser ); + ConfigData& configData(); + Config& config(); + private: + int runInternal(); + + clara::Parser m_cli; + ConfigData m_configData; + std::shared_ptr m_config; + bool m_startupExceptions = false; + }; + +} // end namespace Catch + +// end catch_session.h +// start catch_version.h + +#include + +namespace Catch { + + // Versioning information + struct Version { + Version( Version const& ) = delete; + Version& operator=( Version const& ) = delete; + Version( unsigned int _majorVersion, + unsigned int _minorVersion, + unsigned int _patchNumber, + char const * const _branchName, + unsigned int _buildNumber ); + + unsigned int const majorVersion; + unsigned int const minorVersion; + unsigned int const patchNumber; + + // buildNumber is only used if branchName is not null + char const * const branchName; + unsigned int const buildNumber; + + friend std::ostream& operator << ( std::ostream& os, Version const& version ); + }; + + Version const& libraryVersion(); +} + +// end catch_version.h +#include +#include + +namespace Catch { + + namespace { + const int MaxExitCode = 255; + + IStreamingReporterPtr createReporter(std::string const& reporterName, IConfigPtr const& config) { + auto reporter = Catch::getRegistryHub().getReporterRegistry().create(reporterName, config); + CATCH_ENFORCE(reporter, "No reporter registered with name: '" << reporterName << "'"); + + return reporter; + } + + IStreamingReporterPtr makeReporter(std::shared_ptr const& config) { + if (Catch::getRegistryHub().getReporterRegistry().getListeners().empty()) { + return createReporter(config->getReporterName(), config); + } + + auto multi = std::unique_ptr(new ListeningReporter); + + auto const& listeners = Catch::getRegistryHub().getReporterRegistry().getListeners(); + for (auto const& listener : listeners) { + multi->addListener(listener->create(Catch::ReporterConfig(config))); + } + multi->addReporter(createReporter(config->getReporterName(), config)); + return std::move(multi); + } + + Catch::Totals runTests(std::shared_ptr const& config) { + // FixMe: Add listeners in order first, then add reporters. + + auto reporter = makeReporter(config); + + RunContext context(config, std::move(reporter)); + + Totals totals; + + context.testGroupStarting(config->name(), 1, 1); + + TestSpec testSpec = config->testSpec(); + + auto const& allTestCases = getAllTestCasesSorted(*config); + for (auto const& testCase : allTestCases) { + if (!context.aborting() && matchTest(testCase, testSpec, *config)) + totals += context.runTest(testCase); + else + context.reporter().skipTest(testCase); + } + + if (config->warnAboutNoTests() && totals.testCases.total() == 0) { + ReusableStringStream testConfig; + + bool first = true; + for (const auto& input : config->getTestsOrTags()) { + if (!first) { testConfig << ' '; } + first = false; + testConfig << input; + } + + context.reporter().noMatchingTestCases(testConfig.str()); + totals.error = -1; + } + + context.testGroupEnded(config->name(), totals, 1, 1); + return totals; + } + + void applyFilenamesAsTags(Catch::IConfig const& config) { + auto& tests = const_cast&>(getAllTestCasesSorted(config)); + for (auto& testCase : tests) { + auto tags = testCase.tags; + + std::string filename = testCase.lineInfo.file; + auto lastSlash = filename.find_last_of("\\/"); + if (lastSlash != std::string::npos) { + filename.erase(0, lastSlash); + filename[0] = '#'; + } + + auto lastDot = filename.find_last_of('.'); + if (lastDot != std::string::npos) { + filename.erase(lastDot); + } + + tags.push_back(std::move(filename)); + setTags(testCase, tags); + } + } + + } // anon namespace + + Session::Session() { + static bool alreadyInstantiated = false; + if( alreadyInstantiated ) { + CATCH_TRY { CATCH_INTERNAL_ERROR( "Only one instance of Catch::Session can ever be used" ); } + CATCH_CATCH_ALL { getMutableRegistryHub().registerStartupException(); } + } + + // There cannot be exceptions at startup in no-exception mode. +#if !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) + const auto& exceptions = getRegistryHub().getStartupExceptionRegistry().getExceptions(); + if ( !exceptions.empty() ) { + m_startupExceptions = true; + Colour colourGuard( Colour::Red ); + Catch::cerr() << "Errors occurred during startup!" << '\n'; + // iterate over all exceptions and notify user + for ( const auto& ex_ptr : exceptions ) { + try { + std::rethrow_exception(ex_ptr); + } catch ( std::exception const& ex ) { + Catch::cerr() << Column( ex.what() ).indent(2) << '\n'; + } + } + } +#endif + + alreadyInstantiated = true; + m_cli = makeCommandLineParser( m_configData ); + } + Session::~Session() { + Catch::cleanUp(); + } + + void Session::showHelp() const { + Catch::cout() + << "\nCatch v" << libraryVersion() << "\n" + << m_cli << std::endl + << "For more detailed usage please see the project docs\n" << std::endl; + } + void Session::libIdentify() { + Catch::cout() + << std::left << std::setw(16) << "description: " << "A Catch test executable\n" + << std::left << std::setw(16) << "category: " << "testframework\n" + << std::left << std::setw(16) << "framework: " << "Catch Test\n" + << std::left << std::setw(16) << "version: " << libraryVersion() << std::endl; + } + + int Session::applyCommandLine( int argc, char const * const * argv ) { + if( m_startupExceptions ) + return 1; + + auto result = m_cli.parse( clara::Args( argc, argv ) ); + if( !result ) { + Catch::cerr() + << Colour( Colour::Red ) + << "\nError(s) in input:\n" + << Column( result.errorMessage() ).indent( 2 ) + << "\n\n"; + Catch::cerr() << "Run with -? for usage\n" << std::endl; + return MaxExitCode; + } + + if( m_configData.showHelp ) + showHelp(); + if( m_configData.libIdentify ) + libIdentify(); + m_config.reset(); + return 0; + } + +#if defined(CATCH_CONFIG_WCHAR) && defined(WIN32) && defined(UNICODE) + int Session::applyCommandLine( int argc, wchar_t const * const * argv ) { + + char **utf8Argv = new char *[ argc ]; + + for ( int i = 0; i < argc; ++i ) { + int bufSize = WideCharToMultiByte( CP_UTF8, 0, argv[i], -1, NULL, 0, NULL, NULL ); + + utf8Argv[ i ] = new char[ bufSize ]; + + WideCharToMultiByte( CP_UTF8, 0, argv[i], -1, utf8Argv[i], bufSize, NULL, NULL ); + } + + int returnCode = applyCommandLine( argc, utf8Argv ); + + for ( int i = 0; i < argc; ++i ) + delete [] utf8Argv[ i ]; + + delete [] utf8Argv; + + return returnCode; + } +#endif + + void Session::useConfigData( ConfigData const& configData ) { + m_configData = configData; + m_config.reset(); + } + + int Session::run() { + if( ( m_configData.waitForKeypress & WaitForKeypress::BeforeStart ) != 0 ) { + Catch::cout() << "...waiting for enter/ return before starting" << std::endl; + static_cast(std::getchar()); + } + int exitCode = runInternal(); + if( ( m_configData.waitForKeypress & WaitForKeypress::BeforeExit ) != 0 ) { + Catch::cout() << "...waiting for enter/ return before exiting, with code: " << exitCode << std::endl; + static_cast(std::getchar()); + } + return exitCode; + } + + clara::Parser const& Session::cli() const { + return m_cli; + } + void Session::cli( clara::Parser const& newParser ) { + m_cli = newParser; + } + ConfigData& Session::configData() { + return m_configData; + } + Config& Session::config() { + if( !m_config ) + m_config = std::make_shared( m_configData ); + return *m_config; + } + + int Session::runInternal() { + if( m_startupExceptions ) + return 1; + + if (m_configData.showHelp || m_configData.libIdentify) { + return 0; + } + + CATCH_TRY { + config(); // Force config to be constructed + + seedRng( *m_config ); + + if( m_configData.filenamesAsTags ) + applyFilenamesAsTags( *m_config ); + + // Handle list request + if( Option listed = list( config() ) ) + return static_cast( *listed ); + + auto totals = runTests( m_config ); + // Note that on unices only the lower 8 bits are usually used, clamping + // the return value to 255 prevents false negative when some multiple + // of 256 tests has failed + return (std::min) (MaxExitCode, (std::max) (totals.error, static_cast(totals.assertions.failed))); + } +#if !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) + catch( std::exception& ex ) { + Catch::cerr() << ex.what() << std::endl; + return MaxExitCode; + } +#endif + } + +} // end namespace Catch +// end catch_session.cpp +// start catch_singletons.cpp + +#include + +namespace Catch { + + namespace { + static auto getSingletons() -> std::vector*& { + static std::vector* g_singletons = nullptr; + if( !g_singletons ) + g_singletons = new std::vector(); + return g_singletons; + } + } + + ISingleton::~ISingleton() {} + + void addSingleton(ISingleton* singleton ) { + getSingletons()->push_back( singleton ); + } + void cleanupSingletons() { + auto& singletons = getSingletons(); + for( auto singleton : *singletons ) + delete singleton; + delete singletons; + singletons = nullptr; + } + +} // namespace Catch +// end catch_singletons.cpp +// start catch_startup_exception_registry.cpp + +namespace Catch { +void StartupExceptionRegistry::add( std::exception_ptr const& exception ) noexcept { + CATCH_TRY { + m_exceptions.push_back(exception); + } CATCH_CATCH_ALL { + // If we run out of memory during start-up there's really not a lot more we can do about it + std::terminate(); + } + } + + std::vector const& StartupExceptionRegistry::getExceptions() const noexcept { + return m_exceptions; + } + +} // end namespace Catch +// end catch_startup_exception_registry.cpp +// start catch_stream.cpp + +#include +#include +#include +#include +#include +#include + +namespace Catch { + + Catch::IStream::~IStream() = default; + + namespace detail { namespace { + template + class StreamBufImpl : public std::streambuf { + char data[bufferSize]; + WriterF m_writer; + + public: + StreamBufImpl() { + setp( data, data + sizeof(data) ); + } + + ~StreamBufImpl() noexcept { + StreamBufImpl::sync(); + } + + private: + int overflow( int c ) override { + sync(); + + if( c != EOF ) { + if( pbase() == epptr() ) + m_writer( std::string( 1, static_cast( c ) ) ); + else + sputc( static_cast( c ) ); + } + return 0; + } + + int sync() override { + if( pbase() != pptr() ) { + m_writer( std::string( pbase(), static_cast( pptr() - pbase() ) ) ); + setp( pbase(), epptr() ); + } + return 0; + } + }; + + /////////////////////////////////////////////////////////////////////////// + + struct OutputDebugWriter { + + void operator()( std::string const&str ) { + writeToDebugConsole( str ); + } + }; + + /////////////////////////////////////////////////////////////////////////// + + class FileStream : public IStream { + mutable std::ofstream m_ofs; + public: + FileStream( StringRef filename ) { + m_ofs.open( filename.c_str() ); + CATCH_ENFORCE( !m_ofs.fail(), "Unable to open file: '" << filename << "'" ); + } + ~FileStream() override = default; + public: // IStream + std::ostream& stream() const override { + return m_ofs; + } + }; + + /////////////////////////////////////////////////////////////////////////// + + class CoutStream : public IStream { + mutable std::ostream m_os; + public: + // Store the streambuf from cout up-front because + // cout may get redirected when running tests + CoutStream() : m_os( Catch::cout().rdbuf() ) {} + ~CoutStream() override = default; + + public: // IStream + std::ostream& stream() const override { return m_os; } + }; + + /////////////////////////////////////////////////////////////////////////// + + class DebugOutStream : public IStream { + std::unique_ptr> m_streamBuf; + mutable std::ostream m_os; + public: + DebugOutStream() + : m_streamBuf( new StreamBufImpl() ), + m_os( m_streamBuf.get() ) + {} + + ~DebugOutStream() override = default; + + public: // IStream + std::ostream& stream() const override { return m_os; } + }; + + }} // namespace anon::detail + + /////////////////////////////////////////////////////////////////////////// + + auto makeStream( StringRef const &filename ) -> IStream const* { + if( filename.empty() ) + return new detail::CoutStream(); + else if( filename[0] == '%' ) { + if( filename == "%debug" ) + return new detail::DebugOutStream(); + else + CATCH_ERROR( "Unrecognised stream: '" << filename << "'" ); + } + else + return new detail::FileStream( filename ); + } + + // This class encapsulates the idea of a pool of ostringstreams that can be reused. + struct StringStreams { + std::vector> m_streams; + std::vector m_unused; + std::ostringstream m_referenceStream; // Used for copy state/ flags from + + auto add() -> std::size_t { + if( m_unused.empty() ) { + m_streams.push_back( std::unique_ptr( new std::ostringstream ) ); + return m_streams.size()-1; + } + else { + auto index = m_unused.back(); + m_unused.pop_back(); + return index; + } + } + + void release( std::size_t index ) { + m_streams[index]->copyfmt( m_referenceStream ); // Restore initial flags and other state + m_unused.push_back(index); + } + }; + + ReusableStringStream::ReusableStringStream() + : m_index( Singleton::getMutable().add() ), + m_oss( Singleton::getMutable().m_streams[m_index].get() ) + {} + + ReusableStringStream::~ReusableStringStream() { + static_cast( m_oss )->str(""); + m_oss->clear(); + Singleton::getMutable().release( m_index ); + } + + auto ReusableStringStream::str() const -> std::string { + return static_cast( m_oss )->str(); + } + + /////////////////////////////////////////////////////////////////////////// + +#ifndef CATCH_CONFIG_NOSTDOUT // If you #define this you must implement these functions + std::ostream& cout() { return std::cout; } + std::ostream& cerr() { return std::cerr; } + std::ostream& clog() { return std::clog; } +#endif +} +// end catch_stream.cpp +// start catch_string_manip.cpp + +#include +#include +#include +#include + +namespace Catch { + + namespace { + char toLowerCh(char c) { + return static_cast( std::tolower( c ) ); + } + } + + bool startsWith( std::string const& s, std::string const& prefix ) { + return s.size() >= prefix.size() && std::equal(prefix.begin(), prefix.end(), s.begin()); + } + bool startsWith( std::string const& s, char prefix ) { + return !s.empty() && s[0] == prefix; + } + bool endsWith( std::string const& s, std::string const& suffix ) { + return s.size() >= suffix.size() && std::equal(suffix.rbegin(), suffix.rend(), s.rbegin()); + } + bool endsWith( std::string const& s, char suffix ) { + return !s.empty() && s[s.size()-1] == suffix; + } + bool contains( std::string const& s, std::string const& infix ) { + return s.find( infix ) != std::string::npos; + } + void toLowerInPlace( std::string& s ) { + std::transform( s.begin(), s.end(), s.begin(), toLowerCh ); + } + std::string toLower( std::string const& s ) { + std::string lc = s; + toLowerInPlace( lc ); + return lc; + } + std::string trim( std::string const& str ) { + static char const* whitespaceChars = "\n\r\t "; + std::string::size_type start = str.find_first_not_of( whitespaceChars ); + std::string::size_type end = str.find_last_not_of( whitespaceChars ); + + return start != std::string::npos ? str.substr( start, 1+end-start ) : std::string(); + } + + bool replaceInPlace( std::string& str, std::string const& replaceThis, std::string const& withThis ) { + bool replaced = false; + std::size_t i = str.find( replaceThis ); + while( i != std::string::npos ) { + replaced = true; + str = str.substr( 0, i ) + withThis + str.substr( i+replaceThis.size() ); + if( i < str.size()-withThis.size() ) + i = str.find( replaceThis, i+withThis.size() ); + else + i = std::string::npos; + } + return replaced; + } + + pluralise::pluralise( std::size_t count, std::string const& label ) + : m_count( count ), + m_label( label ) + {} + + std::ostream& operator << ( std::ostream& os, pluralise const& pluraliser ) { + os << pluraliser.m_count << ' ' << pluraliser.m_label; + if( pluraliser.m_count != 1 ) + os << 's'; + return os; + } + +} +// end catch_string_manip.cpp +// start catch_stringref.cpp + +#if defined(__clang__) +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wexit-time-destructors" +#endif + +#include +#include +#include + +namespace { + const uint32_t byte_2_lead = 0xC0; + const uint32_t byte_3_lead = 0xE0; + const uint32_t byte_4_lead = 0xF0; +} + +namespace Catch { + StringRef::StringRef( char const* rawChars ) noexcept + : StringRef( rawChars, static_cast(std::strlen(rawChars) ) ) + {} + + StringRef::operator std::string() const { + return std::string( m_start, m_size ); + } + + void StringRef::swap( StringRef& other ) noexcept { + std::swap( m_start, other.m_start ); + std::swap( m_size, other.m_size ); + std::swap( m_data, other.m_data ); + } + + auto StringRef::c_str() const -> char const* { + if( isSubstring() ) + const_cast( this )->takeOwnership(); + return m_start; + } + auto StringRef::currentData() const noexcept -> char const* { + return m_start; + } + + auto StringRef::isOwned() const noexcept -> bool { + return m_data != nullptr; + } + auto StringRef::isSubstring() const noexcept -> bool { + return m_start[m_size] != '\0'; + } + + void StringRef::takeOwnership() { + if( !isOwned() ) { + m_data = new char[m_size+1]; + memcpy( m_data, m_start, m_size ); + m_data[m_size] = '\0'; + m_start = m_data; + } + } + auto StringRef::substr( size_type start, size_type size ) const noexcept -> StringRef { + if( start < m_size ) + return StringRef( m_start+start, size ); + else + return StringRef(); + } + auto StringRef::operator == ( StringRef const& other ) const noexcept -> bool { + return + size() == other.size() && + (std::strncmp( m_start, other.m_start, size() ) == 0); + } + auto StringRef::operator != ( StringRef const& other ) const noexcept -> bool { + return !operator==( other ); + } + + auto StringRef::operator[](size_type index) const noexcept -> char { + return m_start[index]; + } + + auto StringRef::numberOfCharacters() const noexcept -> size_type { + size_type noChars = m_size; + // Make adjustments for uft encodings + for( size_type i=0; i < m_size; ++i ) { + char c = m_start[i]; + if( ( c & byte_2_lead ) == byte_2_lead ) { + noChars--; + if (( c & byte_3_lead ) == byte_3_lead ) + noChars--; + if( ( c & byte_4_lead ) == byte_4_lead ) + noChars--; + } + } + return noChars; + } + + auto operator + ( StringRef const& lhs, StringRef const& rhs ) -> std::string { + std::string str; + str.reserve( lhs.size() + rhs.size() ); + str += lhs; + str += rhs; + return str; + } + auto operator + ( StringRef const& lhs, const char* rhs ) -> std::string { + return std::string( lhs ) + std::string( rhs ); + } + auto operator + ( char const* lhs, StringRef const& rhs ) -> std::string { + return std::string( lhs ) + std::string( rhs ); + } + + auto operator << ( std::ostream& os, StringRef const& str ) -> std::ostream& { + return os.write(str.currentData(), str.size()); + } + + auto operator+=( std::string& lhs, StringRef const& rhs ) -> std::string& { + lhs.append(rhs.currentData(), rhs.size()); + return lhs; + } + +} // namespace Catch + +#if defined(__clang__) +# pragma clang diagnostic pop +#endif +// end catch_stringref.cpp +// start catch_tag_alias.cpp + +namespace Catch { + TagAlias::TagAlias(std::string const & _tag, SourceLineInfo _lineInfo): tag(_tag), lineInfo(_lineInfo) {} +} +// end catch_tag_alias.cpp +// start catch_tag_alias_autoregistrar.cpp + +namespace Catch { + + RegistrarForTagAliases::RegistrarForTagAliases(char const* alias, char const* tag, SourceLineInfo const& lineInfo) { + CATCH_TRY { + getMutableRegistryHub().registerTagAlias(alias, tag, lineInfo); + } CATCH_CATCH_ALL { + // Do not throw when constructing global objects, instead register the exception to be processed later + getMutableRegistryHub().registerStartupException(); + } + } + +} +// end catch_tag_alias_autoregistrar.cpp +// start catch_tag_alias_registry.cpp + +#include + +namespace Catch { + + TagAliasRegistry::~TagAliasRegistry() {} + + TagAlias const* TagAliasRegistry::find( std::string const& alias ) const { + auto it = m_registry.find( alias ); + if( it != m_registry.end() ) + return &(it->second); + else + return nullptr; + } + + std::string TagAliasRegistry::expandAliases( std::string const& unexpandedTestSpec ) const { + std::string expandedTestSpec = unexpandedTestSpec; + for( auto const& registryKvp : m_registry ) { + std::size_t pos = expandedTestSpec.find( registryKvp.first ); + if( pos != std::string::npos ) { + expandedTestSpec = expandedTestSpec.substr( 0, pos ) + + registryKvp.second.tag + + expandedTestSpec.substr( pos + registryKvp.first.size() ); + } + } + return expandedTestSpec; + } + + void TagAliasRegistry::add( std::string const& alias, std::string const& tag, SourceLineInfo const& lineInfo ) { + CATCH_ENFORCE( startsWith(alias, "[@") && endsWith(alias, ']'), + "error: tag alias, '" << alias << "' is not of the form [@alias name].\n" << lineInfo ); + + CATCH_ENFORCE( m_registry.insert(std::make_pair(alias, TagAlias(tag, lineInfo))).second, + "error: tag alias, '" << alias << "' already registered.\n" + << "\tFirst seen at: " << find(alias)->lineInfo << "\n" + << "\tRedefined at: " << lineInfo ); + } + + ITagAliasRegistry::~ITagAliasRegistry() {} + + ITagAliasRegistry const& ITagAliasRegistry::get() { + return getRegistryHub().getTagAliasRegistry(); + } + +} // end namespace Catch +// end catch_tag_alias_registry.cpp +// start catch_test_case_info.cpp + +#include +#include +#include +#include + +namespace Catch { + + namespace { + TestCaseInfo::SpecialProperties parseSpecialTag( std::string const& tag ) { + if( startsWith( tag, '.' ) || + tag == "!hide" ) + return TestCaseInfo::IsHidden; + else if( tag == "!throws" ) + return TestCaseInfo::Throws; + else if( tag == "!shouldfail" ) + return TestCaseInfo::ShouldFail; + else if( tag == "!mayfail" ) + return TestCaseInfo::MayFail; + else if( tag == "!nonportable" ) + return TestCaseInfo::NonPortable; + else if( tag == "!benchmark" ) + return static_cast( TestCaseInfo::Benchmark | TestCaseInfo::IsHidden ); + else + return TestCaseInfo::None; + } + bool isReservedTag( std::string const& tag ) { + return parseSpecialTag( tag ) == TestCaseInfo::None && tag.size() > 0 && !std::isalnum( static_cast(tag[0]) ); + } + void enforceNotReservedTag( std::string const& tag, SourceLineInfo const& _lineInfo ) { + CATCH_ENFORCE( !isReservedTag(tag), + "Tag name: [" << tag << "] is not allowed.\n" + << "Tag names starting with non alpha-numeric characters are reserved\n" + << _lineInfo ); + } + } + + TestCase makeTestCase( ITestInvoker* _testCase, + std::string const& _className, + NameAndTags const& nameAndTags, + SourceLineInfo const& _lineInfo ) + { + bool isHidden = false; + + // Parse out tags + std::vector tags; + std::string desc, tag; + bool inTag = false; + std::string _descOrTags = nameAndTags.tags; + for (char c : _descOrTags) { + if( !inTag ) { + if( c == '[' ) + inTag = true; + else + desc += c; + } + else { + if( c == ']' ) { + TestCaseInfo::SpecialProperties prop = parseSpecialTag( tag ); + if( ( prop & TestCaseInfo::IsHidden ) != 0 ) + isHidden = true; + else if( prop == TestCaseInfo::None ) + enforceNotReservedTag( tag, _lineInfo ); + + tags.push_back( tag ); + tag.clear(); + inTag = false; + } + else + tag += c; + } + } + if( isHidden ) { + tags.push_back( "." ); + } + + TestCaseInfo info( nameAndTags.name, _className, desc, tags, _lineInfo ); + return TestCase( _testCase, std::move(info) ); + } + + void setTags( TestCaseInfo& testCaseInfo, std::vector tags ) { + std::sort(begin(tags), end(tags)); + tags.erase(std::unique(begin(tags), end(tags)), end(tags)); + testCaseInfo.lcaseTags.clear(); + + for( auto const& tag : tags ) { + std::string lcaseTag = toLower( tag ); + testCaseInfo.properties = static_cast( testCaseInfo.properties | parseSpecialTag( lcaseTag ) ); + testCaseInfo.lcaseTags.push_back( lcaseTag ); + } + testCaseInfo.tags = std::move(tags); + } + + TestCaseInfo::TestCaseInfo( std::string const& _name, + std::string const& _className, + std::string const& _description, + std::vector const& _tags, + SourceLineInfo const& _lineInfo ) + : name( _name ), + className( _className ), + description( _description ), + lineInfo( _lineInfo ), + properties( None ) + { + setTags( *this, _tags ); + } + + bool TestCaseInfo::isHidden() const { + return ( properties & IsHidden ) != 0; + } + bool TestCaseInfo::throws() const { + return ( properties & Throws ) != 0; + } + bool TestCaseInfo::okToFail() const { + return ( properties & (ShouldFail | MayFail ) ) != 0; + } + bool TestCaseInfo::expectedToFail() const { + return ( properties & (ShouldFail ) ) != 0; + } + + std::string TestCaseInfo::tagsAsString() const { + std::string ret; + // '[' and ']' per tag + std::size_t full_size = 2 * tags.size(); + for (const auto& tag : tags) { + full_size += tag.size(); + } + ret.reserve(full_size); + for (const auto& tag : tags) { + ret.push_back('['); + ret.append(tag); + ret.push_back(']'); + } + + return ret; + } + + TestCase::TestCase( ITestInvoker* testCase, TestCaseInfo&& info ) : TestCaseInfo( std::move(info) ), test( testCase ) {} + + TestCase TestCase::withName( std::string const& _newName ) const { + TestCase other( *this ); + other.name = _newName; + return other; + } + + void TestCase::invoke() const { + test->invoke(); + } + + bool TestCase::operator == ( TestCase const& other ) const { + return test.get() == other.test.get() && + name == other.name && + className == other.className; + } + + bool TestCase::operator < ( TestCase const& other ) const { + return name < other.name; + } + + TestCaseInfo const& TestCase::getTestCaseInfo() const + { + return *this; + } + +} // end namespace Catch +// end catch_test_case_info.cpp +// start catch_test_case_registry_impl.cpp + +#include + +namespace Catch { + + std::vector sortTests( IConfig const& config, std::vector const& unsortedTestCases ) { + + std::vector sorted = unsortedTestCases; + + switch( config.runOrder() ) { + case RunTests::InLexicographicalOrder: + std::sort( sorted.begin(), sorted.end() ); + break; + case RunTests::InRandomOrder: + seedRng( config ); + std::shuffle( sorted.begin(), sorted.end(), rng() ); + break; + case RunTests::InDeclarationOrder: + // already in declaration order + break; + } + return sorted; + } + bool matchTest( TestCase const& testCase, TestSpec const& testSpec, IConfig const& config ) { + return testSpec.matches( testCase ) && ( config.allowThrows() || !testCase.throws() ); + } + + void enforceNoDuplicateTestCases( std::vector const& functions ) { + std::set seenFunctions; + for( auto const& function : functions ) { + auto prev = seenFunctions.insert( function ); + CATCH_ENFORCE( prev.second, + "error: TEST_CASE( \"" << function.name << "\" ) already defined.\n" + << "\tFirst seen at " << prev.first->getTestCaseInfo().lineInfo << "\n" + << "\tRedefined at " << function.getTestCaseInfo().lineInfo ); + } + } + + std::vector filterTests( std::vector const& testCases, TestSpec const& testSpec, IConfig const& config ) { + std::vector filtered; + filtered.reserve( testCases.size() ); + for( auto const& testCase : testCases ) + if( matchTest( testCase, testSpec, config ) ) + filtered.push_back( testCase ); + return filtered; + } + std::vector const& getAllTestCasesSorted( IConfig const& config ) { + return getRegistryHub().getTestCaseRegistry().getAllTestsSorted( config ); + } + + void TestRegistry::registerTest( TestCase const& testCase ) { + std::string name = testCase.getTestCaseInfo().name; + if( name.empty() ) { + ReusableStringStream rss; + rss << "Anonymous test case " << ++m_unnamedCount; + return registerTest( testCase.withName( rss.str() ) ); + } + m_functions.push_back( testCase ); + } + + std::vector const& TestRegistry::getAllTests() const { + return m_functions; + } + std::vector const& TestRegistry::getAllTestsSorted( IConfig const& config ) const { + if( m_sortedFunctions.empty() ) + enforceNoDuplicateTestCases( m_functions ); + + if( m_currentSortOrder != config.runOrder() || m_sortedFunctions.empty() ) { + m_sortedFunctions = sortTests( config, m_functions ); + m_currentSortOrder = config.runOrder(); + } + return m_sortedFunctions; + } + + /////////////////////////////////////////////////////////////////////////// + TestInvokerAsFunction::TestInvokerAsFunction( void(*testAsFunction)() ) noexcept : m_testAsFunction( testAsFunction ) {} + + void TestInvokerAsFunction::invoke() const { + m_testAsFunction(); + } + + std::string extractClassName( StringRef const& classOrQualifiedMethodName ) { + std::string className = classOrQualifiedMethodName; + if( startsWith( className, '&' ) ) + { + std::size_t lastColons = className.rfind( "::" ); + std::size_t penultimateColons = className.rfind( "::", lastColons-1 ); + if( penultimateColons == std::string::npos ) + penultimateColons = 1; + className = className.substr( penultimateColons, lastColons-penultimateColons ); + } + return className; + } + +} // end namespace Catch +// end catch_test_case_registry_impl.cpp +// start catch_test_case_tracker.cpp + +#include +#include +#include +#include +#include + +#if defined(__clang__) +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wexit-time-destructors" +#endif + +namespace Catch { +namespace TestCaseTracking { + + NameAndLocation::NameAndLocation( std::string const& _name, SourceLineInfo const& _location ) + : name( _name ), + location( _location ) + {} + + ITracker::~ITracker() = default; + + TrackerContext& TrackerContext::instance() { + static TrackerContext s_instance; + return s_instance; + } + + ITracker& TrackerContext::startRun() { + m_rootTracker = std::make_shared( NameAndLocation( "{root}", CATCH_INTERNAL_LINEINFO ), *this, nullptr ); + m_currentTracker = nullptr; + m_runState = Executing; + return *m_rootTracker; + } + + void TrackerContext::endRun() { + m_rootTracker.reset(); + m_currentTracker = nullptr; + m_runState = NotStarted; + } + + void TrackerContext::startCycle() { + m_currentTracker = m_rootTracker.get(); + m_runState = Executing; + } + void TrackerContext::completeCycle() { + m_runState = CompletedCycle; + } + + bool TrackerContext::completedCycle() const { + return m_runState == CompletedCycle; + } + ITracker& TrackerContext::currentTracker() { + return *m_currentTracker; + } + void TrackerContext::setCurrentTracker( ITracker* tracker ) { + m_currentTracker = tracker; + } + + TrackerBase::TrackerBase( NameAndLocation const& nameAndLocation, TrackerContext& ctx, ITracker* parent ) + : m_nameAndLocation( nameAndLocation ), + m_ctx( ctx ), + m_parent( parent ) + {} + + NameAndLocation const& TrackerBase::nameAndLocation() const { + return m_nameAndLocation; + } + bool TrackerBase::isComplete() const { + return m_runState == CompletedSuccessfully || m_runState == Failed; + } + bool TrackerBase::isSuccessfullyCompleted() const { + return m_runState == CompletedSuccessfully; + } + bool TrackerBase::isOpen() const { + return m_runState != NotStarted && !isComplete(); + } + bool TrackerBase::hasChildren() const { + return !m_children.empty(); + } + + void TrackerBase::addChild( ITrackerPtr const& child ) { + m_children.push_back( child ); + } + + ITrackerPtr TrackerBase::findChild( NameAndLocation const& nameAndLocation ) { + auto it = std::find_if( m_children.begin(), m_children.end(), + [&nameAndLocation]( ITrackerPtr const& tracker ){ + return + tracker->nameAndLocation().location == nameAndLocation.location && + tracker->nameAndLocation().name == nameAndLocation.name; + } ); + return( it != m_children.end() ) + ? *it + : nullptr; + } + ITracker& TrackerBase::parent() { + assert( m_parent ); // Should always be non-null except for root + return *m_parent; + } + + void TrackerBase::openChild() { + if( m_runState != ExecutingChildren ) { + m_runState = ExecutingChildren; + if( m_parent ) + m_parent->openChild(); + } + } + + bool TrackerBase::isSectionTracker() const { return false; } + bool TrackerBase::isIndexTracker() const { return false; } + + void TrackerBase::open() { + m_runState = Executing; + moveToThis(); + if( m_parent ) + m_parent->openChild(); + } + + void TrackerBase::close() { + + // Close any still open children (e.g. generators) + while( &m_ctx.currentTracker() != this ) + m_ctx.currentTracker().close(); + + switch( m_runState ) { + case NeedsAnotherRun: + break; + + case Executing: + m_runState = CompletedSuccessfully; + break; + case ExecutingChildren: + if( m_children.empty() || m_children.back()->isComplete() ) + m_runState = CompletedSuccessfully; + break; + + case NotStarted: + case CompletedSuccessfully: + case Failed: + CATCH_INTERNAL_ERROR( "Illogical state: " << m_runState ); + + default: + CATCH_INTERNAL_ERROR( "Unknown state: " << m_runState ); + } + moveToParent(); + m_ctx.completeCycle(); + } + void TrackerBase::fail() { + m_runState = Failed; + if( m_parent ) + m_parent->markAsNeedingAnotherRun(); + moveToParent(); + m_ctx.completeCycle(); + } + void TrackerBase::markAsNeedingAnotherRun() { + m_runState = NeedsAnotherRun; + } + + void TrackerBase::moveToParent() { + assert( m_parent ); + m_ctx.setCurrentTracker( m_parent ); + } + void TrackerBase::moveToThis() { + m_ctx.setCurrentTracker( this ); + } + + SectionTracker::SectionTracker( NameAndLocation const& nameAndLocation, TrackerContext& ctx, ITracker* parent ) + : TrackerBase( nameAndLocation, ctx, parent ) + { + if( parent ) { + while( !parent->isSectionTracker() ) + parent = &parent->parent(); + + SectionTracker& parentSection = static_cast( *parent ); + addNextFilters( parentSection.m_filters ); + } + } + + bool SectionTracker::isSectionTracker() const { return true; } + + SectionTracker& SectionTracker::acquire( TrackerContext& ctx, NameAndLocation const& nameAndLocation ) { + std::shared_ptr section; + + ITracker& currentTracker = ctx.currentTracker(); + if( ITrackerPtr childTracker = currentTracker.findChild( nameAndLocation ) ) { + assert( childTracker ); + assert( childTracker->isSectionTracker() ); + section = std::static_pointer_cast( childTracker ); + } + else { + section = std::make_shared( nameAndLocation, ctx, ¤tTracker ); + currentTracker.addChild( section ); + } + if( !ctx.completedCycle() ) + section->tryOpen(); + return *section; + } + + void SectionTracker::tryOpen() { + if( !isComplete() && (m_filters.empty() || m_filters[0].empty() || m_filters[0] == m_nameAndLocation.name ) ) + open(); + } + + void SectionTracker::addInitialFilters( std::vector const& filters ) { + if( !filters.empty() ) { + m_filters.push_back(""); // Root - should never be consulted + m_filters.push_back(""); // Test Case - not a section filter + m_filters.insert( m_filters.end(), filters.begin(), filters.end() ); + } + } + void SectionTracker::addNextFilters( std::vector const& filters ) { + if( filters.size() > 1 ) + m_filters.insert( m_filters.end(), ++filters.begin(), filters.end() ); + } + + IndexTracker::IndexTracker( NameAndLocation const& nameAndLocation, TrackerContext& ctx, ITracker* parent, int size ) + : TrackerBase( nameAndLocation, ctx, parent ), + m_size( size ) + {} + + bool IndexTracker::isIndexTracker() const { return true; } + + IndexTracker& IndexTracker::acquire( TrackerContext& ctx, NameAndLocation const& nameAndLocation, int size ) { + std::shared_ptr tracker; + + ITracker& currentTracker = ctx.currentTracker(); + if( ITrackerPtr childTracker = currentTracker.findChild( nameAndLocation ) ) { + assert( childTracker ); + assert( childTracker->isIndexTracker() ); + tracker = std::static_pointer_cast( childTracker ); + } + else { + tracker = std::make_shared( nameAndLocation, ctx, ¤tTracker, size ); + currentTracker.addChild( tracker ); + } + + if( !ctx.completedCycle() && !tracker->isComplete() ) { + if( tracker->m_runState != ExecutingChildren && tracker->m_runState != NeedsAnotherRun ) + tracker->moveNext(); + tracker->open(); + } + + return *tracker; + } + + int IndexTracker::index() const { return m_index; } + + void IndexTracker::moveNext() { + m_index++; + m_children.clear(); + } + + void IndexTracker::close() { + TrackerBase::close(); + if( m_runState == CompletedSuccessfully && m_index < m_size-1 ) + m_runState = Executing; + } + +} // namespace TestCaseTracking + +using TestCaseTracking::ITracker; +using TestCaseTracking::TrackerContext; +using TestCaseTracking::SectionTracker; +using TestCaseTracking::IndexTracker; + +} // namespace Catch + +#if defined(__clang__) +# pragma clang diagnostic pop +#endif +// end catch_test_case_tracker.cpp +// start catch_test_registry.cpp + +namespace Catch { + + auto makeTestInvoker( void(*testAsFunction)() ) noexcept -> ITestInvoker* { + return new(std::nothrow) TestInvokerAsFunction( testAsFunction ); + } + + NameAndTags::NameAndTags( StringRef const& name_ , StringRef const& tags_ ) noexcept : name( name_ ), tags( tags_ ) {} + + AutoReg::AutoReg( ITestInvoker* invoker, SourceLineInfo const& lineInfo, StringRef const& classOrMethod, NameAndTags const& nameAndTags ) noexcept { + CATCH_TRY { + getMutableRegistryHub() + .registerTest( + makeTestCase( + invoker, + extractClassName( classOrMethod ), + nameAndTags, + lineInfo)); + } CATCH_CATCH_ALL { + // Do not throw when constructing global objects, instead register the exception to be processed later + getMutableRegistryHub().registerStartupException(); + } + } + + AutoReg::~AutoReg() = default; +} +// end catch_test_registry.cpp +// start catch_test_spec.cpp + +#include +#include +#include +#include + +namespace Catch { + + TestSpec::Pattern::~Pattern() = default; + TestSpec::NamePattern::~NamePattern() = default; + TestSpec::TagPattern::~TagPattern() = default; + TestSpec::ExcludedPattern::~ExcludedPattern() = default; + + TestSpec::NamePattern::NamePattern( std::string const& name ) + : m_wildcardPattern( toLower( name ), CaseSensitive::No ) + {} + bool TestSpec::NamePattern::matches( TestCaseInfo const& testCase ) const { + return m_wildcardPattern.matches( toLower( testCase.name ) ); + } + + TestSpec::TagPattern::TagPattern( std::string const& tag ) : m_tag( toLower( tag ) ) {} + bool TestSpec::TagPattern::matches( TestCaseInfo const& testCase ) const { + return std::find(begin(testCase.lcaseTags), + end(testCase.lcaseTags), + m_tag) != end(testCase.lcaseTags); + } + + TestSpec::ExcludedPattern::ExcludedPattern( PatternPtr const& underlyingPattern ) : m_underlyingPattern( underlyingPattern ) {} + bool TestSpec::ExcludedPattern::matches( TestCaseInfo const& testCase ) const { return !m_underlyingPattern->matches( testCase ); } + + bool TestSpec::Filter::matches( TestCaseInfo const& testCase ) const { + // All patterns in a filter must match for the filter to be a match + for( auto const& pattern : m_patterns ) { + if( !pattern->matches( testCase ) ) + return false; + } + return true; + } + + bool TestSpec::hasFilters() const { + return !m_filters.empty(); + } + bool TestSpec::matches( TestCaseInfo const& testCase ) const { + // A TestSpec matches if any filter matches + for( auto const& filter : m_filters ) + if( filter.matches( testCase ) ) + return true; + return false; + } +} +// end catch_test_spec.cpp +// start catch_test_spec_parser.cpp + +namespace Catch { + + TestSpecParser::TestSpecParser( ITagAliasRegistry const& tagAliases ) : m_tagAliases( &tagAliases ) {} + + TestSpecParser& TestSpecParser::parse( std::string const& arg ) { + m_mode = None; + m_exclusion = false; + m_start = std::string::npos; + m_arg = m_tagAliases->expandAliases( arg ); + m_escapeChars.clear(); + for( m_pos = 0; m_pos < m_arg.size(); ++m_pos ) + visitChar( m_arg[m_pos] ); + if( m_mode == Name ) + addPattern(); + return *this; + } + TestSpec TestSpecParser::testSpec() { + addFilter(); + return m_testSpec; + } + + void TestSpecParser::visitChar( char c ) { + if( m_mode == None ) { + switch( c ) { + case ' ': return; + case '~': m_exclusion = true; return; + case '[': return startNewMode( Tag, ++m_pos ); + case '"': return startNewMode( QuotedName, ++m_pos ); + case '\\': return escape(); + default: startNewMode( Name, m_pos ); break; + } + } + if( m_mode == Name ) { + if( c == ',' ) { + addPattern(); + addFilter(); + } + else if( c == '[' ) { + if( subString() == "exclude:" ) + m_exclusion = true; + else + addPattern(); + startNewMode( Tag, ++m_pos ); + } + else if( c == '\\' ) + escape(); + } + else if( m_mode == EscapedName ) + m_mode = Name; + else if( m_mode == QuotedName && c == '"' ) + addPattern(); + else if( m_mode == Tag && c == ']' ) + addPattern(); + } + void TestSpecParser::startNewMode( Mode mode, std::size_t start ) { + m_mode = mode; + m_start = start; + } + void TestSpecParser::escape() { + if( m_mode == None ) + m_start = m_pos; + m_mode = EscapedName; + m_escapeChars.push_back( m_pos ); + } + std::string TestSpecParser::subString() const { return m_arg.substr( m_start, m_pos - m_start ); } + + void TestSpecParser::addFilter() { + if( !m_currentFilter.m_patterns.empty() ) { + m_testSpec.m_filters.push_back( m_currentFilter ); + m_currentFilter = TestSpec::Filter(); + } + } + + TestSpec parseTestSpec( std::string const& arg ) { + return TestSpecParser( ITagAliasRegistry::get() ).parse( arg ).testSpec(); + } + +} // namespace Catch +// end catch_test_spec_parser.cpp +// start catch_timer.cpp + +#include + +static const uint64_t nanosecondsInSecond = 1000000000; + +namespace Catch { + + auto getCurrentNanosecondsSinceEpoch() -> uint64_t { + return std::chrono::duration_cast( std::chrono::high_resolution_clock::now().time_since_epoch() ).count(); + } + + namespace { + auto estimateClockResolution() -> uint64_t { + uint64_t sum = 0; + static const uint64_t iterations = 1000000; + + auto startTime = getCurrentNanosecondsSinceEpoch(); + + for( std::size_t i = 0; i < iterations; ++i ) { + + uint64_t ticks; + uint64_t baseTicks = getCurrentNanosecondsSinceEpoch(); + do { + ticks = getCurrentNanosecondsSinceEpoch(); + } while( ticks == baseTicks ); + + auto delta = ticks - baseTicks; + sum += delta; + + // If we have been calibrating for over 3 seconds -- the clock + // is terrible and we should move on. + // TBD: How to signal that the measured resolution is probably wrong? + if (ticks > startTime + 3 * nanosecondsInSecond) { + return sum / i; + } + } + + // We're just taking the mean, here. To do better we could take the std. dev and exclude outliers + // - and potentially do more iterations if there's a high variance. + return sum/iterations; + } + } + auto getEstimatedClockResolution() -> uint64_t { + static auto s_resolution = estimateClockResolution(); + return s_resolution; + } + + void Timer::start() { + m_nanoseconds = getCurrentNanosecondsSinceEpoch(); + } + auto Timer::getElapsedNanoseconds() const -> uint64_t { + return getCurrentNanosecondsSinceEpoch() - m_nanoseconds; + } + auto Timer::getElapsedMicroseconds() const -> uint64_t { + return getElapsedNanoseconds()/1000; + } + auto Timer::getElapsedMilliseconds() const -> unsigned int { + return static_cast(getElapsedMicroseconds()/1000); + } + auto Timer::getElapsedSeconds() const -> double { + return getElapsedMicroseconds()/1000000.0; + } + +} // namespace Catch +// end catch_timer.cpp +// start catch_tostring.cpp + +#if defined(__clang__) +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wexit-time-destructors" +# pragma clang diagnostic ignored "-Wglobal-constructors" +#endif + +// Enable specific decls locally +#if !defined(CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER) +#define CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER +#endif + +#include +#include + +namespace Catch { + +namespace Detail { + + const std::string unprintableString = "{?}"; + + namespace { + const int hexThreshold = 255; + + struct Endianness { + enum Arch { Big, Little }; + + static Arch which() { + union _{ + int asInt; + char asChar[sizeof (int)]; + } u; + + u.asInt = 1; + return ( u.asChar[sizeof(int)-1] == 1 ) ? Big : Little; + } + }; + } + + std::string rawMemoryToString( const void *object, std::size_t size ) { + // Reverse order for little endian architectures + int i = 0, end = static_cast( size ), inc = 1; + if( Endianness::which() == Endianness::Little ) { + i = end-1; + end = inc = -1; + } + + unsigned char const *bytes = static_cast(object); + ReusableStringStream rss; + rss << "0x" << std::setfill('0') << std::hex; + for( ; i != end; i += inc ) + rss << std::setw(2) << static_cast(bytes[i]); + return rss.str(); + } +} + +template +std::string fpToString( T value, int precision ) { + if (std::isnan(value)) { + return "nan"; + } + + ReusableStringStream rss; + rss << std::setprecision( precision ) + << std::fixed + << value; + std::string d = rss.str(); + std::size_t i = d.find_last_not_of( '0' ); + if( i != std::string::npos && i != d.size()-1 ) { + if( d[i] == '.' ) + i++; + d = d.substr( 0, i+1 ); + } + return d; +} + +//// ======================================================= //// +// +// Out-of-line defs for full specialization of StringMaker +// +//// ======================================================= //// + +std::string StringMaker::convert(const std::string& str) { + if (!getCurrentContext().getConfig()->showInvisibles()) { + return '"' + str + '"'; + } + + std::string s("\""); + for (char c : str) { + switch (c) { + case '\n': + s.append("\\n"); + break; + case '\t': + s.append("\\t"); + break; + default: + s.push_back(c); + break; + } + } + s.append("\""); + return s; +} + +#ifdef CATCH_CONFIG_CPP17_STRING_VIEW +std::string StringMaker::convert(std::string_view str) { + return ::Catch::Detail::stringify(std::string{ str }); +} +#endif + +std::string StringMaker::convert(char const* str) { + if (str) { + return ::Catch::Detail::stringify(std::string{ str }); + } else { + return{ "{null string}" }; + } +} +std::string StringMaker::convert(char* str) { + if (str) { + return ::Catch::Detail::stringify(std::string{ str }); + } else { + return{ "{null string}" }; + } +} + +#ifdef CATCH_CONFIG_WCHAR +std::string StringMaker::convert(const std::wstring& wstr) { + std::string s; + s.reserve(wstr.size()); + for (auto c : wstr) { + s += (c <= 0xff) ? static_cast(c) : '?'; + } + return ::Catch::Detail::stringify(s); +} + +# ifdef CATCH_CONFIG_CPP17_STRING_VIEW +std::string StringMaker::convert(std::wstring_view str) { + return StringMaker::convert(std::wstring(str)); +} +# endif + +std::string StringMaker::convert(wchar_t const * str) { + if (str) { + return ::Catch::Detail::stringify(std::wstring{ str }); + } else { + return{ "{null string}" }; + } +} +std::string StringMaker::convert(wchar_t * str) { + if (str) { + return ::Catch::Detail::stringify(std::wstring{ str }); + } else { + return{ "{null string}" }; + } +} +#endif + +std::string StringMaker::convert(int value) { + return ::Catch::Detail::stringify(static_cast(value)); +} +std::string StringMaker::convert(long value) { + return ::Catch::Detail::stringify(static_cast(value)); +} +std::string StringMaker::convert(long long value) { + ReusableStringStream rss; + rss << value; + if (value > Detail::hexThreshold) { + rss << " (0x" << std::hex << value << ')'; + } + return rss.str(); +} + +std::string StringMaker::convert(unsigned int value) { + return ::Catch::Detail::stringify(static_cast(value)); +} +std::string StringMaker::convert(unsigned long value) { + return ::Catch::Detail::stringify(static_cast(value)); +} +std::string StringMaker::convert(unsigned long long value) { + ReusableStringStream rss; + rss << value; + if (value > Detail::hexThreshold) { + rss << " (0x" << std::hex << value << ')'; + } + return rss.str(); +} + +std::string StringMaker::convert(bool b) { + return b ? "true" : "false"; +} + +std::string StringMaker::convert(signed char value) { + if (value == '\r') { + return "'\\r'"; + } else if (value == '\f') { + return "'\\f'"; + } else if (value == '\n') { + return "'\\n'"; + } else if (value == '\t') { + return "'\\t'"; + } else if ('\0' <= value && value < ' ') { + return ::Catch::Detail::stringify(static_cast(value)); + } else { + char chstr[] = "' '"; + chstr[1] = value; + return chstr; + } +} +std::string StringMaker::convert(char c) { + return ::Catch::Detail::stringify(static_cast(c)); +} +std::string StringMaker::convert(unsigned char c) { + return ::Catch::Detail::stringify(static_cast(c)); +} + +std::string StringMaker::convert(std::nullptr_t) { + return "nullptr"; +} + +std::string StringMaker::convert(float value) { + return fpToString(value, 5) + 'f'; +} +std::string StringMaker::convert(double value) { + return fpToString(value, 10); +} + +std::string ratio_string::symbol() { return "a"; } +std::string ratio_string::symbol() { return "f"; } +std::string ratio_string::symbol() { return "p"; } +std::string ratio_string::symbol() { return "n"; } +std::string ratio_string::symbol() { return "u"; } +std::string ratio_string::symbol() { return "m"; } + +} // end namespace Catch + +#if defined(__clang__) +# pragma clang diagnostic pop +#endif + +// end catch_tostring.cpp +// start catch_totals.cpp + +namespace Catch { + + Counts Counts::operator - ( Counts const& other ) const { + Counts diff; + diff.passed = passed - other.passed; + diff.failed = failed - other.failed; + diff.failedButOk = failedButOk - other.failedButOk; + return diff; + } + + Counts& Counts::operator += ( Counts const& other ) { + passed += other.passed; + failed += other.failed; + failedButOk += other.failedButOk; + return *this; + } + + std::size_t Counts::total() const { + return passed + failed + failedButOk; + } + bool Counts::allPassed() const { + return failed == 0 && failedButOk == 0; + } + bool Counts::allOk() const { + return failed == 0; + } + + Totals Totals::operator - ( Totals const& other ) const { + Totals diff; + diff.assertions = assertions - other.assertions; + diff.testCases = testCases - other.testCases; + return diff; + } + + Totals& Totals::operator += ( Totals const& other ) { + assertions += other.assertions; + testCases += other.testCases; + return *this; + } + + Totals Totals::delta( Totals const& prevTotals ) const { + Totals diff = *this - prevTotals; + if( diff.assertions.failed > 0 ) + ++diff.testCases.failed; + else if( diff.assertions.failedButOk > 0 ) + ++diff.testCases.failedButOk; + else + ++diff.testCases.passed; + return diff; + } + +} +// end catch_totals.cpp +// start catch_uncaught_exceptions.cpp + +#include + +namespace Catch { + bool uncaught_exceptions() { +#if defined(CATCH_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS) + return std::uncaught_exceptions() > 0; +#else + return std::uncaught_exception(); +#endif + } +} // end namespace Catch +// end catch_uncaught_exceptions.cpp +// start catch_version.cpp + +#include + +namespace Catch { + + Version::Version + ( unsigned int _majorVersion, + unsigned int _minorVersion, + unsigned int _patchNumber, + char const * const _branchName, + unsigned int _buildNumber ) + : majorVersion( _majorVersion ), + minorVersion( _minorVersion ), + patchNumber( _patchNumber ), + branchName( _branchName ), + buildNumber( _buildNumber ) + {} + + std::ostream& operator << ( std::ostream& os, Version const& version ) { + os << version.majorVersion << '.' + << version.minorVersion << '.' + << version.patchNumber; + // branchName is never null -> 0th char is \0 if it is empty + if (version.branchName[0]) { + os << '-' << version.branchName + << '.' << version.buildNumber; + } + return os; + } + + Version const& libraryVersion() { + static Version version( 2, 4, 1, "", 0 ); + return version; + } + +} +// end catch_version.cpp +// start catch_wildcard_pattern.cpp + +#include + +namespace Catch { + + WildcardPattern::WildcardPattern( std::string const& pattern, + CaseSensitive::Choice caseSensitivity ) + : m_caseSensitivity( caseSensitivity ), + m_pattern( adjustCase( pattern ) ) + { + if( startsWith( m_pattern, '*' ) ) { + m_pattern = m_pattern.substr( 1 ); + m_wildcard = WildcardAtStart; + } + if( endsWith( m_pattern, '*' ) ) { + m_pattern = m_pattern.substr( 0, m_pattern.size()-1 ); + m_wildcard = static_cast( m_wildcard | WildcardAtEnd ); + } + } + + bool WildcardPattern::matches( std::string const& str ) const { + switch( m_wildcard ) { + case NoWildcard: + return m_pattern == adjustCase( str ); + case WildcardAtStart: + return endsWith( adjustCase( str ), m_pattern ); + case WildcardAtEnd: + return startsWith( adjustCase( str ), m_pattern ); + case WildcardAtBothEnds: + return contains( adjustCase( str ), m_pattern ); + default: + CATCH_INTERNAL_ERROR( "Unknown enum" ); + } + } + + std::string WildcardPattern::adjustCase( std::string const& str ) const { + return m_caseSensitivity == CaseSensitive::No ? toLower( str ) : str; + } +} +// end catch_wildcard_pattern.cpp +// start catch_xmlwriter.cpp + +#include + +using uchar = unsigned char; + +namespace Catch { + +namespace { + + size_t trailingBytes(unsigned char c) { + if ((c & 0xE0) == 0xC0) { + return 2; + } + if ((c & 0xF0) == 0xE0) { + return 3; + } + if ((c & 0xF8) == 0xF0) { + return 4; + } + CATCH_INTERNAL_ERROR("Invalid multibyte utf-8 start byte encountered"); + } + + uint32_t headerValue(unsigned char c) { + if ((c & 0xE0) == 0xC0) { + return c & 0x1F; + } + if ((c & 0xF0) == 0xE0) { + return c & 0x0F; + } + if ((c & 0xF8) == 0xF0) { + return c & 0x07; + } + CATCH_INTERNAL_ERROR("Invalid multibyte utf-8 start byte encountered"); + } + + void hexEscapeChar(std::ostream& os, unsigned char c) { + os << "\\x" + << std::uppercase << std::hex << std::setfill('0') << std::setw(2) + << static_cast(c); + } + +} // anonymous namespace + + XmlEncode::XmlEncode( std::string const& str, ForWhat forWhat ) + : m_str( str ), + m_forWhat( forWhat ) + {} + + void XmlEncode::encodeTo( std::ostream& os ) const { + // Apostrophe escaping not necessary if we always use " to write attributes + // (see: http://www.w3.org/TR/xml/#syntax) + + for( std::size_t idx = 0; idx < m_str.size(); ++ idx ) { + uchar c = m_str[idx]; + switch (c) { + case '<': os << "<"; break; + case '&': os << "&"; break; + + case '>': + // See: http://www.w3.org/TR/xml/#syntax + if (idx > 2 && m_str[idx - 1] == ']' && m_str[idx - 2] == ']') + os << ">"; + else + os << c; + break; + + case '\"': + if (m_forWhat == ForAttributes) + os << """; + else + os << c; + break; + + default: + // Check for control characters and invalid utf-8 + + // Escape control characters in standard ascii + // see http://stackoverflow.com/questions/404107/why-are-control-characters-illegal-in-xml-1-0 + if (c < 0x09 || (c > 0x0D && c < 0x20) || c == 0x7F) { + hexEscapeChar(os, c); + break; + } + + // Plain ASCII: Write it to stream + if (c < 0x7F) { + os << c; + break; + } + + // UTF-8 territory + // Check if the encoding is valid and if it is not, hex escape bytes. + // Important: We do not check the exact decoded values for validity, only the encoding format + // First check that this bytes is a valid lead byte: + // This means that it is not encoded as 1111 1XXX + // Or as 10XX XXXX + if (c < 0xC0 || + c >= 0xF8) { + hexEscapeChar(os, c); + break; + } + + auto encBytes = trailingBytes(c); + // Are there enough bytes left to avoid accessing out-of-bounds memory? + if (idx + encBytes - 1 >= m_str.size()) { + hexEscapeChar(os, c); + break; + } + // The header is valid, check data + // The next encBytes bytes must together be a valid utf-8 + // This means: bitpattern 10XX XXXX and the extracted value is sane (ish) + bool valid = true; + uint32_t value = headerValue(c); + for (std::size_t n = 1; n < encBytes; ++n) { + uchar nc = m_str[idx + n]; + valid &= ((nc & 0xC0) == 0x80); + value = (value << 6) | (nc & 0x3F); + } + + if ( + // Wrong bit pattern of following bytes + (!valid) || + // Overlong encodings + (value < 0x80) || + (0x80 <= value && value < 0x800 && encBytes > 2) || + (0x800 < value && value < 0x10000 && encBytes > 3) || + // Encoded value out of range + (value >= 0x110000) + ) { + hexEscapeChar(os, c); + break; + } + + // If we got here, this is in fact a valid(ish) utf-8 sequence + for (std::size_t n = 0; n < encBytes; ++n) { + os << m_str[idx + n]; + } + idx += encBytes - 1; + break; + } + } + } + + std::ostream& operator << ( std::ostream& os, XmlEncode const& xmlEncode ) { + xmlEncode.encodeTo( os ); + return os; + } + + XmlWriter::ScopedElement::ScopedElement( XmlWriter* writer ) + : m_writer( writer ) + {} + + XmlWriter::ScopedElement::ScopedElement( ScopedElement&& other ) noexcept + : m_writer( other.m_writer ){ + other.m_writer = nullptr; + } + XmlWriter::ScopedElement& XmlWriter::ScopedElement::operator=( ScopedElement&& other ) noexcept { + if ( m_writer ) { + m_writer->endElement(); + } + m_writer = other.m_writer; + other.m_writer = nullptr; + return *this; + } + + XmlWriter::ScopedElement::~ScopedElement() { + if( m_writer ) + m_writer->endElement(); + } + + XmlWriter::ScopedElement& XmlWriter::ScopedElement::writeText( std::string const& text, bool indent ) { + m_writer->writeText( text, indent ); + return *this; + } + + XmlWriter::XmlWriter( std::ostream& os ) : m_os( os ) + { + writeDeclaration(); + } + + XmlWriter::~XmlWriter() { + while( !m_tags.empty() ) + endElement(); + } + + XmlWriter& XmlWriter::startElement( std::string const& name ) { + ensureTagClosed(); + newlineIfNecessary(); + m_os << m_indent << '<' << name; + m_tags.push_back( name ); + m_indent += " "; + m_tagIsOpen = true; + return *this; + } + + XmlWriter::ScopedElement XmlWriter::scopedElement( std::string const& name ) { + ScopedElement scoped( this ); + startElement( name ); + return scoped; + } + + XmlWriter& XmlWriter::endElement() { + newlineIfNecessary(); + m_indent = m_indent.substr( 0, m_indent.size()-2 ); + if( m_tagIsOpen ) { + m_os << "/>"; + m_tagIsOpen = false; + } + else { + m_os << m_indent << ""; + } + m_os << std::endl; + m_tags.pop_back(); + return *this; + } + + XmlWriter& XmlWriter::writeAttribute( std::string const& name, std::string const& attribute ) { + if( !name.empty() && !attribute.empty() ) + m_os << ' ' << name << "=\"" << XmlEncode( attribute, XmlEncode::ForAttributes ) << '"'; + return *this; + } + + XmlWriter& XmlWriter::writeAttribute( std::string const& name, bool attribute ) { + m_os << ' ' << name << "=\"" << ( attribute ? "true" : "false" ) << '"'; + return *this; + } + + XmlWriter& XmlWriter::writeText( std::string const& text, bool indent ) { + if( !text.empty() ){ + bool tagWasOpen = m_tagIsOpen; + ensureTagClosed(); + if( tagWasOpen && indent ) + m_os << m_indent; + m_os << XmlEncode( text ); + m_needsNewline = true; + } + return *this; + } + + XmlWriter& XmlWriter::writeComment( std::string const& text ) { + ensureTagClosed(); + m_os << m_indent << ""; + m_needsNewline = true; + return *this; + } + + void XmlWriter::writeStylesheetRef( std::string const& url ) { + m_os << "\n"; + } + + XmlWriter& XmlWriter::writeBlankLine() { + ensureTagClosed(); + m_os << '\n'; + return *this; + } + + void XmlWriter::ensureTagClosed() { + if( m_tagIsOpen ) { + m_os << ">" << std::endl; + m_tagIsOpen = false; + } + } + + void XmlWriter::writeDeclaration() { + m_os << "\n"; + } + + void XmlWriter::newlineIfNecessary() { + if( m_needsNewline ) { + m_os << std::endl; + m_needsNewline = false; + } + } +} +// end catch_xmlwriter.cpp +// start catch_reporter_bases.cpp + +#include +#include +#include +#include +#include + +namespace Catch { + void prepareExpandedExpression(AssertionResult& result) { + result.getExpandedExpression(); + } + + // Because formatting using c++ streams is stateful, drop down to C is required + // Alternatively we could use stringstream, but its performance is... not good. + std::string getFormattedDuration( double duration ) { + // Max exponent + 1 is required to represent the whole part + // + 1 for decimal point + // + 3 for the 3 decimal places + // + 1 for null terminator + const std::size_t maxDoubleSize = DBL_MAX_10_EXP + 1 + 1 + 3 + 1; + char buffer[maxDoubleSize]; + + // Save previous errno, to prevent sprintf from overwriting it + ErrnoGuard guard; +#ifdef _MSC_VER + sprintf_s(buffer, "%.3f", duration); +#else + sprintf(buffer, "%.3f", duration); +#endif + return std::string(buffer); + } + + TestEventListenerBase::TestEventListenerBase(ReporterConfig const & _config) + :StreamingReporterBase(_config) {} + + void TestEventListenerBase::assertionStarting(AssertionInfo const &) {} + + bool TestEventListenerBase::assertionEnded(AssertionStats const &) { + return false; + } + +} // end namespace Catch +// end catch_reporter_bases.cpp +// start catch_reporter_compact.cpp + +namespace { + +#ifdef CATCH_PLATFORM_MAC + const char* failedString() { return "FAILED"; } + const char* passedString() { return "PASSED"; } +#else + const char* failedString() { return "failed"; } + const char* passedString() { return "passed"; } +#endif + + // Colour::LightGrey + Catch::Colour::Code dimColour() { return Catch::Colour::FileName; } + + std::string bothOrAll( std::size_t count ) { + return count == 1 ? std::string() : + count == 2 ? "both " : "all " ; + } + +} // anon namespace + +namespace Catch { +namespace { +// Colour, message variants: +// - white: No tests ran. +// - red: Failed [both/all] N test cases, failed [both/all] M assertions. +// - white: Passed [both/all] N test cases (no assertions). +// - red: Failed N tests cases, failed M assertions. +// - green: Passed [both/all] N tests cases with M assertions. +void printTotals(std::ostream& out, const Totals& totals) { + if (totals.testCases.total() == 0) { + out << "No tests ran."; + } else if (totals.testCases.failed == totals.testCases.total()) { + Colour colour(Colour::ResultError); + const std::string qualify_assertions_failed = + totals.assertions.failed == totals.assertions.total() ? + bothOrAll(totals.assertions.failed) : std::string(); + out << + "Failed " << bothOrAll(totals.testCases.failed) + << pluralise(totals.testCases.failed, "test case") << ", " + "failed " << qualify_assertions_failed << + pluralise(totals.assertions.failed, "assertion") << '.'; + } else if (totals.assertions.total() == 0) { + out << + "Passed " << bothOrAll(totals.testCases.total()) + << pluralise(totals.testCases.total(), "test case") + << " (no assertions)."; + } else if (totals.assertions.failed) { + Colour colour(Colour::ResultError); + out << + "Failed " << pluralise(totals.testCases.failed, "test case") << ", " + "failed " << pluralise(totals.assertions.failed, "assertion") << '.'; + } else { + Colour colour(Colour::ResultSuccess); + out << + "Passed " << bothOrAll(totals.testCases.passed) + << pluralise(totals.testCases.passed, "test case") << + " with " << pluralise(totals.assertions.passed, "assertion") << '.'; + } +} + +// Implementation of CompactReporter formatting +class AssertionPrinter { +public: + AssertionPrinter& operator= (AssertionPrinter const&) = delete; + AssertionPrinter(AssertionPrinter const&) = delete; + AssertionPrinter(std::ostream& _stream, AssertionStats const& _stats, bool _printInfoMessages) + : stream(_stream) + , result(_stats.assertionResult) + , messages(_stats.infoMessages) + , itMessage(_stats.infoMessages.begin()) + , printInfoMessages(_printInfoMessages) {} + + void print() { + printSourceInfo(); + + itMessage = messages.begin(); + + switch (result.getResultType()) { + case ResultWas::Ok: + printResultType(Colour::ResultSuccess, passedString()); + printOriginalExpression(); + printReconstructedExpression(); + if (!result.hasExpression()) + printRemainingMessages(Colour::None); + else + printRemainingMessages(); + break; + case ResultWas::ExpressionFailed: + if (result.isOk()) + printResultType(Colour::ResultSuccess, failedString() + std::string(" - but was ok")); + else + printResultType(Colour::Error, failedString()); + printOriginalExpression(); + printReconstructedExpression(); + printRemainingMessages(); + break; + case ResultWas::ThrewException: + printResultType(Colour::Error, failedString()); + printIssue("unexpected exception with message:"); + printMessage(); + printExpressionWas(); + printRemainingMessages(); + break; + case ResultWas::FatalErrorCondition: + printResultType(Colour::Error, failedString()); + printIssue("fatal error condition with message:"); + printMessage(); + printExpressionWas(); + printRemainingMessages(); + break; + case ResultWas::DidntThrowException: + printResultType(Colour::Error, failedString()); + printIssue("expected exception, got none"); + printExpressionWas(); + printRemainingMessages(); + break; + case ResultWas::Info: + printResultType(Colour::None, "info"); + printMessage(); + printRemainingMessages(); + break; + case ResultWas::Warning: + printResultType(Colour::None, "warning"); + printMessage(); + printRemainingMessages(); + break; + case ResultWas::ExplicitFailure: + printResultType(Colour::Error, failedString()); + printIssue("explicitly"); + printRemainingMessages(Colour::None); + break; + // These cases are here to prevent compiler warnings + case ResultWas::Unknown: + case ResultWas::FailureBit: + case ResultWas::Exception: + printResultType(Colour::Error, "** internal error **"); + break; + } + } + +private: + void printSourceInfo() const { + Colour colourGuard(Colour::FileName); + stream << result.getSourceInfo() << ':'; + } + + void printResultType(Colour::Code colour, std::string const& passOrFail) const { + if (!passOrFail.empty()) { + { + Colour colourGuard(colour); + stream << ' ' << passOrFail; + } + stream << ':'; + } + } + + void printIssue(std::string const& issue) const { + stream << ' ' << issue; + } + + void printExpressionWas() { + if (result.hasExpression()) { + stream << ';'; + { + Colour colour(dimColour()); + stream << " expression was:"; + } + printOriginalExpression(); + } + } + + void printOriginalExpression() const { + if (result.hasExpression()) { + stream << ' ' << result.getExpression(); + } + } + + void printReconstructedExpression() const { + if (result.hasExpandedExpression()) { + { + Colour colour(dimColour()); + stream << " for: "; + } + stream << result.getExpandedExpression(); + } + } + + void printMessage() { + if (itMessage != messages.end()) { + stream << " '" << itMessage->message << '\''; + ++itMessage; + } + } + + void printRemainingMessages(Colour::Code colour = dimColour()) { + if (itMessage == messages.end()) + return; + + // using messages.end() directly yields (or auto) compilation error: + std::vector::const_iterator itEnd = messages.end(); + const std::size_t N = static_cast(std::distance(itMessage, itEnd)); + + { + Colour colourGuard(colour); + stream << " with " << pluralise(N, "message") << ':'; + } + + for (; itMessage != itEnd; ) { + // If this assertion is a warning ignore any INFO messages + if (printInfoMessages || itMessage->type != ResultWas::Info) { + stream << " '" << itMessage->message << '\''; + if (++itMessage != itEnd) { + Colour colourGuard(dimColour()); + stream << " and"; + } + } + } + } + +private: + std::ostream& stream; + AssertionResult const& result; + std::vector messages; + std::vector::const_iterator itMessage; + bool printInfoMessages; +}; + +} // anon namespace + + std::string CompactReporter::getDescription() { + return "Reports test results on a single line, suitable for IDEs"; + } + + ReporterPreferences CompactReporter::getPreferences() const { + return m_reporterPrefs; + } + + void CompactReporter::noMatchingTestCases( std::string const& spec ) { + stream << "No test cases matched '" << spec << '\'' << std::endl; + } + + void CompactReporter::assertionStarting( AssertionInfo const& ) {} + + bool CompactReporter::assertionEnded( AssertionStats const& _assertionStats ) { + AssertionResult const& result = _assertionStats.assertionResult; + + bool printInfoMessages = true; + + // Drop out if result was successful and we're not printing those + if( !m_config->includeSuccessfulResults() && result.isOk() ) { + if( result.getResultType() != ResultWas::Warning ) + return false; + printInfoMessages = false; + } + + AssertionPrinter printer( stream, _assertionStats, printInfoMessages ); + printer.print(); + + stream << std::endl; + return true; + } + + void CompactReporter::sectionEnded(SectionStats const& _sectionStats) { + if (m_config->showDurations() == ShowDurations::Always) { + stream << getFormattedDuration(_sectionStats.durationInSeconds) << " s: " << _sectionStats.sectionInfo.name << std::endl; + } + } + + void CompactReporter::testRunEnded( TestRunStats const& _testRunStats ) { + printTotals( stream, _testRunStats.totals ); + stream << '\n' << std::endl; + StreamingReporterBase::testRunEnded( _testRunStats ); + } + + CompactReporter::~CompactReporter() {} + + CATCH_REGISTER_REPORTER( "compact", CompactReporter ) + +} // end namespace Catch +// end catch_reporter_compact.cpp +// start catch_reporter_console.cpp + +#include +#include + +#if defined(_MSC_VER) +#pragma warning(push) +#pragma warning(disable:4061) // Not all labels are EXPLICITLY handled in switch + // Note that 4062 (not all labels are handled + // and default is missing) is enabled +#endif + +namespace Catch { + +namespace { + +// Formatter impl for ConsoleReporter +class ConsoleAssertionPrinter { +public: + ConsoleAssertionPrinter& operator= (ConsoleAssertionPrinter const&) = delete; + ConsoleAssertionPrinter(ConsoleAssertionPrinter const&) = delete; + ConsoleAssertionPrinter(std::ostream& _stream, AssertionStats const& _stats, bool _printInfoMessages) + : stream(_stream), + stats(_stats), + result(_stats.assertionResult), + colour(Colour::None), + message(result.getMessage()), + messages(_stats.infoMessages), + printInfoMessages(_printInfoMessages) { + switch (result.getResultType()) { + case ResultWas::Ok: + colour = Colour::Success; + passOrFail = "PASSED"; + //if( result.hasMessage() ) + if (_stats.infoMessages.size() == 1) + messageLabel = "with message"; + if (_stats.infoMessages.size() > 1) + messageLabel = "with messages"; + break; + case ResultWas::ExpressionFailed: + if (result.isOk()) { + colour = Colour::Success; + passOrFail = "FAILED - but was ok"; + } else { + colour = Colour::Error; + passOrFail = "FAILED"; + } + if (_stats.infoMessages.size() == 1) + messageLabel = "with message"; + if (_stats.infoMessages.size() > 1) + messageLabel = "with messages"; + break; + case ResultWas::ThrewException: + colour = Colour::Error; + passOrFail = "FAILED"; + messageLabel = "due to unexpected exception with "; + if (_stats.infoMessages.size() == 1) + messageLabel += "message"; + if (_stats.infoMessages.size() > 1) + messageLabel += "messages"; + break; + case ResultWas::FatalErrorCondition: + colour = Colour::Error; + passOrFail = "FAILED"; + messageLabel = "due to a fatal error condition"; + break; + case ResultWas::DidntThrowException: + colour = Colour::Error; + passOrFail = "FAILED"; + messageLabel = "because no exception was thrown where one was expected"; + break; + case ResultWas::Info: + messageLabel = "info"; + break; + case ResultWas::Warning: + messageLabel = "warning"; + break; + case ResultWas::ExplicitFailure: + passOrFail = "FAILED"; + colour = Colour::Error; + if (_stats.infoMessages.size() == 1) + messageLabel = "explicitly with message"; + if (_stats.infoMessages.size() > 1) + messageLabel = "explicitly with messages"; + break; + // These cases are here to prevent compiler warnings + case ResultWas::Unknown: + case ResultWas::FailureBit: + case ResultWas::Exception: + passOrFail = "** internal error **"; + colour = Colour::Error; + break; + } + } + + void print() const { + printSourceInfo(); + if (stats.totals.assertions.total() > 0) { + if (result.isOk()) + stream << '\n'; + printResultType(); + printOriginalExpression(); + printReconstructedExpression(); + } else { + stream << '\n'; + } + printMessage(); + } + +private: + void printResultType() const { + if (!passOrFail.empty()) { + Colour colourGuard(colour); + stream << passOrFail << ":\n"; + } + } + void printOriginalExpression() const { + if (result.hasExpression()) { + Colour colourGuard(Colour::OriginalExpression); + stream << " "; + stream << result.getExpressionInMacro(); + stream << '\n'; + } + } + void printReconstructedExpression() const { + if (result.hasExpandedExpression()) { + stream << "with expansion:\n"; + Colour colourGuard(Colour::ReconstructedExpression); + stream << Column(result.getExpandedExpression()).indent(2) << '\n'; + } + } + void printMessage() const { + if (!messageLabel.empty()) + stream << messageLabel << ':' << '\n'; + for (auto const& msg : messages) { + // If this assertion is a warning ignore any INFO messages + if (printInfoMessages || msg.type != ResultWas::Info) + stream << Column(msg.message).indent(2) << '\n'; + } + } + void printSourceInfo() const { + Colour colourGuard(Colour::FileName); + stream << result.getSourceInfo() << ": "; + } + + std::ostream& stream; + AssertionStats const& stats; + AssertionResult const& result; + Colour::Code colour; + std::string passOrFail; + std::string messageLabel; + std::string message; + std::vector messages; + bool printInfoMessages; +}; + +std::size_t makeRatio(std::size_t number, std::size_t total) { + std::size_t ratio = total > 0 ? CATCH_CONFIG_CONSOLE_WIDTH * number / total : 0; + return (ratio == 0 && number > 0) ? 1 : ratio; +} + +std::size_t& findMax(std::size_t& i, std::size_t& j, std::size_t& k) { + if (i > j && i > k) + return i; + else if (j > k) + return j; + else + return k; +} + +struct ColumnInfo { + enum Justification { Left, Right }; + std::string name; + int width; + Justification justification; +}; +struct ColumnBreak {}; +struct RowBreak {}; + +class Duration { + enum class Unit { + Auto, + Nanoseconds, + Microseconds, + Milliseconds, + Seconds, + Minutes + }; + static const uint64_t s_nanosecondsInAMicrosecond = 1000; + static const uint64_t s_nanosecondsInAMillisecond = 1000 * s_nanosecondsInAMicrosecond; + static const uint64_t s_nanosecondsInASecond = 1000 * s_nanosecondsInAMillisecond; + static const uint64_t s_nanosecondsInAMinute = 60 * s_nanosecondsInASecond; + + uint64_t m_inNanoseconds; + Unit m_units; + +public: + explicit Duration(uint64_t inNanoseconds, Unit units = Unit::Auto) + : m_inNanoseconds(inNanoseconds), + m_units(units) { + if (m_units == Unit::Auto) { + if (m_inNanoseconds < s_nanosecondsInAMicrosecond) + m_units = Unit::Nanoseconds; + else if (m_inNanoseconds < s_nanosecondsInAMillisecond) + m_units = Unit::Microseconds; + else if (m_inNanoseconds < s_nanosecondsInASecond) + m_units = Unit::Milliseconds; + else if (m_inNanoseconds < s_nanosecondsInAMinute) + m_units = Unit::Seconds; + else + m_units = Unit::Minutes; + } + + } + + auto value() const -> double { + switch (m_units) { + case Unit::Microseconds: + return m_inNanoseconds / static_cast(s_nanosecondsInAMicrosecond); + case Unit::Milliseconds: + return m_inNanoseconds / static_cast(s_nanosecondsInAMillisecond); + case Unit::Seconds: + return m_inNanoseconds / static_cast(s_nanosecondsInASecond); + case Unit::Minutes: + return m_inNanoseconds / static_cast(s_nanosecondsInAMinute); + default: + return static_cast(m_inNanoseconds); + } + } + auto unitsAsString() const -> std::string { + switch (m_units) { + case Unit::Nanoseconds: + return "ns"; + case Unit::Microseconds: + return "µs"; + case Unit::Milliseconds: + return "ms"; + case Unit::Seconds: + return "s"; + case Unit::Minutes: + return "m"; + default: + return "** internal error **"; + } + + } + friend auto operator << (std::ostream& os, Duration const& duration) -> std::ostream& { + return os << duration.value() << " " << duration.unitsAsString(); + } +}; +} // end anon namespace + +class TablePrinter { + std::ostream& m_os; + std::vector m_columnInfos; + std::ostringstream m_oss; + int m_currentColumn = -1; + bool m_isOpen = false; + +public: + TablePrinter( std::ostream& os, std::vector columnInfos ) + : m_os( os ), + m_columnInfos( std::move( columnInfos ) ) {} + + auto columnInfos() const -> std::vector const& { + return m_columnInfos; + } + + void open() { + if (!m_isOpen) { + m_isOpen = true; + *this << RowBreak(); + for (auto const& info : m_columnInfos) + *this << info.name << ColumnBreak(); + *this << RowBreak(); + m_os << Catch::getLineOfChars<'-'>() << "\n"; + } + } + void close() { + if (m_isOpen) { + *this << RowBreak(); + m_os << std::endl; + m_isOpen = false; + } + } + + template + friend TablePrinter& operator << (TablePrinter& tp, T const& value) { + tp.m_oss << value; + return tp; + } + + friend TablePrinter& operator << (TablePrinter& tp, ColumnBreak) { + auto colStr = tp.m_oss.str(); + // This takes account of utf8 encodings + auto strSize = Catch::StringRef(colStr).numberOfCharacters(); + tp.m_oss.str(""); + tp.open(); + if (tp.m_currentColumn == static_cast(tp.m_columnInfos.size() - 1)) { + tp.m_currentColumn = -1; + tp.m_os << "\n"; + } + tp.m_currentColumn++; + + auto colInfo = tp.m_columnInfos[tp.m_currentColumn]; + auto padding = (strSize + 2 < static_cast(colInfo.width)) + ? std::string(colInfo.width - (strSize + 2), ' ') + : std::string(); + if (colInfo.justification == ColumnInfo::Left) + tp.m_os << colStr << padding << " "; + else + tp.m_os << padding << colStr << " "; + return tp; + } + + friend TablePrinter& operator << (TablePrinter& tp, RowBreak) { + if (tp.m_currentColumn > 0) { + tp.m_os << "\n"; + tp.m_currentColumn = -1; + } + return tp; + } +}; + +ConsoleReporter::ConsoleReporter(ReporterConfig const& config) + : StreamingReporterBase(config), + m_tablePrinter(new TablePrinter(config.stream(), + { + { "benchmark name", CATCH_CONFIG_CONSOLE_WIDTH - 32, ColumnInfo::Left }, + { "iters", 8, ColumnInfo::Right }, + { "elapsed ns", 14, ColumnInfo::Right }, + { "average", 14, ColumnInfo::Right } + })) {} +ConsoleReporter::~ConsoleReporter() = default; + +std::string ConsoleReporter::getDescription() { + return "Reports test results as plain lines of text"; +} + +void ConsoleReporter::noMatchingTestCases(std::string const& spec) { + stream << "No test cases matched '" << spec << '\'' << std::endl; +} + +void ConsoleReporter::assertionStarting(AssertionInfo const&) {} + +bool ConsoleReporter::assertionEnded(AssertionStats const& _assertionStats) { + AssertionResult const& result = _assertionStats.assertionResult; + + bool includeResults = m_config->includeSuccessfulResults() || !result.isOk(); + + // Drop out if result was successful but we're not printing them. + if (!includeResults && result.getResultType() != ResultWas::Warning) + return false; + + lazyPrint(); + + ConsoleAssertionPrinter printer(stream, _assertionStats, includeResults); + printer.print(); + stream << std::endl; + return true; +} + +void ConsoleReporter::sectionStarting(SectionInfo const& _sectionInfo) { + m_headerPrinted = false; + StreamingReporterBase::sectionStarting(_sectionInfo); +} +void ConsoleReporter::sectionEnded(SectionStats const& _sectionStats) { + m_tablePrinter->close(); + if (_sectionStats.missingAssertions) { + lazyPrint(); + Colour colour(Colour::ResultError); + if (m_sectionStack.size() > 1) + stream << "\nNo assertions in section"; + else + stream << "\nNo assertions in test case"; + stream << " '" << _sectionStats.sectionInfo.name << "'\n" << std::endl; + } + if (m_config->showDurations() == ShowDurations::Always) { + stream << getFormattedDuration(_sectionStats.durationInSeconds) << " s: " << _sectionStats.sectionInfo.name << std::endl; + } + if (m_headerPrinted) { + m_headerPrinted = false; + } + StreamingReporterBase::sectionEnded(_sectionStats); +} + +void ConsoleReporter::benchmarkStarting(BenchmarkInfo const& info) { + lazyPrintWithoutClosingBenchmarkTable(); + + auto nameCol = Column( info.name ).width( static_cast( m_tablePrinter->columnInfos()[0].width - 2 ) ); + + bool firstLine = true; + for (auto line : nameCol) { + if (!firstLine) + (*m_tablePrinter) << ColumnBreak() << ColumnBreak() << ColumnBreak(); + else + firstLine = false; + + (*m_tablePrinter) << line << ColumnBreak(); + } +} +void ConsoleReporter::benchmarkEnded(BenchmarkStats const& stats) { + Duration average(stats.elapsedTimeInNanoseconds / stats.iterations); + (*m_tablePrinter) + << stats.iterations << ColumnBreak() + << stats.elapsedTimeInNanoseconds << ColumnBreak() + << average << ColumnBreak(); +} + +void ConsoleReporter::testCaseEnded(TestCaseStats const& _testCaseStats) { + m_tablePrinter->close(); + StreamingReporterBase::testCaseEnded(_testCaseStats); + m_headerPrinted = false; +} +void ConsoleReporter::testGroupEnded(TestGroupStats const& _testGroupStats) { + if (currentGroupInfo.used) { + printSummaryDivider(); + stream << "Summary for group '" << _testGroupStats.groupInfo.name << "':\n"; + printTotals(_testGroupStats.totals); + stream << '\n' << std::endl; + } + StreamingReporterBase::testGroupEnded(_testGroupStats); +} +void ConsoleReporter::testRunEnded(TestRunStats const& _testRunStats) { + printTotalsDivider(_testRunStats.totals); + printTotals(_testRunStats.totals); + stream << std::endl; + StreamingReporterBase::testRunEnded(_testRunStats); +} + +void ConsoleReporter::lazyPrint() { + + m_tablePrinter->close(); + lazyPrintWithoutClosingBenchmarkTable(); +} + +void ConsoleReporter::lazyPrintWithoutClosingBenchmarkTable() { + + if (!currentTestRunInfo.used) + lazyPrintRunInfo(); + if (!currentGroupInfo.used) + lazyPrintGroupInfo(); + + if (!m_headerPrinted) { + printTestCaseAndSectionHeader(); + m_headerPrinted = true; + } +} +void ConsoleReporter::lazyPrintRunInfo() { + stream << '\n' << getLineOfChars<'~'>() << '\n'; + Colour colour(Colour::SecondaryText); + stream << currentTestRunInfo->name + << " is a Catch v" << libraryVersion() << " host application.\n" + << "Run with -? for options\n\n"; + + if (m_config->rngSeed() != 0) + stream << "Randomness seeded to: " << m_config->rngSeed() << "\n\n"; + + currentTestRunInfo.used = true; +} +void ConsoleReporter::lazyPrintGroupInfo() { + if (!currentGroupInfo->name.empty() && currentGroupInfo->groupsCounts > 1) { + printClosedHeader("Group: " + currentGroupInfo->name); + currentGroupInfo.used = true; + } +} +void ConsoleReporter::printTestCaseAndSectionHeader() { + assert(!m_sectionStack.empty()); + printOpenHeader(currentTestCaseInfo->name); + + if (m_sectionStack.size() > 1) { + Colour colourGuard(Colour::Headers); + + auto + it = m_sectionStack.begin() + 1, // Skip first section (test case) + itEnd = m_sectionStack.end(); + for (; it != itEnd; ++it) + printHeaderString(it->name, 2); + } + + SourceLineInfo lineInfo = m_sectionStack.back().lineInfo; + + if (!lineInfo.empty()) { + stream << getLineOfChars<'-'>() << '\n'; + Colour colourGuard(Colour::FileName); + stream << lineInfo << '\n'; + } + stream << getLineOfChars<'.'>() << '\n' << std::endl; +} + +void ConsoleReporter::printClosedHeader(std::string const& _name) { + printOpenHeader(_name); + stream << getLineOfChars<'.'>() << '\n'; +} +void ConsoleReporter::printOpenHeader(std::string const& _name) { + stream << getLineOfChars<'-'>() << '\n'; + { + Colour colourGuard(Colour::Headers); + printHeaderString(_name); + } +} + +// if string has a : in first line will set indent to follow it on +// subsequent lines +void ConsoleReporter::printHeaderString(std::string const& _string, std::size_t indent) { + std::size_t i = _string.find(": "); + if (i != std::string::npos) + i += 2; + else + i = 0; + stream << Column(_string).indent(indent + i).initialIndent(indent) << '\n'; +} + +struct SummaryColumn { + + SummaryColumn( std::string _label, Colour::Code _colour ) + : label( std::move( _label ) ), + colour( _colour ) {} + SummaryColumn addRow( std::size_t count ) { + ReusableStringStream rss; + rss << count; + std::string row = rss.str(); + for (auto& oldRow : rows) { + while (oldRow.size() < row.size()) + oldRow = ' ' + oldRow; + while (oldRow.size() > row.size()) + row = ' ' + row; + } + rows.push_back(row); + return *this; + } + + std::string label; + Colour::Code colour; + std::vector rows; + +}; + +void ConsoleReporter::printTotals( Totals const& totals ) { + if (totals.testCases.total() == 0) { + stream << Colour(Colour::Warning) << "No tests ran\n"; + } else if (totals.assertions.total() > 0 && totals.testCases.allPassed()) { + stream << Colour(Colour::ResultSuccess) << "All tests passed"; + stream << " (" + << pluralise(totals.assertions.passed, "assertion") << " in " + << pluralise(totals.testCases.passed, "test case") << ')' + << '\n'; + } else { + + std::vector columns; + columns.push_back(SummaryColumn("", Colour::None) + .addRow(totals.testCases.total()) + .addRow(totals.assertions.total())); + columns.push_back(SummaryColumn("passed", Colour::Success) + .addRow(totals.testCases.passed) + .addRow(totals.assertions.passed)); + columns.push_back(SummaryColumn("failed", Colour::ResultError) + .addRow(totals.testCases.failed) + .addRow(totals.assertions.failed)); + columns.push_back(SummaryColumn("failed as expected", Colour::ResultExpectedFailure) + .addRow(totals.testCases.failedButOk) + .addRow(totals.assertions.failedButOk)); + + printSummaryRow("test cases", columns, 0); + printSummaryRow("assertions", columns, 1); + } +} +void ConsoleReporter::printSummaryRow(std::string const& label, std::vector const& cols, std::size_t row) { + for (auto col : cols) { + std::string value = col.rows[row]; + if (col.label.empty()) { + stream << label << ": "; + if (value != "0") + stream << value; + else + stream << Colour(Colour::Warning) << "- none -"; + } else if (value != "0") { + stream << Colour(Colour::LightGrey) << " | "; + stream << Colour(col.colour) + << value << ' ' << col.label; + } + } + stream << '\n'; +} + +void ConsoleReporter::printTotalsDivider(Totals const& totals) { + if (totals.testCases.total() > 0) { + std::size_t failedRatio = makeRatio(totals.testCases.failed, totals.testCases.total()); + std::size_t failedButOkRatio = makeRatio(totals.testCases.failedButOk, totals.testCases.total()); + std::size_t passedRatio = makeRatio(totals.testCases.passed, totals.testCases.total()); + while (failedRatio + failedButOkRatio + passedRatio < CATCH_CONFIG_CONSOLE_WIDTH - 1) + findMax(failedRatio, failedButOkRatio, passedRatio)++; + while (failedRatio + failedButOkRatio + passedRatio > CATCH_CONFIG_CONSOLE_WIDTH - 1) + findMax(failedRatio, failedButOkRatio, passedRatio)--; + + stream << Colour(Colour::Error) << std::string(failedRatio, '='); + stream << Colour(Colour::ResultExpectedFailure) << std::string(failedButOkRatio, '='); + if (totals.testCases.allPassed()) + stream << Colour(Colour::ResultSuccess) << std::string(passedRatio, '='); + else + stream << Colour(Colour::Success) << std::string(passedRatio, '='); + } else { + stream << Colour(Colour::Warning) << std::string(CATCH_CONFIG_CONSOLE_WIDTH - 1, '='); + } + stream << '\n'; +} +void ConsoleReporter::printSummaryDivider() { + stream << getLineOfChars<'-'>() << '\n'; +} + +CATCH_REGISTER_REPORTER("console", ConsoleReporter) + +} // end namespace Catch + +#if defined(_MSC_VER) +#pragma warning(pop) +#endif +// end catch_reporter_console.cpp +// start catch_reporter_junit.cpp + +#include +#include +#include +#include + +namespace Catch { + + namespace { + std::string getCurrentTimestamp() { + // Beware, this is not reentrant because of backward compatibility issues + // Also, UTC only, again because of backward compatibility (%z is C++11) + time_t rawtime; + std::time(&rawtime); + auto const timeStampSize = sizeof("2017-01-16T17:06:45Z"); + +#ifdef _MSC_VER + std::tm timeInfo = {}; + gmtime_s(&timeInfo, &rawtime); +#else + std::tm* timeInfo; + timeInfo = std::gmtime(&rawtime); +#endif + + char timeStamp[timeStampSize]; + const char * const fmt = "%Y-%m-%dT%H:%M:%SZ"; + +#ifdef _MSC_VER + std::strftime(timeStamp, timeStampSize, fmt, &timeInfo); +#else + std::strftime(timeStamp, timeStampSize, fmt, timeInfo); +#endif + return std::string(timeStamp); + } + + std::string fileNameTag(const std::vector &tags) { + auto it = std::find_if(begin(tags), + end(tags), + [] (std::string const& tag) {return tag.front() == '#'; }); + if (it != tags.end()) + return it->substr(1); + return std::string(); + } + } // anonymous namespace + + JunitReporter::JunitReporter( ReporterConfig const& _config ) + : CumulativeReporterBase( _config ), + xml( _config.stream() ) + { + m_reporterPrefs.shouldRedirectStdOut = true; + m_reporterPrefs.shouldReportAllAssertions = true; + } + + JunitReporter::~JunitReporter() {} + + std::string JunitReporter::getDescription() { + return "Reports test results in an XML format that looks like Ant's junitreport target"; + } + + void JunitReporter::noMatchingTestCases( std::string const& /*spec*/ ) {} + + void JunitReporter::testRunStarting( TestRunInfo const& runInfo ) { + CumulativeReporterBase::testRunStarting( runInfo ); + xml.startElement( "testsuites" ); + } + + void JunitReporter::testGroupStarting( GroupInfo const& groupInfo ) { + suiteTimer.start(); + stdOutForSuite.clear(); + stdErrForSuite.clear(); + unexpectedExceptions = 0; + CumulativeReporterBase::testGroupStarting( groupInfo ); + } + + void JunitReporter::testCaseStarting( TestCaseInfo const& testCaseInfo ) { + m_okToFail = testCaseInfo.okToFail(); + } + + bool JunitReporter::assertionEnded( AssertionStats const& assertionStats ) { + if( assertionStats.assertionResult.getResultType() == ResultWas::ThrewException && !m_okToFail ) + unexpectedExceptions++; + return CumulativeReporterBase::assertionEnded( assertionStats ); + } + + void JunitReporter::testCaseEnded( TestCaseStats const& testCaseStats ) { + stdOutForSuite += testCaseStats.stdOut; + stdErrForSuite += testCaseStats.stdErr; + CumulativeReporterBase::testCaseEnded( testCaseStats ); + } + + void JunitReporter::testGroupEnded( TestGroupStats const& testGroupStats ) { + double suiteTime = suiteTimer.getElapsedSeconds(); + CumulativeReporterBase::testGroupEnded( testGroupStats ); + writeGroup( *m_testGroups.back(), suiteTime ); + } + + void JunitReporter::testRunEndedCumulative() { + xml.endElement(); + } + + void JunitReporter::writeGroup( TestGroupNode const& groupNode, double suiteTime ) { + XmlWriter::ScopedElement e = xml.scopedElement( "testsuite" ); + TestGroupStats const& stats = groupNode.value; + xml.writeAttribute( "name", stats.groupInfo.name ); + xml.writeAttribute( "errors", unexpectedExceptions ); + xml.writeAttribute( "failures", stats.totals.assertions.failed-unexpectedExceptions ); + xml.writeAttribute( "tests", stats.totals.assertions.total() ); + xml.writeAttribute( "hostname", "tbd" ); // !TBD + if( m_config->showDurations() == ShowDurations::Never ) + xml.writeAttribute( "time", "" ); + else + xml.writeAttribute( "time", suiteTime ); + xml.writeAttribute( "timestamp", getCurrentTimestamp() ); + + // Write test cases + for( auto const& child : groupNode.children ) + writeTestCase( *child ); + + xml.scopedElement( "system-out" ).writeText( trim( stdOutForSuite ), false ); + xml.scopedElement( "system-err" ).writeText( trim( stdErrForSuite ), false ); + } + + void JunitReporter::writeTestCase( TestCaseNode const& testCaseNode ) { + TestCaseStats const& stats = testCaseNode.value; + + // All test cases have exactly one section - which represents the + // test case itself. That section may have 0-n nested sections + assert( testCaseNode.children.size() == 1 ); + SectionNode const& rootSection = *testCaseNode.children.front(); + + std::string className = stats.testInfo.className; + + if( className.empty() ) { + className = fileNameTag(stats.testInfo.tags); + if ( className.empty() ) + className = "global"; + } + + if ( !m_config->name().empty() ) + className = m_config->name() + "." + className; + + writeSection( className, "", rootSection ); + } + + void JunitReporter::writeSection( std::string const& className, + std::string const& rootName, + SectionNode const& sectionNode ) { + std::string name = trim( sectionNode.stats.sectionInfo.name ); + if( !rootName.empty() ) + name = rootName + '/' + name; + + if( !sectionNode.assertions.empty() || + !sectionNode.stdOut.empty() || + !sectionNode.stdErr.empty() ) { + XmlWriter::ScopedElement e = xml.scopedElement( "testcase" ); + if( className.empty() ) { + xml.writeAttribute( "classname", name ); + xml.writeAttribute( "name", "root" ); + } + else { + xml.writeAttribute( "classname", className ); + xml.writeAttribute( "name", name ); + } + xml.writeAttribute( "time", ::Catch::Detail::stringify( sectionNode.stats.durationInSeconds ) ); + + writeAssertions( sectionNode ); + + if( !sectionNode.stdOut.empty() ) + xml.scopedElement( "system-out" ).writeText( trim( sectionNode.stdOut ), false ); + if( !sectionNode.stdErr.empty() ) + xml.scopedElement( "system-err" ).writeText( trim( sectionNode.stdErr ), false ); + } + for( auto const& childNode : sectionNode.childSections ) + if( className.empty() ) + writeSection( name, "", *childNode ); + else + writeSection( className, name, *childNode ); + } + + void JunitReporter::writeAssertions( SectionNode const& sectionNode ) { + for( auto const& assertion : sectionNode.assertions ) + writeAssertion( assertion ); + } + + void JunitReporter::writeAssertion( AssertionStats const& stats ) { + AssertionResult const& result = stats.assertionResult; + if( !result.isOk() ) { + std::string elementName; + switch( result.getResultType() ) { + case ResultWas::ThrewException: + case ResultWas::FatalErrorCondition: + elementName = "error"; + break; + case ResultWas::ExplicitFailure: + elementName = "failure"; + break; + case ResultWas::ExpressionFailed: + elementName = "failure"; + break; + case ResultWas::DidntThrowException: + elementName = "failure"; + break; + + // We should never see these here: + case ResultWas::Info: + case ResultWas::Warning: + case ResultWas::Ok: + case ResultWas::Unknown: + case ResultWas::FailureBit: + case ResultWas::Exception: + elementName = "internalError"; + break; + } + + XmlWriter::ScopedElement e = xml.scopedElement( elementName ); + + xml.writeAttribute( "message", result.getExpandedExpression() ); + xml.writeAttribute( "type", result.getTestMacroName() ); + + ReusableStringStream rss; + if( !result.getMessage().empty() ) + rss << result.getMessage() << '\n'; + for( auto const& msg : stats.infoMessages ) + if( msg.type == ResultWas::Info ) + rss << msg.message << '\n'; + + rss << "at " << result.getSourceInfo(); + xml.writeText( rss.str(), false ); + } + } + + CATCH_REGISTER_REPORTER( "junit", JunitReporter ) + +} // end namespace Catch +// end catch_reporter_junit.cpp +// start catch_reporter_listening.cpp + +#include + +namespace Catch { + + ListeningReporter::ListeningReporter() { + // We will assume that listeners will always want all assertions + m_preferences.shouldReportAllAssertions = true; + } + + void ListeningReporter::addListener( IStreamingReporterPtr&& listener ) { + m_listeners.push_back( std::move( listener ) ); + } + + void ListeningReporter::addReporter(IStreamingReporterPtr&& reporter) { + assert(!m_reporter && "Listening reporter can wrap only 1 real reporter"); + m_reporter = std::move( reporter ); + m_preferences.shouldRedirectStdOut = m_reporter->getPreferences().shouldRedirectStdOut; + } + + ReporterPreferences ListeningReporter::getPreferences() const { + return m_preferences; + } + + std::set ListeningReporter::getSupportedVerbosities() { + return std::set{ }; + } + + void ListeningReporter::noMatchingTestCases( std::string const& spec ) { + for ( auto const& listener : m_listeners ) { + listener->noMatchingTestCases( spec ); + } + m_reporter->noMatchingTestCases( spec ); + } + + void ListeningReporter::benchmarkStarting( BenchmarkInfo const& benchmarkInfo ) { + for ( auto const& listener : m_listeners ) { + listener->benchmarkStarting( benchmarkInfo ); + } + m_reporter->benchmarkStarting( benchmarkInfo ); + } + void ListeningReporter::benchmarkEnded( BenchmarkStats const& benchmarkStats ) { + for ( auto const& listener : m_listeners ) { + listener->benchmarkEnded( benchmarkStats ); + } + m_reporter->benchmarkEnded( benchmarkStats ); + } + + void ListeningReporter::testRunStarting( TestRunInfo const& testRunInfo ) { + for ( auto const& listener : m_listeners ) { + listener->testRunStarting( testRunInfo ); + } + m_reporter->testRunStarting( testRunInfo ); + } + + void ListeningReporter::testGroupStarting( GroupInfo const& groupInfo ) { + for ( auto const& listener : m_listeners ) { + listener->testGroupStarting( groupInfo ); + } + m_reporter->testGroupStarting( groupInfo ); + } + + void ListeningReporter::testCaseStarting( TestCaseInfo const& testInfo ) { + for ( auto const& listener : m_listeners ) { + listener->testCaseStarting( testInfo ); + } + m_reporter->testCaseStarting( testInfo ); + } + + void ListeningReporter::sectionStarting( SectionInfo const& sectionInfo ) { + for ( auto const& listener : m_listeners ) { + listener->sectionStarting( sectionInfo ); + } + m_reporter->sectionStarting( sectionInfo ); + } + + void ListeningReporter::assertionStarting( AssertionInfo const& assertionInfo ) { + for ( auto const& listener : m_listeners ) { + listener->assertionStarting( assertionInfo ); + } + m_reporter->assertionStarting( assertionInfo ); + } + + // The return value indicates if the messages buffer should be cleared: + bool ListeningReporter::assertionEnded( AssertionStats const& assertionStats ) { + for( auto const& listener : m_listeners ) { + static_cast( listener->assertionEnded( assertionStats ) ); + } + return m_reporter->assertionEnded( assertionStats ); + } + + void ListeningReporter::sectionEnded( SectionStats const& sectionStats ) { + for ( auto const& listener : m_listeners ) { + listener->sectionEnded( sectionStats ); + } + m_reporter->sectionEnded( sectionStats ); + } + + void ListeningReporter::testCaseEnded( TestCaseStats const& testCaseStats ) { + for ( auto const& listener : m_listeners ) { + listener->testCaseEnded( testCaseStats ); + } + m_reporter->testCaseEnded( testCaseStats ); + } + + void ListeningReporter::testGroupEnded( TestGroupStats const& testGroupStats ) { + for ( auto const& listener : m_listeners ) { + listener->testGroupEnded( testGroupStats ); + } + m_reporter->testGroupEnded( testGroupStats ); + } + + void ListeningReporter::testRunEnded( TestRunStats const& testRunStats ) { + for ( auto const& listener : m_listeners ) { + listener->testRunEnded( testRunStats ); + } + m_reporter->testRunEnded( testRunStats ); + } + + void ListeningReporter::skipTest( TestCaseInfo const& testInfo ) { + for ( auto const& listener : m_listeners ) { + listener->skipTest( testInfo ); + } + m_reporter->skipTest( testInfo ); + } + + bool ListeningReporter::isMulti() const { + return true; + } + +} // end namespace Catch +// end catch_reporter_listening.cpp +// start catch_reporter_xml.cpp + +#if defined(_MSC_VER) +#pragma warning(push) +#pragma warning(disable:4061) // Not all labels are EXPLICITLY handled in switch + // Note that 4062 (not all labels are handled + // and default is missing) is enabled +#endif + +namespace Catch { + XmlReporter::XmlReporter( ReporterConfig const& _config ) + : StreamingReporterBase( _config ), + m_xml(_config.stream()) + { + m_reporterPrefs.shouldRedirectStdOut = true; + m_reporterPrefs.shouldReportAllAssertions = true; + } + + XmlReporter::~XmlReporter() = default; + + std::string XmlReporter::getDescription() { + return "Reports test results as an XML document"; + } + + std::string XmlReporter::getStylesheetRef() const { + return std::string(); + } + + void XmlReporter::writeSourceInfo( SourceLineInfo const& sourceInfo ) { + m_xml + .writeAttribute( "filename", sourceInfo.file ) + .writeAttribute( "line", sourceInfo.line ); + } + + void XmlReporter::noMatchingTestCases( std::string const& s ) { + StreamingReporterBase::noMatchingTestCases( s ); + } + + void XmlReporter::testRunStarting( TestRunInfo const& testInfo ) { + StreamingReporterBase::testRunStarting( testInfo ); + std::string stylesheetRef = getStylesheetRef(); + if( !stylesheetRef.empty() ) + m_xml.writeStylesheetRef( stylesheetRef ); + m_xml.startElement( "Catch" ); + if( !m_config->name().empty() ) + m_xml.writeAttribute( "name", m_config->name() ); + if( m_config->rngSeed() != 0 ) + m_xml.scopedElement( "Randomness" ) + .writeAttribute( "seed", m_config->rngSeed() ); + } + + void XmlReporter::testGroupStarting( GroupInfo const& groupInfo ) { + StreamingReporterBase::testGroupStarting( groupInfo ); + m_xml.startElement( "Group" ) + .writeAttribute( "name", groupInfo.name ); + } + + void XmlReporter::testCaseStarting( TestCaseInfo const& testInfo ) { + StreamingReporterBase::testCaseStarting(testInfo); + m_xml.startElement( "TestCase" ) + .writeAttribute( "name", trim( testInfo.name ) ) + .writeAttribute( "description", testInfo.description ) + .writeAttribute( "tags", testInfo.tagsAsString() ); + + writeSourceInfo( testInfo.lineInfo ); + + if ( m_config->showDurations() == ShowDurations::Always ) + m_testCaseTimer.start(); + m_xml.ensureTagClosed(); + } + + void XmlReporter::sectionStarting( SectionInfo const& sectionInfo ) { + StreamingReporterBase::sectionStarting( sectionInfo ); + if( m_sectionDepth++ > 0 ) { + m_xml.startElement( "Section" ) + .writeAttribute( "name", trim( sectionInfo.name ) ); + writeSourceInfo( sectionInfo.lineInfo ); + m_xml.ensureTagClosed(); + } + } + + void XmlReporter::assertionStarting( AssertionInfo const& ) { } + + bool XmlReporter::assertionEnded( AssertionStats const& assertionStats ) { + + AssertionResult const& result = assertionStats.assertionResult; + + bool includeResults = m_config->includeSuccessfulResults() || !result.isOk(); + + if( includeResults || result.getResultType() == ResultWas::Warning ) { + // Print any info messages in tags. + for( auto const& msg : assertionStats.infoMessages ) { + if( msg.type == ResultWas::Info && includeResults ) { + m_xml.scopedElement( "Info" ) + .writeText( msg.message ); + } else if ( msg.type == ResultWas::Warning ) { + m_xml.scopedElement( "Warning" ) + .writeText( msg.message ); + } + } + } + + // Drop out if result was successful but we're not printing them. + if( !includeResults && result.getResultType() != ResultWas::Warning ) + return true; + + // Print the expression if there is one. + if( result.hasExpression() ) { + m_xml.startElement( "Expression" ) + .writeAttribute( "success", result.succeeded() ) + .writeAttribute( "type", result.getTestMacroName() ); + + writeSourceInfo( result.getSourceInfo() ); + + m_xml.scopedElement( "Original" ) + .writeText( result.getExpression() ); + m_xml.scopedElement( "Expanded" ) + .writeText( result.getExpandedExpression() ); + } + + // And... Print a result applicable to each result type. + switch( result.getResultType() ) { + case ResultWas::ThrewException: + m_xml.startElement( "Exception" ); + writeSourceInfo( result.getSourceInfo() ); + m_xml.writeText( result.getMessage() ); + m_xml.endElement(); + break; + case ResultWas::FatalErrorCondition: + m_xml.startElement( "FatalErrorCondition" ); + writeSourceInfo( result.getSourceInfo() ); + m_xml.writeText( result.getMessage() ); + m_xml.endElement(); + break; + case ResultWas::Info: + m_xml.scopedElement( "Info" ) + .writeText( result.getMessage() ); + break; + case ResultWas::Warning: + // Warning will already have been written + break; + case ResultWas::ExplicitFailure: + m_xml.startElement( "Failure" ); + writeSourceInfo( result.getSourceInfo() ); + m_xml.writeText( result.getMessage() ); + m_xml.endElement(); + break; + default: + break; + } + + if( result.hasExpression() ) + m_xml.endElement(); + + return true; + } + + void XmlReporter::sectionEnded( SectionStats const& sectionStats ) { + StreamingReporterBase::sectionEnded( sectionStats ); + if( --m_sectionDepth > 0 ) { + XmlWriter::ScopedElement e = m_xml.scopedElement( "OverallResults" ); + e.writeAttribute( "successes", sectionStats.assertions.passed ); + e.writeAttribute( "failures", sectionStats.assertions.failed ); + e.writeAttribute( "expectedFailures", sectionStats.assertions.failedButOk ); + + if ( m_config->showDurations() == ShowDurations::Always ) + e.writeAttribute( "durationInSeconds", sectionStats.durationInSeconds ); + + m_xml.endElement(); + } + } + + void XmlReporter::testCaseEnded( TestCaseStats const& testCaseStats ) { + StreamingReporterBase::testCaseEnded( testCaseStats ); + XmlWriter::ScopedElement e = m_xml.scopedElement( "OverallResult" ); + e.writeAttribute( "success", testCaseStats.totals.assertions.allOk() ); + + if ( m_config->showDurations() == ShowDurations::Always ) + e.writeAttribute( "durationInSeconds", m_testCaseTimer.getElapsedSeconds() ); + + if( !testCaseStats.stdOut.empty() ) + m_xml.scopedElement( "StdOut" ).writeText( trim( testCaseStats.stdOut ), false ); + if( !testCaseStats.stdErr.empty() ) + m_xml.scopedElement( "StdErr" ).writeText( trim( testCaseStats.stdErr ), false ); + + m_xml.endElement(); + } + + void XmlReporter::testGroupEnded( TestGroupStats const& testGroupStats ) { + StreamingReporterBase::testGroupEnded( testGroupStats ); + // TODO: Check testGroupStats.aborting and act accordingly. + m_xml.scopedElement( "OverallResults" ) + .writeAttribute( "successes", testGroupStats.totals.assertions.passed ) + .writeAttribute( "failures", testGroupStats.totals.assertions.failed ) + .writeAttribute( "expectedFailures", testGroupStats.totals.assertions.failedButOk ); + m_xml.endElement(); + } + + void XmlReporter::testRunEnded( TestRunStats const& testRunStats ) { + StreamingReporterBase::testRunEnded( testRunStats ); + m_xml.scopedElement( "OverallResults" ) + .writeAttribute( "successes", testRunStats.totals.assertions.passed ) + .writeAttribute( "failures", testRunStats.totals.assertions.failed ) + .writeAttribute( "expectedFailures", testRunStats.totals.assertions.failedButOk ); + m_xml.endElement(); + } + + CATCH_REGISTER_REPORTER( "xml", XmlReporter ) + +} // end namespace Catch + +#if defined(_MSC_VER) +#pragma warning(pop) +#endif +// end catch_reporter_xml.cpp + +namespace Catch { + LeakDetector leakDetector; +} + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +// end catch_impl.hpp +#endif + +#ifdef CATCH_CONFIG_MAIN +// start catch_default_main.hpp + +#ifndef __OBJC__ + +#if defined(CATCH_CONFIG_WCHAR) && defined(WIN32) && defined(_UNICODE) && !defined(DO_NOT_USE_WMAIN) +// Standard C/C++ Win32 Unicode wmain entry point +extern "C" int wmain (int argc, wchar_t * argv[], wchar_t * []) { +#else +// Standard C/C++ main entry point +int main (int argc, char * argv[]) { +#endif + + return Catch::Session().run( argc, argv ); +} + +#else // __OBJC__ + +// Objective-C entry point +int main (int argc, char * const argv[]) { +#if !CATCH_ARC_ENABLED + NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; +#endif + + Catch::registerTestMethods(); + int result = Catch::Session().run( argc, (char**)argv ); + +#if !CATCH_ARC_ENABLED + [pool drain]; +#endif + + return result; +} + +#endif // __OBJC__ + +// end catch_default_main.hpp +#endif + +#if !defined(CATCH_CONFIG_IMPL_ONLY) + +#ifdef CLARA_CONFIG_MAIN_NOT_DEFINED +# undef CLARA_CONFIG_MAIN +#endif + +#if !defined(CATCH_CONFIG_DISABLE) +////// +// If this config identifier is defined then all CATCH macros are prefixed with CATCH_ +#ifdef CATCH_CONFIG_PREFIX_ALL + +#define CATCH_REQUIRE( ... ) INTERNAL_CATCH_TEST( "CATCH_REQUIRE", Catch::ResultDisposition::Normal, __VA_ARGS__ ) +#define CATCH_REQUIRE_FALSE( ... ) INTERNAL_CATCH_TEST( "CATCH_REQUIRE_FALSE", Catch::ResultDisposition::Normal | Catch::ResultDisposition::FalseTest, __VA_ARGS__ ) + +#define CATCH_REQUIRE_THROWS( ... ) INTERNAL_CATCH_THROWS( "CATCH_REQUIRE_THROWS", Catch::ResultDisposition::Normal, "", __VA_ARGS__ ) +#define CATCH_REQUIRE_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( "CATCH_REQUIRE_THROWS_AS", exceptionType, Catch::ResultDisposition::Normal, expr ) +#define CATCH_REQUIRE_THROWS_WITH( expr, matcher ) INTERNAL_CATCH_THROWS_STR_MATCHES( "CATCH_REQUIRE_THROWS_WITH", Catch::ResultDisposition::Normal, matcher, expr ) +#if !defined(CATCH_CONFIG_DISABLE_MATCHERS) +#define CATCH_REQUIRE_THROWS_MATCHES( expr, exceptionType, matcher ) INTERNAL_CATCH_THROWS_MATCHES( "CATCH_REQUIRE_THROWS_MATCHES", exceptionType, Catch::ResultDisposition::Normal, matcher, expr ) +#endif// CATCH_CONFIG_DISABLE_MATCHERS +#define CATCH_REQUIRE_NOTHROW( ... ) INTERNAL_CATCH_NO_THROW( "CATCH_REQUIRE_NOTHROW", Catch::ResultDisposition::Normal, __VA_ARGS__ ) + +#define CATCH_CHECK( ... ) INTERNAL_CATCH_TEST( "CATCH_CHECK", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) +#define CATCH_CHECK_FALSE( ... ) INTERNAL_CATCH_TEST( "CATCH_CHECK_FALSE", Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::FalseTest, __VA_ARGS__ ) +#define CATCH_CHECKED_IF( ... ) INTERNAL_CATCH_IF( "CATCH_CHECKED_IF", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) +#define CATCH_CHECKED_ELSE( ... ) INTERNAL_CATCH_ELSE( "CATCH_CHECKED_ELSE", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) +#define CATCH_CHECK_NOFAIL( ... ) INTERNAL_CATCH_TEST( "CATCH_CHECK_NOFAIL", Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::SuppressFail, __VA_ARGS__ ) + +#define CATCH_CHECK_THROWS( ... ) INTERNAL_CATCH_THROWS( "CATCH_CHECK_THROWS", Catch::ResultDisposition::ContinueOnFailure, "", __VA_ARGS__ ) +#define CATCH_CHECK_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( "CATCH_CHECK_THROWS_AS", exceptionType, Catch::ResultDisposition::ContinueOnFailure, expr ) +#define CATCH_CHECK_THROWS_WITH( expr, matcher ) INTERNAL_CATCH_THROWS_STR_MATCHES( "CATCH_CHECK_THROWS_WITH", Catch::ResultDisposition::ContinueOnFailure, matcher, expr ) +#if !defined(CATCH_CONFIG_DISABLE_MATCHERS) +#define CATCH_CHECK_THROWS_MATCHES( expr, exceptionType, matcher ) INTERNAL_CATCH_THROWS_MATCHES( "CATCH_CHECK_THROWS_MATCHES", exceptionType, Catch::ResultDisposition::ContinueOnFailure, matcher, expr ) +#endif // CATCH_CONFIG_DISABLE_MATCHERS +#define CATCH_CHECK_NOTHROW( ... ) INTERNAL_CATCH_NO_THROW( "CATCH_CHECK_NOTHROW", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) + +#if !defined(CATCH_CONFIG_DISABLE_MATCHERS) +#define CATCH_CHECK_THAT( arg, matcher ) INTERNAL_CHECK_THAT( "CATCH_CHECK_THAT", matcher, Catch::ResultDisposition::ContinueOnFailure, arg ) + +#define CATCH_REQUIRE_THAT( arg, matcher ) INTERNAL_CHECK_THAT( "CATCH_REQUIRE_THAT", matcher, Catch::ResultDisposition::Normal, arg ) +#endif // CATCH_CONFIG_DISABLE_MATCHERS + +#define CATCH_INFO( msg ) INTERNAL_CATCH_INFO( "CATCH_INFO", msg ) +#define CATCH_WARN( msg ) INTERNAL_CATCH_MSG( "CATCH_WARN", Catch::ResultWas::Warning, Catch::ResultDisposition::ContinueOnFailure, msg ) +#define CATCH_CAPTURE( ... ) INTERNAL_CATCH_CAPTURE( INTERNAL_CATCH_UNIQUE_NAME(capturer), "CATCH_CAPTURE",__VA_ARGS__ ) + +#define CATCH_TEST_CASE( ... ) INTERNAL_CATCH_TESTCASE( __VA_ARGS__ ) +#define CATCH_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TEST_CASE_METHOD( className, __VA_ARGS__ ) +#define CATCH_METHOD_AS_TEST_CASE( method, ... ) INTERNAL_CATCH_METHOD_AS_TEST_CASE( method, __VA_ARGS__ ) +#define CATCH_REGISTER_TEST_CASE( Function, ... ) INTERNAL_CATCH_REGISTER_TESTCASE( Function, __VA_ARGS__ ) +#define CATCH_SECTION( ... ) INTERNAL_CATCH_SECTION( __VA_ARGS__ ) +#define CATCH_DYNAMIC_SECTION( ... ) INTERNAL_CATCH_DYNAMIC_SECTION( __VA_ARGS__ ) +#define CATCH_FAIL( ... ) INTERNAL_CATCH_MSG( "CATCH_FAIL", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::Normal, __VA_ARGS__ ) +#define CATCH_FAIL_CHECK( ... ) INTERNAL_CATCH_MSG( "CATCH_FAIL_CHECK", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) +#define CATCH_SUCCEED( ... ) INTERNAL_CATCH_MSG( "CATCH_SUCCEED", Catch::ResultWas::Ok, Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) + +#define CATCH_ANON_TEST_CASE() INTERNAL_CATCH_TESTCASE() + +// "BDD-style" convenience wrappers +#define CATCH_SCENARIO( ... ) CATCH_TEST_CASE( "Scenario: " __VA_ARGS__ ) +#define CATCH_SCENARIO_METHOD( className, ... ) INTERNAL_CATCH_TEST_CASE_METHOD( className, "Scenario: " __VA_ARGS__ ) +#define CATCH_GIVEN( desc ) INTERNAL_CATCH_DYNAMIC_SECTION( " Given: " << desc ) +#define CATCH_AND_GIVEN( desc ) INTERNAL_CATCH_DYNAMIC_SECTION( "And given: " << desc ) +#define CATCH_WHEN( desc ) INTERNAL_CATCH_DYNAMIC_SECTION( " When: " << desc ) +#define CATCH_AND_WHEN( desc ) INTERNAL_CATCH_DYNAMIC_SECTION( " And when: " << desc ) +#define CATCH_THEN( desc ) INTERNAL_CATCH_DYNAMIC_SECTION( " Then: " << desc ) +#define CATCH_AND_THEN( desc ) INTERNAL_CATCH_DYNAMIC_SECTION( " And: " << desc ) + +// If CATCH_CONFIG_PREFIX_ALL is not defined then the CATCH_ prefix is not required +#else + +#define REQUIRE( ... ) INTERNAL_CATCH_TEST( "REQUIRE", Catch::ResultDisposition::Normal, __VA_ARGS__ ) +#define REQUIRE_FALSE( ... ) INTERNAL_CATCH_TEST( "REQUIRE_FALSE", Catch::ResultDisposition::Normal | Catch::ResultDisposition::FalseTest, __VA_ARGS__ ) + +#define REQUIRE_THROWS( ... ) INTERNAL_CATCH_THROWS( "REQUIRE_THROWS", Catch::ResultDisposition::Normal, __VA_ARGS__ ) +#define REQUIRE_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( "REQUIRE_THROWS_AS", exceptionType, Catch::ResultDisposition::Normal, expr ) +#define REQUIRE_THROWS_WITH( expr, matcher ) INTERNAL_CATCH_THROWS_STR_MATCHES( "REQUIRE_THROWS_WITH", Catch::ResultDisposition::Normal, matcher, expr ) +#if !defined(CATCH_CONFIG_DISABLE_MATCHERS) +#define REQUIRE_THROWS_MATCHES( expr, exceptionType, matcher ) INTERNAL_CATCH_THROWS_MATCHES( "REQUIRE_THROWS_MATCHES", exceptionType, Catch::ResultDisposition::Normal, matcher, expr ) +#endif // CATCH_CONFIG_DISABLE_MATCHERS +#define REQUIRE_NOTHROW( ... ) INTERNAL_CATCH_NO_THROW( "REQUIRE_NOTHROW", Catch::ResultDisposition::Normal, __VA_ARGS__ ) + +#define CHECK( ... ) INTERNAL_CATCH_TEST( "CHECK", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) +#define CHECK_FALSE( ... ) INTERNAL_CATCH_TEST( "CHECK_FALSE", Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::FalseTest, __VA_ARGS__ ) +#define CHECKED_IF( ... ) INTERNAL_CATCH_IF( "CHECKED_IF", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) +#define CHECKED_ELSE( ... ) INTERNAL_CATCH_ELSE( "CHECKED_ELSE", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) +#define CHECK_NOFAIL( ... ) INTERNAL_CATCH_TEST( "CHECK_NOFAIL", Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::SuppressFail, __VA_ARGS__ ) + +#define CHECK_THROWS( ... ) INTERNAL_CATCH_THROWS( "CHECK_THROWS", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) +#define CHECK_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( "CHECK_THROWS_AS", exceptionType, Catch::ResultDisposition::ContinueOnFailure, expr ) +#define CHECK_THROWS_WITH( expr, matcher ) INTERNAL_CATCH_THROWS_STR_MATCHES( "CHECK_THROWS_WITH", Catch::ResultDisposition::ContinueOnFailure, matcher, expr ) +#if !defined(CATCH_CONFIG_DISABLE_MATCHERS) +#define CHECK_THROWS_MATCHES( expr, exceptionType, matcher ) INTERNAL_CATCH_THROWS_MATCHES( "CHECK_THROWS_MATCHES", exceptionType, Catch::ResultDisposition::ContinueOnFailure, matcher, expr ) +#endif // CATCH_CONFIG_DISABLE_MATCHERS +#define CHECK_NOTHROW( ... ) INTERNAL_CATCH_NO_THROW( "CHECK_NOTHROW", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) + +#if !defined(CATCH_CONFIG_DISABLE_MATCHERS) +#define CHECK_THAT( arg, matcher ) INTERNAL_CHECK_THAT( "CHECK_THAT", matcher, Catch::ResultDisposition::ContinueOnFailure, arg ) + +#define REQUIRE_THAT( arg, matcher ) INTERNAL_CHECK_THAT( "REQUIRE_THAT", matcher, Catch::ResultDisposition::Normal, arg ) +#endif // CATCH_CONFIG_DISABLE_MATCHERS + +#define INFO( msg ) INTERNAL_CATCH_INFO( "INFO", msg ) +#define WARN( msg ) INTERNAL_CATCH_MSG( "WARN", Catch::ResultWas::Warning, Catch::ResultDisposition::ContinueOnFailure, msg ) +#define CAPTURE( ... ) INTERNAL_CATCH_CAPTURE( INTERNAL_CATCH_UNIQUE_NAME(capturer), "CAPTURE",__VA_ARGS__ ) + +#define TEST_CASE( ... ) INTERNAL_CATCH_TESTCASE( __VA_ARGS__ ) +#define TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TEST_CASE_METHOD( className, __VA_ARGS__ ) +#define METHOD_AS_TEST_CASE( method, ... ) INTERNAL_CATCH_METHOD_AS_TEST_CASE( method, __VA_ARGS__ ) +#define REGISTER_TEST_CASE( Function, ... ) INTERNAL_CATCH_REGISTER_TESTCASE( Function, __VA_ARGS__ ) +#define SECTION( ... ) INTERNAL_CATCH_SECTION( __VA_ARGS__ ) +#define DYNAMIC_SECTION( ... ) INTERNAL_CATCH_DYNAMIC_SECTION( __VA_ARGS__ ) +#define FAIL( ... ) INTERNAL_CATCH_MSG( "FAIL", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::Normal, __VA_ARGS__ ) +#define FAIL_CHECK( ... ) INTERNAL_CATCH_MSG( "FAIL_CHECK", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) +#define SUCCEED( ... ) INTERNAL_CATCH_MSG( "SUCCEED", Catch::ResultWas::Ok, Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) +#define ANON_TEST_CASE() INTERNAL_CATCH_TESTCASE() + +#endif + +#define CATCH_TRANSLATE_EXCEPTION( signature ) INTERNAL_CATCH_TRANSLATE_EXCEPTION( signature ) + +// "BDD-style" convenience wrappers +#define SCENARIO( ... ) TEST_CASE( "Scenario: " __VA_ARGS__ ) +#define SCENARIO_METHOD( className, ... ) INTERNAL_CATCH_TEST_CASE_METHOD( className, "Scenario: " __VA_ARGS__ ) + +#define GIVEN( desc ) INTERNAL_CATCH_DYNAMIC_SECTION( " Given: " << desc ) +#define AND_GIVEN( desc ) INTERNAL_CATCH_DYNAMIC_SECTION( "And given: " << desc ) +#define WHEN( desc ) INTERNAL_CATCH_DYNAMIC_SECTION( " When: " << desc ) +#define AND_WHEN( desc ) INTERNAL_CATCH_DYNAMIC_SECTION( " And when: " << desc ) +#define THEN( desc ) INTERNAL_CATCH_DYNAMIC_SECTION( " Then: " << desc ) +#define AND_THEN( desc ) INTERNAL_CATCH_DYNAMIC_SECTION( " And: " << desc ) + +using Catch::Detail::Approx; + +#else // CATCH_CONFIG_DISABLE + +////// +// If this config identifier is defined then all CATCH macros are prefixed with CATCH_ +#ifdef CATCH_CONFIG_PREFIX_ALL + +#define CATCH_REQUIRE( ... ) (void)(0) +#define CATCH_REQUIRE_FALSE( ... ) (void)(0) + +#define CATCH_REQUIRE_THROWS( ... ) (void)(0) +#define CATCH_REQUIRE_THROWS_AS( expr, exceptionType ) (void)(0) +#define CATCH_REQUIRE_THROWS_WITH( expr, matcher ) (void)(0) +#if !defined(CATCH_CONFIG_DISABLE_MATCHERS) +#define CATCH_REQUIRE_THROWS_MATCHES( expr, exceptionType, matcher ) (void)(0) +#endif// CATCH_CONFIG_DISABLE_MATCHERS +#define CATCH_REQUIRE_NOTHROW( ... ) (void)(0) + +#define CATCH_CHECK( ... ) (void)(0) +#define CATCH_CHECK_FALSE( ... ) (void)(0) +#define CATCH_CHECKED_IF( ... ) if (__VA_ARGS__) +#define CATCH_CHECKED_ELSE( ... ) if (!(__VA_ARGS__)) +#define CATCH_CHECK_NOFAIL( ... ) (void)(0) + +#define CATCH_CHECK_THROWS( ... ) (void)(0) +#define CATCH_CHECK_THROWS_AS( expr, exceptionType ) (void)(0) +#define CATCH_CHECK_THROWS_WITH( expr, matcher ) (void)(0) +#if !defined(CATCH_CONFIG_DISABLE_MATCHERS) +#define CATCH_CHECK_THROWS_MATCHES( expr, exceptionType, matcher ) (void)(0) +#endif // CATCH_CONFIG_DISABLE_MATCHERS +#define CATCH_CHECK_NOTHROW( ... ) (void)(0) + +#if !defined(CATCH_CONFIG_DISABLE_MATCHERS) +#define CATCH_CHECK_THAT( arg, matcher ) (void)(0) + +#define CATCH_REQUIRE_THAT( arg, matcher ) (void)(0) +#endif // CATCH_CONFIG_DISABLE_MATCHERS + +#define CATCH_INFO( msg ) (void)(0) +#define CATCH_WARN( msg ) (void)(0) +#define CATCH_CAPTURE( msg ) (void)(0) + +#define CATCH_TEST_CASE( ... ) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ )) +#define CATCH_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ )) +#define CATCH_METHOD_AS_TEST_CASE( method, ... ) +#define CATCH_REGISTER_TEST_CASE( Function, ... ) (void)(0) +#define CATCH_SECTION( ... ) +#define CATCH_DYNAMIC_SECTION( ... ) +#define CATCH_FAIL( ... ) (void)(0) +#define CATCH_FAIL_CHECK( ... ) (void)(0) +#define CATCH_SUCCEED( ... ) (void)(0) + +#define CATCH_ANON_TEST_CASE() INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ )) + +// "BDD-style" convenience wrappers +#define CATCH_SCENARIO( ... ) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ )) +#define CATCH_SCENARIO_METHOD( className, ... ) INTERNAL_CATCH_TESTCASE_METHOD_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ), className ) +#define CATCH_GIVEN( desc ) +#define CATCH_AND_GIVEN( desc ) +#define CATCH_WHEN( desc ) +#define CATCH_AND_WHEN( desc ) +#define CATCH_THEN( desc ) +#define CATCH_AND_THEN( desc ) + +// If CATCH_CONFIG_PREFIX_ALL is not defined then the CATCH_ prefix is not required +#else + +#define REQUIRE( ... ) (void)(0) +#define REQUIRE_FALSE( ... ) (void)(0) + +#define REQUIRE_THROWS( ... ) (void)(0) +#define REQUIRE_THROWS_AS( expr, exceptionType ) (void)(0) +#define REQUIRE_THROWS_WITH( expr, matcher ) (void)(0) +#if !defined(CATCH_CONFIG_DISABLE_MATCHERS) +#define REQUIRE_THROWS_MATCHES( expr, exceptionType, matcher ) (void)(0) +#endif // CATCH_CONFIG_DISABLE_MATCHERS +#define REQUIRE_NOTHROW( ... ) (void)(0) + +#define CHECK( ... ) (void)(0) +#define CHECK_FALSE( ... ) (void)(0) +#define CHECKED_IF( ... ) if (__VA_ARGS__) +#define CHECKED_ELSE( ... ) if (!(__VA_ARGS__)) +#define CHECK_NOFAIL( ... ) (void)(0) + +#define CHECK_THROWS( ... ) (void)(0) +#define CHECK_THROWS_AS( expr, exceptionType ) (void)(0) +#define CHECK_THROWS_WITH( expr, matcher ) (void)(0) +#if !defined(CATCH_CONFIG_DISABLE_MATCHERS) +#define CHECK_THROWS_MATCHES( expr, exceptionType, matcher ) (void)(0) +#endif // CATCH_CONFIG_DISABLE_MATCHERS +#define CHECK_NOTHROW( ... ) (void)(0) + +#if !defined(CATCH_CONFIG_DISABLE_MATCHERS) +#define CHECK_THAT( arg, matcher ) (void)(0) + +#define REQUIRE_THAT( arg, matcher ) (void)(0) +#endif // CATCH_CONFIG_DISABLE_MATCHERS + +#define INFO( msg ) (void)(0) +#define WARN( msg ) (void)(0) +#define CAPTURE( msg ) (void)(0) + +#define TEST_CASE( ... ) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ )) +#define TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ )) +#define METHOD_AS_TEST_CASE( method, ... ) +#define REGISTER_TEST_CASE( Function, ... ) (void)(0) +#define SECTION( ... ) +#define DYNAMIC_SECTION( ... ) +#define FAIL( ... ) (void)(0) +#define FAIL_CHECK( ... ) (void)(0) +#define SUCCEED( ... ) (void)(0) +#define ANON_TEST_CASE() INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ )) + +#endif + +#define CATCH_TRANSLATE_EXCEPTION( signature ) INTERNAL_CATCH_TRANSLATE_EXCEPTION_NO_REG( INTERNAL_CATCH_UNIQUE_NAME( catch_internal_ExceptionTranslator ), signature ) + +// "BDD-style" convenience wrappers +#define SCENARIO( ... ) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ) ) +#define SCENARIO_METHOD( className, ... ) INTERNAL_CATCH_TESTCASE_METHOD_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ), className ) + +#define GIVEN( desc ) +#define AND_GIVEN( desc ) +#define WHEN( desc ) +#define AND_WHEN( desc ) +#define THEN( desc ) +#define AND_THEN( desc ) + +using Catch::Detail::Approx; + +#endif + +#endif // ! CATCH_CONFIG_IMPL_ONLY + +// start catch_reenable_warnings.h + + +#ifdef __clang__ +# ifdef __ICC // icpc defines the __clang__ macro +# pragma warning(pop) +# else +# pragma clang diagnostic pop +# endif +#elif defined __GNUC__ +# pragma GCC diagnostic pop +#endif + +// end catch_reenable_warnings.h +// end catch.hpp +#endif // TWOBLUECUBES_SINGLE_INCLUDE_CATCH_HPP_INCLUDED + diff --git a/src/test/suite1.cpp b/src/test/cpp/suite1.cpp similarity index 100% rename from src/test/suite1.cpp rename to src/test/cpp/suite1.cpp diff --git a/src/test/suite2.cpp b/src/test/cpp/suite2.cpp similarity index 100% rename from src/test/suite2.cpp rename to src/test/cpp/suite2.cpp diff --git a/src/test/suite3.cpp b/src/test/cpp/suite3.cpp similarity index 100% rename from src/test/suite3.cpp rename to src/test/cpp/suite3.cpp From 01ff406fa5003679e1be80c8c4a367791b9e9e79 Mon Sep 17 00:00:00 2001 From: Mate Pek Date: Sat, 27 Oct 2018 17:55:22 +0200 Subject: [PATCH 15/49] better fswatch event handling --- src/C2ExecutableInfo.ts | 206 +++++++++--------- src/test/C2TestAdapter.test.ts | 372 ++++++++++++++------------------- 2 files changed, 257 insertions(+), 321 deletions(-) diff --git a/src/C2ExecutableInfo.ts b/src/C2ExecutableInfo.ts index 33eb0e4b..0dc3de2c 100644 --- a/src/C2ExecutableInfo.ts +++ b/src/C2ExecutableInfo.ts @@ -2,30 +2,30 @@ // 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. -import * as path from 'path'; -import {inspect, promisify} from 'util'; -import * as vscode from 'vscode'; - -import {C2AllTestSuiteInfo} from './C2AllTestSuiteInfo'; -import {C2TestAdapter} from './C2TestAdapter'; -import {C2TestSuiteInfo} from './C2TestSuiteInfo'; -import * as c2fs from './FsWrapper'; -import {resolveVariables} from './Helpers'; +import * as path from "path"; +import { inspect, promisify } from "util"; +import * as vscode from "vscode"; +import { C2AllTestSuiteInfo } from "./C2AllTestSuiteInfo"; +import { C2TestAdapter } from "./C2TestAdapter"; +import { C2TestSuiteInfo } from "./C2TestSuiteInfo"; +import * as c2fs from "./FsWrapper"; +import { resolveVariables } from "./Helpers"; export class C2ExecutableInfo implements vscode.Disposable { constructor( - private _adapter: C2TestAdapter, - private readonly _allTests: C2AllTestSuiteInfo, - public readonly name: string, public readonly pattern: string, - public readonly cwd: string, public readonly env: {[prop: string]: any}) { - } + private _adapter: C2TestAdapter, + private readonly _allTests: C2AllTestSuiteInfo, + public readonly name: string, + public readonly pattern: string, + public readonly cwd: string, + public readonly env: { [prop: string]: any } + ) {} private _disposables: vscode.Disposable[] = []; - private _watcher: vscode.FileSystemWatcher|undefined = undefined; + private _watcher: vscode.FileSystemWatcher | undefined = undefined; - private readonly _executables: Map = - new Map(); + private readonly _executables: Map = new Map(); dispose() { if (this._watcher) this._watcher.dispose(); @@ -35,33 +35,32 @@ export class C2ExecutableInfo implements vscode.Disposable { async load(): Promise { const wsUri = this._adapter.workspaceFolder.uri; - const isAbsolute = path.isAbsolute(this.pattern); - const absPattern = isAbsolute ? path.normalize(this.pattern) : - path.resolve(wsUri.fsPath, this.pattern); + const absPattern = isAbsolute + ? path.normalize(this.pattern) + : path.resolve(wsUri.fsPath, this.pattern); const absPatternAsUri = vscode.Uri.file(absPattern); const relativeToWs = path.relative(wsUri.fsPath, absPatternAsUri.fsPath); - const isPartOfWs = !relativeToWs.startsWith('..'); + const isPartOfWs = !relativeToWs.startsWith(".."); let fileUris: vscode.Uri[] = []; if (!isAbsolute || (isAbsolute && isPartOfWs)) { let relativePattern: vscode.RelativePattern; - relativePattern = new vscode.RelativePattern( - this._adapter.workspaceFolder, relativeToWs); - fileUris = - await vscode.workspace.findFiles(relativePattern, undefined, 1000); + relativePattern = new vscode.RelativePattern(this._adapter.workspaceFolder, relativeToWs); + fileUris = await vscode.workspace.findFiles(relativePattern, undefined, 1000); try { // abs path is required this._watcher = vscode.workspace.createFileSystemWatcher( - relativePattern, false, false, false); + relativePattern, + false, + false, + false + ); this._disposables.push(this._watcher); - this._disposables.push( - this._watcher.onDidCreate(this._handleCreate, this)); - this._disposables.push( - this._watcher.onDidChange(this._handleChange, this)); - this._disposables.push( - this._watcher.onDidDelete(this._handleDelete, this)); + this._disposables.push(this._watcher.onDidCreate(this._handleCreate, this)); + this._disposables.push(this._watcher.onDidChange(this._handleChange, this)); + this._disposables.push(this._watcher.onDidDelete(this._handleDelete, this)); } catch (e) { this._adapter.log.error(inspect([e, this])); } @@ -88,7 +87,7 @@ export class C2ExecutableInfo implements vscode.Disposable { let resolvedName = this.name; let resolvedCwd = this.cwd; - let resolvedEnv: {[prop: string]: string} = this.env; + let resolvedEnv: { [prop: string]: string } = this.env; try { const relPath = path.relative(wsUri.fsPath, file.fsPath); @@ -102,17 +101,17 @@ export class C2ExecutableInfo implements vscode.Disposable { const varToValue: [string, string][] = [ ...this._adapter.variableToValue, - ['${absPath}', file.path], - ['${relPath}', relPath], - ['${absDirpath}', path.dirname(file.fsPath)], - ['${relDirpath}', path.dirname(relPath)], - ['${filename}', filename], - ['${extFilename}', extFilename], - ['${baseFilename}', baseFilename], - ['${ext2Filename}', ext2Filename], - ['${base2Filename}', base2Filename], - ['${ext3Filename}', ext3Filename], - ['${base3Filename}', base3Filename], + ["${absPath}", file.path], + ["${relPath}", relPath], + ["${absDirpath}", path.dirname(file.fsPath)], + ["${relDirpath}", path.dirname(relPath)], + ["${filename}", filename], + ["${extFilename}", extFilename], + ["${baseFilename}", baseFilename], + ["${ext2Filename}", ext2Filename], + ["${base2Filename}", base2Filename], + ["${ext3Filename}", ext3Filename], + ["${base3Filename}", base3Filename] ]; resolvedName = resolveVariables(this.name, varToValue); resolvedCwd = path.normalize(resolveVariables(this.cwd, varToValue)); @@ -121,16 +120,20 @@ export class C2ExecutableInfo implements vscode.Disposable { this._adapter.log.error(inspect([e, this])); } - const suite = this._allTests.createChildSuite( - resolvedName, file.path, {cwd: resolvedCwd, env: resolvedEnv}); + const suite = this._allTests.createChildSuite(resolvedName, file.path, { + cwd: resolvedCwd, + env: resolvedEnv + }); this._executables.set(file.fsPath, suite); return suite; } - private readonly _lastEventArrivedAt: - Map = new Map(); + private readonly _lastEventArrivedAt: Map< + string /* fsPath */, + number /** Date.now */ + > = new Map(); private _handleEverything(uri: vscode.Uri) { let suite = this._executables.get(uri.fsPath); @@ -140,56 +143,54 @@ export class C2ExecutableInfo implements vscode.Disposable { this._uniquifySuiteNames(); } - const x = - (exists: boolean, startTime: number, timeout: number, - delay: number): Promise => { - let lastEventArrivedAt = this._lastEventArrivedAt.get(uri.fsPath); - if (lastEventArrivedAt === undefined) { - lastEventArrivedAt = Date.now(); - this._lastEventArrivedAt.set(uri.fsPath, lastEventArrivedAt); - this._adapter.log.error('assert in ' + __filename); - } - if ((Date.now() - lastEventArrivedAt!) > timeout) { - this._executables.delete(uri.fsPath); - this._adapter.testsEmitter.fire({type: 'started'}); - this._allTests.removeChild(suite!); - this._adapter.testsEmitter.fire( - {type: 'finished', suite: this._allTests}); - return Promise.resolve(); - } else if (exists) { - this._adapter.testsEmitter.fire({type: 'started'}); - return suite!.reloadChildren().then( - () => { - this._adapter.testsEmitter.fire( - {type: 'finished', suite: this._allTests}); - }, - (err: any) => { - this._adapter.testsEmitter.fire( - {type: 'finished', suite: this._allTests}); - this._adapter.log.warn(inspect(err)); - return x( - false, startTime, timeout, Math.min(delay * 2, 2000)); - }); + const isRunning = this._lastEventArrivedAt.get(uri.fsPath) !== undefined; + if (isRunning) { + this._lastEventArrivedAt.set(uri.fsPath, Date.now()); + return; + } + + this._lastEventArrivedAt.set(uri.fsPath, Date.now()); + + const x = (exists: boolean, timeout: number, delay: number): Promise => { + let lastEventArrivedAt = this._lastEventArrivedAt.get(uri.fsPath); + if (lastEventArrivedAt === undefined) { + this._adapter.log.error("assert in " + __filename); + debugger; + return Promise.resolve(); + } + if (Date.now() - lastEventArrivedAt! > timeout) { + this._lastEventArrivedAt.delete(uri.fsPath); + this._executables.delete(uri.fsPath); + this._adapter.testsEmitter.fire({ type: "started" }); + this._allTests.removeChild(suite!); + this._adapter.testsEmitter.fire({ type: "finished", suite: this._allTests }); + return Promise.resolve(); + } else if (exists) { + this._adapter.testsEmitter.fire({ type: "started" }); + return suite!.reloadChildren().then( + () => { + this._adapter.testsEmitter.fire({ type: "finished", suite: this._allTests }); + this._lastEventArrivedAt.delete(uri.fsPath); + }, + (err: any) => { + this._adapter.testsEmitter.fire({ type: "finished", suite: this._allTests }); + this._adapter.log.warn(inspect(err)); + 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, startTime, 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)); + }); + }); + }; // change event can arrive during debug session on osx (why?) // if (!this.isDebugging) { - // TODO filter multiple events and dont mess with 'load' - x(false, Date.now(), this._adapter.getExecWatchTimeout(), 64); - //} + x(false, this._adapter.getExecWatchTimeout(), 64); } private _handleCreate(uri: vscode.Uri) { - if (this._executables.has(uri.fsPath)) { - // we are fine: the delete event is still running until it's timeout - return; - } - return this._handleEverything(uri); } @@ -217,20 +218,21 @@ export class C2ExecutableInfo implements vscode.Disposable { if (suites.length > 1) { let i = 1; for (const suite of suites) { - suite.label = String(i++) + ') ' + suite.origLabel; + suite.label = String(i++) + ") " + suite.origLabel; } } } } private _verifyIsCatch2TestExecutable(path: string): Promise { - return c2fs.spawnAsync(path, ['--help']) - .then((res) => { - return res.stdout.indexOf('Catch v2.') != -1; - }) - .catch((e) => { - this._adapter.log.error(inspect(e)); - return false; - }); + return c2fs + .spawnAsync(path, ["--help"]) + .then(res => { + return res.stdout.indexOf("Catch v2.") != -1; + }) + .catch(e => { + this._adapter.log.error(inspect(e)); + return false; + }); } -} \ No newline at end of file +} diff --git a/src/test/C2TestAdapter.test.ts b/src/test/C2TestAdapter.test.ts index 39f8f298..f8af7b3e 100644 --- a/src/test/C2TestAdapter.test.ts +++ b/src/test/C2TestAdapter.test.ts @@ -41,13 +41,13 @@ describe('C2TestAdapter', function() { this.enableTimeouts(false); // TODO let testsEvents: (TestLoadStartedEvent|TestLoadFinishedEvent)[] = []; - let testStatesEvents: (TestRunStartedEvent|TestRunFinishedEvent| + let testStatesEvents: (|TestRunStartedEvent|TestRunFinishedEvent| TestSuiteEvent|TestEvent)[] = []; function getConfig() { return vscode.workspace.getConfiguration( - 'catch2TestExplorer', workspaceFolderUri) - }; + 'catch2TestExplorer', workspaceFolderUri); + } async function updateConfig(key: string, value: any) { let count = testsEvents.length; @@ -73,7 +73,7 @@ describe('C2TestAdapter', function() { let t: Thenable = Promise.resolve(); Object.keys(properties).forEach(key => { assert.ok(key.startsWith('catch2TestExplorer.')); - const k = key.replace('catch2TestExplorer.', '') + const k = key.replace('catch2TestExplorer.', ''); t = t.then(function() { return getConfig().update(k, undefined); }); @@ -87,8 +87,9 @@ describe('C2TestAdapter', function() { TestEvent) => { if (o.type == v.type) if (o.type == 'suite' || o.type == 'test') - return o.state === (v).state && - o[o.type] === (v)[v.type]; + return ( + o.state === (v).state && + o[o.type] === (v)[v.type]); return deepStrictEqual(o, v); }); assert.notEqual( @@ -97,7 +98,7 @@ describe('C2TestAdapter', function() { inspect(testStatesEvents)); assert.deepStrictEqual(testStatesEvents[i], o); return i; - }; + } function createAdapterAndSubscribe() { adapter = new C2TestAdapter(workspaceFolder, logger); @@ -123,7 +124,7 @@ describe('C2TestAdapter', function() { const start = Date.now(); let c = await condition(); while (!(c = await condition()) && - ((Date.now() - start) < timeout || !test.enableTimeouts())) + (Date.now() - start < timeout || !test.enableTimeouts())) await promisify(setTimeout)(10); assert.ok(c); } @@ -134,7 +135,7 @@ describe('C2TestAdapter', function() { const origCount = testsEvents.length; await action(); await waitFor(test, () => { - return testsEvents.length == origCount + 2; + return testsEvents.length >= origCount + 2; }, timeout); assert.equal(testsEvents.length, origCount + 2); const e = testsEvents[testsEvents.length - 1]!; @@ -210,37 +211,37 @@ describe('C2TestAdapter', function() { before(function() { adapter = createAdapterAndSubscribe(); assert.deepStrictEqual(testsEvents, []); - }) + }); after(function() { disposeAdapterAndSubscribers(); return resetConfig(); - }) + }); it('defaultEnv', function() { return doAndWaitForReloadEvent(this, () => { - return updateConfig('defaultEnv', {'APPLE': 'apple'}); + return updateConfig('defaultEnv', {APPLE: 'apple'}); }); - }) + }); it('defaultCwd', function() { return doAndWaitForReloadEvent(this, () => { return updateConfig('defaultCwd', 'apple/peach'); }); - }) + }); it('enableSourceDecoration', function() { return updateConfig('enableSourceDecoration', false).then(function() { assert.ok(!adapter.getIsEnabledSourceDecoration()); }); - }) + }); it('defaultRngSeed', function() { return updateConfig('defaultRngSeed', 987).then(function() { assert.equal(adapter.getRngSeed(), 987); }); - }) - }) + }); + }); it('load with empty config', async function() { this.slow(500); @@ -253,7 +254,7 @@ describe('C2TestAdapter', function() { assert.notEqual(suite, undefined); assert.equal(suite!.children.length, 0); disposeAdapterAndSubscribers(); - }) + }); context('example1', function() { const watchers: Map = new Map(); @@ -339,15 +340,15 @@ describe('C2TestAdapter', function() { vsFindFilesStub.withArgs(matchRelativePattern(p.path)).returns([p]); } }); - }) + }); after(function() { stubsResetToMyDefault(); - }) + }); afterEach(function() { watchers.clear(); - }) + }); describe('load', function() { const uniqueIdC = new Set(); @@ -362,13 +363,13 @@ describe('C2TestAdapter', function() { let s2t2: TestInfo|any; let s2t3: TestInfo|any; - before(function(){ - return updateConfig("workerMaxNumber", 4); - }) + before(function() { + return updateConfig('workerMaxNumber', 4); + }); - after(function(){ - return updateConfig("workerMaxNumber", undefined); - }) + after(function() { + return updateConfig('workerMaxNumber', undefined); + }); beforeEach(async function() { adapter = createAdapterAndSubscribe(); @@ -426,8 +427,7 @@ describe('C2TestAdapter', function() { await adapter.run(['not existing id']); assert.deepStrictEqual(testStatesEvents, [ - {type: 'started', tests: ['not existing id']}, - {type: 'finished'}, + {type: 'started', tests: ['not existing id']}, {type: 'finished'} ]); }); @@ -437,8 +437,7 @@ describe('C2TestAdapter', function() { const expected = [ {type: 'started', tests: [s1t1.id]}, {type: 'suite', state: 'running', suite: suite1}, - {type: 'test', state: 'running', test: s1t1}, - { + {type: 'test', state: 'running', test: s1t1}, { type: 'test', state: 'passed', test: s1t1, @@ -446,7 +445,7 @@ describe('C2TestAdapter', function() { message: 'Duration: 0.000112 second(s)\n' }, {type: 'suite', state: 'completed', suite: suite1}, - {type: 'finished'}, + {type: 'finished'} ]; assert.deepStrictEqual(testStatesEvents, expected); @@ -459,16 +458,14 @@ describe('C2TestAdapter', function() { const expected = [ {type: 'started', tests: [suite1.id]}, {type: 'suite', state: 'running', suite: suite1}, - {type: 'test', state: 'running', test: s1t1}, - { + {type: 'test', state: 'running', test: s1t1}, { type: 'test', state: 'passed', test: s1t1, decorations: undefined, message: 'Duration: 0.000132 second(s)\n' }, - {type: 'test', state: 'running', test: s1t2}, - { + {type: 'test', state: 'running', test: s1t2}, { type: 'test', state: 'failed', test: s1t2, @@ -477,7 +474,7 @@ describe('C2TestAdapter', function() { 'Duration: 0.000204 second(s)\n>>> s1t2(line: 13) REQUIRE (line: 15) \n Original:\n std::false_type::value\n Expanded:\n false\n<<<\n' }, {type: 'suite', state: 'completed', suite: suite1}, - {type: 'finished'}, + {type: 'finished'} ]; assert.deepStrictEqual(testStatesEvents, expected); @@ -490,16 +487,14 @@ describe('C2TestAdapter', function() { const expected = [ {type: 'started', tests: [root.id]}, {type: 'suite', state: 'running', suite: suite1}, - {type: 'test', state: 'running', test: s1t1}, - { + {type: 'test', state: 'running', test: s1t1}, { type: 'test', state: 'passed', test: s1t1, decorations: undefined, message: 'Duration: 0.000132 second(s)\n' }, - {type: 'test', state: 'running', test: s1t2}, - { + {type: 'test', state: 'running', test: s1t2}, { type: 'test', state: 'failed', test: s1t2, @@ -508,7 +503,7 @@ describe('C2TestAdapter', function() { 'Duration: 0.000204 second(s)\n>>> s1t2(line: 13) REQUIRE (line: 15) \n Original:\n std::false_type::value\n Expanded:\n false\n<<<\n' }, {type: 'suite', state: 'completed', suite: suite1}, - {type: 'finished'}, + {type: 'finished'} ]; assert.deepStrictEqual(testStatesEvents, expected); @@ -529,8 +524,7 @@ describe('C2TestAdapter', function() { const expected = [ {type: 'started', tests: [s1t1.id]}, {type: 'suite', state: 'running', suite: suite1}, - {type: 'test', state: 'running', test: s1t1}, - { + {type: 'test', state: 'running', test: s1t1}, { type: 'test', state: 'passed', test: s1t1, @@ -538,7 +532,7 @@ describe('C2TestAdapter', function() { message: 'Duration: 0.000112 second(s)\n' }, {type: 'suite', state: 'completed', suite: suite1}, - {type: 'finished'}, + {type: 'finished'} ]; assert.deepStrictEqual(testStatesEvents, expected); @@ -550,19 +544,18 @@ describe('C2TestAdapter', function() { context('with config: defaultRngSeed=2', function() { before(function() { return updateConfig('defaultRngSeed', 2); - }) + }); after(function() { return updateConfig('defaultRngSeed', undefined); - }) + }); it('should run s1t1 with success', async function() { await adapter.run([s1t1.id]); const expected = [ {type: 'started', tests: [s1t1.id]}, {type: 'suite', state: 'running', suite: suite1}, - {type: 'test', state: 'running', test: s1t1}, - { + {type: 'test', state: 'running', test: s1t1}, { type: 'test', state: 'passed', test: s1t1, @@ -571,16 +564,16 @@ describe('C2TestAdapter', function() { 'Randomness seeded to: 2\nDuration: 0.000327 second(s)\n' }, {type: 'suite', state: 'completed', suite: suite1}, - {type: 'finished'}, + {type: 'finished'} ]; assert.deepStrictEqual(testStatesEvents, expected); await adapter.run([s1t1.id]); assert.deepStrictEqual( testStatesEvents, [...expected, ...expected]); - }) - }) - }) + }); + }); + }); context('suite1 and suite2 are used', function() { beforeEach(function() { @@ -610,7 +603,7 @@ describe('C2TestAdapter', function() { s2t2 = suite2.children[1]; assert.equal(suite2.children[2].type, 'test'); s2t3 = suite2.children[2]; - }) + }); const testsForAdapterWithSuite1AndSuite2: Mocha.Test[] = [ new Mocha.Test( @@ -773,7 +766,7 @@ describe('C2TestAdapter', function() { assert.deepStrictEqual(testStatesEvents, [ {type: 'started', tests: ['not existing id']}, - {type: 'finished'}, + {type: 'finished'} ]); }), new Mocha.Test( @@ -783,8 +776,7 @@ describe('C2TestAdapter', function() { const expected = [ {type: 'started', tests: [s1t1.id]}, {type: 'suite', state: 'running', suite: suite1}, - {type: 'test', state: 'running', test: s1t1}, - { + {type: 'test', state: 'running', test: s1t1}, { type: 'test', state: 'passed', test: s1t1, @@ -792,7 +784,7 @@ describe('C2TestAdapter', function() { message: 'Duration: 0.000112 second(s)\n' }, {type: 'suite', state: 'completed', suite: suite1}, - {type: 'finished'}, + {type: 'finished'} ]; assert.deepStrictEqual(testStatesEvents, expected); @@ -807,8 +799,7 @@ describe('C2TestAdapter', function() { const expected = [ {type: 'started', tests: [s2t2.id]}, {type: 'suite', state: 'running', suite: suite2}, - {type: 'test', state: 'running', test: s2t2}, - { + {type: 'test', state: 'running', test: s2t2}, { type: 'test', state: 'passed', test: s2t2, @@ -816,7 +807,7 @@ describe('C2TestAdapter', function() { message: 'Duration: 0.001294 second(s)\n' }, {type: 'suite', state: 'completed', suite: suite2}, - {type: 'finished'}, + {type: 'finished'} ]; assert.deepStrictEqual(testStatesEvents, expected); @@ -831,8 +822,7 @@ describe('C2TestAdapter', function() { const expected = [ {type: 'started', tests: [s2t3.id]}, {type: 'suite', state: 'running', suite: suite2}, - {type: 'test', state: 'running', test: s2t3}, - { + {type: 'test', state: 'running', test: s2t3}, { type: 'test', state: 'failed', test: s2t3, @@ -841,7 +831,7 @@ describe('C2TestAdapter', function() { 'Duration: 0.000596 second(s)\n>>> s2t3(line: 19) REQUIRE (line: 21) \n Original:\n std::false_type::value\n Expanded:\n false\n<<<\n' }, {type: 'suite', state: 'completed', suite: suite2}, - {type: 'finished'}, + {type: 'finished'} ]; assert.deepStrictEqual(testStatesEvents, expected); @@ -862,8 +852,7 @@ describe('C2TestAdapter', function() { const expected = [ {type: 'started', tests: [s2t3.id]}, {type: 'suite', state: 'running', suite: suite2}, - {type: 'test', state: 'running', test: s2t3}, - { + {type: 'test', state: 'running', test: s2t3}, { type: 'test', state: 'failed', test: s2t3, @@ -872,7 +861,7 @@ describe('C2TestAdapter', function() { 'Duration: 0.000596 second(s)\n>>> s2t3(line: 19) REQUIRE (line: 21) \n Original:\n std::false_type::value\n Expanded:\n false\n<<<\n' }, {type: 'suite', state: 'completed', suite: suite2}, - {type: 'finished'}, + {type: 'finished'} ]; assert.deepStrictEqual(testStatesEvents, expected); @@ -887,16 +876,14 @@ describe('C2TestAdapter', function() { const expected = [ {type: 'started', tests: [suite1.id]}, {type: 'suite', state: 'running', suite: suite1}, - {type: 'test', state: 'running', test: s1t1}, - { + {type: 'test', state: 'running', test: s1t1}, { type: 'test', state: 'passed', test: s1t1, decorations: undefined, message: 'Duration: 0.000132 second(s)\n' }, - {type: 'test', state: 'running', test: s1t2}, - { + {type: 'test', state: 'running', test: s1t2}, { type: 'test', state: 'failed', test: s1t2, @@ -905,7 +892,7 @@ describe('C2TestAdapter', function() { 'Duration: 0.000204 second(s)\n>>> s1t2(line: 13) REQUIRE (line: 15) \n Original:\n std::false_type::value\n Expanded:\n false\n<<<\n' }, {type: 'suite', state: 'completed', suite: suite1}, - {type: 'finished'}, + {type: 'finished'} ]; assert.deepStrictEqual(testStatesEvents, expected); @@ -932,26 +919,23 @@ describe('C2TestAdapter', function() { const expected = [ {type: 'started', tests: [s1t1.id]}, {type: 'suite', state: 'running', suite: suite1}, - {type: 'test', state: 'running', test: s1t1}, - { + {type: 'test', state: 'running', test: s1t1}, { type: 'test', state: 'failed', test: s1t1, message: 'Unexpected test error. (Is Catch2 crashed?)\n' }, {type: 'suite', state: 'completed', suite: suite1}, - {type: 'finished'}, + {type: 'finished'} ]; assert.deepStrictEqual(testStatesEvents, expected); // this tests the sinon stubs too await adapter.run([s1t1.id]); assert.deepStrictEqual(testStatesEvents, [ - ...expected, - {type: 'started', tests: [s1t1.id]}, + ...expected, {type: 'started', tests: [s1t1.id]}, {type: 'suite', state: 'running', suite: suite1}, - {type: 'test', state: 'running', test: s1t1}, - { + {type: 'test', state: 'running', test: s1t1}, { type: 'test', state: 'passed', test: s1t1, @@ -959,7 +943,7 @@ describe('C2TestAdapter', function() { message: 'Duration: 0.000112 second(s)\n' }, {type: 'suite', state: 'completed', suite: suite1}, - {type: 'finished'}, + {type: 'finished'} ]); }), new Mocha.Test( @@ -1075,7 +1059,7 @@ describe('C2TestAdapter', function() { assert.equal(spyKill1.callCount, 0); assert.equal(spyKill2.callCount, 0); }); - }), + }) ]; context('executables=["execPath1", "./execPath2"]', function() { @@ -1099,10 +1083,10 @@ describe('C2TestAdapter', function() { example1.suite2.assert( './execPath2', ['s2t1', 's2t2 [.]', 's2t3'], suite2, uniqueIdC); - }) + }); - for (let t of testsForAdapterWithSuite1AndSuite2) this.addTest( - t.clone()); + for (let t of testsForAdapterWithSuite1AndSuite2) + this.addTest(t.clone()); it('reload because of fswatcher event: touch(changed)', async function() { @@ -1111,73 +1095,46 @@ describe('C2TestAdapter', function() { suite1Watcher.sendChange(); }); assert.deepStrictEqual(newRoot, root); - assert.deepStrictEqual(testsEvents, [ - {type: 'started'}, - {type: 'finished', suite: root}, - ]); - }) + assert.deepStrictEqual( + testsEvents, + [{type: 'started'}, {type: 'finished', suite: root}]); + }); it('reload because of fswatcher event: double touch(changed)', async function() { - this.slow(200); + this.slow(300); const oldRoot = root; suite1Watcher.sendChange(); suite1Watcher.sendChange(); await waitFor(this, async () => { - return testsEvents.length >= 4; + return testsEvents.length >= 2; }); - assert.ok( - deepStrictEqual( - testsEvents, - [ - {type: 'started'}, - {type: 'finished', suite: oldRoot}, - {type: 'started'}, - {type: 'finished', suite: oldRoot}, - ]) || - deepStrictEqual(testsEvents, [ - {type: 'started'}, - {type: 'started'}, - {type: 'finished', suite: oldRoot}, - {type: 'finished', suite: oldRoot}, - ])); - testsEvents.pop(); - testsEvents.pop(); + await promisify(setTimeout)(100); + assert.deepStrictEqual( + testsEvents, + [{type: 'started'}, {type: 'finished', suite: oldRoot}]); testsEvents.pop(); testsEvents.pop(); - }) + }); it('reload because of fswatcher event: double touch(changed) with delay', async function() { - this.slow(200); + this.slow(300); const oldRoot = root; suite1Watcher.sendChange(); setTimeout(() => { suite1Watcher.sendChange(); }, 20); await waitFor(this, async () => { - return testsEvents.length >= 4; + return testsEvents.length >= 2; }); - assert.ok( - deepStrictEqual( - testsEvents, - [ - {type: 'started'}, - {type: 'finished', suite: oldRoot}, - {type: 'started'}, - {type: 'finished', suite: oldRoot}, - ]) || - deepStrictEqual(testsEvents, [ - {type: 'started'}, - {type: 'started'}, - {type: 'finished', suite: oldRoot}, - {type: 'finished', suite: oldRoot}, - ])); - testsEvents.pop(); - testsEvents.pop(); + await promisify(setTimeout)(100); + assert.deepStrictEqual( + testsEvents, + [{type: 'started'}, {type: 'finished', suite: oldRoot}]); testsEvents.pop(); testsEvents.pop(); - }) + }); it('reload because of fswatcher event: touch(delete,create)', async function() { @@ -1187,73 +1144,48 @@ describe('C2TestAdapter', function() { suite1Watcher.sendCreate(); }); assert.deepStrictEqual(newRoot, root); - assert.deepStrictEqual(testsEvents, [ - {type: 'started'}, - {type: 'finished', suite: root}, - ]); - }) + assert.deepStrictEqual( + testsEvents, + [{type: 'started'}, {type: 'finished', suite: root}]); + }); it('reload because of fswatcher event: double touch(delete,create)', async function() { - this.slow(200); + this.slow(300); const oldRoot = root; suite1Watcher.sendChange(); suite1Watcher.sendChange(); await waitFor(this, async () => { - return testsEvents.length >= 4; + return testsEvents.length >= 2; }); - assert.ok( - deepStrictEqual( - testsEvents, - [ - {type: 'started'}, - {type: 'finished', suite: oldRoot}, - {type: 'started'}, - {type: 'finished', suite: oldRoot}, - ]) || - deepStrictEqual(testsEvents, [ - {type: 'started'}, - {type: 'started'}, - {type: 'finished', suite: oldRoot}, - {type: 'finished', suite: oldRoot}, - ])); - testsEvents.pop(); - testsEvents.pop(); + await promisify(setTimeout)(100); + assert.deepStrictEqual(testsEvents, [ + {type: 'started'}, + {type: 'finished', suite: oldRoot} + ]); testsEvents.pop(); testsEvents.pop(); - }) + }); it('reload because of fswatcher event: double touch(delete,create) with delay', async function() { - this.slow(200); + this.slow(300); const oldRoot = root; suite1Watcher.sendChange(); setTimeout(() => { suite1Watcher.sendChange(); }, 20); await waitFor(this, async () => { - return testsEvents.length >= 4; + return testsEvents.length >= 2; }); - assert.ok( - deepStrictEqual( - testsEvents, - [ - {type: 'started'}, - {type: 'finished', suite: oldRoot}, - {type: 'started'}, - {type: 'finished', suite: oldRoot}, - ]) || - deepStrictEqual(testsEvents, [ - {type: 'started'}, + await promisify(setTimeout)(100); + assert.deepStrictEqual(testsEvents, [ {type: 'started'}, - {type: 'finished', suite: oldRoot}, - {type: 'finished', suite: oldRoot}, - ])); + {type: 'finished', suite: oldRoot} + ]); testsEvents.pop(); testsEvents.pop(); - testsEvents.pop(); - testsEvents.pop(); - }) + }); it('reload because of fswatcher event: test added', async function(this: Mocha.Context) { @@ -1295,7 +1227,7 @@ describe('C2TestAdapter', function() { for (let i = 0; i < suite2.children.length; i++) { assert.equal(suite2.children[i], oldSuite2Children[i]); } - }) + }); it('reload because of fswatcher event: test deleted', async function(this: Mocha.Context) { this.slow(200); @@ -1332,8 +1264,8 @@ describe('C2TestAdapter', function() { for (let i = 0; i < suite2.children.length; i++) { assert.equal(suite2.children[i], oldSuite2Children[i]); } - }) - }) + }); + }); context('executables=[{}] and env={...}', function() { before(async function() { @@ -1343,8 +1275,8 @@ describe('C2TestAdapter', function() { path: 'execPath{1,2}', cwd: '${workspaceFolder}/cwd', env: { - 'C2LOCALTESTENV': 'c2localtestenv', - 'C2OVERRIDETESTENV': 'c2overridetestenv-l', + C2LOCALTESTENV: 'c2localtestenv', + C2OVERRIDETESTENV: 'c2overridetestenv-l' } }]); @@ -1358,11 +1290,11 @@ describe('C2TestAdapter', function() { path.join(workspaceFolderUri.path, 'execPath{1,2}'))) .returns([ vscode.Uri.file(example1.suite1.execPath), - vscode.Uri.file(example1.suite2.execPath), + vscode.Uri.file(example1.suite2.execPath) ]); await updateConfig('defaultEnv', { - 'C2GLOBALTESTENV': 'c2globaltestenv', - 'C2OVERRIDETESTENV': 'c2overridetestenv-g', + C2GLOBALTESTENV: 'c2globaltestenv', + C2OVERRIDETESTENV: 'c2overridetestenv-g' }); }); @@ -1379,10 +1311,10 @@ describe('C2TestAdapter', function() { example1.suite2.assert( './execPath2 (' + workspaceFolderUri.path + ')', ['s2t1', 's2t2 [.]', 's2t3'], suite2, uniqueIdC); - }) + }); - for (let t of testsForAdapterWithSuite1AndSuite2) this.addTest( - t.clone()); + for (let t of testsForAdapterWithSuite1AndSuite2) + this.addTest(t.clone()); it('should get execution options', async function() { { @@ -1401,7 +1333,7 @@ describe('C2TestAdapter', function() { const cc = withArgs.callCount; await adapter.run([suite1.id]); - assert.equal(withArgs.callCount, cc + 1) + assert.equal(withArgs.callCount, cc + 1); } { const withArgs = spawnStub.withArgs( @@ -1418,11 +1350,11 @@ describe('C2TestAdapter', function() { }); const cc = withArgs.callCount; await adapter.run([suite2.id]); - assert.equal(withArgs.callCount, cc + 1) + assert.equal(withArgs.callCount, cc + 1); } - }) - }) - }) + }); + }); + }); context( 'executables=["execPath1", "execPath2", "execPath3"]', @@ -1481,9 +1413,9 @@ describe('C2TestAdapter', function() { assert.equal(test.type, 'test'); await runAndCheckEvents(test); } - }) - }) - }) + }); + }); + }); specify('load executables=', async function() { this.slow(300); @@ -1501,13 +1433,13 @@ describe('C2TestAdapter', function() { disposeAdapterAndSubscribers(); await updateConfig('executables', undefined); - }) + }); specify( 'load executables=["execPath1", "execPath2"] with error', async function() { this.slow(300); - await updateConfig('executables', ['execPath1', 'execPath2']) + await updateConfig('executables', ['execPath1', 'execPath2']); adapter = createAdapterAndSubscribe(); const withArgs = spawnStub.withArgs( @@ -1521,7 +1453,7 @@ describe('C2TestAdapter', function() { disposeAdapterAndSubscribers(); await updateConfig('executables', undefined); - }) + }); specify( 'load executables=["execPath1", "execPath2Copy"]; delete; sleep 3; create', @@ -1549,7 +1481,7 @@ describe('C2TestAdapter', function() { vsFindFilesStub.withArgs(matchRelativePattern(execPath2CopyPath)) .returns([vscode.Uri.file(execPath2CopyPath)]); - await updateConfig('executables', ['execPath1', 'execPath2Copy']) + await updateConfig('executables', ['execPath1', 'execPath2Copy']); adapter = createAdapterAndSubscribe(); await adapter.load(); @@ -1607,7 +1539,7 @@ describe('C2TestAdapter', function() { assert.equal(newRoot.children.length, 2); assert.ok(3000 < elapsed, inspect(elapsed)); assert.ok(elapsed < watchTimeout * 1000 + 2400, inspect(elapsed)); - }) + }); specify( 'load executables=["execPath1", "execPath2Copy"]; delete second', @@ -1615,7 +1547,7 @@ describe('C2TestAdapter', function() { const watchTimeout = 5; await updateConfig('defaultWatchTimeoutSec', watchTimeout); this.timeout(watchTimeout * 1000 + 2500 /* because of 'delay' */); - this.slow(watchTimeout* 1000 + 2500 /* because of 'delay' */); + this.slow(watchTimeout * 1000 + 2500 /* because of 'delay' */); const execPath2CopyPath = path.join(workspaceFolderUri.path, 'execPath2Copy'); @@ -1635,7 +1567,7 @@ describe('C2TestAdapter', function() { vsFindFilesStub.withArgs(matchRelativePattern(execPath2CopyPath)) .returns([vscode.Uri.file(execPath2CopyPath)]); - await updateConfig('executables', ['execPath1', 'execPath2Copy']) + await updateConfig('executables', ['execPath1', 'execPath2Copy']); adapter = createAdapterAndSubscribe(); await adapter.load(); @@ -1683,7 +1615,7 @@ describe('C2TestAdapter', function() { assert.equal(newRoot.children.length, 1); assert.ok(watchTimeout * 1000 < elapsed, inspect(elapsed)); assert.ok(elapsed < watchTimeout * 1000 + 2400, inspect(elapsed)); - }) + }); specify('wrong executables format', async function() { this.slow(300); @@ -1701,31 +1633,33 @@ describe('C2TestAdapter', function() { disposeAdapterAndSubscribers(); await updateConfig('executables', undefined); - }) + }); specify('variable substitution with executables={...}', async function() { this.slow(300); - const wsPath = workspaceFolderUri.fsPath + const wsPath = workspaceFolderUri.fsPath; const execPath2CopyRelPath = 'foo/bar/base.second.first'; const execPath2CopyPath = path.join(wsPath, execPath2CopyRelPath); const envArray: [string, string][] = [ - ['${absPath}', execPath2CopyPath], - ['${relPath}', execPath2CopyRelPath], + ['${absPath}', execPath2CopyPath], ['${relPath}', execPath2CopyRelPath], ['${absDirpath}', path.join(wsPath, 'foo/bar')], - ['${relDirpath}', 'foo/bar'], - ['${filename}', 'base.second.first'], - ['${baseFilename}', 'base.second'], - ['${extFilename}', '.first'], - ['${base2Filename}', 'base'], - ['${ext2Filename}', '.second'], - ['${base3Filename}', 'base'], - ['${ext3Filename}', ''], - ['${workspaceDirectory}', wsPath], - ['${workspaceFolder}', wsPath], + ['${relDirpath}', 'foo/bar'], ['${filename}', 'base.second.first'], + ['${baseFilename}', 'base.second'], ['${extFilename}', '.first'], + ['${base2Filename}', 'base'], ['${ext2Filename}', '.second'], + ['${base3Filename}', 'base'], ['${ext3Filename}', ''], + ['${workspaceDirectory}', wsPath], ['${workspaceFolder}', wsPath] ]; - const envsStr = envArray.map(v => {return v[0]}).join(' , '); - const expectStr = envArray.map(v => {return v[1]}).join(' , '); + const envsStr = envArray + .map(v => { + return v[0]; + }) + .join(' , '); + const expectStr = envArray + .map(v => { + return v[1]; + }) + .join(' , '); await updateConfig('executables', { name: envsStr, @@ -1792,6 +1726,6 @@ describe('C2TestAdapter', function() { }); disposeAdapterAndSubscribers(); await updateConfig('executables', undefined); - }) - }) -}) + }); + }); +}); From 9f74c5c5e699e08be66ba2d4b97de2938914f455 Mon Sep 17 00:00:00 2001 From: Mate Pek Date: Sat, 27 Oct 2018 18:25:36 +0200 Subject: [PATCH 16/49] QueueGraph fix --- src/QueueGraph.ts | 38 +++++++++-------- src/test/QueueGraph.test.ts | 85 +++++++++++++++++++++---------------- 2 files changed, 68 insertions(+), 55 deletions(-) diff --git a/src/QueueGraph.ts b/src/QueueGraph.ts index eb90084e..437307b2 100644 --- a/src/QueueGraph.ts +++ b/src/QueueGraph.ts @@ -4,7 +4,7 @@ export class QueueGraphNode { constructor( - public readonly name?: string, depends: Iterable < QueueGraphNode >= [], + public readonly name?: string, depends: Iterable = [], private readonly _handleError?: ((reason: any) => void)) { this._depends = [...depends]; // TODO check circular dependency @@ -18,24 +18,27 @@ export class QueueGraphNode { return this._count; } - then( - task?: (() => void|PromiseLike)|undefined|null, - taskErrorHandler?: ((reason: any) => void|PromiseLike)|undefined| - null) { + then( + task?: (() => T | PromiseLike)|undefined|null, + taskErrorHandler?: ((reason: any) => T2 | PromiseLike)|undefined| + null): Promise { this._count++; const previous = this._queue; - this._queue = Promise.all(this._depends.map(v => v._queue)).then(() => { - return previous.then(task); - }); - - if (taskErrorHandler) - this._queue = this._queue.catch(taskErrorHandler); - else if (this._handleError) - this._queue = this._queue.catch(this._handleError); - this._queue = this._queue.then(() => { - this._count--; - }); + const current = + Promise.all(this._depends.map(v => v._queue)) + .then(() => { + return previous; + }) + .then(task, taskErrorHandler ? taskErrorHandler : this._handleError) + .then((value: T) => { + this._count--; + return value; + }); + + this._queue = current.then(() => {}); + + return current; } dependsOn(depends: Iterable): void { @@ -45,8 +48,7 @@ export class QueueGraphNode { // TODO check recursion } - private _count: number = 0; private _queue: Promise = Promise.resolve(); private readonly _depends: Array; -} \ No newline at end of file +} diff --git a/src/test/QueueGraph.test.ts b/src/test/QueueGraph.test.ts index 28484e1f..6ec69c76 100644 --- a/src/test/QueueGraph.test.ts +++ b/src/test/QueueGraph.test.ts @@ -2,24 +2,23 @@ // 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. -import * as assert from 'assert'; -import {promisify} from 'util'; +import * as assert from "assert"; +import { promisify } from "util"; -import {QueueGraphNode} from '../QueueGraph'; +import { QueueGraphNode } from "../QueueGraph"; -describe('QueueGraphNode', function() { - async function waitFor( - test: Mocha.Context, condition: Function, timeout: number = 1000) { +describe.only("QueueGraphNode", function() { + async function waitFor(test: Mocha.Context, condition: Function, timeout: number = 1000) { const start = Date.now(); let c = await condition(); - while (!c && ((Date.now() - start) < timeout || !test.enableTimeouts())) { + while (!c && (Date.now() - start < timeout || !test.enableTimeouts())) { await promisify(setTimeout)(10); c = await condition(); } return c; } - it('promise practice 1', async function() { + it("promise practice 1", async function() { let resolve: Function; let second = false; new Promise(r => { @@ -34,9 +33,9 @@ describe('QueueGraphNode', function() { return second; }); assert.ok(second); - }) + }); - it('promise practice 2', async function() { + it("promise practice 2", async function() { let resolve: Function; let second = false; const p = new Promise(r => { @@ -54,18 +53,18 @@ describe('QueueGraphNode', function() { return second; }); assert.ok(second); - }) + }); - context('example 1', function() { + context("example 1", function() { /** * node1 <___ node * node2 <___/ */ - const node1 = new QueueGraphNode('node1'); - const node2 = new QueueGraphNode('node2'); - const nodeD = new QueueGraphNode('nodeD', [node1, node2]); + const node1 = new QueueGraphNode("node1"); + const node2 = new QueueGraphNode("node2"); + const nodeD = new QueueGraphNode("nodeD", [node1, node2]); - it('add:depends before', async function() { + it("add:depends before", async function() { this.slow(150); let startD: Function; let hasRunDatOnce = false; @@ -113,17 +112,23 @@ describe('QueueGraphNode', function() { await promisify(setTimeout)(20); - assert.ok(await waitFor(this, async () => { - return hasRunDatOnce; - })); + assert.ok( + await waitFor(this, async () => { + return hasRunDatOnce; + }) + ); assert.equal(nodeD.size, 1); - assert.ok(await waitFor(this, async () => { - return hasRun1atOnce; - })); + assert.ok( + await waitFor(this, async () => { + return hasRun1atOnce; + }) + ); assert.equal(node1.size, 2); - assert.ok(await waitFor(this, async () => { - return hasRun2atOnce; - })); + assert.ok( + await waitFor(this, async () => { + return hasRun2atOnce; + }) + ); assert.equal(node2.size, 2); let hasRunD2second = false; @@ -143,9 +148,11 @@ describe('QueueGraphNode', function() { start1!(); await promisify(setTimeout)(20); - assert.ok(await waitFor(this, async () => { - return hasRun1afterStart; - })); + assert.ok( + await waitFor(this, async () => { + return hasRun1afterStart; + }) + ); assert.equal(node1.size, 0); assert.equal(node2.size, 2); assert.equal(nodeD.size, 1); @@ -154,15 +161,19 @@ describe('QueueGraphNode', function() { start2!(); await promisify(setTimeout)(20); - assert.ok(await waitFor(this, async () => { - return hasRun2afterStart; - })); + assert.ok( + await waitFor(this, async () => { + return hasRun2afterStart; + }) + ); assert.equal(node2.size, 0); - assert.ok(await waitFor(this, async () => { - return hasRunD2second; - })); + assert.ok( + await waitFor(this, async () => { + return hasRunD2second; + }) + ); assert.equal(nodeD.size, 0); - }) - }) -}) \ No newline at end of file + }); + }); +}); From ac94481ac48767cf949a795976c505f970ef8b47 Mon Sep 17 00:00:00 2001 From: Mate Pek Date: Sat, 27 Oct 2018 19:03:20 +0200 Subject: [PATCH 17/49] queuing events vol1. --- src/C2ExecutableInfo.ts | 182 ++++++++++++++++----------------- src/C2TestAdapter.ts | 9 +- src/QueueGraph.ts | 16 +-- src/test/C2TestAdapter.test.ts | 14 ++- src/test/QueueGraph.test.ts | 73 ++++++------- 5 files changed, 138 insertions(+), 156 deletions(-) diff --git a/src/C2ExecutableInfo.ts b/src/C2ExecutableInfo.ts index 0dc3de2c..08823a14 100644 --- a/src/C2ExecutableInfo.ts +++ b/src/C2ExecutableInfo.ts @@ -2,30 +2,29 @@ // 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. -import * as path from "path"; -import { inspect, promisify } from "util"; -import * as vscode from "vscode"; +import * as path from 'path'; +import {inspect, promisify} from 'util'; +import * as vscode from 'vscode'; -import { C2AllTestSuiteInfo } from "./C2AllTestSuiteInfo"; -import { C2TestAdapter } from "./C2TestAdapter"; -import { C2TestSuiteInfo } from "./C2TestSuiteInfo"; -import * as c2fs from "./FsWrapper"; -import { resolveVariables } from "./Helpers"; +import {C2AllTestSuiteInfo} from './C2AllTestSuiteInfo'; +import {C2TestAdapter} from './C2TestAdapter'; +import {C2TestSuiteInfo} from './C2TestSuiteInfo'; +import * as c2fs from './FsWrapper'; +import {resolveVariables} from './Helpers'; export class C2ExecutableInfo implements vscode.Disposable { constructor( - private _adapter: C2TestAdapter, - private readonly _allTests: C2AllTestSuiteInfo, - public readonly name: string, - public readonly pattern: string, - public readonly cwd: string, - public readonly env: { [prop: string]: any } - ) {} + private _adapter: C2TestAdapter, + private readonly _allTests: C2AllTestSuiteInfo, + public readonly name: string, public readonly pattern: string, + public readonly cwd: string, public readonly env: {[prop: string]: any}) { + } private _disposables: vscode.Disposable[] = []; - private _watcher: vscode.FileSystemWatcher | undefined = undefined; + private _watcher: vscode.FileSystemWatcher|undefined = undefined; - private readonly _executables: Map = new Map(); + private readonly _executables: Map = + new Map(); dispose() { if (this._watcher) this._watcher.dispose(); @@ -36,31 +35,31 @@ export class C2ExecutableInfo implements vscode.Disposable { const wsUri = this._adapter.workspaceFolder.uri; const isAbsolute = path.isAbsolute(this.pattern); - const absPattern = isAbsolute - ? path.normalize(this.pattern) - : path.resolve(wsUri.fsPath, this.pattern); + const absPattern = isAbsolute ? path.normalize(this.pattern) : + path.resolve(wsUri.fsPath, this.pattern); const absPatternAsUri = vscode.Uri.file(absPattern); const relativeToWs = path.relative(wsUri.fsPath, absPatternAsUri.fsPath); - const isPartOfWs = !relativeToWs.startsWith(".."); + const isPartOfWs = !relativeToWs.startsWith('..'); let fileUris: vscode.Uri[] = []; if (!isAbsolute || (isAbsolute && isPartOfWs)) { let relativePattern: vscode.RelativePattern; - relativePattern = new vscode.RelativePattern(this._adapter.workspaceFolder, relativeToWs); - fileUris = await vscode.workspace.findFiles(relativePattern, undefined, 1000); + relativePattern = new vscode.RelativePattern( + this._adapter.workspaceFolder, relativeToWs); + fileUris = + await vscode.workspace.findFiles(relativePattern, undefined, 1000); try { // abs path is required this._watcher = vscode.workspace.createFileSystemWatcher( - relativePattern, - false, - false, - false - ); + relativePattern, false, false, false); this._disposables.push(this._watcher); - this._disposables.push(this._watcher.onDidCreate(this._handleCreate, this)); - this._disposables.push(this._watcher.onDidChange(this._handleChange, this)); - this._disposables.push(this._watcher.onDidDelete(this._handleDelete, this)); + this._disposables.push( + this._watcher.onDidCreate(this._handleCreate, this)); + this._disposables.push( + this._watcher.onDidChange(this._handleChange, this)); + this._disposables.push( + this._watcher.onDidDelete(this._handleDelete, this)); } catch (e) { this._adapter.log.error(inspect([e, this])); } @@ -87,7 +86,7 @@ export class C2ExecutableInfo implements vscode.Disposable { let resolvedName = this.name; let resolvedCwd = this.cwd; - let resolvedEnv: { [prop: string]: string } = this.env; + let resolvedEnv: {[prop: string]: string} = this.env; try { const relPath = path.relative(wsUri.fsPath, file.fsPath); @@ -100,18 +99,12 @@ export class C2ExecutableInfo implements vscode.Disposable { const base3Filename = path.basename(base2Filename, ext3Filename); const varToValue: [string, string][] = [ - ...this._adapter.variableToValue, - ["${absPath}", file.path], - ["${relPath}", relPath], - ["${absDirpath}", path.dirname(file.fsPath)], - ["${relDirpath}", path.dirname(relPath)], - ["${filename}", filename], - ["${extFilename}", extFilename], - ["${baseFilename}", baseFilename], - ["${ext2Filename}", ext2Filename], - ["${base2Filename}", base2Filename], - ["${ext3Filename}", ext3Filename], - ["${base3Filename}", base3Filename] + ...this._adapter.variableToValue, ['${absPath}', file.path], + ['${relPath}', relPath], ['${absDirpath}', path.dirname(file.fsPath)], + ['${relDirpath}', path.dirname(relPath)], ['${filename}', filename], + ['${extFilename}', extFilename], ['${baseFilename}', baseFilename], + ['${ext2Filename}', ext2Filename], ['${base2Filename}', base2Filename], + ['${ext3Filename}', ext3Filename], ['${base3Filename}', base3Filename] ]; resolvedName = resolveVariables(this.name, varToValue); resolvedCwd = path.normalize(resolveVariables(this.cwd, varToValue)); @@ -120,20 +113,17 @@ export class C2ExecutableInfo implements vscode.Disposable { this._adapter.log.error(inspect([e, this])); } - const suite = this._allTests.createChildSuite(resolvedName, file.path, { - cwd: resolvedCwd, - env: resolvedEnv - }); + const suite = this._allTests.createChildSuite( + resolvedName, file.path, {cwd: resolvedCwd, env: resolvedEnv}); this._executables.set(file.fsPath, suite); return suite; } - private readonly _lastEventArrivedAt: Map< - string /* fsPath */, - number /** Date.now */ - > = new Map(); + private readonly _lastEventArrivedAt: + Map = new Map(); private _handleEverything(uri: vscode.Uri) { let suite = this._executables.get(uri.fsPath); @@ -151,40 +141,45 @@ export class C2ExecutableInfo implements vscode.Disposable { this._lastEventArrivedAt.set(uri.fsPath, Date.now()); - const x = (exists: boolean, timeout: number, delay: number): Promise => { - let lastEventArrivedAt = this._lastEventArrivedAt.get(uri.fsPath); - if (lastEventArrivedAt === undefined) { - this._adapter.log.error("assert in " + __filename); - debugger; - return Promise.resolve(); - } - if (Date.now() - lastEventArrivedAt! > timeout) { - this._lastEventArrivedAt.delete(uri.fsPath); - this._executables.delete(uri.fsPath); - this._adapter.testsEmitter.fire({ type: "started" }); - this._allTests.removeChild(suite!); - this._adapter.testsEmitter.fire({ type: "finished", suite: this._allTests }); - return Promise.resolve(); - } else if (exists) { - this._adapter.testsEmitter.fire({ type: "started" }); - return suite!.reloadChildren().then( - () => { - this._adapter.testsEmitter.fire({ type: "finished", suite: this._allTests }); + const x = + (exists: boolean, timeout: number, delay: number): Promise => { + let lastEventArrivedAt = this._lastEventArrivedAt.get(uri.fsPath); + if (lastEventArrivedAt === undefined) { + this._adapter.log.error('assert in ' + __filename); + debugger; + return Promise.resolve(); + } + if (Date.now() - lastEventArrivedAt! > timeout) { this._lastEventArrivedAt.delete(uri.fsPath); - }, - (err: any) => { - this._adapter.testsEmitter.fire({ type: "finished", suite: this._allTests }); - this._adapter.log.warn(inspect(err)); - return x(false, timeout, Math.min(delay * 2, 2000)); + this._executables.delete(uri.fsPath); + this._adapter.testsEmitter.fire({type: 'started'}); + this._allTests.removeChild(suite!); + this._adapter.testsEmitter.fire( + {type: 'finished', suite: this._allTests}); + return Promise.resolve(); + } else if (exists) { + return this._adapter.queue.then(() => { + this._adapter.testsEmitter.fire({type: 'started'}); + return suite!.reloadChildren().then( + () => { + this._adapter.testsEmitter.fire( + {type: 'finished', suite: this._allTests}); + this._lastEventArrivedAt.delete(uri.fsPath); + }, + (err: any) => { + this._adapter.testsEmitter.fire( + {type: 'finished', suite: this._allTests}); + this._adapter.log.warn(inspect(err)); + 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)); - }); - }); - }; + 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)); + }); + }); + }; // change event can arrive during debug session on osx (why?) // if (!this.isDebugging) { x(false, this._adapter.getExecWatchTimeout(), 64); @@ -218,21 +213,20 @@ export class C2ExecutableInfo implements vscode.Disposable { if (suites.length > 1) { let i = 1; for (const suite of suites) { - suite.label = String(i++) + ") " + suite.origLabel; + suite.label = String(i++) + ') ' + suite.origLabel; } } } } private _verifyIsCatch2TestExecutable(path: string): Promise { - return c2fs - .spawnAsync(path, ["--help"]) - .then(res => { - return res.stdout.indexOf("Catch v2.") != -1; - }) - .catch(e => { - this._adapter.log.error(inspect(e)); - return false; - }); + return c2fs.spawnAsync(path, ['--help']) + .then(res => { + return res.stdout.indexOf('Catch v2.') != -1; + }) + .catch(e => { + this._adapter.log.error(inspect(e)); + return false; + }); } } diff --git a/src/C2TestAdapter.ts b/src/C2TestAdapter.ts index e7e9ebca..de8d29e0 100644 --- a/src/C2TestAdapter.ts +++ b/src/C2TestAdapter.ts @@ -12,6 +12,7 @@ import {C2AllTestSuiteInfo} from './C2AllTestSuiteInfo'; import {C2ExecutableInfo} from './C2ExecutableInfo'; import {C2TestInfo} from './C2TestInfo'; import {resolveVariables} from './Helpers'; +import {QueueGraphNode} from './QueueGraph'; export class C2TestAdapter implements TestAdapter, vscode.Disposable { readonly testsEmitter = @@ -23,14 +24,15 @@ export class C2TestAdapter implements TestAdapter, vscode.Disposable { readonly variableToValue: [string, string][] = [ ['${workspaceDirectory}', this.workspaceFolder.uri.fsPath], - ['${workspaceFolder}', this.workspaceFolder.uri.fsPath], + ['${workspaceFolder}', this.workspaceFolder.uri.fsPath] ]; private allTests: C2AllTestSuiteInfo; - private readonly disposables: Array = new Array(); - private isEnabledSourceDecoration = true; + readonly queue: QueueGraphNode = new QueueGraphNode(); + private readonly disposables: Array = new Array(); + constructor( public readonly workspaceFolder: vscode.WorkspaceFolder, public readonly log: util.Log) { @@ -103,7 +105,6 @@ export class C2TestAdapter implements TestAdapter, vscode.Disposable { private isDebugging: boolean = false; private isRunning: number = 0; - async load(): Promise { try { this.cancel(); diff --git a/src/QueueGraph.ts b/src/QueueGraph.ts index 437307b2..eda50f2b 100644 --- a/src/QueueGraph.ts +++ b/src/QueueGraph.ts @@ -5,7 +5,7 @@ export class QueueGraphNode { constructor( public readonly name?: string, depends: Iterable = [], - private readonly _handleError?: ((reason: any) => void)) { + private readonly _handleError?: ((reason: any) => any)) { this._depends = [...depends]; // TODO check circular dependency } @@ -18,20 +18,20 @@ export class QueueGraphNode { return this._count; } - then( - task?: (() => T | PromiseLike)|undefined|null, - taskErrorHandler?: ((reason: any) => T2 | PromiseLike)|undefined| - null): Promise { + then( + task: (() => TResult1 | PromiseLike), + taskErrorHandler?: ((reason: any) => TResult2 | PromiseLike)| + undefined|null): Promise { this._count++; const previous = this._queue; const current = Promise.all(this._depends.map(v => v._queue)) .then(() => { - return previous; + return previous.then(task); }) - .then(task, taskErrorHandler ? taskErrorHandler : this._handleError) - .then((value: T) => { + .catch(taskErrorHandler ? taskErrorHandler : this._handleError) + .then((value: TResult1|PromiseLike) => { this._count--; return value; }); diff --git a/src/test/C2TestAdapter.test.ts b/src/test/C2TestAdapter.test.ts index f8af7b3e..7fa5cf42 100644 --- a/src/test/C2TestAdapter.test.ts +++ b/src/test/C2TestAdapter.test.ts @@ -1159,10 +1159,9 @@ describe('C2TestAdapter', function() { return testsEvents.length >= 2; }); await promisify(setTimeout)(100); - assert.deepStrictEqual(testsEvents, [ - {type: 'started'}, - {type: 'finished', suite: oldRoot} - ]); + assert.deepStrictEqual( + testsEvents, + [{type: 'started'}, {type: 'finished', suite: oldRoot}]); testsEvents.pop(); testsEvents.pop(); }); @@ -1179,10 +1178,9 @@ describe('C2TestAdapter', function() { return testsEvents.length >= 2; }); await promisify(setTimeout)(100); - assert.deepStrictEqual(testsEvents, [ - {type: 'started'}, - {type: 'finished', suite: oldRoot} - ]); + assert.deepStrictEqual( + testsEvents, + [{type: 'started'}, {type: 'finished', suite: oldRoot}]); testsEvents.pop(); testsEvents.pop(); }); diff --git a/src/test/QueueGraph.test.ts b/src/test/QueueGraph.test.ts index 6ec69c76..9b83b580 100644 --- a/src/test/QueueGraph.test.ts +++ b/src/test/QueueGraph.test.ts @@ -2,13 +2,14 @@ // 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. -import * as assert from "assert"; -import { promisify } from "util"; +import * as assert from 'assert'; +import {promisify} from 'util'; -import { QueueGraphNode } from "../QueueGraph"; +import {QueueGraphNode} from '../QueueGraph'; -describe.only("QueueGraphNode", function() { - async function waitFor(test: Mocha.Context, condition: Function, timeout: number = 1000) { +describe('QueueGraphNode', function() { + async function waitFor( + test: Mocha.Context, condition: Function, timeout: number = 1000) { const start = Date.now(); let c = await condition(); while (!c && (Date.now() - start < timeout || !test.enableTimeouts())) { @@ -18,7 +19,7 @@ describe.only("QueueGraphNode", function() { return c; } - it("promise practice 1", async function() { + it('promise practice 1', async function() { let resolve: Function; let second = false; new Promise(r => { @@ -35,7 +36,7 @@ describe.only("QueueGraphNode", function() { assert.ok(second); }); - it("promise practice 2", async function() { + it('promise practice 2', async function() { let resolve: Function; let second = false; const p = new Promise(r => { @@ -55,16 +56,16 @@ describe.only("QueueGraphNode", function() { assert.ok(second); }); - context("example 1", function() { + context('example 1', function() { /** * node1 <___ node * node2 <___/ */ - const node1 = new QueueGraphNode("node1"); - const node2 = new QueueGraphNode("node2"); - const nodeD = new QueueGraphNode("nodeD", [node1, node2]); + const node1 = new QueueGraphNode('node1'); + const node2 = new QueueGraphNode('node2'); + const nodeD = new QueueGraphNode('nodeD', [node1, node2]); - it("add:depends before", async function() { + it('add:depends before', async function() { this.slow(150); let startD: Function; let hasRunDatOnce = false; @@ -112,23 +113,17 @@ describe.only("QueueGraphNode", function() { await promisify(setTimeout)(20); - assert.ok( - await waitFor(this, async () => { - return hasRunDatOnce; - }) - ); + assert.ok(await waitFor(this, async () => { + return hasRunDatOnce; + })); assert.equal(nodeD.size, 1); - assert.ok( - await waitFor(this, async () => { - return hasRun1atOnce; - }) - ); + assert.ok(await waitFor(this, async () => { + return hasRun1atOnce; + })); assert.equal(node1.size, 2); - assert.ok( - await waitFor(this, async () => { - return hasRun2atOnce; - }) - ); + assert.ok(await waitFor(this, async () => { + return hasRun2atOnce; + })); assert.equal(node2.size, 2); let hasRunD2second = false; @@ -148,11 +143,9 @@ describe.only("QueueGraphNode", function() { start1!(); await promisify(setTimeout)(20); - assert.ok( - await waitFor(this, async () => { - return hasRun1afterStart; - }) - ); + assert.ok(await waitFor(this, async () => { + return hasRun1afterStart; + })); assert.equal(node1.size, 0); assert.equal(node2.size, 2); assert.equal(nodeD.size, 1); @@ -161,18 +154,14 @@ describe.only("QueueGraphNode", function() { start2!(); await promisify(setTimeout)(20); - assert.ok( - await waitFor(this, async () => { - return hasRun2afterStart; - }) - ); + assert.ok(await waitFor(this, async () => { + return hasRun2afterStart; + })); assert.equal(node2.size, 0); - assert.ok( - await waitFor(this, async () => { - return hasRunD2second; - }) - ); + assert.ok(await waitFor(this, async () => { + return hasRunD2second; + })); assert.equal(nodeD.size, 0); }); }); From a2ce2510d59b5e029636da889e4d3668b420420f Mon Sep 17 00:00:00 2001 From: Mate Pek Date: Sat, 27 Oct 2018 19:46:31 +0200 Subject: [PATCH 18/49] variable substitution fix --- package.json | 4 ++-- src/C2ExecutableInfo.ts | 20 +++++++++++++------- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/package.json b/package.json index 29e812af..65659baf 100644 --- a/package.json +++ b/package.json @@ -83,11 +83,11 @@ ], "default": [ { - "name": "${relName} (${relDirname}/)", + "name": "${relPath} (${relDirpath}/)", "path": "{build,Build,BUILD,out,Out,OUT}/**/*{test,Test,TEST}*", "cwd": "${absDirname}", "env": { - "C2TESTEXECUTABLEPATH": "${absName}" + "C2TESTEXECUTABLEPATH": "${absPath}" } } ], diff --git a/src/C2ExecutableInfo.ts b/src/C2ExecutableInfo.ts index 08823a14..46556590 100644 --- a/src/C2ExecutableInfo.ts +++ b/src/C2ExecutableInfo.ts @@ -99,12 +99,18 @@ export class C2ExecutableInfo implements vscode.Disposable { const base3Filename = path.basename(base2Filename, ext3Filename); const varToValue: [string, string][] = [ - ...this._adapter.variableToValue, ['${absPath}', file.path], - ['${relPath}', relPath], ['${absDirpath}', path.dirname(file.fsPath)], - ['${relDirpath}', path.dirname(relPath)], ['${filename}', filename], - ['${extFilename}', extFilename], ['${baseFilename}', baseFilename], - ['${ext2Filename}', ext2Filename], ['${base2Filename}', base2Filename], - ['${ext3Filename}', ext3Filename], ['${base3Filename}', base3Filename] + ...this._adapter.variableToValue, + ['${absPath}', file.fsPath], + ['${relPath}', relPath], + ['${absDirpath}', path.dirname(file.fsPath)], + ['${relDirpath}', path.dirname(relPath)], + ['${filename}', filename], + ['${extFilename}', extFilename], + ['${baseFilename}', baseFilename], + ['${ext2Filename}', ext2Filename], + ['${base2Filename}', base2Filename], + ['${ext3Filename}', ext3Filename], + ['${base3Filename}', base3Filename], ]; resolvedName = resolveVariables(this.name, varToValue); resolvedCwd = path.normalize(resolveVariables(this.cwd, varToValue)); @@ -114,7 +120,7 @@ export class C2ExecutableInfo implements vscode.Disposable { } const suite = this._allTests.createChildSuite( - resolvedName, file.path, {cwd: resolvedCwd, env: resolvedEnv}); + resolvedName, file.fsPath, {cwd: resolvedCwd, env: resolvedEnv}); this._executables.set(file.fsPath, suite); From 73ee1db43c4ac5f1074998b44c5f64ac31e1f479 Mon Sep 17 00:00:00 2001 From: Mate Pek Date: Sun, 28 Oct 2018 01:34:09 +0700 Subject: [PATCH 19/49] windows test fixes --- src/C2ExecutableInfo.ts | 126 +- src/FsWrapper.ts | 34 +- src/test/C2TestAdapter.test.ts | 2188 ++++++++++++++++---------------- src/test/FsWrapper.test.ts | 46 +- src/test/cpp/suite1.cpp | 2 +- src/test/cpp/suite2.cpp | 2 +- src/test/cpp/suite3.cpp | 2 +- src/test/example1.ts | 76 +- 8 files changed, 1243 insertions(+), 1233 deletions(-) diff --git a/src/C2ExecutableInfo.ts b/src/C2ExecutableInfo.ts index 33eb0e4b..d679f841 100644 --- a/src/C2ExecutableInfo.ts +++ b/src/C2ExecutableInfo.ts @@ -3,29 +3,29 @@ // public domain. The author hereby disclaims copyright to this source code. import * as path from 'path'; -import {inspect, promisify} from 'util'; +import { inspect, promisify } from 'util'; import * as vscode from 'vscode'; -import {C2AllTestSuiteInfo} from './C2AllTestSuiteInfo'; -import {C2TestAdapter} from './C2TestAdapter'; -import {C2TestSuiteInfo} from './C2TestSuiteInfo'; +import { C2AllTestSuiteInfo } from './C2AllTestSuiteInfo'; +import { C2TestAdapter } from './C2TestAdapter'; +import { C2TestSuiteInfo } from './C2TestSuiteInfo'; import * as c2fs from './FsWrapper'; -import {resolveVariables} from './Helpers'; +import { resolveVariables } from './Helpers'; export class C2ExecutableInfo implements vscode.Disposable { constructor( - private _adapter: C2TestAdapter, - private readonly _allTests: C2AllTestSuiteInfo, - public readonly name: string, public readonly pattern: string, - public readonly cwd: string, public readonly env: {[prop: string]: any}) { + private _adapter: C2TestAdapter, + private readonly _allTests: C2AllTestSuiteInfo, + public readonly name: string, public readonly pattern: string, + public readonly cwd: string, public readonly env: { [prop: string]: any }) { } private _disposables: vscode.Disposable[] = []; - private _watcher: vscode.FileSystemWatcher|undefined = undefined; + private _watcher: vscode.FileSystemWatcher | undefined = undefined; private readonly _executables: Map = - new Map(); + new Map(); dispose() { if (this._watcher) this._watcher.dispose(); @@ -38,7 +38,7 @@ export class C2ExecutableInfo implements vscode.Disposable { const isAbsolute = path.isAbsolute(this.pattern); const absPattern = isAbsolute ? path.normalize(this.pattern) : - path.resolve(wsUri.fsPath, this.pattern); + path.resolve(wsUri.fsPath, this.pattern); const absPatternAsUri = vscode.Uri.file(absPattern); const relativeToWs = path.relative(wsUri.fsPath, absPatternAsUri.fsPath); const isPartOfWs = !relativeToWs.startsWith('..'); @@ -48,20 +48,20 @@ export class C2ExecutableInfo implements vscode.Disposable { if (!isAbsolute || (isAbsolute && isPartOfWs)) { let relativePattern: vscode.RelativePattern; relativePattern = new vscode.RelativePattern( - this._adapter.workspaceFolder, relativeToWs); + this._adapter.workspaceFolder, relativeToWs); fileUris = - await vscode.workspace.findFiles(relativePattern, undefined, 1000); + await vscode.workspace.findFiles(relativePattern, undefined, 1000); try { // abs path is required this._watcher = vscode.workspace.createFileSystemWatcher( - relativePattern, false, false, false); + relativePattern, false, false, false); this._disposables.push(this._watcher); this._disposables.push( - this._watcher.onDidCreate(this._handleCreate, this)); + this._watcher.onDidCreate(this._handleCreate, this)); this._disposables.push( - this._watcher.onDidChange(this._handleChange, this)); + this._watcher.onDidChange(this._handleChange, this)); this._disposables.push( - this._watcher.onDidDelete(this._handleDelete, this)); + this._watcher.onDidDelete(this._handleDelete, this)); } catch (e) { this._adapter.log.error(inspect([e, this])); } @@ -88,7 +88,7 @@ export class C2ExecutableInfo implements vscode.Disposable { let resolvedName = this.name; let resolvedCwd = this.cwd; - let resolvedEnv: {[prop: string]: string} = this.env; + let resolvedEnv: { [prop: string]: string } = this.env; try { const relPath = path.relative(wsUri.fsPath, file.fsPath); @@ -102,7 +102,7 @@ export class C2ExecutableInfo implements vscode.Disposable { const varToValue: [string, string][] = [ ...this._adapter.variableToValue, - ['${absPath}', file.path], + ['${absPath}', file.fsPath], ['${relPath}', relPath], ['${absDirpath}', path.dirname(file.fsPath)], ['${relDirpath}', path.dirname(relPath)], @@ -122,7 +122,7 @@ export class C2ExecutableInfo implements vscode.Disposable { } const suite = this._allTests.createChildSuite( - resolvedName, file.path, {cwd: resolvedCwd, env: resolvedEnv}); + resolvedName, file.fsPath, { cwd: resolvedCwd, env: resolvedEnv }); this._executables.set(file.fsPath, suite); @@ -130,7 +130,7 @@ export class C2ExecutableInfo implements vscode.Disposable { } private readonly _lastEventArrivedAt: - Map = new Map(); + Map = new Map(); private _handleEverything(uri: vscode.Uri) { let suite = this._executables.get(uri.fsPath); @@ -141,42 +141,42 @@ export class C2ExecutableInfo implements vscode.Disposable { } const x = - (exists: boolean, startTime: number, timeout: number, - delay: number): Promise => { - let lastEventArrivedAt = this._lastEventArrivedAt.get(uri.fsPath); - if (lastEventArrivedAt === undefined) { - lastEventArrivedAt = Date.now(); - this._lastEventArrivedAt.set(uri.fsPath, lastEventArrivedAt); - this._adapter.log.error('assert in ' + __filename); - } - if ((Date.now() - lastEventArrivedAt!) > timeout) { - this._executables.delete(uri.fsPath); - this._adapter.testsEmitter.fire({type: 'started'}); - this._allTests.removeChild(suite!); - this._adapter.testsEmitter.fire( - {type: 'finished', suite: this._allTests}); - return Promise.resolve(); - } else if (exists) { - this._adapter.testsEmitter.fire({type: 'started'}); - return suite!.reloadChildren().then( - () => { - this._adapter.testsEmitter.fire( - {type: 'finished', suite: this._allTests}); - }, - (err: any) => { - this._adapter.testsEmitter.fire( - {type: 'finished', suite: this._allTests}); - this._adapter.log.warn(inspect(err)); - return x( - false, startTime, 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, startTime, timeout, Math.min(delay * 2, 2000)); + (exists: boolean, startTime: number, timeout: number, + delay: number): Promise => { + let lastEventArrivedAt = this._lastEventArrivedAt.get(uri.fsPath); + if (lastEventArrivedAt === undefined) { + lastEventArrivedAt = Date.now(); + this._lastEventArrivedAt.set(uri.fsPath, lastEventArrivedAt); + this._adapter.log.error('assert in ' + __filename); + } + if ((Date.now() - lastEventArrivedAt!) > timeout) { + this._executables.delete(uri.fsPath); + this._adapter.testsEmitter.fire({ type: 'started' }); + this._allTests.removeChild(suite!); + this._adapter.testsEmitter.fire( + { type: 'finished', suite: this._allTests }); + return Promise.resolve(); + } else if (exists) { + this._adapter.testsEmitter.fire({ type: 'started' }); + return suite!.reloadChildren().then( + () => { + this._adapter.testsEmitter.fire( + { type: 'finished', suite: this._allTests }); + }, + (err: any) => { + this._adapter.testsEmitter.fire( + { type: 'finished', suite: this._allTests }); + this._adapter.log.warn(inspect(err)); + return x( + false, startTime, 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, startTime, timeout, Math.min(delay * 2, 2000)); }); - }; + }); + }; // change event can arrive during debug session on osx (why?) // if (!this.isDebugging) { // TODO filter multiple events and dont mess with 'load' @@ -225,12 +225,12 @@ export class C2ExecutableInfo implements vscode.Disposable { private _verifyIsCatch2TestExecutable(path: string): Promise { return c2fs.spawnAsync(path, ['--help']) - .then((res) => { - return res.stdout.indexOf('Catch v2.') != -1; - }) - .catch((e) => { - this._adapter.log.error(inspect(e)); - return false; - }); + .then((res) => { + return res.stdout.indexOf('Catch v2.') != -1; + }) + .catch((e) => { + this._adapter.log.error(inspect(e)); + return false; + }); } } \ No newline at end of file diff --git a/src/FsWrapper.ts b/src/FsWrapper.ts index ab8047fe..2a203a9c 100644 --- a/src/FsWrapper.ts +++ b/src/FsWrapper.ts @@ -8,8 +8,8 @@ import * as fs from 'fs'; export type SpawnReturns = cp.SpawnSyncReturns; export function spawnAsync( - cmd: string, args?: string[], - options?: cp.SpawnOptions): Promise { + cmd: string, args?: string[], + options?: cp.SpawnOptions): Promise { return new Promise((resolve) => { const ret: SpawnReturns = { pid: 0, @@ -22,15 +22,15 @@ export function spawnAsync( }; const command = cp.spawn(cmd, args, options); ret.pid = command.pid; - command.stdout.on('data', function(data) { + command.stdout.on('data', function (data) { ret.stdout += data; ret.output[0] = ret.stdout; }); - command.on('close', function(code) { + command.on('close', function (code) { ret.status = code; resolve(ret) }); - command.on('error', function(err) { + command.on('error', function (err) { ret.error = err; resolve(ret); }); @@ -42,23 +42,23 @@ export type Stats = fs.Stats; export function statAsync(path: string): Promise { return new Promise((resolve, reject) => { fs.stat( - path, (err: NodeJS.ErrnoException|null, stats: fs.Stats|undefined) => { - if (stats) - resolve(stats); - else - reject(err); - }); + path, (err: NodeJS.ErrnoException | null, stats: fs.Stats | undefined) => { + if (stats) + resolve(stats); + else + reject(err); + }); }); } export function existsAsync(path: string): Promise { return statAsync(path).then( - () => { - return true; - }, - () => { - return false; - }); + () => { + return true; + }, + () => { + return false; + }); } export function existsSync(path: string): boolean { diff --git a/src/test/C2TestAdapter.test.ts b/src/test/C2TestAdapter.test.ts index 39f8f298..977a7ef7 100644 --- a/src/test/C2TestAdapter.test.ts +++ b/src/test/C2TestAdapter.test.ts @@ -11,13 +11,13 @@ import * as fse from 'fs-extra'; import * as assert from 'assert'; import * as vscode from 'vscode'; import * as sinon from 'sinon'; -import {TestEvent, TestLoadFinishedEvent, TestLoadStartedEvent, TestRunFinishedEvent, TestRunStartedEvent, TestSuiteEvent, TestSuiteInfo, TestInfo, TestAdapter} from 'vscode-test-adapter-api'; -import {Log} from 'vscode-test-adapter-util'; -import {inspect, promisify} from 'util'; +import { TestEvent, TestLoadFinishedEvent, TestLoadStartedEvent, TestRunFinishedEvent, TestRunStartedEvent, TestSuiteEvent, TestSuiteInfo, TestInfo, TestAdapter } from 'vscode-test-adapter-api'; +import { Log } from 'vscode-test-adapter-util'; +import { inspect, promisify } from 'util'; -import {C2TestAdapter} from '../C2TestAdapter'; -import {example1} from './example1'; -import {ChildProcessStub, FileSystemWatcherStub} from './Helpers'; +import { C2TestAdapter } from '../C2TestAdapter'; +import { example1 } from './example1'; +import { ChildProcessStub, FileSystemWatcherStub } from './Helpers'; import * as Mocha from 'mocha'; assert.notEqual(vscode.workspace.workspaceFolders, undefined); @@ -26,27 +26,27 @@ assert.equal(vscode.workspace.workspaceFolders!.length, 1); const workspaceFolderUri = vscode.workspace.workspaceFolders![0].uri; const workspaceFolder = - vscode.workspace.getWorkspaceFolder(workspaceFolderUri)!; + vscode.workspace.getWorkspaceFolder(workspaceFolderUri)!; const logger = - new Log('Catch2TestAdapter', workspaceFolder, 'Catch2TestAdapter'); + new Log('Catch2TestAdapter', workspaceFolder, 'Catch2TestAdapter'); -const dotVscodePath = path.join(workspaceFolderUri.path, '.vscode'); +const dotVscodePath = path.join(workspaceFolderUri.fsPath, '.vscode'); const sinonSandbox = sinon.createSandbox(); /// -describe('C2TestAdapter', function() { +describe('C2TestAdapter', function () { this.enableTimeouts(false); // TODO - let testsEvents: (TestLoadStartedEvent|TestLoadFinishedEvent)[] = []; - let testStatesEvents: (TestRunStartedEvent|TestRunFinishedEvent| - TestSuiteEvent|TestEvent)[] = []; + let testsEvents: (TestLoadStartedEvent | TestLoadFinishedEvent)[] = []; + let testStatesEvents: (TestRunStartedEvent | TestRunFinishedEvent | + TestSuiteEvent | TestEvent)[] = []; function getConfig() { return vscode.workspace.getConfiguration( - 'catch2TestExplorer', workspaceFolderUri) + 'catch2TestExplorer', workspaceFolderUri) }; async function updateConfig(key: string, value: any) { @@ -56,9 +56,9 @@ describe('C2TestAdapter', function() { while (testsEvents.length < count--) testsEvents.pop(); } - let adapter: C2TestAdapter|undefined; - let testsEventsConnection: vscode.Disposable|undefined; - let testStatesEventsConnection: vscode.Disposable|undefined; + let adapter: C2TestAdapter | undefined; + let testsEventsConnection: vscode.Disposable | undefined; + let testStatesEventsConnection: vscode.Disposable | undefined; let spawnStub: sinon.SinonStub; let vsfsWatchStub: sinon.SinonStub; @@ -67,14 +67,14 @@ describe('C2TestAdapter', function() { function resetConfig(): Thenable { const packageJson = fse.readJSONSync( - path.join(workspaceFolderUri.path, '../..', 'package.json')); - const properties: {[prop: string]: any}[] = - packageJson['contributes']['configuration']['properties']; + path.join(workspaceFolderUri.fsPath, '../..', 'package.json')); + const properties: { [prop: string]: any }[] = + packageJson['contributes']['configuration']['properties']; let t: Thenable = Promise.resolve(); Object.keys(properties).forEach(key => { assert.ok(key.startsWith('catch2TestExplorer.')); const k = key.replace('catch2TestExplorer.', '') - t = t.then(function() { + t = t.then(function () { return getConfig().update(k, undefined); }); }); @@ -83,18 +83,18 @@ describe('C2TestAdapter', function() { function testStatesEvI(o: any) { const i = testStatesEvents.findIndex( - (v: TestRunStartedEvent|TestRunFinishedEvent|TestSuiteEvent| - TestEvent) => { - if (o.type == v.type) - if (o.type == 'suite' || o.type == 'test') - return o.state === (v).state && - o[o.type] === (v)[v.type]; - return deepStrictEqual(o, v); - }); + (v: TestRunStartedEvent | TestRunFinishedEvent | TestSuiteEvent | + TestEvent) => { + if (o.type == v.type) + if (o.type == 'suite' || o.type == 'test') + return o.state === (v).state && + o[o.type] === (v)[v.type]; + return deepStrictEqual(o, v); + }); assert.notEqual( - i, -1, - 'testStatesEvI failed to find: ' + inspect(o) + '\n\nin\n\n' + - inspect(testStatesEvents)); + i, -1, + 'testStatesEvI failed to find: ' + inspect(o) + '\n\nin\n\n' + + inspect(testStatesEvents)); assert.deepStrictEqual(testStatesEvents[i], o); return i; }; @@ -103,34 +103,34 @@ describe('C2TestAdapter', function() { adapter = new C2TestAdapter(workspaceFolder, logger); testsEventsConnection = - adapter.tests((e: TestLoadStartedEvent|TestLoadFinishedEvent) => { - testsEvents.push(e); - }); + adapter.tests((e: TestLoadStartedEvent | TestLoadFinishedEvent) => { + testsEvents.push(e); + }); testStatesEvents = []; testStatesEventsConnection = adapter.testStates( - (e: TestRunStartedEvent|TestRunFinishedEvent|TestSuiteEvent| - TestEvent) => { - testStatesEvents.push(e); - }); + (e: TestRunStartedEvent | TestRunFinishedEvent | TestSuiteEvent | + TestEvent) => { + testStatesEvents.push(e); + }); return adapter!; } async function waitFor( - test: Mocha.Context, condition: Function, - timeout: number = 1000): Promise { + test: Mocha.Context, condition: Function, + timeout: number = 1000): Promise { const start = Date.now(); let c = await condition(); while (!(c = await condition()) && - ((Date.now() - start) < timeout || !test.enableTimeouts())) + ((Date.now() - start) < timeout || !test.enableTimeouts())) await promisify(setTimeout)(10); assert.ok(c); } async function doAndWaitForReloadEvent( - test: Mocha.Context, action: Function, - timeout: number = 1000): Promise { + test: Mocha.Context, action: Function, + timeout: number = 1000): Promise { const origCount = testsEvents.length; await action(); await waitFor(test, () => { @@ -151,18 +151,18 @@ describe('C2TestAdapter', function() { if (check) { for (let i = 0; i < testsEvents.length; i++) { assert.deepStrictEqual( - {type: 'started'}, testsEvents[i], - inspect({index: i, testsEvents: testsEvents})); + { type: 'started' }, testsEvents[i], + inspect({ index: i, testsEvents: testsEvents })); i++; assert.ok( - i < testsEvents.length, - inspect({index: i, testsEvents: testsEvents})); + i < testsEvents.length, + inspect({ index: i, testsEvents: testsEvents })); assert.equal( - testsEvents[i].type, 'finished', - inspect({index: i, testsEvents: testsEvents})); + testsEvents[i].type, 'finished', + inspect({ index: i, testsEvents: testsEvents })); assert.ok( - (testsEvents[i]).suite, - inspect({index: i, testsEvents: testsEvents})); + (testsEvents[i]).suite, + inspect({ index: i, testsEvents: testsEvents })); } } testsEvents = []; @@ -179,17 +179,17 @@ describe('C2TestAdapter', function() { vsFindFilesStub.callThrough(); } - before(function() { + before(function () { fse.removeSync(dotVscodePath); adapter = undefined; spawnStub = sinonSandbox.stub(child_process, 'spawn').named('spawnStub'); vsfsWatchStub = - sinonSandbox.stub(vscode.workspace, 'createFileSystemWatcher') - .named('vscode.createFileSystemWatcher'); + sinonSandbox.stub(vscode.workspace, 'createFileSystemWatcher') + .named('vscode.createFileSystemWatcher'); c2fsStatStub = sinonSandbox.stub(fs, 'stat').named('fsStat'); vsFindFilesStub = sinonSandbox.stub(vscode.workspace, 'findFiles') - .named('vsFindFilesStub'); + .named('vsFindFilesStub'); stubsResetToMyDefault(); @@ -197,52 +197,52 @@ describe('C2TestAdapter', function() { return resetConfig(); }); - after(function() { + after(function () { disposeAdapterAndSubscribers(); sinonSandbox.restore(); }); - describe('detect config change', function() { + describe('detect config change', function () { this.slow(200); let adapter: C2TestAdapter; - before(function() { + before(function () { adapter = createAdapterAndSubscribe(); assert.deepStrictEqual(testsEvents, []); }) - after(function() { + after(function () { disposeAdapterAndSubscribers(); return resetConfig(); }) - it('defaultEnv', function() { + it('defaultEnv', function () { return doAndWaitForReloadEvent(this, () => { - return updateConfig('defaultEnv', {'APPLE': 'apple'}); + return updateConfig('defaultEnv', { 'APPLE': 'apple' }); }); }) - it('defaultCwd', function() { + it('defaultCwd', function () { return doAndWaitForReloadEvent(this, () => { return updateConfig('defaultCwd', 'apple/peach'); }); }) - it('enableSourceDecoration', function() { - return updateConfig('enableSourceDecoration', false).then(function() { + it('enableSourceDecoration', function () { + return updateConfig('enableSourceDecoration', false).then(function () { assert.ok(!adapter.getIsEnabledSourceDecoration()); }); }) - it('defaultRngSeed', function() { - return updateConfig('defaultRngSeed', 987).then(function() { + it('defaultRngSeed', function () { + return updateConfig('defaultRngSeed', 987).then(function () { assert.equal(adapter.getRngSeed(), 987); }); }) }) - it('load with empty config', async function() { + it('load with empty config', async function () { this.slow(500); const adapter = createAdapterAndSubscribe(); await adapter.load(); @@ -255,24 +255,24 @@ describe('C2TestAdapter', function() { disposeAdapterAndSubscribers(); }) - context('example1', function() { + context('example1', function () { const watchers: Map = new Map(); function handleCreateWatcherCb( - p: vscode.RelativePattern, ignoreCreateEvents: boolean, - ignoreChangeEvents: boolean, ignoreDeleteEvents: boolean) { + p: vscode.RelativePattern, ignoreCreateEvents: boolean, + ignoreChangeEvents: boolean, ignoreDeleteEvents: boolean) { const pp = path.join(p.base, p.pattern); const e = new FileSystemWatcherStub( - vscode.Uri.file(pp), ignoreCreateEvents, ignoreChangeEvents, - ignoreDeleteEvents); + vscode.Uri.file(pp), ignoreCreateEvents, ignoreChangeEvents, + ignoreDeleteEvents); watchers.set(pp, e); return e; } function handleStatExistsFile( - path: string, - cb: (err: NodeJS.ErrnoException|null, stats: fs.Stats|undefined) => - void) { + path: string, + cb: (err: NodeJS.ErrnoException | null, stats: fs.Stats | undefined) => + void) { cb(null, { isFile() { return true; @@ -284,9 +284,9 @@ describe('C2TestAdapter', function() { } function handleStatNotExists( - path: string, - cb: (err: NodeJS.ErrnoException|null|any, stats: fs.Stats|undefined) => - void) { + path: string, + cb: (err: NodeJS.ErrnoException | null | any, stats: fs.Stats | undefined) => + void) { cb({ code: 'ENOENT', errno: -2, @@ -294,22 +294,22 @@ describe('C2TestAdapter', function() { path: path, syscall: 'stat' }, - undefined); + undefined); } function matchRelativePattern(p: string) { return sinon.match((actual: vscode.RelativePattern) => { const required = new vscode.RelativePattern( - workspaceFolder, path.relative(workspaceFolderUri.path, p)); + workspaceFolder, path.relative(workspaceFolderUri.fsPath, p)); return required.base == actual.base && - required.pattern == actual.pattern; + required.pattern == actual.pattern; }); } - before(function() { + before(function () { for (let suite of example1.outputs) { for (let scenario of suite[1]) { - spawnStub.withArgs(suite[0], scenario[0]).callsFake(function() { + spawnStub.withArgs(suite[0], scenario[0]).callsFake(function () { return new ChildProcessStub(scenario[1]); }); } @@ -317,12 +317,12 @@ describe('C2TestAdapter', function() { c2fsStatStub.withArgs(suite[0]).callsFake(handleStatExistsFile); vsfsWatchStub.withArgs(matchRelativePattern(suite[0])) - .callsFake(handleCreateWatcherCb); + .callsFake(handleCreateWatcherCb); } const dirContent: Map = new Map(); for (let p of example1.outputs) { - const parent = path.dirname(p[0]); + const parent = vscode.Uri.file(path.dirname(p[0])).fsPath; let children: vscode.Uri[] = []; if (dirContent.has(parent)) children = dirContent.get(parent)!; @@ -333,44 +333,44 @@ describe('C2TestAdapter', function() { } dirContent.forEach((v: vscode.Uri[], k: string) => { - assert.equal(workspaceFolderUri.path, k); + assert.equal(workspaceFolderUri.fsPath, k); vsFindFilesStub.withArgs(matchRelativePattern(k)).returns(v); for (const p of v) { - vsFindFilesStub.withArgs(matchRelativePattern(p.path)).returns([p]); + vsFindFilesStub.withArgs(matchRelativePattern(p.fsPath)).returns([p]); } }); }) - after(function() { + after(function () { stubsResetToMyDefault(); }) - afterEach(function() { + afterEach(function () { watchers.clear(); }) - describe('load', function() { + describe('load', function () { const uniqueIdC = new Set(); let adapter: TestAdapter; let root: TestSuiteInfo; - let suite1: TestSuiteInfo|any; - let s1t1: TestInfo|any; - let s1t2: TestInfo|any; - let suite2: TestSuiteInfo|any; - let s2t1: TestInfo|any; - let s2t2: TestInfo|any; - let s2t3: TestInfo|any; - - before(function(){ + let suite1: TestSuiteInfo | any; + let s1t1: TestInfo | any; + let s1t2: TestInfo | any; + let suite2: TestSuiteInfo | any; + let s2t1: TestInfo | any; + let s2t2: TestInfo | any; + let s2t3: TestInfo | any; + + before(function () { return updateConfig("workerMaxNumber", 4); }) - after(function(){ + after(function () { return updateConfig("workerMaxNumber", undefined); }) - beforeEach(async function() { + beforeEach(async function () { adapter = createAdapterAndSubscribe(); await adapter.load(); @@ -393,28 +393,28 @@ describe('C2TestAdapter', function() { assert.deepStrictEqual(testStatesEvents, []); }); - afterEach(function() { + afterEach(function () { uniqueIdC.clear(); disposeAdapterAndSubscribers(); }); - context('executables="execPath1"', function() { - before(function() { + context('executables="execPath1"', function () { + before(function () { return updateConfig('executables', 'execPath1'); }); - after(function() { + after(function () { return updateConfig('executables', undefined); }); - beforeEach(async function() { + beforeEach(async function () { assert.deepStrictEqual( - getConfig().get('executables'), 'execPath1'); + getConfig().get('executables'), 'execPath1'); assert.equal(root.children.length, 1); assert.equal(root.children[0].type, 'suite'); suite1 = root.children[0]; example1.suite1.assert( - 'execPath1', ['s1t1', 's1t2'], suite1, uniqueIdC); + 'execPath1', ['s1t1', 's1t2'], suite1, uniqueIdC); assert.equal(suite1.children.length, 2); assert.equal(suite1.children[0].type, 'test'); s1t1 = suite1.children[0]; @@ -422,22 +422,22 @@ describe('C2TestAdapter', function() { s1t2 = suite1.children[1]; }); - it('should run with not existing test id', async function() { + it('should run with not existing test id', async function () { await adapter.run(['not existing id']); assert.deepStrictEqual(testStatesEvents, [ - {type: 'started', tests: ['not existing id']}, - {type: 'finished'}, + { type: 'started', tests: ['not existing id'] }, + { type: 'finished' }, ]); }); - it('should run s1t1 with success', async function() { + it('should run s1t1 with success', async function () { assert.equal(getConfig().get('executables'), 'execPath1'); await adapter.run([s1t1.id]); const expected = [ - {type: 'started', tests: [s1t1.id]}, - {type: 'suite', state: 'running', suite: suite1}, - {type: 'test', state: 'running', test: s1t1}, + { type: 'started', tests: [s1t1.id] }, + { type: 'suite', state: 'running', suite: suite1 }, + { type: 'test', state: 'running', test: s1t1 }, { type: 'test', state: 'passed', @@ -445,8 +445,8 @@ describe('C2TestAdapter', function() { decorations: undefined, message: 'Duration: 0.000112 second(s)\n' }, - {type: 'suite', state: 'completed', suite: suite1}, - {type: 'finished'}, + { type: 'suite', state: 'completed', suite: suite1 }, + { type: 'finished' }, ]; assert.deepStrictEqual(testStatesEvents, expected); @@ -454,12 +454,12 @@ describe('C2TestAdapter', function() { assert.deepStrictEqual(testStatesEvents, [...expected, ...expected]); }); - it('should run suite1', async function() { + it('should run suite1', async function () { await adapter.run([suite1.id]); const expected = [ - {type: 'started', tests: [suite1.id]}, - {type: 'suite', state: 'running', suite: suite1}, - {type: 'test', state: 'running', test: s1t1}, + { type: 'started', tests: [suite1.id] }, + { type: 'suite', state: 'running', suite: suite1 }, + { type: 'test', state: 'running', test: s1t1 }, { type: 'test', state: 'passed', @@ -467,17 +467,17 @@ describe('C2TestAdapter', function() { decorations: undefined, message: 'Duration: 0.000132 second(s)\n' }, - {type: 'test', state: 'running', test: s1t2}, + { type: 'test', state: 'running', test: s1t2 }, { type: 'test', state: 'failed', test: s1t2, - decorations: [{line: 14, message: 'Expanded: false'}], + decorations: [{ line: 14, message: 'Expanded: false' }], message: - 'Duration: 0.000204 second(s)\n>>> s1t2(line: 13) REQUIRE (line: 15) \n Original:\n std::false_type::value\n Expanded:\n false\n<<<\n' + 'Duration: 0.000204 second(s)\n>>> s1t2(line: 13) REQUIRE (line: 15) \n Original:\n std::false_type::value\n Expanded:\n false\n<<<\n' }, - {type: 'suite', state: 'completed', suite: suite1}, - {type: 'finished'}, + { type: 'suite', state: 'completed', suite: suite1 }, + { type: 'finished' }, ]; assert.deepStrictEqual(testStatesEvents, expected); @@ -485,12 +485,12 @@ describe('C2TestAdapter', function() { assert.deepStrictEqual(testStatesEvents, [...expected, ...expected]); }); - it('should run all', async function() { + it('should run all', async function () { await adapter.run([root.id]); const expected = [ - {type: 'started', tests: [root.id]}, - {type: 'suite', state: 'running', suite: suite1}, - {type: 'test', state: 'running', test: s1t1}, + { type: 'started', tests: [root.id] }, + { type: 'suite', state: 'running', suite: suite1 }, + { type: 'test', state: 'running', test: s1t1 }, { type: 'test', state: 'passed', @@ -498,17 +498,17 @@ describe('C2TestAdapter', function() { decorations: undefined, message: 'Duration: 0.000132 second(s)\n' }, - {type: 'test', state: 'running', test: s1t2}, + { type: 'test', state: 'running', test: s1t2 }, { type: 'test', state: 'failed', test: s1t2, - decorations: [{line: 14, message: 'Expanded: false'}], + decorations: [{ line: 14, message: 'Expanded: false' }], message: - 'Duration: 0.000204 second(s)\n>>> s1t2(line: 13) REQUIRE (line: 15) \n Original:\n std::false_type::value\n Expanded:\n false\n<<<\n' + 'Duration: 0.000204 second(s)\n>>> s1t2(line: 13) REQUIRE (line: 15) \n Original:\n std::false_type::value\n Expanded:\n false\n<<<\n' }, - {type: 'suite', state: 'completed', suite: suite1}, - {type: 'finished'}, + { type: 'suite', state: 'completed', suite: suite1 }, + { type: 'finished' }, ]; assert.deepStrictEqual(testStatesEvents, expected); @@ -516,7 +516,7 @@ describe('C2TestAdapter', function() { assert.deepStrictEqual(testStatesEvents, [...expected, ...expected]); }); - it('cancels without any problem', async function() { + it('cancels without any problem', async function () { adapter.cancel(); assert.deepStrictEqual(testsEvents, []); assert.deepStrictEqual(testStatesEvents, []); @@ -527,9 +527,9 @@ describe('C2TestAdapter', function() { await adapter.run([s1t1.id]); const expected = [ - {type: 'started', tests: [s1t1.id]}, - {type: 'suite', state: 'running', suite: suite1}, - {type: 'test', state: 'running', test: s1t1}, + { type: 'started', tests: [s1t1.id] }, + { type: 'suite', state: 'running', suite: suite1 }, + { type: 'test', state: 'running', test: s1t1 }, { type: 'test', state: 'passed', @@ -537,8 +537,8 @@ describe('C2TestAdapter', function() { decorations: undefined, message: 'Duration: 0.000112 second(s)\n' }, - {type: 'suite', state: 'completed', suite: suite1}, - {type: 'finished'}, + { type: 'suite', state: 'completed', suite: suite1 }, + { type: 'finished' }, ]; assert.deepStrictEqual(testStatesEvents, expected); @@ -547,43 +547,43 @@ describe('C2TestAdapter', function() { assert.deepStrictEqual(testStatesEvents, expected); }); - context('with config: defaultRngSeed=2', function() { - before(function() { + context('with config: defaultRngSeed=2', function () { + before(function () { return updateConfig('defaultRngSeed', 2); }) - after(function() { + after(function () { return updateConfig('defaultRngSeed', undefined); }) - it('should run s1t1 with success', async function() { + it('should run s1t1 with success', async function () { await adapter.run([s1t1.id]); const expected = [ - {type: 'started', tests: [s1t1.id]}, - {type: 'suite', state: 'running', suite: suite1}, - {type: 'test', state: 'running', test: s1t1}, + { type: 'started', tests: [s1t1.id] }, + { type: 'suite', state: 'running', suite: suite1 }, + { type: 'test', state: 'running', test: s1t1 }, { type: 'test', state: 'passed', test: s1t1, decorations: undefined, message: - 'Randomness seeded to: 2\nDuration: 0.000327 second(s)\n' + 'Randomness seeded to: 2\nDuration: 0.000327 second(s)\n' }, - {type: 'suite', state: 'completed', suite: suite1}, - {type: 'finished'}, + { type: 'suite', state: 'completed', suite: suite1 }, + { type: 'finished' }, ]; assert.deepStrictEqual(testStatesEvents, expected); await adapter.run([s1t1.id]); assert.deepStrictEqual( - testStatesEvents, [...expected, ...expected]); + testStatesEvents, [...expected, ...expected]); }) }) }) - context('suite1 and suite2 are used', function() { - beforeEach(function() { + context('suite1 and suite2 are used', function () { + beforeEach(function () { assert.equal(root.children.length, 2); assert.equal(root.children[0].type, 'suite'); @@ -614,790 +614,790 @@ describe('C2TestAdapter', function() { const testsForAdapterWithSuite1AndSuite2: Mocha.Test[] = [ new Mocha.Test( - 'test variables are fine, suite1 and suite1 are loaded', - function() { - assert.equal(root.children.length, 2); - assert.ok(suite1 != undefined); - assert.ok(s1t1 != undefined); - assert.ok(s1t2 != undefined); - assert.ok(suite2 != undefined); - assert.ok(s2t1 != undefined); - assert.ok(s2t2 != undefined); - assert.ok(s2t3 != undefined); - }), + 'test variables are fine, suite1 and suite1 are loaded', + function () { + assert.equal(root.children.length, 2); + assert.ok(suite1 != undefined); + assert.ok(s1t1 != undefined); + assert.ok(s1t2 != undefined); + assert.ok(suite2 != undefined); + assert.ok(s2t1 != undefined); + assert.ok(s2t2 != undefined); + assert.ok(s2t3 != undefined); + }), new Mocha.Test( - 'should run all', - async function() { - assert.equal(root.children.length, 2); - await adapter.run([root.id]); - - const running = {type: 'started', tests: [root.id]}; - - const s1running = { - type: 'suite', - state: 'running', - suite: suite1 - }; - const s1finished = { - type: 'suite', - state: 'completed', - suite: suite1 - }; - assert.ok(testStatesEvI(running) < testStatesEvI(s1running)); - assert.ok(testStatesEvI(s1running) < testStatesEvI(s1finished)); - - const s2running = { - type: 'suite', - state: 'running', - suite: suite2 - }; - const s2finished = { - type: 'suite', - state: 'completed', - suite: suite2 - }; - assert.ok(testStatesEvI(running) < testStatesEvI(s1running)); - assert.ok(testStatesEvI(s2running) < testStatesEvI(s2finished)); - - const s1t1running = { - type: 'test', - state: 'running', - test: s1t1 - }; - assert.ok( - testStatesEvI(s1running) < testStatesEvI(s1t1running)); + 'should run all', + async function () { + assert.equal(root.children.length, 2); + await adapter.run([root.id]); + + const running = { type: 'started', tests: [root.id] }; + + const s1running = { + type: 'suite', + state: 'running', + suite: suite1 + }; + const s1finished = { + type: 'suite', + state: 'completed', + suite: suite1 + }; + assert.ok(testStatesEvI(running) < testStatesEvI(s1running)); + assert.ok(testStatesEvI(s1running) < testStatesEvI(s1finished)); + + const s2running = { + type: 'suite', + state: 'running', + suite: suite2 + }; + const s2finished = { + type: 'suite', + state: 'completed', + suite: suite2 + }; + assert.ok(testStatesEvI(running) < testStatesEvI(s1running)); + assert.ok(testStatesEvI(s2running) < testStatesEvI(s2finished)); + + const s1t1running = { + type: 'test', + state: 'running', + test: s1t1 + }; + assert.ok( + testStatesEvI(s1running) < testStatesEvI(s1t1running)); + + const s1t1finished = { + type: 'test', + state: 'passed', + test: s1t1, + decorations: undefined, + message: 'Duration: 0.000132 second(s)\n' + }; + assert.ok( + testStatesEvI(s1t1running) < testStatesEvI(s1t1finished)); + assert.ok( + testStatesEvI(s1t1finished) < testStatesEvI(s1finished)); + + const s1t2running = { + type: 'test', + state: 'running', + test: s1t2 + }; + assert.ok( + testStatesEvI(s1running) < testStatesEvI(s1t2running)); + + const s1t2finished = { + type: 'test', + state: 'failed', + test: s1t2, + decorations: [{ line: 14, message: 'Expanded: false' }], + message: + 'Duration: 0.000204 second(s)\n>>> s1t2(line: 13) REQUIRE (line: 15) \n Original:\n std::false_type::value\n Expanded:\n false\n<<<\n' + }; + assert.ok( + testStatesEvI(s1t2running) < testStatesEvI(s1t2finished)); + assert.ok( + testStatesEvI(s1t2finished) < testStatesEvI(s1finished)); + + const s2t1running = { + type: 'test', + state: 'running', + test: s2t1 + }; + assert.ok( + testStatesEvI(s2running) < testStatesEvI(s2t1running)); - const s1t1finished = { + const s2t1finished = { + type: 'test', + state: 'passed', + test: s2t1, + decorations: undefined, + message: 'Duration: 0.00037 second(s)\n' + }; + assert.ok( + testStatesEvI(s2t1running) < testStatesEvI(s2t1finished)); + assert.ok( + testStatesEvI(s2t1finished) < testStatesEvI(s2finished)); + + const s2t2running = { + type: 'test', + state: 'running', + test: s2t2 + }; + assert.ok( + testStatesEvI(s2running) < testStatesEvI(s2t2running)); + + const s2t2finished = { + type: 'test', + state: 'skipped', + test: s2t2 + }; + assert.ok( + testStatesEvI(s2t2running) < testStatesEvI(s2t2finished)); + assert.ok( + testStatesEvI(s2t2finished) < testStatesEvI(s2finished)); + + const s2t3running = { + type: 'test', + state: 'running', + test: s2t3 + }; + assert.ok( + testStatesEvI(s2running) < testStatesEvI(s2t3running)); + + const s2t3finished = { + type: 'test', + state: 'failed', + test: s2t3, + decorations: [{ line: 20, message: 'Expanded: false' }], + message: + 'Duration: 0.000178 second(s)\n>>> s2t3(line: 19) REQUIRE (line: 21) \n Original:\n std::false_type::value\n Expanded:\n false\n<<<\n' + }; + assert.ok( + testStatesEvI(s2t3running) < testStatesEvI(s2t3finished)); + assert.ok( + testStatesEvI(s2t3finished) < testStatesEvI(s2finished)); + + const finished = { type: 'finished' }; + assert.ok(testStatesEvI(s1finished) < testStatesEvI(finished)); + assert.ok(testStatesEvI(s2finished) < testStatesEvI(finished)); + + assert.equal( + testStatesEvents.length, 16, inspect(testStatesEvents)); + }), + new Mocha.Test( + 'should run with not existing test id', + async function () { + await adapter.run(['not existing id']); + + assert.deepStrictEqual(testStatesEvents, [ + { type: 'started', tests: ['not existing id'] }, + { type: 'finished' }, + ]); + }), + new Mocha.Test( + 'should run s1t1', + async function () { + await adapter.run([s1t1.id]); + const expected = [ + { type: 'started', tests: [s1t1.id] }, + { type: 'suite', state: 'running', suite: suite1 }, + { type: 'test', state: 'running', test: s1t1 }, + { type: 'test', state: 'passed', test: s1t1, decorations: undefined, - message: 'Duration: 0.000132 second(s)\n' - }; - assert.ok( - testStatesEvI(s1t1running) < testStatesEvI(s1t1finished)); - assert.ok( - testStatesEvI(s1t1finished) < testStatesEvI(s1finished)); - - const s1t2running = { + message: 'Duration: 0.000112 second(s)\n' + }, + { type: 'suite', state: 'completed', suite: suite1 }, + { type: 'finished' }, + ]; + assert.deepStrictEqual(testStatesEvents, expected); + + await adapter.run([s1t1.id]); + assert.deepStrictEqual( + testStatesEvents, [...expected, ...expected]); + }), + new Mocha.Test( + 'should run skipped s2t2', + async function () { + await adapter.run([s2t2.id]); + const expected = [ + { type: 'started', tests: [s2t2.id] }, + { type: 'suite', state: 'running', suite: suite2 }, + { type: 'test', state: 'running', test: s2t2 }, + { type: 'test', - state: 'running', - test: s1t2 - }; - assert.ok( - testStatesEvI(s1running) < testStatesEvI(s1t2running)); - - const s1t2finished = { + state: 'passed', + test: s2t2, + decorations: undefined, + message: 'Duration: 0.001294 second(s)\n' + }, + { type: 'suite', state: 'completed', suite: suite2 }, + { type: 'finished' }, + ]; + assert.deepStrictEqual(testStatesEvents, expected); + + await adapter.run([s2t2.id]); + assert.deepStrictEqual( + testStatesEvents, [...expected, ...expected]); + }), + new Mocha.Test( + 'should run failing test s2t3', + async function () { + await adapter.run([s2t3.id]); + const expected = [ + { type: 'started', tests: [s2t3.id] }, + { type: 'suite', state: 'running', suite: suite2 }, + { type: 'test', state: 'running', test: s2t3 }, + { type: 'test', state: 'failed', - test: s1t2, - decorations: [{line: 14, message: 'Expanded: false'}], + test: s2t3, + decorations: [{ line: 20, message: 'Expanded: false' }], message: - 'Duration: 0.000204 second(s)\n>>> s1t2(line: 13) REQUIRE (line: 15) \n Original:\n std::false_type::value\n Expanded:\n false\n<<<\n' - }; - assert.ok( - testStatesEvI(s1t2running) < testStatesEvI(s1t2finished)); - assert.ok( - testStatesEvI(s1t2finished) < testStatesEvI(s1finished)); - - const s2t1running = { + 'Duration: 0.000596 second(s)\n>>> s2t3(line: 19) REQUIRE (line: 21) \n Original:\n std::false_type::value\n Expanded:\n false\n<<<\n' + }, + { type: 'suite', state: 'completed', suite: suite2 }, + { type: 'finished' }, + ]; + assert.deepStrictEqual(testStatesEvents, expected); + + await adapter.run([s2t3.id]); + assert.deepStrictEqual( + testStatesEvents, [...expected, ...expected]); + }), + new Mocha.Test( + 'should run failing test s2t3 with chunks', + async function () { + const withArgs = spawnStub.withArgs( + example1.suite2.execPath, example1.suite2.t3.outputs[0][0]); + withArgs.onCall(withArgs.callCount) + .returns( + new ChildProcessStub(example1.suite2.t3.outputs[0][1])); + + await adapter.run([s2t3.id]); + const expected = [ + { type: 'started', tests: [s2t3.id] }, + { type: 'suite', state: 'running', suite: suite2 }, + { type: 'test', state: 'running', test: s2t3 }, + { type: 'test', - state: 'running', - test: s2t1 - }; - assert.ok( - testStatesEvI(s2running) < testStatesEvI(s2t1running)); - - const s2t1finished = { + state: 'failed', + test: s2t3, + decorations: [{ line: 20, message: 'Expanded: false' }], + message: + 'Duration: 0.000596 second(s)\n>>> s2t3(line: 19) REQUIRE (line: 21) \n Original:\n std::false_type::value\n Expanded:\n false\n<<<\n' + }, + { type: 'suite', state: 'completed', suite: suite2 }, + { type: 'finished' }, + ]; + assert.deepStrictEqual(testStatesEvents, expected); + + await adapter.run([s2t3.id]); + assert.deepStrictEqual( + testStatesEvents, [...expected, ...expected]); + }), + new Mocha.Test( + 'should run suite1', + async function () { + await adapter.run([suite1.id]); + const expected = [ + { type: 'started', tests: [suite1.id] }, + { type: 'suite', state: 'running', suite: suite1 }, + { type: 'test', state: 'running', test: s1t1 }, + { type: 'test', state: 'passed', - test: s2t1, + test: s1t1, decorations: undefined, - message: 'Duration: 0.00037 second(s)\n' - }; - assert.ok( - testStatesEvI(s2t1running) < testStatesEvI(s2t1finished)); - assert.ok( - testStatesEvI(s2t1finished) < testStatesEvI(s2finished)); - - const s2t2running = { + message: 'Duration: 0.000132 second(s)\n' + }, + { type: 'test', state: 'running', test: s1t2 }, + { type: 'test', - state: 'running', - test: s2t2 - }; - assert.ok( - testStatesEvI(s2running) < testStatesEvI(s2t2running)); + state: 'failed', + test: s1t2, + decorations: [{ line: 14, message: 'Expanded: false' }], + message: + 'Duration: 0.000204 second(s)\n>>> s1t2(line: 13) REQUIRE (line: 15) \n Original:\n std::false_type::value\n Expanded:\n false\n<<<\n' + }, + { type: 'suite', state: 'completed', suite: suite1 }, + { type: 'finished' }, + ]; + assert.deepStrictEqual(testStatesEvents, expected); - const s2t2finished = { - type: 'test', - state: 'skipped', - test: s2t2 - }; - assert.ok( - testStatesEvI(s2t2running) < testStatesEvI(s2t2finished)); - assert.ok( - testStatesEvI(s2t2finished) < testStatesEvI(s2finished)); - - const s2t3running = { - type: 'test', - state: 'running', - test: s2t3 - }; - assert.ok( - testStatesEvI(s2running) < testStatesEvI(s2t3running)); + await adapter.run([suite1.id]); + assert.deepStrictEqual( + testStatesEvents, [...expected, ...expected]); + }), + new Mocha.Test( + 'should run with wrong xml', + async function () { + const m = + example1.suite1.t1.outputs[0][1].match(']+>'); + assert.notEqual(m, undefined); + assert.notEqual(m!.input, undefined); + assert.notEqual(m!.index, undefined); + const part = m!.input!.substr(0, m!.index! + m![0].length); + const withArgs = spawnStub.withArgs( + example1.suite1.execPath, example1.suite1.t1.outputs[0][0]); + withArgs.onCall(withArgs.callCount) + .returns(new ChildProcessStub(part)); - const s2t3finished = { + await adapter.run([s1t1.id]); + + const expected = [ + { type: 'started', tests: [s1t1.id] }, + { type: 'suite', state: 'running', suite: suite1 }, + { type: 'test', state: 'running', test: s1t1 }, + { type: 'test', state: 'failed', - test: s2t3, - decorations: [{line: 20, message: 'Expanded: false'}], - message: - 'Duration: 0.000178 second(s)\n>>> s2t3(line: 19) REQUIRE (line: 21) \n Original:\n std::false_type::value\n Expanded:\n false\n<<<\n' - }; - assert.ok( - testStatesEvI(s2t3running) < testStatesEvI(s2t3finished)); - assert.ok( - testStatesEvI(s2t3finished) < testStatesEvI(s2finished)); - - const finished = {type: 'finished'}; - assert.ok(testStatesEvI(s1finished) < testStatesEvI(finished)); - assert.ok(testStatesEvI(s2finished) < testStatesEvI(finished)); - - assert.equal( - testStatesEvents.length, 16, inspect(testStatesEvents)); - }), - new Mocha.Test( - 'should run with not existing test id', - async function() { - await adapter.run(['not existing id']); - - assert.deepStrictEqual(testStatesEvents, [ - {type: 'started', tests: ['not existing id']}, - {type: 'finished'}, - ]); - }), - new Mocha.Test( - 'should run s1t1', - async function() { - await adapter.run([s1t1.id]); - const expected = [ - {type: 'started', tests: [s1t1.id]}, - {type: 'suite', state: 'running', suite: suite1}, - {type: 'test', state: 'running', test: s1t1}, - { - type: 'test', - state: 'passed', - test: s1t1, - decorations: undefined, - message: 'Duration: 0.000112 second(s)\n' - }, - {type: 'suite', state: 'completed', suite: suite1}, - {type: 'finished'}, - ]; - assert.deepStrictEqual(testStatesEvents, expected); - - await adapter.run([s1t1.id]); - assert.deepStrictEqual( - testStatesEvents, [...expected, ...expected]); - }), - new Mocha.Test( - 'should run skipped s2t2', - async function() { - await adapter.run([s2t2.id]); - const expected = [ - {type: 'started', tests: [s2t2.id]}, - {type: 'suite', state: 'running', suite: suite2}, - {type: 'test', state: 'running', test: s2t2}, - { - type: 'test', - state: 'passed', - test: s2t2, - decorations: undefined, - message: 'Duration: 0.001294 second(s)\n' - }, - {type: 'suite', state: 'completed', suite: suite2}, - {type: 'finished'}, - ]; - assert.deepStrictEqual(testStatesEvents, expected); - - await adapter.run([s2t2.id]); - assert.deepStrictEqual( - testStatesEvents, [...expected, ...expected]); - }), + test: s1t1, + message: 'Unexpected test error. (Is Catch2 crashed?)\n' + }, + { type: 'suite', state: 'completed', suite: suite1 }, + { type: 'finished' }, + ]; + assert.deepStrictEqual(testStatesEvents, expected); + + // this tests the sinon stubs too + await adapter.run([s1t1.id]); + assert.deepStrictEqual(testStatesEvents, [ + ...expected, + { type: 'started', tests: [s1t1.id] }, + { type: 'suite', state: 'running', suite: suite1 }, + { type: 'test', state: 'running', test: s1t1 }, + { + type: 'test', + state: 'passed', + test: s1t1, + decorations: undefined, + message: 'Duration: 0.000112 second(s)\n' + }, + { type: 'suite', state: 'completed', suite: suite1 }, + { type: 'finished' }, + ]); + }), new Mocha.Test( - 'should run failing test s2t3', - async function() { - await adapter.run([s2t3.id]); - const expected = [ - {type: 'started', tests: [s2t3.id]}, - {type: 'suite', state: 'running', suite: suite2}, - {type: 'test', state: 'running', test: s2t3}, - { - type: 'test', - state: 'failed', - test: s2t3, - decorations: [{line: 20, message: 'Expanded: false'}], - message: - 'Duration: 0.000596 second(s)\n>>> s2t3(line: 19) REQUIRE (line: 21) \n Original:\n std::false_type::value\n Expanded:\n false\n<<<\n' - }, - {type: 'suite', state: 'completed', suite: suite2}, - {type: 'finished'}, - ]; - assert.deepStrictEqual(testStatesEvents, expected); - - await adapter.run([s2t3.id]); - assert.deepStrictEqual( - testStatesEvents, [...expected, ...expected]); - }), + 'should cancel without error', + function () { + adapter.cancel(); + }), new Mocha.Test( - 'should run failing test s2t3 with chunks', - async function() { + 'cancel', + async function () { + let spyKill1: sinon.SinonSpy; + let spyKill2: sinon.SinonSpy; + { + const spawnEvent = + new ChildProcessStub(example1.suite1.outputs[2][1]); + spyKill1 = sinon.spy(spawnEvent, 'kill'); const withArgs = spawnStub.withArgs( - example1.suite2.execPath, example1.suite2.t3.outputs[0][0]); - withArgs.onCall(withArgs.callCount) - .returns( - new ChildProcessStub(example1.suite2.t3.outputs[0][1])); - - await adapter.run([s2t3.id]); - const expected = [ - {type: 'started', tests: [s2t3.id]}, - {type: 'suite', state: 'running', suite: suite2}, - {type: 'test', state: 'running', test: s2t3}, - { - type: 'test', - state: 'failed', - test: s2t3, - decorations: [{line: 20, message: 'Expanded: false'}], - message: - 'Duration: 0.000596 second(s)\n>>> s2t3(line: 19) REQUIRE (line: 21) \n Original:\n std::false_type::value\n Expanded:\n false\n<<<\n' - }, - {type: 'suite', state: 'completed', suite: suite2}, - {type: 'finished'}, - ]; - assert.deepStrictEqual(testStatesEvents, expected); - - await adapter.run([s2t3.id]); - assert.deepStrictEqual( - testStatesEvents, [...expected, ...expected]); - }), - new Mocha.Test( - 'should run suite1', - async function() { - await adapter.run([suite1.id]); - const expected = [ - {type: 'started', tests: [suite1.id]}, - {type: 'suite', state: 'running', suite: suite1}, - {type: 'test', state: 'running', test: s1t1}, - { - type: 'test', - state: 'passed', - test: s1t1, - decorations: undefined, - message: 'Duration: 0.000132 second(s)\n' - }, - {type: 'test', state: 'running', test: s1t2}, - { - type: 'test', - state: 'failed', - test: s1t2, - decorations: [{line: 14, message: 'Expanded: false'}], - message: - 'Duration: 0.000204 second(s)\n>>> s1t2(line: 13) REQUIRE (line: 15) \n Original:\n std::false_type::value\n Expanded:\n false\n<<<\n' - }, - {type: 'suite', state: 'completed', suite: suite1}, - {type: 'finished'}, - ]; - assert.deepStrictEqual(testStatesEvents, expected); - - await adapter.run([suite1.id]); - assert.deepStrictEqual( - testStatesEvents, [...expected, ...expected]); - }), - new Mocha.Test( - 'should run with wrong xml', - async function() { - const m = - example1.suite1.t1.outputs[0][1].match(']+>'); - assert.notEqual(m, undefined); - assert.notEqual(m!.input, undefined); - assert.notEqual(m!.index, undefined); - const part = m!.input!.substr(0, m!.index! + m![0].length); + example1.suite1.execPath, example1.suite1.outputs[2][0]); + withArgs.onCall(withArgs.callCount).returns(spawnEvent); + } + { + const spawnEvent = + new ChildProcessStub(example1.suite2.outputs[2][1]); + spyKill2 = sinon.spy(spawnEvent, 'kill'); const withArgs = spawnStub.withArgs( - example1.suite1.execPath, example1.suite1.t1.outputs[0][0]); - withArgs.onCall(withArgs.callCount) - .returns(new ChildProcessStub(part)); - - await adapter.run([s1t1.id]); - - const expected = [ - {type: 'started', tests: [s1t1.id]}, - {type: 'suite', state: 'running', suite: suite1}, - {type: 'test', state: 'running', test: s1t1}, - { - type: 'test', - state: 'failed', - test: s1t1, - message: 'Unexpected test error. (Is Catch2 crashed?)\n' - }, - {type: 'suite', state: 'completed', suite: suite1}, - {type: 'finished'}, - ]; - assert.deepStrictEqual(testStatesEvents, expected); - - // this tests the sinon stubs too - await adapter.run([s1t1.id]); - assert.deepStrictEqual(testStatesEvents, [ - ...expected, - {type: 'started', tests: [s1t1.id]}, - {type: 'suite', state: 'running', suite: suite1}, - {type: 'test', state: 'running', test: s1t1}, - { - type: 'test', - state: 'passed', - test: s1t1, - decorations: undefined, - message: 'Duration: 0.000112 second(s)\n' - }, - {type: 'suite', state: 'completed', suite: suite1}, - {type: 'finished'}, - ]); - }), - new Mocha.Test( - 'should cancel without error', - function() { - adapter.cancel(); - }), - new Mocha.Test( - 'cancel', - async function() { - let spyKill1: sinon.SinonSpy; - let spyKill2: sinon.SinonSpy; - { - const spawnEvent = - new ChildProcessStub(example1.suite1.outputs[2][1]); - spyKill1 = sinon.spy(spawnEvent, 'kill'); - const withArgs = spawnStub.withArgs( - example1.suite1.execPath, example1.suite1.outputs[2][0]); - withArgs.onCall(withArgs.callCount).returns(spawnEvent); - } - { - const spawnEvent = - new ChildProcessStub(example1.suite2.outputs[2][1]); - spyKill2 = sinon.spy(spawnEvent, 'kill'); - const withArgs = spawnStub.withArgs( - example1.suite2.execPath, example1.suite2.outputs[2][0]); - withArgs.onCall(withArgs.callCount).returns(spawnEvent); - } - const run = adapter.run([root.id]); - adapter.cancel(); - await run; - - assert.equal(spyKill1.callCount, 1); - assert.equal(spyKill2.callCount, 1); - - const running = {type: 'started', tests: [root.id]}; - - const s1running = { - type: 'suite', - state: 'running', - suite: suite1 - }; - const s1finished = { - type: 'suite', - state: 'completed', - suite: suite1 - }; - assert.ok(testStatesEvI(running) < testStatesEvI(s1running)); - assert.ok(testStatesEvI(s1running) < testStatesEvI(s1finished)); - - const s2running = { - type: 'suite', - state: 'running', - suite: suite2 - }; - const s2finished = { - type: 'suite', - state: 'completed', - suite: suite2 - }; - assert.ok(testStatesEvI(running) < testStatesEvI(s1running)); - assert.ok(testStatesEvI(s2running) < testStatesEvI(s2finished)); - - const s2t2running = { - type: 'test', - state: 'running', - test: s2t2 - }; - assert.ok( - testStatesEvI(s2running) < testStatesEvI(s2t2running)); + example1.suite2.execPath, example1.suite2.outputs[2][0]); + withArgs.onCall(withArgs.callCount).returns(spawnEvent); + } + const run = adapter.run([root.id]); + adapter.cancel(); + await run; - const s2t2finished = { - type: 'test', - state: 'skipped', - test: s2t2 - }; - assert.ok( - testStatesEvI(s2t2running) < testStatesEvI(s2t2finished)); - assert.ok( - testStatesEvI(s2t2finished) < testStatesEvI(s2finished)); - - const finished = {type: 'finished'}; - assert.ok(testStatesEvI(s1finished) < testStatesEvI(finished)); - assert.ok(testStatesEvI(s2finished) < testStatesEvI(finished)); - - assert.equal( - testStatesEvents.length, 8, inspect(testStatesEvents)); - }), + assert.equal(spyKill1.callCount, 1); + assert.equal(spyKill2.callCount, 1); + + const running = { type: 'started', tests: [root.id] }; + + const s1running = { + type: 'suite', + state: 'running', + suite: suite1 + }; + const s1finished = { + type: 'suite', + state: 'completed', + suite: suite1 + }; + assert.ok(testStatesEvI(running) < testStatesEvI(s1running)); + assert.ok(testStatesEvI(s1running) < testStatesEvI(s1finished)); + + const s2running = { + type: 'suite', + state: 'running', + suite: suite2 + }; + const s2finished = { + type: 'suite', + state: 'completed', + suite: suite2 + }; + assert.ok(testStatesEvI(running) < testStatesEvI(s1running)); + assert.ok(testStatesEvI(s2running) < testStatesEvI(s2finished)); + + const s2t2running = { + type: 'test', + state: 'running', + test: s2t2 + }; + assert.ok( + testStatesEvI(s2running) < testStatesEvI(s2t2running)); + + const s2t2finished = { + type: 'test', + state: 'skipped', + test: s2t2 + }; + assert.ok( + testStatesEvI(s2t2running) < testStatesEvI(s2t2finished)); + assert.ok( + testStatesEvI(s2t2finished) < testStatesEvI(s2finished)); + + const finished = { type: 'finished' }; + assert.ok(testStatesEvI(s1finished) < testStatesEvI(finished)); + assert.ok(testStatesEvI(s2finished) < testStatesEvI(finished)); + + assert.equal( + testStatesEvents.length, 8, inspect(testStatesEvents)); + }), new Mocha.Test( - 'cancel after run finished', - function() { - let spyKill1: sinon.SinonSpy; - let spyKill2: sinon.SinonSpy; - { - const spawnEvent = - new ChildProcessStub(example1.suite1.outputs[2][1]); - spyKill1 = sinon.spy(spawnEvent, 'kill'); - const withArgs = spawnStub.withArgs( - example1.suite1.execPath, example1.suite1.outputs[2][0]); - withArgs.onCall(withArgs.callCount).returns(spawnEvent); - } - { - const spawnEvent = - new ChildProcessStub(example1.suite2.outputs[2][1]); - spyKill2 = sinon.spy(spawnEvent, 'kill'); - const withArgs = spawnStub.withArgs( - example1.suite2.execPath, example1.suite2.outputs[2][0]); - withArgs.onCall(withArgs.callCount).returns(spawnEvent); - } - const run = adapter.run([root.id]); - return run.then(function() { - adapter.cancel(); - assert.equal(spyKill1.callCount, 0); - assert.equal(spyKill2.callCount, 0); - }); - }), + 'cancel after run finished', + function () { + let spyKill1: sinon.SinonSpy; + let spyKill2: sinon.SinonSpy; + { + const spawnEvent = + new ChildProcessStub(example1.suite1.outputs[2][1]); + spyKill1 = sinon.spy(spawnEvent, 'kill'); + const withArgs = spawnStub.withArgs( + example1.suite1.execPath, example1.suite1.outputs[2][0]); + withArgs.onCall(withArgs.callCount).returns(spawnEvent); + } + { + const spawnEvent = + new ChildProcessStub(example1.suite2.outputs[2][1]); + spyKill2 = sinon.spy(spawnEvent, 'kill'); + const withArgs = spawnStub.withArgs( + example1.suite2.execPath, example1.suite2.outputs[2][0]); + withArgs.onCall(withArgs.callCount).returns(spawnEvent); + } + const run = adapter.run([root.id]); + return run.then(function () { + adapter.cancel(); + assert.equal(spyKill1.callCount, 0); + assert.equal(spyKill2.callCount, 0); + }); + }), ]; - context('executables=["execPath1", "./execPath2"]', function() { - before(function() { + context('executables=["execPath1", "./execPath2"]', function () { + before(function () { return updateConfig('executables', ['execPath1', './execPath2']); }); - after(function() { + after(function () { return updateConfig('executables', undefined); }); let suite1Watcher: FileSystemWatcherStub; - beforeEach(async function() { + beforeEach(async function () { assert.equal(watchers.size, 2); assert.ok(watchers.has(example1.suite1.execPath)); suite1Watcher = watchers.get(example1.suite1.execPath)!; example1.suite1.assert( - 'execPath1', ['s1t1', 's1t2'], suite1, uniqueIdC); + 'execPath1', ['s1t1', 's1t2'], suite1, uniqueIdC); example1.suite2.assert( - './execPath2', ['s2t1', 's2t2 [.]', 's2t3'], suite2, uniqueIdC); + './execPath2', ['s2t1', 's2t2 [.]', 's2t3'], suite2, uniqueIdC); }) for (let t of testsForAdapterWithSuite1AndSuite2) this.addTest( - t.clone()); + t.clone()); it('reload because of fswatcher event: touch(changed)', - async function() { - this.slow(200); - const newRoot = await doAndWaitForReloadEvent(this, async () => { - suite1Watcher.sendChange(); - }); - assert.deepStrictEqual(newRoot, root); - assert.deepStrictEqual(testsEvents, [ - {type: 'started'}, - {type: 'finished', suite: root}, - ]); - }) + async function () { + this.slow(200); + const newRoot = await doAndWaitForReloadEvent(this, async () => { + suite1Watcher.sendChange(); + }); + assert.deepStrictEqual(newRoot, root); + assert.deepStrictEqual(testsEvents, [ + { type: 'started' }, + { type: 'finished', suite: root }, + ]); + }) it('reload because of fswatcher event: double touch(changed)', - async function() { - this.slow(200); - const oldRoot = root; - suite1Watcher.sendChange(); - suite1Watcher.sendChange(); - await waitFor(this, async () => { - return testsEvents.length >= 4; - }); - assert.ok( - deepStrictEqual( - testsEvents, - [ - {type: 'started'}, - {type: 'finished', suite: oldRoot}, - {type: 'started'}, - {type: 'finished', suite: oldRoot}, - ]) || - deepStrictEqual(testsEvents, [ - {type: 'started'}, - {type: 'started'}, - {type: 'finished', suite: oldRoot}, - {type: 'finished', suite: oldRoot}, - ])); - testsEvents.pop(); - testsEvents.pop(); - testsEvents.pop(); - testsEvents.pop(); - }) + async function () { + this.slow(200); + const oldRoot = root; + suite1Watcher.sendChange(); + suite1Watcher.sendChange(); + await waitFor(this, async () => { + return testsEvents.length >= 4; + }); + assert.ok( + deepStrictEqual( + testsEvents, + [ + { type: 'started' }, + { type: 'finished', suite: oldRoot }, + { type: 'started' }, + { type: 'finished', suite: oldRoot }, + ]) || + deepStrictEqual(testsEvents, [ + { type: 'started' }, + { type: 'started' }, + { type: 'finished', suite: oldRoot }, + { type: 'finished', suite: oldRoot }, + ])); + testsEvents.pop(); + testsEvents.pop(); + testsEvents.pop(); + testsEvents.pop(); + }) it('reload because of fswatcher event: double touch(changed) with delay', - async function() { - this.slow(200); - const oldRoot = root; - suite1Watcher.sendChange(); - setTimeout(() => { - suite1Watcher.sendChange(); - }, 20); - await waitFor(this, async () => { - return testsEvents.length >= 4; - }); - assert.ok( - deepStrictEqual( - testsEvents, - [ - {type: 'started'}, - {type: 'finished', suite: oldRoot}, - {type: 'started'}, - {type: 'finished', suite: oldRoot}, - ]) || - deepStrictEqual(testsEvents, [ - {type: 'started'}, - {type: 'started'}, - {type: 'finished', suite: oldRoot}, - {type: 'finished', suite: oldRoot}, - ])); - testsEvents.pop(); - testsEvents.pop(); - testsEvents.pop(); - testsEvents.pop(); - }) + async function () { + this.slow(200); + const oldRoot = root; + suite1Watcher.sendChange(); + setTimeout(() => { + suite1Watcher.sendChange(); + }, 20); + await waitFor(this, async () => { + return testsEvents.length >= 4; + }); + assert.ok( + deepStrictEqual( + testsEvents, + [ + { type: 'started' }, + { type: 'finished', suite: oldRoot }, + { type: 'started' }, + { type: 'finished', suite: oldRoot }, + ]) || + deepStrictEqual(testsEvents, [ + { type: 'started' }, + { type: 'started' }, + { type: 'finished', suite: oldRoot }, + { type: 'finished', suite: oldRoot }, + ])); + testsEvents.pop(); + testsEvents.pop(); + testsEvents.pop(); + testsEvents.pop(); + }) it('reload because of fswatcher event: touch(delete,create)', - async function() { - this.slow(200); - const newRoot = await doAndWaitForReloadEvent(this, async () => { - suite1Watcher.sendDelete(); - suite1Watcher.sendCreate(); - }); - assert.deepStrictEqual(newRoot, root); - assert.deepStrictEqual(testsEvents, [ - {type: 'started'}, - {type: 'finished', suite: root}, - ]); - }) + async function () { + this.slow(200); + const newRoot = await doAndWaitForReloadEvent(this, async () => { + suite1Watcher.sendDelete(); + suite1Watcher.sendCreate(); + }); + assert.deepStrictEqual(newRoot, root); + assert.deepStrictEqual(testsEvents, [ + { type: 'started' }, + { type: 'finished', suite: root }, + ]); + }) it('reload because of fswatcher event: double touch(delete,create)', - async function() { - this.slow(200); - const oldRoot = root; - suite1Watcher.sendChange(); - suite1Watcher.sendChange(); - await waitFor(this, async () => { - return testsEvents.length >= 4; - }); - assert.ok( - deepStrictEqual( - testsEvents, - [ - {type: 'started'}, - {type: 'finished', suite: oldRoot}, - {type: 'started'}, - {type: 'finished', suite: oldRoot}, - ]) || - deepStrictEqual(testsEvents, [ - {type: 'started'}, - {type: 'started'}, - {type: 'finished', suite: oldRoot}, - {type: 'finished', suite: oldRoot}, - ])); - testsEvents.pop(); - testsEvents.pop(); - testsEvents.pop(); - testsEvents.pop(); - }) + async function () { + this.slow(200); + const oldRoot = root; + suite1Watcher.sendChange(); + suite1Watcher.sendChange(); + await waitFor(this, async () => { + return testsEvents.length >= 4; + }); + assert.ok( + deepStrictEqual( + testsEvents, + [ + { type: 'started' }, + { type: 'finished', suite: oldRoot }, + { type: 'started' }, + { type: 'finished', suite: oldRoot }, + ]) || + deepStrictEqual(testsEvents, [ + { type: 'started' }, + { type: 'started' }, + { type: 'finished', suite: oldRoot }, + { type: 'finished', suite: oldRoot }, + ])); + testsEvents.pop(); + testsEvents.pop(); + testsEvents.pop(); + testsEvents.pop(); + }) it('reload because of fswatcher event: double touch(delete,create) with delay', - async function() { - this.slow(200); - const oldRoot = root; - suite1Watcher.sendChange(); - setTimeout(() => { - suite1Watcher.sendChange(); - }, 20); - await waitFor(this, async () => { - return testsEvents.length >= 4; - }); - assert.ok( - deepStrictEqual( - testsEvents, - [ - {type: 'started'}, - {type: 'finished', suite: oldRoot}, - {type: 'started'}, - {type: 'finished', suite: oldRoot}, - ]) || - deepStrictEqual(testsEvents, [ - {type: 'started'}, - {type: 'started'}, - {type: 'finished', suite: oldRoot}, - {type: 'finished', suite: oldRoot}, - ])); - testsEvents.pop(); - testsEvents.pop(); - testsEvents.pop(); - testsEvents.pop(); - }) + async function () { + this.slow(200); + const oldRoot = root; + suite1Watcher.sendChange(); + setTimeout(() => { + suite1Watcher.sendChange(); + }, 20); + await waitFor(this, async () => { + return testsEvents.length >= 4; + }); + assert.ok( + deepStrictEqual( + testsEvents, + [ + { type: 'started' }, + { type: 'finished', suite: oldRoot }, + { type: 'started' }, + { type: 'finished', suite: oldRoot }, + ]) || + deepStrictEqual(testsEvents, [ + { type: 'started' }, + { type: 'started' }, + { type: 'finished', suite: oldRoot }, + { type: 'finished', suite: oldRoot }, + ])); + testsEvents.pop(); + testsEvents.pop(); + testsEvents.pop(); + testsEvents.pop(); + }) it('reload because of fswatcher event: test added', - async function(this: Mocha.Context) { - this.slow(200); - const testListOutput = example1.suite1.outputs[1][1].split('\n'); - assert.equal(testListOutput.length, 10); - testListOutput.splice( - 1, 0, ' s1t0', ' suite1.cpp:6', ' tag1'); - const withArgs = spawnStub.withArgs( - example1.suite1.execPath, example1.suite1.outputs[1][0]); - withArgs.onCall(withArgs.callCount) - .returns(new ChildProcessStub(testListOutput.join('\n'))); - - const oldRootChildren = [...root.children]; - const oldSuite1Children = [...suite1.children]; - const oldSuite2Children = [...suite2.children]; - - const newRoot = await doAndWaitForReloadEvent(this, async () => { - suite1Watcher.sendDelete(); - suite1Watcher.sendCreate(); - }); - - assert.equal(newRoot, root); - assert.equal(root.children.length, oldRootChildren.length); - for (let i = 0; i < oldRootChildren.length; i++) { - assert.equal(root.children[i], oldRootChildren[i]); - } - - assert.equal( - suite1.children.length, oldSuite1Children.length + 1); - for (let i = 0; i < suite1.children.length; i++) { - assert.equal(suite1.children[i + 1], oldSuite1Children[i]); - } - const newTest = suite1.children[0]; - assert.ok(!uniqueIdC.has(newTest.id)); - assert.equal(newTest.label, 's1t0'); - - assert.equal(suite2.children.length, oldSuite2Children.length); - for (let i = 0; i < suite2.children.length; i++) { - assert.equal(suite2.children[i], oldSuite2Children[i]); - } - }) + async function (this: Mocha.Context) { + this.slow(200); + const testListOutput = example1.suite1.outputs[1][1].split('\n'); + assert.equal(testListOutput.length, 10); + testListOutput.splice( + 1, 0, ' s1t0', ' suite1.cpp:6', ' tag1'); + const withArgs = spawnStub.withArgs( + example1.suite1.execPath, example1.suite1.outputs[1][0]); + withArgs.onCall(withArgs.callCount) + .returns(new ChildProcessStub(testListOutput.join('\n'))); + + const oldRootChildren = [...root.children]; + const oldSuite1Children = [...suite1.children]; + const oldSuite2Children = [...suite2.children]; + + const newRoot = await doAndWaitForReloadEvent(this, async () => { + suite1Watcher.sendDelete(); + suite1Watcher.sendCreate(); + }); + + assert.equal(newRoot, root); + assert.equal(root.children.length, oldRootChildren.length); + for (let i = 0; i < oldRootChildren.length; i++) { + assert.equal(root.children[i], oldRootChildren[i]); + } + + assert.equal( + suite1.children.length, oldSuite1Children.length + 1); + for (let i = 0; i < suite1.children.length; i++) { + assert.equal(suite1.children[i + 1], oldSuite1Children[i]); + } + const newTest = suite1.children[0]; + assert.ok(!uniqueIdC.has(newTest.id)); + assert.equal(newTest.label, 's1t0'); + + assert.equal(suite2.children.length, oldSuite2Children.length); + for (let i = 0; i < suite2.children.length; i++) { + assert.equal(suite2.children[i], oldSuite2Children[i]); + } + }) it('reload because of fswatcher event: test deleted', - async function(this: Mocha.Context) { - this.slow(200); - const testListOutput = example1.suite1.outputs[1][1].split('\n'); - assert.equal(testListOutput.length, 10); - testListOutput.splice(1, 3); - const withArgs = spawnStub.withArgs( - example1.suite1.execPath, example1.suite1.outputs[1][0]); - withArgs.onCall(withArgs.callCount) - .returns(new ChildProcessStub(testListOutput.join('\n'))); - - const oldRootChildren = [...root.children]; - const oldSuite1Children = [...suite1.children]; - const oldSuite2Children = [...suite2.children]; - - const newRoot = await doAndWaitForReloadEvent(this, async () => { - suite1Watcher.sendDelete(); - suite1Watcher.sendCreate(); - }); - - assert.equal(newRoot, root); - assert.equal(root.children.length, oldRootChildren.length); - for (let i = 0; i < oldRootChildren.length; i++) { - assert.equal(root.children[i], oldRootChildren[i]); - } - - assert.equal( - suite1.children.length + 1, oldSuite1Children.length); - for (let i = 0; i < suite1.children.length; i++) { - assert.equal(suite1.children[i], oldSuite1Children[i + 1]); - } - - assert.equal(suite2.children.length, oldSuite2Children.length); - for (let i = 0; i < suite2.children.length; i++) { - assert.equal(suite2.children[i], oldSuite2Children[i]); - } - }) + async function (this: Mocha.Context) { + this.slow(200); + const testListOutput = example1.suite1.outputs[1][1].split('\n'); + assert.equal(testListOutput.length, 10); + testListOutput.splice(1, 3); + const withArgs = spawnStub.withArgs( + example1.suite1.execPath, example1.suite1.outputs[1][0]); + withArgs.onCall(withArgs.callCount) + .returns(new ChildProcessStub(testListOutput.join('\n'))); + + const oldRootChildren = [...root.children]; + const oldSuite1Children = [...suite1.children]; + const oldSuite2Children = [...suite2.children]; + + const newRoot = await doAndWaitForReloadEvent(this, async () => { + suite1Watcher.sendDelete(); + suite1Watcher.sendCreate(); + }); + + assert.equal(newRoot, root); + assert.equal(root.children.length, oldRootChildren.length); + for (let i = 0; i < oldRootChildren.length; i++) { + assert.equal(root.children[i], oldRootChildren[i]); + } + + assert.equal( + suite1.children.length + 1, oldSuite1Children.length); + for (let i = 0; i < suite1.children.length; i++) { + assert.equal(suite1.children[i], oldSuite1Children[i + 1]); + } + + assert.equal(suite2.children.length, oldSuite2Children.length); + for (let i = 0; i < suite2.children.length; i++) { + assert.equal(suite2.children[i], oldSuite2Children[i]); + } + }) }) - context('executables=[{}] and env={...}', function() { - before(async function() { + context('executables=[{}] and env={...}', function () { + before(async function () { await updateConfig( - 'executables', [{ - name: '${relDirpath}/${filename} (${absDirpath})', - path: 'execPath{1,2}', - cwd: '${workspaceFolder}/cwd', - env: { - 'C2LOCALTESTENV': 'c2localtestenv', - 'C2OVERRIDETESTENV': 'c2overridetestenv-l', - } - }]); + 'executables', [{ + name: '${relDirpath}/${filename} (${absDirpath})', + path: 'execPath{1,2}', + cwd: '${workspaceFolder}/cwd', + env: { + 'C2LOCALTESTENV': 'c2localtestenv', + 'C2OVERRIDETESTENV': 'c2overridetestenv-l', + } + }]); vsfsWatchStub - .withArgs(matchRelativePattern( - path.join(workspaceFolderUri.path, 'execPath{1,2}'))) - .callsFake(handleCreateWatcherCb); + .withArgs(matchRelativePattern( + path.join(workspaceFolderUri.fsPath, 'execPath{1,2}'))) + .callsFake(handleCreateWatcherCb); vsFindFilesStub - .withArgs(matchRelativePattern( - path.join(workspaceFolderUri.path, 'execPath{1,2}'))) - .returns([ - vscode.Uri.file(example1.suite1.execPath), - vscode.Uri.file(example1.suite2.execPath), - ]); + .withArgs(matchRelativePattern( + path.join(workspaceFolderUri.fsPath, 'execPath{1,2}'))) + .returns([ + vscode.Uri.file(example1.suite1.execPath), + vscode.Uri.file(example1.suite2.execPath), + ]); await updateConfig('defaultEnv', { 'C2GLOBALTESTENV': 'c2globaltestenv', 'C2OVERRIDETESTENV': 'c2overridetestenv-g', }); }); - after(async function() { + after(async function () { await updateConfig('executables', undefined); await updateConfig('defaultEnv', undefined); }); - beforeEach(async function() { + beforeEach(async function () { example1.suite1.assert( - './execPath1 (' + workspaceFolderUri.path + ')', - ['s1t1', 's1t2'], suite1, uniqueIdC); + './execPath1 (' + workspaceFolderUri.fsPath + ')', + ['s1t1', 's1t2'], suite1, uniqueIdC); example1.suite2.assert( - './execPath2 (' + workspaceFolderUri.path + ')', - ['s2t1', 's2t2 [.]', 's2t3'], suite2, uniqueIdC); + './execPath2 (' + workspaceFolderUri.fsPath + ')', + ['s2t1', 's2t2 [.]', 's2t3'], suite2, uniqueIdC); }) for (let t of testsForAdapterWithSuite1AndSuite2) this.addTest( - t.clone()); + t.clone()); - it('should get execution options', async function() { + it('should get execution options', async function () { { const withArgs = spawnStub.withArgs( - example1.suite1.execPath, example1.suite1.outputs[2][0]); + example1.suite1.execPath, example1.suite1.outputs[2][0]); withArgs.onCall(withArgs.callCount) - .callsFake((p: string, args: string[], ops: any) => { - assert.equal( - ops.cwd, path.join(workspaceFolderUri.path, 'cwd')); - assert.equal(ops.env.C2LOCALTESTENV, 'c2localtestenv'); - assert.ok(!ops.env.hasOwnProperty('C2GLOBALTESTENV')); - assert.equal( - ops.env.C2OVERRIDETESTENV, 'c2overridetestenv-l'); - return new ChildProcessStub(example1.suite1.outputs[2][1]); - }); + .callsFake((p: string, args: string[], ops: any) => { + assert.equal( + ops.cwd, path.join(workspaceFolderUri.fsPath, 'cwd')); + assert.equal(ops.env.C2LOCALTESTENV, 'c2localtestenv'); + assert.ok(!ops.env.hasOwnProperty('C2GLOBALTESTENV')); + assert.equal( + ops.env.C2OVERRIDETESTENV, 'c2overridetestenv-l'); + return new ChildProcessStub(example1.suite1.outputs[2][1]); + }); const cc = withArgs.callCount; await adapter.run([suite1.id]); @@ -1405,17 +1405,17 @@ describe('C2TestAdapter', function() { } { const withArgs = spawnStub.withArgs( - example1.suite2.execPath, example1.suite2.outputs[2][0]); + example1.suite2.execPath, example1.suite2.outputs[2][0]); withArgs.onCall(withArgs.callCount) - .callsFake((p: string, args: string[], ops: any) => { - assert.equal( - ops.cwd, path.join(workspaceFolderUri.path, 'cwd')); - assert.equal(ops.env.C2LOCALTESTENV, 'c2localtestenv'); - assert.ok(!ops.env.hasOwnProperty('C2GLOBALTESTENV')); - assert.equal( - ops.env.C2OVERRIDETESTENV, 'c2overridetestenv-l'); - return new ChildProcessStub(example1.suite2.outputs[2][1]); - }); + .callsFake((p: string, args: string[], ops: any) => { + assert.equal( + ops.cwd, path.join(workspaceFolderUri.fsPath, 'cwd')); + assert.equal(ops.env.C2LOCALTESTENV, 'c2localtestenv'); + assert.ok(!ops.env.hasOwnProperty('C2GLOBALTESTENV')); + assert.equal( + ops.env.C2OVERRIDETESTENV, 'c2overridetestenv-l'); + return new ChildProcessStub(example1.suite2.outputs[2][1]); + }); const cc = withArgs.callCount; await adapter.run([suite2.id]); assert.equal(withArgs.callCount, cc + 1) @@ -1425,67 +1425,67 @@ describe('C2TestAdapter', function() { }) context( - 'executables=["execPath1", "execPath2", "execPath3"]', - async function() { - before(function() { - return updateConfig( - 'executables', ['execPath1', 'execPath2', 'execPath3']); - }); + 'executables=["execPath1", "execPath2", "execPath3"]', + async function () { + before(function () { + return updateConfig( + 'executables', ['execPath1', 'execPath2', 'execPath3']); + }); - after(function() { - return updateConfig('executables', undefined); - }); + after(function () { + return updateConfig('executables', undefined); + }); - it('run suite3 one-by-one', async function() { - this.slow(300); - assert.equal(root.children.length, 3); - assert.equal(root.children[0].type, 'suite'); - const suite3 = root.children[2]; - assert.equal(suite3.children.length, 33); + it('run suite3 one-by-one', async function () { + this.slow(300); + assert.equal(root.children.length, 3); + assert.equal(root.children[0].type, 'suite'); + const suite3 = root.children[2]; + assert.equal(suite3.children.length, 33); - spawnStub.withArgs(example1.suite3.execPath).throwsArg(1); + spawnStub.withArgs(example1.suite3.execPath).throwsArg(1); - const runAndCheckEvents = async (test: TestInfo) => { - assert.equal(testStatesEvents.length, 0); + const runAndCheckEvents = async (test: TestInfo) => { + assert.equal(testStatesEvents.length, 0); - await adapter.run([test.id]); + await adapter.run([test.id]); - assert.equal(testStatesEvents.length, 6, inspect(test)); + assert.equal(testStatesEvents.length, 6, inspect(test)); - assert.deepStrictEqual( - {type: 'started', tests: [test.id]}, testStatesEvents[0]); - assert.deepStrictEqual( - {type: 'suite', state: 'running', suite: suite3}, - testStatesEvents[1]); + assert.deepStrictEqual( + { type: 'started', tests: [test.id] }, testStatesEvents[0]); + assert.deepStrictEqual( + { type: 'suite', state: 'running', suite: suite3 }, + testStatesEvents[1]); - assert.equal(testStatesEvents[2].type, 'test'); - assert.equal((testStatesEvents[2]).state, 'running'); - assert.equal((testStatesEvents[2]).test, test); + assert.equal(testStatesEvents[2].type, 'test'); + assert.equal((testStatesEvents[2]).state, 'running'); + assert.equal((testStatesEvents[2]).test, test); - assert.equal(testStatesEvents[3].type, 'test'); - assert.ok( - (testStatesEvents[3]).state == 'passed' || - (testStatesEvents[3]).state == 'skipped' || - (testStatesEvents[3]).state == 'failed'); - assert.equal((testStatesEvents[3]).test, test); + assert.equal(testStatesEvents[3].type, 'test'); + assert.ok( + (testStatesEvents[3]).state == 'passed' || + (testStatesEvents[3]).state == 'skipped' || + (testStatesEvents[3]).state == 'failed'); + assert.equal((testStatesEvents[3]).test, test); - assert.deepStrictEqual( - {type: 'suite', state: 'completed', suite: suite3}, - testStatesEvents[4]); - assert.deepStrictEqual({type: 'finished'}, testStatesEvents[5]); + assert.deepStrictEqual( + { type: 'suite', state: 'completed', suite: suite3 }, + testStatesEvents[4]); + assert.deepStrictEqual({ type: 'finished' }, testStatesEvents[5]); - while (testStatesEvents.length) testStatesEvents.pop(); - }; + while (testStatesEvents.length) testStatesEvents.pop(); + }; - for (let test of suite3.children) { - assert.equal(test.type, 'test'); - await runAndCheckEvents(test); - } - }) + for (let test of suite3.children) { + assert.equal(test.type, 'test'); + await runAndCheckEvents(test); + } }) + }) }) - specify('load executables=', async function() { + specify('load executables=', async function () { this.slow(300); await updateConfig('executables', example1.suite1.execPath); adapter = createAdapterAndSubscribe(); @@ -1493,9 +1493,9 @@ describe('C2TestAdapter', function() { await adapter.load(); assert.equal(testsEvents.length, 2); assert.equal( - (testsEvents[testsEvents.length - 1]) - .suite!.children.length, - 1); + (testsEvents[testsEvents.length - 1]) + .suite!.children.length, + 1); testsEvents.pop(); testsEvents.pop(); @@ -1504,197 +1504,197 @@ describe('C2TestAdapter', function() { }) specify( - 'load executables=["execPath1", "execPath2"] with error', - async function() { - this.slow(300); - await updateConfig('executables', ['execPath1', 'execPath2']) - adapter = createAdapterAndSubscribe(); - - const withArgs = spawnStub.withArgs( - example1.suite2.execPath, example1.suite2.outputs[1][0]); - withArgs.onCall(withArgs.callCount).throws( - 'dummy error for testing (should be handled)'); - - await adapter.load(); - testsEvents.pop(); - testsEvents.pop(); - - disposeAdapterAndSubscribers(); - await updateConfig('executables', undefined); - }) + 'load executables=["execPath1", "execPath2"] with error', + async function () { + this.slow(300); + await updateConfig('executables', ['execPath1', 'execPath2']) + adapter = createAdapterAndSubscribe(); + + const withArgs = spawnStub.withArgs( + example1.suite2.execPath, example1.suite2.outputs[1][0]); + withArgs.onCall(withArgs.callCount).throws( + 'dummy error for testing (should be handled)'); + + await adapter.load(); + testsEvents.pop(); + testsEvents.pop(); + + disposeAdapterAndSubscribers(); + await updateConfig('executables', undefined); + }) specify( - 'load executables=["execPath1", "execPath2Copy"]; delete; sleep 3; create', - async function() { - const watchTimeout = 6; - await updateConfig('defaultWatchTimeoutSec', watchTimeout); - this.timeout(watchTimeout * 1000 + 2500 /* because of 'delay' */); - this.slow(watchTimeout * 1000 + 2500 /* because of 'delay' */); - const execPath2CopyPath = - path.join(workspaceFolderUri.path, 'execPath2Copy'); - - for (let scenario of example1.suite2.outputs) { - spawnStub.withArgs(execPath2CopyPath, scenario[0]) - .callsFake(function() { - return new ChildProcessStub(scenario[1]); - }); - } + 'load executables=["execPath1", "execPath2Copy"]; delete; sleep 3; create', + async function () { + const watchTimeout = 6; + await updateConfig('defaultWatchTimeoutSec', watchTimeout); + this.timeout(watchTimeout * 1000 + 2500 /* because of 'delay' */); + this.slow(watchTimeout * 1000 + 2500 /* because of 'delay' */); + const execPath2CopyPath = + path.join(workspaceFolderUri.fsPath, 'execPath2Copy'); + + for (let scenario of example1.suite2.outputs) { + spawnStub.withArgs(execPath2CopyPath, scenario[0]) + .callsFake(function () { + return new ChildProcessStub(scenario[1]); + }); + } - c2fsStatStub.withArgs(execPath2CopyPath) - .callsFake(handleStatExistsFile); + c2fsStatStub.withArgs(execPath2CopyPath) + .callsFake(handleStatExistsFile); - vsfsWatchStub.withArgs(matchRelativePattern(execPath2CopyPath)) - .callsFake(handleCreateWatcherCb); + vsfsWatchStub.withArgs(matchRelativePattern(execPath2CopyPath)) + .callsFake(handleCreateWatcherCb); - vsFindFilesStub.withArgs(matchRelativePattern(execPath2CopyPath)) - .returns([vscode.Uri.file(execPath2CopyPath)]); + vsFindFilesStub.withArgs(matchRelativePattern(execPath2CopyPath)) + .returns([vscode.Uri.file(execPath2CopyPath)]); - await updateConfig('executables', ['execPath1', 'execPath2Copy']) - adapter = createAdapterAndSubscribe(); + await updateConfig('executables', ['execPath1', 'execPath2Copy']) + adapter = createAdapterAndSubscribe(); - await adapter.load(); + await adapter.load(); - assert.equal( - (testsEvents[testsEvents.length - 1]) - .suite!.children.length, - 2); - testsEvents.pop(); - testsEvents.pop(); + assert.equal( + (testsEvents[testsEvents.length - 1]) + .suite!.children.length, + 2); + testsEvents.pop(); + testsEvents.pop(); - assert.ok(watchers.has(execPath2CopyPath)); - const watcher = watchers.get(execPath2CopyPath)!; + assert.ok(watchers.has(execPath2CopyPath)); + const watcher = watchers.get(execPath2CopyPath)!; - let start: number = 0; - const newRoot = await doAndWaitForReloadEvent(this, async () => { + let start: number = 0; + const newRoot = await doAndWaitForReloadEvent(this, async () => { + c2fsStatStub.withArgs(execPath2CopyPath) + .callsFake(handleStatNotExists); + start = Date.now(); + watcher.sendDelete(); + setTimeout(() => { + assert.equal(testsEvents.length, 0); + }, 1500); + setTimeout(() => { c2fsStatStub.withArgs(execPath2CopyPath) - .callsFake(handleStatNotExists); - start = Date.now(); - watcher.sendDelete(); - setTimeout(() => { - assert.equal(testsEvents.length, 0); - }, 1500); - setTimeout(() => { - c2fsStatStub.withArgs(execPath2CopyPath) - .callsFake(handleStatExistsFile); - watcher.sendCreate(); - }, 3000); - }, 40000); - const elapsed = Date.now() - start; - - assert.equal(testsEvents.length, 2); - testsEvents.pop(); - testsEvents.pop(); - for (let scenario of example1.suite2.outputs) { - spawnStub.withArgs(execPath2CopyPath, scenario[0]).callsFake(() => { - throw Error('restore'); - }); - } - c2fsStatStub.withArgs(execPath2CopyPath).callsFake(() => { + .callsFake(handleStatExistsFile); + watcher.sendCreate(); + }, 3000); + }, 40000); + const elapsed = Date.now() - start; + + assert.equal(testsEvents.length, 2); + testsEvents.pop(); + testsEvents.pop(); + for (let scenario of example1.suite2.outputs) { + spawnStub.withArgs(execPath2CopyPath, scenario[0]).callsFake(() => { throw Error('restore'); }); - vsfsWatchStub.withArgs(matchRelativePattern(execPath2CopyPath)) - .callsFake(() => { - throw Error('restore'); - }); - vsFindFilesStub.withArgs(matchRelativePattern(execPath2CopyPath)) - .callsFake(() => { - throw Error('restore'); - }); - disposeAdapterAndSubscribers(); - await updateConfig('executables', undefined); - await updateConfig('defaultWatchTimeoutSec', undefined); + } + c2fsStatStub.withArgs(execPath2CopyPath).callsFake(() => { + throw Error('restore'); + }); + vsfsWatchStub.withArgs(matchRelativePattern(execPath2CopyPath)) + .callsFake(() => { + throw Error('restore'); + }); + vsFindFilesStub.withArgs(matchRelativePattern(execPath2CopyPath)) + .callsFake(() => { + throw Error('restore'); + }); + disposeAdapterAndSubscribers(); + await updateConfig('executables', undefined); + await updateConfig('defaultWatchTimeoutSec', undefined); - assert.equal(newRoot.children.length, 2); - assert.ok(3000 < elapsed, inspect(elapsed)); - assert.ok(elapsed < watchTimeout * 1000 + 2400, inspect(elapsed)); - }) + assert.equal(newRoot.children.length, 2); + assert.ok(3000 < elapsed, inspect(elapsed)); + assert.ok(elapsed < watchTimeout * 1000 + 2400, inspect(elapsed)); + }) specify( - 'load executables=["execPath1", "execPath2Copy"]; delete second', - async function() { - const watchTimeout = 5; - await updateConfig('defaultWatchTimeoutSec', watchTimeout); - this.timeout(watchTimeout * 1000 + 2500 /* because of 'delay' */); - this.slow(watchTimeout* 1000 + 2500 /* because of 'delay' */); - const execPath2CopyPath = - path.join(workspaceFolderUri.path, 'execPath2Copy'); - - for (let scenario of example1.suite2.outputs) { - spawnStub.withArgs(execPath2CopyPath, scenario[0]) - .callsFake(function() { - return new ChildProcessStub(scenario[1]); - }); - } + 'load executables=["execPath1", "execPath2Copy"]; delete second', + async function () { + const watchTimeout = 5; + await updateConfig('defaultWatchTimeoutSec', watchTimeout); + this.timeout(watchTimeout * 1000 + 2500 /* because of 'delay' */); + this.slow(watchTimeout * 1000 + 2500 /* because of 'delay' */); + const execPath2CopyPath = + path.join(workspaceFolderUri.fsPath, 'execPath2Copy'); + + for (let scenario of example1.suite2.outputs) { + spawnStub.withArgs(execPath2CopyPath, scenario[0]) + .callsFake(function () { + return new ChildProcessStub(scenario[1]); + }); + } - c2fsStatStub.withArgs(execPath2CopyPath) - .callsFake(handleStatExistsFile); + c2fsStatStub.withArgs(execPath2CopyPath) + .callsFake(handleStatExistsFile); - vsfsWatchStub.withArgs(matchRelativePattern(execPath2CopyPath)) - .callsFake(handleCreateWatcherCb); + vsfsWatchStub.withArgs(matchRelativePattern(execPath2CopyPath)) + .callsFake(handleCreateWatcherCb); - vsFindFilesStub.withArgs(matchRelativePattern(execPath2CopyPath)) - .returns([vscode.Uri.file(execPath2CopyPath)]); + vsFindFilesStub.withArgs(matchRelativePattern(execPath2CopyPath)) + .returns([vscode.Uri.file(execPath2CopyPath)]); - await updateConfig('executables', ['execPath1', 'execPath2Copy']) - adapter = createAdapterAndSubscribe(); + await updateConfig('executables', ['execPath1', 'execPath2Copy']) + adapter = createAdapterAndSubscribe(); - await adapter.load(); + await adapter.load(); - assert.equal( - (testsEvents[testsEvents.length - 1]) - .suite!.children.length, - 2); - testsEvents.pop(); - testsEvents.pop(); + assert.equal( + (testsEvents[testsEvents.length - 1]) + .suite!.children.length, + 2); + testsEvents.pop(); + testsEvents.pop(); - assert.ok(watchers.has(execPath2CopyPath)); - const watcher = watchers.get(execPath2CopyPath)!; + assert.ok(watchers.has(execPath2CopyPath)); + const watcher = watchers.get(execPath2CopyPath)!; - let start: number = 0; - const newRoot = await doAndWaitForReloadEvent(this, async () => { - c2fsStatStub.withArgs(execPath2CopyPath) - .callsFake(handleStatNotExists); - start = Date.now(); - watcher.sendDelete(); - }, 40000); - const elapsed = Date.now() - start; - testsEvents.pop(); - testsEvents.pop(); - for (let scenario of example1.suite2.outputs) { - spawnStub.withArgs(execPath2CopyPath, scenario[0]).callsFake(() => { - throw Error('restore'); - }); - } - c2fsStatStub.withArgs(execPath2CopyPath).callsFake(() => { + let start: number = 0; + const newRoot = await doAndWaitForReloadEvent(this, async () => { + c2fsStatStub.withArgs(execPath2CopyPath) + .callsFake(handleStatNotExists); + start = Date.now(); + watcher.sendDelete(); + }, 40000); + const elapsed = Date.now() - start; + testsEvents.pop(); + testsEvents.pop(); + for (let scenario of example1.suite2.outputs) { + spawnStub.withArgs(execPath2CopyPath, scenario[0]).callsFake(() => { throw Error('restore'); }); - vsfsWatchStub.withArgs(matchRelativePattern(execPath2CopyPath)) - .callsFake(() => { - throw Error('restore'); - }); - vsFindFilesStub.withArgs(matchRelativePattern(execPath2CopyPath)) - .callsFake(() => { - throw Error('restore'); - }); - disposeAdapterAndSubscribers(); - await updateConfig('executables', undefined); - await updateConfig('defaultWatchTimeoutSec', undefined); + } + c2fsStatStub.withArgs(execPath2CopyPath).callsFake(() => { + throw Error('restore'); + }); + vsfsWatchStub.withArgs(matchRelativePattern(execPath2CopyPath)) + .callsFake(() => { + throw Error('restore'); + }); + vsFindFilesStub.withArgs(matchRelativePattern(execPath2CopyPath)) + .callsFake(() => { + throw Error('restore'); + }); + disposeAdapterAndSubscribers(); + await updateConfig('executables', undefined); + await updateConfig('defaultWatchTimeoutSec', undefined); - assert.equal(newRoot.children.length, 1); - assert.ok(watchTimeout * 1000 < elapsed, inspect(elapsed)); - assert.ok(elapsed < watchTimeout * 1000 + 2400, inspect(elapsed)); - }) + assert.equal(newRoot.children.length, 1); + assert.ok(watchTimeout * 1000 < elapsed, inspect(elapsed)); + assert.ok(elapsed < watchTimeout * 1000 + 2400, inspect(elapsed)); + }) - specify('wrong executables format', async function() { + specify('wrong executables format', async function () { this.slow(300); - await updateConfig('executables', {name: ''}); + await updateConfig('executables', { name: '' }); adapter = createAdapterAndSubscribe(); await adapter.load(); const root = - (testsEvents[testsEvents.length - 1]).suite!; + (testsEvents[testsEvents.length - 1]).suite!; assert.equal(root.children.length, 0); testsEvents.pop(); testsEvents.pop(); @@ -1703,17 +1703,17 @@ describe('C2TestAdapter', function() { await updateConfig('executables', undefined); }) - specify('variable substitution with executables={...}', async function() { + specify('variable substitution with executables={...}', async function () { this.slow(300); - const wsPath = workspaceFolderUri.fsPath - const execPath2CopyRelPath = 'foo/bar/base.second.first'; - const execPath2CopyPath = path.join(wsPath, execPath2CopyRelPath); + const wsPath = workspaceFolderUri.fsPath; + const execPath2CopyRelPath = path.normalize('foo/bar/base.second.first'); + const execPath2CopyPath = vscode.Uri.file(path.join(wsPath, execPath2CopyRelPath)).fsPath; const envArray: [string, string][] = [ ['${absPath}', execPath2CopyPath], ['${relPath}', execPath2CopyRelPath], - ['${absDirpath}', path.join(wsPath, 'foo/bar')], - ['${relDirpath}', 'foo/bar'], + ['${absDirpath}', path.join(wsPath, path.normalize('foo/bar'))], + ['${relDirpath}', path.normalize('foo/bar')], ['${filename}', 'base.second.first'], ['${baseFilename}', 'base.second'], ['${extFilename}', '.first'], @@ -1724,44 +1724,44 @@ describe('C2TestAdapter', function() { ['${workspaceDirectory}', wsPath], ['${workspaceFolder}', wsPath], ]; - const envsStr = envArray.map(v => {return v[0]}).join(' , '); - const expectStr = envArray.map(v => {return v[1]}).join(' , '); + const envsStr = envArray.map(v => { return v[0] }).join(' , '); + const expectStr = envArray.map(v => { return v[1] }).join(' , '); await updateConfig('executables', { name: envsStr, pattern: execPath2CopyRelPath, cwd: envsStr, - env: {C2TESTVARS: envsStr} + env: { C2TESTVARS: envsStr } }); for (let scenario of example1.suite2.outputs) { spawnStub.withArgs(execPath2CopyPath, scenario[0]) - .callsFake(function() { - return new ChildProcessStub(scenario[1]); - }); + .callsFake(function () { + return new ChildProcessStub(scenario[1]); + }); } spawnStub.withArgs(execPath2CopyPath, example1.suite2.t1.outputs[0][0]) - .callsFake(function(p: string, args: string[], ops: any) { - assert.equal(ops.cwd, expectStr); - assert.ok(ops.env.hasOwnProperty('C2TESTVARS')); - assert.equal(ops.env.C2TESTVARS, expectStr); - return new ChildProcessStub(example1.suite2.t1.outputs[0][1]); - }); + .callsFake(function (p: string, args: string[], ops: any) { + assert.equal(ops.cwd, expectStr); + assert.ok(ops.env.hasOwnProperty('C2TESTVARS')); + assert.equal(ops.env.C2TESTVARS, expectStr); + return new ChildProcessStub(example1.suite2.t1.outputs[0][1]); + }); c2fsStatStub.withArgs(execPath2CopyPath).callsFake(handleStatExistsFile); vsfsWatchStub.withArgs(matchRelativePattern(execPath2CopyPath)) - .callsFake(handleCreateWatcherCb); + .callsFake(handleCreateWatcherCb); vsFindFilesStub.withArgs(matchRelativePattern(execPath2CopyPath)) - .returns([vscode.Uri.file(execPath2CopyPath)]); + .returns([vscode.Uri.file(execPath2CopyPath)]); adapter = createAdapterAndSubscribe(); await adapter.load(); const root = - (testsEvents[testsEvents.length - 1]).suite!; + (testsEvents[testsEvents.length - 1]).suite!; testsEvents.pop(); testsEvents.pop(); @@ -1783,13 +1783,13 @@ describe('C2TestAdapter', function() { throw Error('restore'); }); vsfsWatchStub.withArgs(matchRelativePattern(execPath2CopyPath)) - .callsFake(() => { - throw Error('restore'); - }); + .callsFake(() => { + throw Error('restore'); + }); vsFindFilesStub.withArgs(matchRelativePattern(execPath2CopyPath)) - .callsFake(() => { - throw Error('restore'); - }); + .callsFake(() => { + throw Error('restore'); + }); disposeAdapterAndSubscribers(); await updateConfig('executables', undefined); }) diff --git a/src/test/FsWrapper.test.ts b/src/test/FsWrapper.test.ts index 6b2b9d9e..e8d48a34 100644 --- a/src/test/FsWrapper.test.ts +++ b/src/test/FsWrapper.test.ts @@ -6,23 +6,27 @@ import * as assert from 'assert'; import * as path from 'path'; import * as vscode from 'vscode'; -import {spawnAsync, statAsync} from '../FsWrapper'; +import { spawnAsync, statAsync } from '../FsWrapper'; +import { SpawnOptions } from 'child_process'; +import { EOL } from 'os'; assert.notEqual(vscode.workspace.workspaceFolders, undefined); assert.equal(vscode.workspace.workspaceFolders!.length, 1); const workspaceFolderUri = vscode.workspace.workspaceFolders![0].uri; -describe('FsWrapper.spawnAsync', function() { - it('echoes', async function() { - const r = await spawnAsync('echo', ['apple']); - assert.equal(r.stdout, 'apple\n'); +describe('FsWrapper.spawnAsync', function () { + it('echoes', async function () { + const isWin = process.platform === "win32"; + const opt: SpawnOptions = isWin ? { shell: true } : {}; + const r = await spawnAsync('echo', ['apple'], opt); + assert.equal(r.stdout, 'apple' + EOL); assert.equal(r.output.length, 2); - assert.equal(r.output[0], 'apple\n'); + assert.equal(r.output[0], 'apple' + EOL); assert.equal(r.status, 0); }) - it.skip('sleeps', async function() { + it.skip('sleeps', async function () { this.timeout(1100); this.slow(1050); if (process.platform === 'darwin') { @@ -34,35 +38,35 @@ describe('FsWrapper.spawnAsync', function() { }) }) -describe('FsWrapper.statAsync', function() { - it('doesnt exists', async function() { +describe('FsWrapper.statAsync', function () { + it('doesnt exists', async function () { try { await statAsync('notexists'); assert.ok(false); } catch (e) { assert.equal(e.code, 'ENOENT'); - assert.equal(e.errno, -2); + assert.notEqual(e.errno, 0); } }) - it('exists', async function() { + it('exists', async function () { const res = await statAsync( - path.join(workspaceFolderUri.path, 'FsWrapper.test.js')); + path.join(workspaceFolderUri.fsPath, 'FsWrapper.test.js')); assert.ok(res.isFile()); assert.ok(!res.isDirectory()); }) }) -describe('path', function() { - describe('Uri', function() { - it('sould resolve', function() { +describe('path', function () { + describe('Uri', function () { + it('sould resolve', function () { const a = vscode.Uri.file('/a/b/c'); const b = vscode.Uri.file('/a/b/c/d/e'); assert.equal(path.relative(a.fsPath, b.fsPath), path.normalize('d/e')); }) }) - describe('extname', function() { - it('extname', function() { + describe('extname', function () { + it('extname', function () { const filename = path.basename('bar/foo/base.ext2.ext1'); assert.equal(filename, 'base.ext2.ext1'); @@ -85,7 +89,7 @@ describe('path', function() { assert.equal(base3Filename, 'base'); }) - it('.extname', function() { + it('.extname', function () { const filename = path.basename('bar/foo/.base.ext2.ext1'); assert.equal(filename, '.base.ext2.ext1'); @@ -108,4 +112,10 @@ describe('path', function() { assert.equal(base3Filename, '.base'); }) }) +}) + +describe('vscode.Uri', function () { + it('!=', function () { + assert.ok(vscode.Uri.file(workspaceFolderUri.path) != workspaceFolderUri); + }) }) \ No newline at end of file diff --git a/src/test/cpp/suite1.cpp b/src/test/cpp/suite1.cpp index 2882a71e..6fd2aa61 100644 --- a/src/test/cpp/suite1.cpp +++ b/src/test/cpp/suite1.cpp @@ -1,5 +1,5 @@ #define CATCH_CONFIG_MAIN -#include +#include // c++ -x c++ -std=c++17 -I ../Catch2/single_include -O0 -g -o suite1 // ../vscode-catch2-test-adapter/src/test/suite1.cpp diff --git a/src/test/cpp/suite2.cpp b/src/test/cpp/suite2.cpp index 9f2a5515..2f97c6cf 100644 --- a/src/test/cpp/suite2.cpp +++ b/src/test/cpp/suite2.cpp @@ -1,5 +1,5 @@ #define CATCH_CONFIG_MAIN -#include +#include // c++ -x c++ -std=c++17 -I ../Catch2/single_include -O0 -g -o suite2 // ../vscode-catch2-test-adapter/src/test/suite2.cpp diff --git a/src/test/cpp/suite3.cpp b/src/test/cpp/suite3.cpp index 596136ef..d067bf24 100644 --- a/src/test/cpp/suite3.cpp +++ b/src/test/cpp/suite3.cpp @@ -1,5 +1,5 @@ #define CATCH_CONFIG_MAIN -#include +#include // clang-format off // c++ -x c++ -std=c++17 -I ../Catch2/single_include -O0 -g -o suite3 ../vscode-catch2-test-adapter/src/test/suite3.cpp diff --git a/src/test/example1.ts b/src/test/example1.ts index b20225c6..4a141fdd 100644 --- a/src/test/example1.ts +++ b/src/test/example1.ts @@ -1,7 +1,7 @@ import * as assert from 'assert'; import * as path from 'path'; import * as vscode from 'vscode'; -import {TestInfo, TestSuiteInfo} from 'vscode-test-adapter-api'; +import { TestInfo, TestSuiteInfo } from 'vscode-test-adapter-api'; assert.notEqual(vscode.workspace.workspaceFolders, undefined); assert.equal(vscode.workspace.workspaceFolders!.length, 1); @@ -10,7 +10,7 @@ const workspaceFolderUri = vscode.workspace.workspaceFolders![0].uri; export const example1 = new class { readonly suite1 = new class { - readonly execPath = path.join(workspaceFolderUri.path, 'execPath1'); + readonly execPath = vscode.Uri.file(path.join(workspaceFolderUri.path, 'execPath1')).fsPath; readonly t1 = new class { readonly fullTestName = 's1t1'; @@ -18,7 +18,7 @@ export const example1 = new class { assert.equal(test.type, 'test'); assert.equal(test.label, label); assert.equal( - test.file, path.join(workspaceFolderUri.path, 'suite1.cpp')); + test.file, vscode.Uri.file(path.join(workspaceFolderUri.path, 'suite1.cpp')).fsPath); assert.equal(test.line, 7 - 1); assert.ok(test.skipped == undefined || test.skipped === false); if (uniqeIdContainer != undefined) { @@ -66,7 +66,7 @@ export const example1 = new class { assert.equal(test.type, 'test'); assert.equal(test.label, label); assert.equal( - test.file, path.join(workspaceFolderUri.path, 'suite1.cpp')); + test.file, vscode.Uri.file(path.join(workspaceFolderUri.path, 'suite1.cpp')).fsPath); assert.equal(test.line, 13 - 1); assert.ok(test.skipped == undefined || test.skipped === false); if (uniqeIdContainer != undefined) { @@ -129,13 +129,13 @@ export const example1 = new class { [ ['[.],*', '--verbosity', 'high', '--list-tests', '--use-colour', 'no'], 'Matching test cases:\n' + - ' s1t1\n' + - ' suite1.cpp:7\n' + - ' tag1\n' + - ' s1t2\n' + - ' suite1.cpp:13\n' + - ' tag1\n' + - '2 matching test cases\n\n' + ' s1t1\n' + + ' suite1.cpp:7\n' + + ' tag1\n' + + ' s1t2\n' + + ' suite1.cpp:13\n' + + ' tag1\n' + + '2 matching test cases\n\n' ], [ ['--reporter', 'xml', '--durations', 'yes'], @@ -191,19 +191,19 @@ export const example1 = new class { ]; assert( - label: string, childLabels: string[], suite: TestSuiteInfo, - uniqeIdContainer?: Set) { + label: string, childLabels: string[], suite: TestSuiteInfo, + uniqeIdContainer?: Set) { assert.equal(suite.type, 'suite'); assert.equal(suite.label, label); assert.equal( - suite.file, path.join(workspaceFolderUri.path, 'suite1.cpp')); + suite.file, vscode.Uri.file(path.join(workspaceFolderUri.path, 'suite1.cpp')).fsPath); assert.equal(suite.line, 0); assert.equal(suite.children.length, 2); assert.equal(childLabels.length, suite.children.length); this.t1.assert( - childLabels[0], suite.children[0], uniqeIdContainer); + childLabels[0], suite.children[0], uniqeIdContainer); this.t2.assert( - childLabels[1], suite.children[1], uniqeIdContainer); + childLabels[1], suite.children[1], uniqeIdContainer); if (uniqeIdContainer != undefined) { assert.ok(!uniqeIdContainer.has(suite.id)); uniqeIdContainer.add(suite.id); @@ -212,7 +212,7 @@ export const example1 = new class { }; readonly suite2 = new class { - readonly execPath = path.join(workspaceFolderUri.path, 'execPath2'); + readonly execPath = vscode.Uri.file(path.join(workspaceFolderUri.path, 'execPath2')).fsPath; readonly t1 = new class { readonly fullTestName = 's2t1'; @@ -220,7 +220,7 @@ export const example1 = new class { assert.equal(test.type, 'test'); assert.equal(test.label, label); assert.equal( - test.file, path.join(workspaceFolderUri.path, 'suite2.cpp')); + test.file, vscode.Uri.file(path.join(workspaceFolderUri.path, 'suite2.cpp')).fsPath); assert.equal(test.line, 7 - 1); assert.ok(test.skipped == undefined || test.skipped === false); if (uniqeIdContainer != undefined) { @@ -268,7 +268,7 @@ export const example1 = new class { assert.equal(test.type, 'test'); assert.equal(test.label, label); assert.equal( - test.file, path.join(workspaceFolderUri.path, 'suite2.cpp')); + test.file, vscode.Uri.file(path.join(workspaceFolderUri.path, 'suite2.cpp')).fsPath); assert.equal(test.line, 13 - 1); assert.ok(test.skipped === true); if (uniqeIdContainer != undefined) { @@ -316,7 +316,7 @@ export const example1 = new class { assert.equal(test.type, 'test'); assert.equal(test.label, label); assert.equal( - test.file, path.join(workspaceFolderUri.path, 'suite2.cpp')); + test.file, vscode.Uri.file(path.join(workspaceFolderUri.path, 'suite2.cpp')).fsPath); assert.equal(test.line, 19 - 1); assert.ok(test.skipped == undefined || test.skipped === false); if (uniqeIdContainer != undefined) { @@ -375,21 +375,21 @@ export const example1 = new class { }; assert( - label: string, childLabels: string[], suite: TestSuiteInfo, - uniqeIdContainer?: Set) { + label: string, childLabels: string[], suite: TestSuiteInfo, + uniqeIdContainer?: Set) { assert.equal(suite.type, 'suite'); assert.equal(suite.label, label); assert.equal( - suite.file, path.join(workspaceFolderUri.path, 'suite2.cpp')); + suite.file, vscode.Uri.file(path.join(workspaceFolderUri.path, 'suite2.cpp')).fsPath); assert.equal(suite.line, 0); assert.equal(suite.children.length, 3); assert.equal(childLabels.length, suite.children.length); this.t1.assert( - childLabels[0], suite.children[0], uniqeIdContainer); + childLabels[0], suite.children[0], uniqeIdContainer); this.t2.assert( - childLabels[1], suite.children[1], uniqeIdContainer); + childLabels[1], suite.children[1], uniqeIdContainer); this.t3.assert( - childLabels[2], suite.children[2], uniqeIdContainer); + childLabels[2], suite.children[2], uniqeIdContainer); if (uniqeIdContainer != undefined) { assert.ok(!uniqeIdContainer.has(suite.id)); uniqeIdContainer.add(suite.id); @@ -401,17 +401,17 @@ export const example1 = new class { [ ['[.],*', '--verbosity', 'high', '--list-tests', '--use-colour', 'no'], 'Matching test cases:\n' + - ' s2t1\n' + - ' suite2.cpp:7\n' + - ' tag1\n' + - ' s2t2\n' + - ' suite2.cpp:13\n' + - ' tag1\n' + - ' [.]\n' + - ' s2t3\n' + - ' suite2.cpp:19\n' + - ' tag1\n' + - '3 matching test cases\n\n' + ' s2t1\n' + + ' suite2.cpp:7\n' + + ' tag1\n' + + ' s2t2\n' + + ' suite2.cpp:13\n' + + ' tag1\n' + + ' [.]\n' + + ' s2t3\n' + + ' suite2.cpp:19\n' + + ' tag1\n' + + '3 matching test cases\n\n' ], [ ['--reporter', 'xml', '--durations', 'yes'], @@ -467,7 +467,7 @@ export const example1 = new class { }; readonly suite3 = new class { - readonly execPath = path.join(workspaceFolderUri.path, 'execPath3'); + readonly execPath = vscode.Uri.file(path.join(workspaceFolderUri.path, 'execPath3')).fsPath; readonly outputs: [string[], string][] = [ From dc46ebeb05a7a39ab99a7c92ad51e01daf2809c8 Mon Sep 17 00:00:00 2001 From: Mate Pek Date: Sat, 27 Oct 2018 20:55:19 +0200 Subject: [PATCH 20/49] appveyor nodejs 8,11 --- appveyor.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index 32236b71..8d3c93dc 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,5 +1,7 @@ environment: - nodejs_version: "10" + matrix: + - nodejs_version: "8" + - nodejs_version: "11" install: - ps: Install-Product node $env:nodejs_version From a404cac7c10cbb676720b3bfbb18903e59a3bce9 Mon Sep 17 00:00:00 2001 From: Mate Pek Date: Sat, 27 Oct 2018 21:38:01 +0200 Subject: [PATCH 21/49] appveyo: nodejs versions --- appveyor.yml | 3 +++ src/test/{ => cpp}/suite3generator.sh | 0 2 files changed, 3 insertions(+) rename src/test/{ => cpp}/suite3generator.sh (100%) diff --git a/appveyor.yml b/appveyor.yml index 8d3c93dc..3870448a 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,7 +1,10 @@ environment: matrix: - nodejs_version: "8" + platform: x86 + platform: x64 - nodejs_version: "11" + platform: x64 install: - ps: Install-Product node $env:nodejs_version diff --git a/src/test/suite3generator.sh b/src/test/cpp/suite3generator.sh similarity index 100% rename from src/test/suite3generator.sh rename to src/test/cpp/suite3generator.sh From 1bbda2af54b59bfce15680779928eb9ff087e47a Mon Sep 17 00:00:00 2001 From: Mate Pek Date: Sat, 27 Oct 2018 21:45:39 +0200 Subject: [PATCH 22/49] appveyo: nodejs versions.. --- appveyor.yml | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index 3870448a..7b9d9d71 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,10 +1,16 @@ +platform: + - x86 + - x64 + environment: matrix: - nodejs_version: "8" - platform: x86 - platform: x64 - nodejs_version: "11" - platform: x64 + +matrix: + allow_failures: + - platform: x86 + nodejs_version: "11" install: - ps: Install-Product node $env:nodejs_version From 1d392b5122160504cd270a96882fd50dca964cdc Mon Sep 17 00:00:00 2001 From: Mate Pek Date: Sat, 27 Oct 2018 21:53:31 +0200 Subject: [PATCH 23/49] appveyor doesnt support nodejs 11 yet --- appveyor.yml | 9 ++------- src/test/C2TestAdapter.test.ts | 28 ++++++++++++++-------------- 2 files changed, 16 insertions(+), 21 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index 7b9d9d71..0e5cb2e7 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -4,13 +4,8 @@ platform: environment: matrix: - - nodejs_version: "8" - - nodejs_version: "11" - -matrix: - allow_failures: - - platform: x86 - nodejs_version: "11" + - nodejs_version: LTS + - nodejs_version: Current install: - ps: Install-Product node $env:nodejs_version diff --git a/src/test/C2TestAdapter.test.ts b/src/test/C2TestAdapter.test.ts index d0d71858..46b1e6d6 100644 --- a/src/test/C2TestAdapter.test.ts +++ b/src/test/C2TestAdapter.test.ts @@ -62,7 +62,7 @@ describe('C2TestAdapter', function() { let spawnStub: sinon.SinonStub; let vsfsWatchStub: sinon.SinonStub; - let c2fsStatStub: sinon.SinonStub; + let fsStatStub: sinon.SinonStub; let vsFindFilesStub: sinon.SinonStub; function resetConfig(): Thenable { @@ -173,8 +173,8 @@ describe('C2TestAdapter', function() { spawnStub.callThrough(); vsfsWatchStub.reset(); vsfsWatchStub.callThrough(); - c2fsStatStub.reset(); - c2fsStatStub.callThrough(); + fsStatStub.reset(); + fsStatStub.callThrough(); vsFindFilesStub.reset(); vsFindFilesStub.callThrough(); } @@ -187,7 +187,7 @@ describe('C2TestAdapter', function() { vsfsWatchStub = sinonSandbox.stub(vscode.workspace, 'createFileSystemWatcher') .named('vscode.createFileSystemWatcher'); - c2fsStatStub = sinonSandbox.stub(fs, 'stat').named('fsStat'); + fsStatStub = sinonSandbox.stub(fs, 'stat').named('fsStat'); vsFindFilesStub = sinonSandbox.stub(vscode.workspace, 'findFiles') .named('vsFindFilesStub'); @@ -314,7 +314,7 @@ describe('C2TestAdapter', function() { }); } - c2fsStatStub.withArgs(suite[0]).callsFake(handleStatExistsFile); + fsStatStub.withArgs(suite[0]).callsFake(handleStatExistsFile); vsfsWatchStub.withArgs(matchRelativePattern(suite[0])) .callsFake(handleCreateWatcherCb); @@ -1475,7 +1475,7 @@ describe('C2TestAdapter', function() { }); } - c2fsStatStub.withArgs(execPath2CopyPath) + fsStatStub.withArgs(execPath2CopyPath) .callsFake(handleStatExistsFile); vsfsWatchStub.withArgs(matchRelativePattern(execPath2CopyPath)) @@ -1501,7 +1501,7 @@ describe('C2TestAdapter', function() { let start: number = 0; const newRoot = await doAndWaitForReloadEvent(this, async () => { - c2fsStatStub.withArgs(execPath2CopyPath) + fsStatStub.withArgs(execPath2CopyPath) .callsFake(handleStatNotExists); start = Date.now(); watcher.sendDelete(); @@ -1509,7 +1509,7 @@ describe('C2TestAdapter', function() { assert.equal(testsEvents.length, 0); }, 1500); setTimeout(() => { - c2fsStatStub.withArgs(execPath2CopyPath) + fsStatStub.withArgs(execPath2CopyPath) .callsFake(handleStatExistsFile); watcher.sendCreate(); }, 3000); @@ -1524,7 +1524,7 @@ describe('C2TestAdapter', function() { throw Error('restore'); }); } - c2fsStatStub.withArgs(execPath2CopyPath).callsFake(() => { + fsStatStub.withArgs(execPath2CopyPath).callsFake(() => { throw Error('restore'); }); vsfsWatchStub.withArgs(matchRelativePattern(execPath2CopyPath)) @@ -1561,7 +1561,7 @@ describe('C2TestAdapter', function() { }); } - c2fsStatStub.withArgs(execPath2CopyPath) + fsStatStub.withArgs(execPath2CopyPath) .callsFake(handleStatExistsFile); vsfsWatchStub.withArgs(matchRelativePattern(execPath2CopyPath)) @@ -1587,7 +1587,7 @@ describe('C2TestAdapter', function() { let start: number = 0; const newRoot = await doAndWaitForReloadEvent(this, async () => { - c2fsStatStub.withArgs(execPath2CopyPath) + fsStatStub.withArgs(execPath2CopyPath) .callsFake(handleStatNotExists); start = Date.now(); watcher.sendDelete(); @@ -1600,7 +1600,7 @@ describe('C2TestAdapter', function() { throw Error('restore'); }); } - c2fsStatStub.withArgs(execPath2CopyPath).callsFake(() => { + fsStatStub.withArgs(execPath2CopyPath).callsFake(() => { throw Error('restore'); }); vsfsWatchStub.withArgs(matchRelativePattern(execPath2CopyPath)) @@ -1684,7 +1684,7 @@ describe('C2TestAdapter', function() { return new ChildProcessStub(example1.suite2.t1.outputs[0][1]); }); - c2fsStatStub.withArgs(execPath2CopyPath).callsFake(handleStatExistsFile); + fsStatStub.withArgs(execPath2CopyPath).callsFake(handleStatExistsFile); vsfsWatchStub.withArgs(matchRelativePattern(execPath2CopyPath)) .callsFake(handleCreateWatcherCb); @@ -1715,7 +1715,7 @@ describe('C2TestAdapter', function() { throw Error('restore'); }); } - c2fsStatStub.withArgs(execPath2CopyPath).callsFake(() => { + fsStatStub.withArgs(execPath2CopyPath).callsFake(() => { throw Error('restore'); }); vsfsWatchStub.withArgs(matchRelativePattern(execPath2CopyPath)) From bfbc990f2f6bb7aed86cc6240cff35ccfa133b63 Mon Sep 17 00:00:00 2001 From: Mate Pek Date: Sun, 28 Oct 2018 01:36:08 +0200 Subject: [PATCH 24/49] c2testadapter.cpp.ts --- .vscode/launch.json | 14 +- appveyor.yml | 4 + src/FsWrapper.ts | 38 ++--- src/test/C2TestAdapter.cpp.test.ts | 226 +++++++++++++++++++++++++++++ src/test/C2TestAdapter.test.ts | 17 +-- src/test/cpp/suite1.cpp | 2 +- src/test/cpp/suite2.cpp | 2 +- src/test/cpp/suite3.cpp | 2 +- 8 files changed, 266 insertions(+), 39 deletions(-) create mode 100644 src/test/C2TestAdapter.cpp.test.ts diff --git a/.vscode/launch.json b/.vscode/launch.json index 03df68e1..c43d3c79 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -6,12 +6,8 @@ "request": "launch", "name": "Catch2 adapter", "runtimeExecutable": "${execPath}", - "args": [ - "--extensionDevelopmentPath=${workspaceFolder}" - ], - "outFiles": [ - "${workspaceFolder}/out" - ] + "args": ["--extensionDevelopmentPath=${workspaceFolder}"], + "outFiles": ["${workspaceFolder}/out"] }, { "name": "Extension Tests", @@ -24,10 +20,8 @@ "--extensionDevelopmentPath=${workspaceFolder}", "--extensionTestsPath=${workspaceFolder}/out/test" ], - "outFiles": [ - "${workspaceFolder}/out/test/**/*.js" - ], + "outFiles": ["${workspaceFolder}/out/test/**/*.js"], "preLaunchTask": "npm: watch" } ] -} \ No newline at end of file +} diff --git a/appveyor.yml b/appveyor.yml index 0e5cb2e7..1abb24ec 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,3 +1,6 @@ +image: + - Visual Studio 2017 + platform: - x86 - x64 @@ -6,6 +9,7 @@ environment: matrix: - nodejs_version: LTS - nodejs_version: Current + - C2AVCVA: "C:\\Program Files (x86)\\Microsoft Visual Studio\\2017\\Community\\VC\\Auxiliary\\Build\\vcvarsall.bat" install: - ps: Install-Product node $env:nodejs_version diff --git a/src/FsWrapper.ts b/src/FsWrapper.ts index 2a203a9c..0d4f64a1 100644 --- a/src/FsWrapper.ts +++ b/src/FsWrapper.ts @@ -8,8 +8,8 @@ import * as fs from 'fs'; export type SpawnReturns = cp.SpawnSyncReturns; export function spawnAsync( - cmd: string, args?: string[], - options?: cp.SpawnOptions): Promise { + cmd: string, args?: string[], + options?: cp.SpawnOptions): Promise { return new Promise((resolve) => { const ret: SpawnReturns = { pid: 0, @@ -22,15 +22,19 @@ export function spawnAsync( }; const command = cp.spawn(cmd, args, options); ret.pid = command.pid; - command.stdout.on('data', function (data) { + command.stdout.on('data', function(data) { ret.stdout += data; ret.output[0] = ret.stdout; }); - command.on('close', function (code) { + command.on('error', function(err: Error) { + ret.error = err; + }); + command.on('close', function(code) { ret.status = code; + ret.error = new Error('code: ' + String(code)); resolve(ret) }); - command.on('error', function (err) { + command.on('error', function(err) { ret.error = err; resolve(ret); }); @@ -42,23 +46,23 @@ export type Stats = fs.Stats; export function statAsync(path: string): Promise { return new Promise((resolve, reject) => { fs.stat( - path, (err: NodeJS.ErrnoException | null, stats: fs.Stats | undefined) => { - if (stats) - resolve(stats); - else - reject(err); - }); + path, (err: NodeJS.ErrnoException|null, stats: fs.Stats|undefined) => { + if (stats) + resolve(stats); + else + reject(err); + }); }); } export function existsAsync(path: string): Promise { return statAsync(path).then( - () => { - return true; - }, - () => { - return false; - }); + () => { + return true; + }, + () => { + return false; + }); } export function existsSync(path: string): boolean { diff --git a/src/test/C2TestAdapter.cpp.test.ts b/src/test/C2TestAdapter.cpp.test.ts new file mode 100644 index 00000000..2149e0ca --- /dev/null +++ b/src/test/C2TestAdapter.cpp.test.ts @@ -0,0 +1,226 @@ +//----------------------------------------------------------------------------- +// 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. + +import * as assert from 'assert'; +import * as cp from 'child_process'; +import * as fse from 'fs-extra'; +import * as path from 'path'; +import {inspect, promisify} from 'util'; +import * as vscode from 'vscode'; +import {TestAdapter, TestEvent, TestLoadFinishedEvent, TestLoadStartedEvent, TestRunFinishedEvent, TestRunStartedEvent, TestSuiteEvent, TestSuiteInfo} from 'vscode-test-adapter-api'; +import {Log} from 'vscode-test-adapter-util'; + +import {C2TestAdapter} from '../C2TestAdapter'; +import * as c2fs from '../FsWrapper'; + +assert.notStrictEqual(vscode.workspace.workspaceFolders, undefined); +assert.equal(vscode.workspace.workspaceFolders!.length, 1); + +const workspaceFolderUri = vscode.workspace.workspaceFolders![0].uri; + +const workspaceFolder = + vscode.workspace.getWorkspaceFolder(workspaceFolderUri)!; + +const logger = + new Log('Catch2TestAdapter', workspaceFolder, 'Catch2TestAdapter'); + +const cppUri = vscode.Uri.file(path.join(workspaceFolderUri.fsPath, 'cpp')); + +function inCpp(relPath: string) { + return vscode.Uri.file(path.join(cppUri.fsPath, relPath)); +} + +/// + +describe('C2TestAdapter.cpp', function() { + async function compile(source: vscode.Uri, output: vscode.Uri) { + const isWin = process.platform === 'win32'; + + if (isWin) { + assert.notStrictEqual(process.env['C2AVCVA'], undefined); + const vcvarsall = vscode.Uri.file(process.env['C2AVCVA']!); + await promisify(cp.exec)('"' + vcvarsall.fsPath + '" x86 && "' + [ + 'cl.exe', + '/I"' + path.dirname(source.fsPath) + '"', + '/Fe"' + output.fsPath + '"', + '"' + source.fsPath + '"', + ].join(' ')); + } else { + await promisify(cp.exec)('"' + [ + 'c++', + '-x', + 'c++', + '-std=c++11', + '-o', + output.fsPath, + source.fsPath, + ].join('" "') + '"'); + } + assert.ok(await c2fs.existsAsync(output.fsPath)); + } + + before(async function() { + await fse.remove(cppUri.fsPath); + await fse.mkdirp(cppUri.fsPath); + + if (!await c2fs.existsAsync(inCpp('../suite1.exe').fsPath)) + await compile( + inCpp('../../../src/test/cpp/suite1.cpp'), inCpp('../suite1.exe')); + + if (!await c2fs.existsAsync(inCpp('../suite2.exe').fsPath)) + await compile( + inCpp('../../../src/test/cpp/suite2.cpp'), inCpp('../suite2.exe')); + + if (!await c2fs.existsAsync(inCpp('../suite3.exe').fsPath)) + await compile( + inCpp('../../../src/test/cpp/suite3.cpp'), inCpp('../suite3.exe')); + }) + + after(function() { + return fse.remove(cppUri.fsPath); + }) + + async function waitFor( + test: Mocha.Context, condition: Function, + timeout: number = 1000): Promise { + const start = Date.now(); + let c; + while (!(c = await condition()) && + (Date.now() - start < timeout || !test.enableTimeouts())) + await promisify(setTimeout)(10); + assert.ok(c); + } + + function copy(from: string, to: string) { + return fse.copy( + vscode.Uri.file(path.join(cppUri.fsPath, from)).fsPath, + vscode.Uri.file(path.join(cppUri.fsPath, to)).fsPath); + } + + let adapter: C2TestAdapter|undefined; + let testsEventsConnection: vscode.Disposable|undefined; + let testStatesEventsConnection: vscode.Disposable|undefined; + let testsEvents: (TestLoadStartedEvent|TestLoadFinishedEvent)[] = []; + let testStatesEvents: (TestRunStartedEvent|TestRunFinishedEvent| + TestSuiteEvent|TestEvent)[] = []; + + function createAdapterAndSubscribe() { + adapter = new C2TestAdapter(workspaceFolder, logger); + + testsEventsConnection = + adapter.tests((e: TestLoadStartedEvent|TestLoadFinishedEvent) => { + testsEvents.push(e); + }); + + testStatesEvents = []; + testStatesEventsConnection = adapter.testStates( + (e: TestRunStartedEvent|TestRunFinishedEvent|TestSuiteEvent| + TestEvent) => { + testStatesEvents.push(e); + }); + + return adapter!; + } + + async function load(adapter: TestAdapter): Promise { + const eventCount = testsEvents.length; + await adapter.load(); + if (testsEvents.length != eventCount + 2) debugger; + assert.strictEqual( + testsEvents.length, eventCount + 2, inspect(testsEvents)); + const finished = testsEvents.pop()!; + assert.strictEqual(finished.type, 'finished'); + assert.strictEqual(testsEvents.pop()!.type, 'started'); + assert.notStrictEqual((finished).suite, undefined); + return (finished).suite!; + } + + function disposeAdapterAndSubscribers(check: boolean = true) { + adapter && adapter.dispose(); + testsEventsConnection && testsEventsConnection.dispose(); + testStatesEventsConnection && testStatesEventsConnection.dispose(); + testStatesEvents = []; + if (check) { + for (let i = 0; i < testsEvents.length; i++) { + assert.deepStrictEqual( + {type: 'started'}, testsEvents[i], + inspect({index: i, testsEvents: testsEvents})); + i++; + assert.ok( + i < testsEvents.length, + inspect({index: i, testsEvents: testsEvents})); + assert.equal( + testsEvents[i].type, 'finished', + inspect({index: i, testsEvents: testsEvents})); + assert.ok( + (testsEvents[i]).suite, + inspect({index: i, testsEvents: testsEvents})); + } + } + testsEvents = []; + } + + function getConfig() { + return vscode.workspace.getConfiguration( + 'catch2TestExplorer', workspaceFolderUri); + } + + async function updateConfig(key: string, value: any) { + let count = testsEvents.length; + await getConfig().update(key, value); + // cleanup + while (testsEvents.length < count--) testsEvents.pop(); + } + + context('example1', function() { + it('shoud be notified by watcher', async function() { + this.timeout(10000); + this.slow(3500); + await updateConfig( + 'executables', [{ + 'name': '${baseFilename}', + 'pattern': 'cpp/{build,Build,BUILD,out,Out,OUT}/**/*suite[0-9]*', + 'cwd': '${workspaceFolder}/cpp', + }]); + + adapter = createAdapterAndSubscribe(); + const root = await load(adapter); + assert.strictEqual(root.children.length, 0); + + await copy('../suite1.exe', 'out/suite1.exe'); + + await waitFor(this, () => { + return root.children.length == 1; + }); + + await copy('../suite2.exe', 'out/sub/suite2X.exe'); + + await waitFor(this, () => { + return root.children.length == 2; + }); + + await copy('../suite2.exe', 'out/sub/suite2.exe'); + + await waitFor(this, () => { + return root.children.length == 3; + }); + + await fse.remove(inCpp('out/sub/suite2X.exe').fsPath); + + await updateConfig('defaultWatchTimeoutSec', 1); + + await waitFor(this, () => { + return root.children.length == 2; + }, 3100); + + const eventCount = testStatesEvents.length; + await adapter.run([root.id]); + assert.strictEqual(testStatesEvents.length, eventCount + 16); + + disposeAdapterAndSubscribers(); + await updateConfig('defaultWatchTimeoutSec', undefined); + await updateConfig('executables', undefined); + }) + }) +}) \ No newline at end of file diff --git a/src/test/C2TestAdapter.test.ts b/src/test/C2TestAdapter.test.ts index 46b1e6d6..27928553 100644 --- a/src/test/C2TestAdapter.test.ts +++ b/src/test/C2TestAdapter.test.ts @@ -14,13 +14,14 @@ import * as sinon from 'sinon'; import {TestEvent, TestLoadFinishedEvent, TestLoadStartedEvent, TestRunFinishedEvent, TestRunStartedEvent, TestSuiteEvent, TestSuiteInfo, TestInfo, TestAdapter} from 'vscode-test-adapter-api'; import {Log} from 'vscode-test-adapter-util'; import {inspect, promisify} from 'util'; +import { EOL } from 'os'; import {C2TestAdapter} from '../C2TestAdapter'; import {example1} from './example1'; import {ChildProcessStub, FileSystemWatcherStub} from './Helpers'; import * as Mocha from 'mocha'; -assert.notEqual(vscode.workspace.workspaceFolders, undefined); +assert.notStrictEqual(vscode.workspace.workspaceFolders, undefined); assert.equal(vscode.workspace.workspaceFolders!.length, 1); const workspaceFolderUri = vscode.workspace.workspaceFolders![0].uri; @@ -38,8 +39,6 @@ const sinonSandbox = sinon.createSandbox(); /// describe('C2TestAdapter', function() { - this.enableTimeouts(false); // TODO - let testsEvents: (TestLoadStartedEvent|TestLoadFinishedEvent)[] = []; let testStatesEvents: (TestRunStartedEvent|TestRunFinishedEvent| TestSuiteEvent|TestEvent)[] = []; @@ -91,7 +90,7 @@ describe('C2TestAdapter', function() { o[o.type] === (v)[v.type]; return deepStrictEqual(o, v); }); - assert.notEqual( + assert.notStrictEqual( i, -1, 'testStatesEvI failed to find: ' + inspect(o) + '\n\nin\n\n' + inspect(testStatesEvents)); @@ -250,7 +249,7 @@ describe('C2TestAdapter', function() { assert.equal(testsEvents[0].type, 'started'); assert.equal(testsEvents[1].type, 'finished'); const suite = (testsEvents[1]).suite; - assert.notEqual(suite, undefined); + assert.notStrictEqual(suite, undefined); assert.equal(suite!.children.length, 0); disposeAdapterAndSubscribers(); }) @@ -909,9 +908,9 @@ describe('C2TestAdapter', function() { async function() { const m = example1.suite1.t1.outputs[0][1].match(']+>'); - assert.notEqual(m, undefined); - assert.notEqual(m!.input, undefined); - assert.notEqual(m!.index, undefined); + assert.notStrictEqual(m, undefined); + assert.notStrictEqual(m!.input, undefined); + assert.notStrictEqual(m!.index, undefined); const part = m!.input!.substr(0, m!.index! + m![0].length); const withArgs = spawnStub.withArgs( example1.suite1.execPath, example1.suite1.t1.outputs[0][0]); @@ -1200,7 +1199,7 @@ describe('C2TestAdapter', function() { example1.suite1.execPath, example1.suite1.outputs[1][0]); withArgs.onCall(withArgs.callCount) .returns(new ChildProcessStub( - testListOutput.join('\n'))); // TODO EOL + testListOutput.join(EOL))); const oldRootChildren = [...root.children]; const oldSuite1Children = [...suite1.children]; diff --git a/src/test/cpp/suite1.cpp b/src/test/cpp/suite1.cpp index 6fd2aa61..0ec23395 100644 --- a/src/test/cpp/suite1.cpp +++ b/src/test/cpp/suite1.cpp @@ -1,5 +1,5 @@ #define CATCH_CONFIG_MAIN -#include +#include "catch.hpp" // c++ -x c++ -std=c++17 -I ../Catch2/single_include -O0 -g -o suite1 // ../vscode-catch2-test-adapter/src/test/suite1.cpp diff --git a/src/test/cpp/suite2.cpp b/src/test/cpp/suite2.cpp index 2f97c6cf..1028cb4b 100644 --- a/src/test/cpp/suite2.cpp +++ b/src/test/cpp/suite2.cpp @@ -1,5 +1,5 @@ #define CATCH_CONFIG_MAIN -#include +#include "catch.hpp" // c++ -x c++ -std=c++17 -I ../Catch2/single_include -O0 -g -o suite2 // ../vscode-catch2-test-adapter/src/test/suite2.cpp diff --git a/src/test/cpp/suite3.cpp b/src/test/cpp/suite3.cpp index d067bf24..8924cf41 100644 --- a/src/test/cpp/suite3.cpp +++ b/src/test/cpp/suite3.cpp @@ -1,5 +1,5 @@ #define CATCH_CONFIG_MAIN -#include +#include "catch.hpp" // clang-format off // c++ -x c++ -std=c++17 -I ../Catch2/single_include -O0 -g -o suite3 ../vscode-catch2-test-adapter/src/test/suite3.cpp From 6fcb63880bed8475e66d0184d23f1b21a9c8e843 Mon Sep 17 00:00:00 2001 From: Mate Pek Date: Sun, 28 Oct 2018 08:19:32 +0700 Subject: [PATCH 25/49] lot of problems --- .vscode/launch.json | 19 +- src/C2ExecutableInfo.ts | 99 +- src/C2TestAdapter.ts | 149 +- src/test/C2TestAdapter.cpp.test.ts | 149 +- src/test/C2TestAdapter.test.ts | 2046 ++++++++++++++-------------- 5 files changed, 1253 insertions(+), 1209 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index c43d3c79..d128583f 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -6,8 +6,12 @@ "request": "launch", "name": "Catch2 adapter", "runtimeExecutable": "${execPath}", - "args": ["--extensionDevelopmentPath=${workspaceFolder}"], - "outFiles": ["${workspaceFolder}/out"] + "args": [ + "--extensionDevelopmentPath=${workspaceFolder}" + ], + "outFiles": [ + "${workspaceFolder}/out" + ] }, { "name": "Extension Tests", @@ -20,8 +24,13 @@ "--extensionDevelopmentPath=${workspaceFolder}", "--extensionTestsPath=${workspaceFolder}/out/test" ], - "outFiles": ["${workspaceFolder}/out/test/**/*.js"], - "preLaunchTask": "npm: watch" + "outFiles": [ + "${workspaceFolder}/out/test/**/*.js" + ], + "preLaunchTask": "npm: watch", + "env": { + "C2AVCVA": "c:\\Program Files (x86)\\Microsoft Visual Studio\\2017\\Community\\VC\\Auxiliary\\Build\\vcvarsall.bat" + } } ] -} +} \ No newline at end of file diff --git a/src/C2ExecutableInfo.ts b/src/C2ExecutableInfo.ts index e06ab14a..c3620e5a 100644 --- a/src/C2ExecutableInfo.ts +++ b/src/C2ExecutableInfo.ts @@ -44,9 +44,8 @@ export class C2ExecutableInfo implements vscode.Disposable { let fileUris: vscode.Uri[] = []; if (!isAbsolute || (isAbsolute && isPartOfWs)) { - let relativePattern: vscode.RelativePattern; - relativePattern = new vscode.RelativePattern( - this._adapter.workspaceFolder, relativeToWs); + const relativePattern = new vscode.RelativePattern( + this._adapter.workspaceFolder, this.pattern); fileUris = await vscode.workspace.findFiles(relativePattern, undefined, 1000); try { @@ -120,7 +119,7 @@ export class C2ExecutableInfo implements vscode.Disposable { } const suite = this._allTests.createChildSuite( - resolvedName, file.fsPath, {cwd: resolvedCwd, env: resolvedEnv}); + resolvedName, file.fsPath, { cwd: resolvedCwd, env: resolvedEnv }); this._executables.set(file.fsPath, suite); @@ -128,8 +127,8 @@ export class C2ExecutableInfo implements vscode.Disposable { } private readonly _lastEventArrivedAt: - Map = new Map(); + Map = new Map(); private _handleEverything(uri: vscode.Uri) { let suite = this._executables.get(uri.fsPath); @@ -148,44 +147,44 @@ export class C2ExecutableInfo implements vscode.Disposable { this._lastEventArrivedAt.set(uri.fsPath, Date.now()); const x = - (exists: boolean, timeout: number, delay: number): Promise => { - let lastEventArrivedAt = this._lastEventArrivedAt.get(uri.fsPath); - if (lastEventArrivedAt === undefined) { - this._adapter.log.error('assert in ' + __filename); - debugger; - return Promise.resolve(); - } - if (Date.now() - lastEventArrivedAt! > timeout) { - this._lastEventArrivedAt.delete(uri.fsPath); - this._executables.delete(uri.fsPath); - this._adapter.testsEmitter.fire({type: 'started'}); - this._allTests.removeChild(suite!); - this._adapter.testsEmitter.fire( - {type: 'finished', suite: this._allTests}); - return Promise.resolve(); - } else if (exists) { - return this._adapter.queue.then(() => { - this._adapter.testsEmitter.fire({type: 'started'}); - return suite!.reloadChildren().then( - () => { - this._adapter.testsEmitter.fire( - {type: 'finished', suite: this._allTests}); - this._lastEventArrivedAt.delete(uri.fsPath); - }, - (err: any) => { - this._adapter.testsEmitter.fire( - {type: 'finished', suite: this._allTests}); - this._adapter.log.warn(inspect(err)); - 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)); - }); + (exists: boolean, timeout: number, delay: number): Promise => { + let lastEventArrivedAt = this._lastEventArrivedAt.get(uri.fsPath); + if (lastEventArrivedAt === undefined) { + this._adapter.log.error('assert in ' + __filename); + debugger; + return Promise.resolve(); + } + if (Date.now() - lastEventArrivedAt! > timeout) { + this._lastEventArrivedAt.delete(uri.fsPath); + this._executables.delete(uri.fsPath); + this._adapter.testsEmitter.fire({ type: 'started' }); + this._allTests.removeChild(suite!); + this._adapter.testsEmitter.fire( + { type: 'finished', suite: this._allTests }); + return Promise.resolve(); + } else if (exists) { + return this._adapter.queue.then(() => { + this._adapter.testsEmitter.fire({ type: 'started' }); + return suite!.reloadChildren().then( + () => { + this._adapter.testsEmitter.fire( + { type: 'finished', suite: this._allTests }); + this._lastEventArrivedAt.delete(uri.fsPath); + }, + (err: any) => { + this._adapter.testsEmitter.fire( + { type: 'finished', suite: this._allTests }); + this._adapter.log.warn(inspect(err)); + 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)); }); - }; + }); + }; // change event can arrive during debug session on osx (why?) // if (!this.isDebugging) { x(false, this._adapter.getExecWatchTimeout(), 64); @@ -227,12 +226,12 @@ export class C2ExecutableInfo implements vscode.Disposable { private _verifyIsCatch2TestExecutable(path: string): Promise { return c2fs.spawnAsync(path, ['--help']) - .then(res => { - return res.stdout.indexOf('Catch v2.') != -1; - }) - .catch(e => { - this._adapter.log.error(inspect(e)); - return false; - }); + .then(res => { + return res.stdout.indexOf('Catch v2.') != -1; + }) + .catch(e => { + this._adapter.log.error(inspect(e)); + return false; + }); } } diff --git a/src/C2TestAdapter.ts b/src/C2TestAdapter.ts index de8d29e0..22a15e47 100644 --- a/src/C2TestAdapter.ts +++ b/src/C2TestAdapter.ts @@ -3,23 +3,24 @@ // public domain. The author hereby disclaims copyright to this source code. import * as path from 'path'; -import {inspect} from 'util'; +import { inspect } from 'util'; import * as vscode from 'vscode'; -import {TestAdapter, TestEvent, TestLoadFinishedEvent, TestLoadStartedEvent, TestRunFinishedEvent, TestRunStartedEvent, TestSuiteEvent} from 'vscode-test-adapter-api'; +import { TestAdapter, TestEvent, TestLoadFinishedEvent, TestLoadStartedEvent, TestRunFinishedEvent, TestRunStartedEvent, TestSuiteEvent } from 'vscode-test-adapter-api'; import * as util from 'vscode-test-adapter-util'; -import {C2AllTestSuiteInfo} from './C2AllTestSuiteInfo'; -import {C2ExecutableInfo} from './C2ExecutableInfo'; -import {C2TestInfo} from './C2TestInfo'; -import {resolveVariables} from './Helpers'; -import {QueueGraphNode} from './QueueGraph'; +import { C2AllTestSuiteInfo } from './C2AllTestSuiteInfo'; +import { C2ExecutableInfo } from './C2ExecutableInfo'; +import { C2TestInfo } from './C2TestInfo'; +import { resolveVariables } from './Helpers'; +import { QueueGraphNode } from './QueueGraph'; +import { generateUniqueId } from './IdGenerator'; export class C2TestAdapter implements TestAdapter, vscode.Disposable { readonly testsEmitter = - new vscode.EventEmitter(); + new vscode.EventEmitter(); readonly testStatesEmitter = - new vscode.EventEmitter(); + new vscode.EventEmitter(); private readonly autorunEmitter = new vscode.EventEmitter(); readonly variableToValue: [string, string][] = [ @@ -34,38 +35,38 @@ export class C2TestAdapter implements TestAdapter, vscode.Disposable { private readonly disposables: Array = new Array(); constructor( - public readonly workspaceFolder: vscode.WorkspaceFolder, - public readonly log: util.Log) { + public readonly workspaceFolder: vscode.WorkspaceFolder, + public readonly log: util.Log) { this.disposables.push(this.testsEmitter); this.disposables.push(this.testStatesEmitter); this.disposables.push(this.autorunEmitter); this.disposables.push( - vscode.workspace.onDidChangeConfiguration(configChange => { - if (configChange.affectsConfiguration( - 'catch2TestExplorer.defaultEnv', this.workspaceFolder.uri) || - configChange.affectsConfiguration( - 'catch2TestExplorer.defaultCwd', this.workspaceFolder.uri) || - configChange.affectsConfiguration( - 'catch2TestExplorer.executables', this.workspaceFolder.uri)) { - this.load(); - } - })); + vscode.workspace.onDidChangeConfiguration(configChange => { + if (configChange.affectsConfiguration( + 'catch2TestExplorer.defaultEnv', this.workspaceFolder.uri) || + configChange.affectsConfiguration( + 'catch2TestExplorer.defaultCwd', this.workspaceFolder.uri) || + configChange.affectsConfiguration( + 'catch2TestExplorer.executables', this.workspaceFolder.uri)) { + this.load(); + } + })); this.disposables.push( - vscode.workspace.onDidChangeConfiguration(configChange => { - if (configChange.affectsConfiguration( - 'catch2TestExplorer.enableSourceDecoration', - this.workspaceFolder.uri)) { - this.isEnabledSourceDecoration = - this.getEnableSourceDecoration(this.getConfiguration()); - } - if (configChange.affectsConfiguration( - 'catch2TestExplorer.defaultRngSeed', - this.workspaceFolder.uri)) { - this.autorunEmitter.fire(); - } - })); + vscode.workspace.onDidChangeConfiguration(configChange => { + if (configChange.affectsConfiguration( + 'catch2TestExplorer.enableSourceDecoration', + this.workspaceFolder.uri)) { + this.isEnabledSourceDecoration = + this.getEnableSourceDecoration(this.getConfiguration()); + } + if (configChange.affectsConfiguration( + 'catch2TestExplorer.defaultRngSeed', + this.workspaceFolder.uri)) { + this.autorunEmitter.fire(); + } + })); this.allTests = new C2AllTestSuiteInfo(this); } @@ -77,12 +78,12 @@ export class C2TestAdapter implements TestAdapter, vscode.Disposable { this.allTests.dispose(); } - get testStates(): vscode.Event { + get testStates(): vscode.Event { return this.testStatesEmitter.event; } - get tests(): vscode.Event { + get tests(): vscode.Event { return this.testsEmitter.event; } @@ -94,7 +95,7 @@ export class C2TestAdapter implements TestAdapter, vscode.Disposable { return this.isEnabledSourceDecoration; } - getRngSeed(): string|number|null { + getRngSeed(): string | number | null { return this.getDefaultRngSeed(this.getConfiguration()); } @@ -112,16 +113,16 @@ export class C2TestAdapter implements TestAdapter, vscode.Disposable { this.allTests.dispose(); this.allTests = new C2AllTestSuiteInfo(this); - this.testsEmitter.fire({type: 'started'}); + this.testsEmitter.fire({ type: 'started', uid: generateUniqueId(), allTests: this.allTests }); const config = this.getConfiguration(); await this.allTests.load(this.getExecutables(config, this.allTests)); - this.testsEmitter.fire({type: 'finished', suite: this.allTests}); + this.testsEmitter.fire({ type: 'finished', suite: this.allTests }); } catch (e) { this.testsEmitter.fire( - {type: 'finished', suite: undefined, errorMessage: e.message}); + { type: 'finished', suite: undefined, errorMessage: e.message }); } } @@ -140,8 +141,8 @@ export class C2TestAdapter implements TestAdapter, vscode.Disposable { this.isRunning -= 1; }; return this.allTests - .run(tests, this.getWorkerMaxNumber(this.getConfiguration())) - .then(always, always); + .run(tests, this.getWorkerMaxNumber(this.getConfiguration())) + .then(always, always); } throw Error('Catch2 Test Adapter: Test(s) are currently being run.'); @@ -174,7 +175,7 @@ export class C2TestAdapter implements TestAdapter, vscode.Disposable { }; const template = - this.getDebugConfigurationTemplate(this.getConfiguration()); + this.getDebugConfigurationTemplate(this.getConfiguration()); let resolveDebugVariables: [string, any][] = this.variableToValue; resolveDebugVariables = resolveDebugVariables.concat([ ['${label}', testInfo.label], ['${exec}', testInfo.parent.execPath], @@ -208,7 +209,7 @@ export class C2TestAdapter implements TestAdapter, vscode.Disposable { this.isDebugging = true; const debugSessionStarted = - await vscode.debug.startDebugging(this.workspaceFolder, debugConfig); + await vscode.debug.startDebugging(this.workspaceFolder, debugConfig); if (!debugSessionStarted) { console.error('Failed starting the debug session - aborting'); @@ -239,16 +240,15 @@ export class C2TestAdapter implements TestAdapter, vscode.Disposable { private getConfiguration(): vscode.WorkspaceConfiguration { return vscode.workspace.getConfiguration( - 'catch2TestExplorer', this.workspaceFolder.uri); + 'catch2TestExplorer', this.workspaceFolder.uri); } - private getDebugConfigurationTemplate(config: vscode.WorkspaceConfiguration): - {[prop: string]: string}|null { + private getDebugConfigurationTemplate(config: vscode.WorkspaceConfiguration): { [prop: string]: string } | null { const o = config.get('debugConfigTemplate', null); if (o === null) return null; - const result: {[prop: string]: string} = {}; + const result: { [prop: string]: string } = {}; for (const prop in o) { const val = o[prop]; @@ -262,12 +262,11 @@ export class C2TestAdapter implements TestAdapter, vscode.Disposable { } private getGlobalAndDefaultEnvironmentVariables( - config: vscode.WorkspaceConfiguration): - {[prop: string]: string|undefined} { + config: vscode.WorkspaceConfiguration): { [prop: string]: string | undefined } { const processEnv = process.env; - const configEnv: {[prop: string]: any} = config.get('defaultEnv') || {}; + const configEnv: { [prop: string]: any } = config.get('defaultEnv') || {}; - const resultEnv = {...processEnv}; + const resultEnv = { ...processEnv }; for (const prop in configEnv) { const val = configEnv[prop]; @@ -284,7 +283,7 @@ export class C2TestAdapter implements TestAdapter, vscode.Disposable { private getDefaultCwd(config: vscode.WorkspaceConfiguration): string { const dirname = this.workspaceFolder.uri.fsPath; const cwd = resolveVariables( - config.get('defaultCwd', dirname), this.variableToValue); + config.get('defaultCwd', dirname), this.variableToValue); if (path.isAbsolute(cwd)) { return cwd; } else { @@ -293,8 +292,8 @@ export class C2TestAdapter implements TestAdapter, vscode.Disposable { } private getDefaultRngSeed(config: vscode.WorkspaceConfiguration): string - |number|null { - return config.get('defaultRngSeed', null); + | number | null { + return config.get('defaultRngSeed', null); } getDefaultExecWatchTimeout(config: vscode.WorkspaceConfiguration): number { @@ -306,10 +305,10 @@ export class C2TestAdapter implements TestAdapter, vscode.Disposable { } private getGlobalAndCurrentEnvironmentVariables( - config: vscode.WorkspaceConfiguration, - configEnv: {[prop: string]: any}): {[prop: string]: any} { + config: vscode.WorkspaceConfiguration, + configEnv: { [prop: string]: any }): { [prop: string]: any } { const processEnv = process.env; - const resultEnv = {...processEnv}; + const resultEnv = { ...processEnv }; for (const prop in configEnv) { const val = configEnv[prop]; @@ -324,23 +323,23 @@ export class C2TestAdapter implements TestAdapter, vscode.Disposable { } private getEnableSourceDecoration(config: vscode.WorkspaceConfiguration): - boolean { + boolean { return config.get('enableSourceDecoration', true); } private getExecutables( - config: vscode.WorkspaceConfiguration, - allTests: C2AllTestSuiteInfo): C2ExecutableInfo[] { + config: vscode.WorkspaceConfiguration, + allTests: C2AllTestSuiteInfo): C2ExecutableInfo[] { const globalWorkingDirectory = this.getDefaultCwd(config); let executables: C2ExecutableInfo[] = []; - const configExecs:|undefined|string|string[]|{[prop: string]: any}| - {[prop: string]: any}[] = config.get('executables'); + const configExecs: |undefined | string | string[] | { [prop: string]: any } | + { [prop: string]: any }[] = config.get('executables'); - const createFromObject = (obj: {[prop: string]: any}): C2ExecutableInfo => { + const createFromObject = (obj: { [prop: string]: any }): C2ExecutableInfo => { const name: string = - obj.hasOwnProperty('name') ? obj.name : '${relName} (${relDirname}/)'; + obj.hasOwnProperty('name') ? obj.name : '${relName} (${relDirname}/)'; let pattern: string = ''; if (obj.hasOwnProperty('pattern') && typeof obj.pattern == 'string') @@ -351,11 +350,11 @@ export class C2TestAdapter implements TestAdapter, vscode.Disposable { throw Error('Error: pattern or path property is required.'); const cwd: string = - obj.hasOwnProperty('cwd') ? obj.cwd : globalWorkingDirectory; + obj.hasOwnProperty('cwd') ? obj.cwd : globalWorkingDirectory; - const env: {[prop: string]: any} = obj.hasOwnProperty('env') ? - this.getGlobalAndCurrentEnvironmentVariables(config, obj.env) : - this.getGlobalAndDefaultEnvironmentVariables(config); + const env: { [prop: string]: any } = obj.hasOwnProperty('env') ? + this.getGlobalAndCurrentEnvironmentVariables(config, obj.env) : + this.getGlobalAndDefaultEnvironmentVariables(config); return new C2ExecutableInfo(this, allTests, name, pattern, cwd, env); }; @@ -363,8 +362,8 @@ export class C2TestAdapter implements TestAdapter, vscode.Disposable { if (typeof configExecs === 'string') { if (configExecs.length == 0) return []; executables.push(new C2ExecutableInfo( - this, allTests, configExecs, configExecs, globalWorkingDirectory, - {})); + this, allTests, configExecs, configExecs, globalWorkingDirectory, + {})); } else if (Array.isArray(configExecs)) { for (var i = 0; i < configExecs.length; ++i) { const configExe = configExecs[i]; @@ -372,8 +371,8 @@ export class C2TestAdapter implements TestAdapter, vscode.Disposable { const configExecsName = String(configExe); if (configExecsName.length > 0) { executables.push(new C2ExecutableInfo( - this, allTests, configExecsName, configExecsName, - globalWorkingDirectory, {})); + this, allTests, configExecsName, configExecsName, + globalWorkingDirectory, {})); } } else { try { diff --git a/src/test/C2TestAdapter.cpp.test.ts b/src/test/C2TestAdapter.cpp.test.ts index 2149e0ca..f2aa528a 100644 --- a/src/test/C2TestAdapter.cpp.test.ts +++ b/src/test/C2TestAdapter.cpp.test.ts @@ -6,12 +6,12 @@ import * as assert from 'assert'; import * as cp from 'child_process'; import * as fse from 'fs-extra'; import * as path from 'path'; -import {inspect, promisify} from 'util'; +import { inspect, promisify } from 'util'; import * as vscode from 'vscode'; -import {TestAdapter, TestEvent, TestLoadFinishedEvent, TestLoadStartedEvent, TestRunFinishedEvent, TestRunStartedEvent, TestSuiteEvent, TestSuiteInfo} from 'vscode-test-adapter-api'; -import {Log} from 'vscode-test-adapter-util'; +import { TestAdapter, TestEvent, TestLoadFinishedEvent, TestLoadStartedEvent, TestRunFinishedEvent, TestRunStartedEvent, TestSuiteEvent, TestSuiteInfo } from 'vscode-test-adapter-api'; +import { Log } from 'vscode-test-adapter-util'; -import {C2TestAdapter} from '../C2TestAdapter'; +import { C2TestAdapter } from '../C2TestAdapter'; import * as c2fs from '../FsWrapper'; assert.notStrictEqual(vscode.workspace.workspaceFolders, undefined); @@ -20,10 +20,10 @@ assert.equal(vscode.workspace.workspaceFolders!.length, 1); const workspaceFolderUri = vscode.workspace.workspaceFolders![0].uri; const workspaceFolder = - vscode.workspace.getWorkspaceFolder(workspaceFolderUri)!; + vscode.workspace.getWorkspaceFolder(workspaceFolderUri)!; const logger = - new Log('Catch2TestAdapter', workspaceFolder, 'Catch2TestAdapter'); + new Log('Catch2TestAdapter', workspaceFolder, 'Catch2TestAdapter'); const cppUri = vscode.Uri.file(path.join(workspaceFolderUri.fsPath, 'cpp')); @@ -31,21 +31,23 @@ function inCpp(relPath: string) { return vscode.Uri.file(path.join(cppUri.fsPath, relPath)); } +const isWin = process.platform === 'win32'; + /// -describe('C2TestAdapter.cpp', function() { +describe.skip('C2TestAdapter.cpp', function () { + this.enableTimeouts(false);//TODO async function compile(source: vscode.Uri, output: vscode.Uri) { - const isWin = process.platform === 'win32'; - if (isWin) { - assert.notStrictEqual(process.env['C2AVCVA'], undefined); + assert.notStrictEqual(process.env['C2AVCVA'], undefined, inspect(process.env)); const vcvarsall = vscode.Uri.file(process.env['C2AVCVA']!); - await promisify(cp.exec)('"' + vcvarsall.fsPath + '" x86 && "' + [ + const command = '"' + vcvarsall.fsPath + '" x86 && ' + [ 'cl.exe', '/I"' + path.dirname(source.fsPath) + '"', '/Fe"' + output.fsPath + '"', '"' + source.fsPath + '"', - ].join(' ')); + ].join(' '); + await promisify(cp.exec)(command); } else { await promisify(cp.exec)('"' + [ 'c++', @@ -60,65 +62,72 @@ describe('C2TestAdapter.cpp', function() { assert.ok(await c2fs.existsAsync(output.fsPath)); } - before(async function() { + before(async function () { + this.timeout(50000); + await fse.remove(cppUri.fsPath); await fse.mkdirp(cppUri.fsPath); if (!await c2fs.existsAsync(inCpp('../suite1.exe').fsPath)) await compile( - inCpp('../../../src/test/cpp/suite1.cpp'), inCpp('../suite1.exe')); + inCpp('../../../src/test/cpp/suite1.cpp'), inCpp('../suite1.exe')); if (!await c2fs.existsAsync(inCpp('../suite2.exe').fsPath)) await compile( - inCpp('../../../src/test/cpp/suite2.cpp'), inCpp('../suite2.exe')); + inCpp('../../../src/test/cpp/suite2.cpp'), inCpp('../suite2.exe')); if (!await c2fs.existsAsync(inCpp('../suite3.exe').fsPath)) await compile( - inCpp('../../../src/test/cpp/suite3.cpp'), inCpp('../suite3.exe')); + inCpp('../../../src/test/cpp/suite3.cpp'), inCpp('../suite3.exe')); }) - after(function() { - return fse.remove(cppUri.fsPath); + after(async function () { + await fse.remove(cppUri.fsPath); + await fse.remove(inCpp('../suite1.exe').fsPath); + await fse.remove(inCpp('../suite2.exe').fsPath); + await fse.remove(inCpp('../suite3.exe').fsPath); }) async function waitFor( - test: Mocha.Context, condition: Function, - timeout: number = 1000): Promise { + test: Mocha.Context, condition: Function, + timeout: number = 1000): Promise { const start = Date.now(); let c; while (!(c = await condition()) && - (Date.now() - start < timeout || !test.enableTimeouts())) + (Date.now() - start < timeout || !test.enableTimeouts())) await promisify(setTimeout)(10); assert.ok(c); } function copy(from: string, to: string) { return fse.copy( - vscode.Uri.file(path.join(cppUri.fsPath, from)).fsPath, - vscode.Uri.file(path.join(cppUri.fsPath, to)).fsPath); + vscode.Uri.file(path.join(cppUri.fsPath, from)).fsPath, + vscode.Uri.file(path.join(cppUri.fsPath, to)).fsPath); } - let adapter: C2TestAdapter|undefined; - let testsEventsConnection: vscode.Disposable|undefined; - let testStatesEventsConnection: vscode.Disposable|undefined; - let testsEvents: (TestLoadStartedEvent|TestLoadFinishedEvent)[] = []; - let testStatesEvents: (TestRunStartedEvent|TestRunFinishedEvent| - TestSuiteEvent|TestEvent)[] = []; + let adapter: C2TestAdapter | undefined; + let testsEventsConnection: vscode.Disposable | undefined; + let testStatesEventsConnection: vscode.Disposable | undefined; + let testsEvents: (TestLoadStartedEvent | TestLoadFinishedEvent)[] = []; + let testStatesEvents: (TestRunStartedEvent | TestRunFinishedEvent | + TestSuiteEvent | TestEvent)[] = []; function createAdapterAndSubscribe() { adapter = new C2TestAdapter(workspaceFolder, logger); + testsEvents = []; testsEventsConnection = - adapter.tests((e: TestLoadStartedEvent|TestLoadFinishedEvent) => { - testsEvents.push(e); - }); + adapter.tests((e: TestLoadStartedEvent | TestLoadFinishedEvent) => { + if (testsEvents.length % 2 == 1 && e.type == 'started') debugger; + testsEvents.push(e); + }); testStatesEvents = []; testStatesEventsConnection = adapter.testStates( - (e: TestRunStartedEvent|TestRunFinishedEvent|TestSuiteEvent| - TestEvent) => { - testStatesEvents.push(e); - }); + (e: TestRunStartedEvent | TestRunFinishedEvent | TestSuiteEvent | + TestEvent) => { + testStatesEvents.push(e); + }); return adapter!; } @@ -128,7 +137,7 @@ describe('C2TestAdapter.cpp', function() { await adapter.load(); if (testsEvents.length != eventCount + 2) debugger; assert.strictEqual( - testsEvents.length, eventCount + 2, inspect(testsEvents)); + testsEvents.length, eventCount + 2, inspect(testsEvents)); const finished = testsEvents.pop()!; assert.strictEqual(finished.type, 'finished'); assert.strictEqual(testsEvents.pop()!.type, 'started'); @@ -144,18 +153,18 @@ describe('C2TestAdapter.cpp', function() { if (check) { for (let i = 0; i < testsEvents.length; i++) { assert.deepStrictEqual( - {type: 'started'}, testsEvents[i], - inspect({index: i, testsEvents: testsEvents})); + { type: 'started' }, testsEvents[i], + inspect({ index: i, testsEvents: testsEvents })); i++; assert.ok( - i < testsEvents.length, - inspect({index: i, testsEvents: testsEvents})); + i < testsEvents.length, + inspect({ index: i, testsEvents: testsEvents })); assert.equal( - testsEvents[i].type, 'finished', - inspect({index: i, testsEvents: testsEvents})); + testsEvents[i].type, 'finished', + inspect({ index: i, testsEvents: testsEvents })); assert.ok( - (testsEvents[i]).suite, - inspect({index: i, testsEvents: testsEvents})); + (testsEvents[i]).suite, + inspect({ index: i, testsEvents: testsEvents })); } } testsEvents = []; @@ -163,7 +172,7 @@ describe('C2TestAdapter.cpp', function() { function getConfig() { return vscode.workspace.getConfiguration( - 'catch2TestExplorer', workspaceFolderUri); + 'catch2TestExplorer', workspaceFolderUri); } async function updateConfig(key: string, value: any) { @@ -173,16 +182,44 @@ describe('C2TestAdapter.cpp', function() { while (testsEvents.length < count--) testsEvents.pop(); } - context('example1', function() { - it('shoud be notified by watcher', async function() { + context('example1', function () { + afterEach(async function () { + await fse.remove(cppUri.fsPath); + }) + + it('shoud be found and run withouth error', async function () { + await updateConfig( + 'executables', [{ + 'name': '${baseFilename}', + 'pattern': 'cpp/{build,Build,BUILD,out,Out,OUT}/**/*suite[0-9]*', + 'cwd': '${workspaceFolder}/cpp', + }]); + + await copy('../suite1.exe', 'out/suite1.exe'); + await copy('../suite2.exe', 'out/suite2.exe'); + await copy('../suite3.exe', 'out/suite3.exe'); + + adapter = createAdapterAndSubscribe(); + const root = await load(adapter); + assert.strictEqual(root.children.length, 3); + + const eventCount = testStatesEvents.length; + await adapter.run([root.id]); + assert.strictEqual(testStatesEvents.length, eventCount + 24); + + disposeAdapterAndSubscribers(); + await updateConfig('executables', undefined); + }) + + it('shoud be notified by watcher', async function () { this.timeout(10000); this.slow(3500); await updateConfig( - 'executables', [{ - 'name': '${baseFilename}', - 'pattern': 'cpp/{build,Build,BUILD,out,Out,OUT}/**/*suite[0-9]*', - 'cwd': '${workspaceFolder}/cpp', - }]); + 'executables', [{ + 'name': '${baseFilename}', + 'pattern': 'cpp/{build,Build,BUILD,out,Out,OUT}/**/*suite[0-9]*', + 'cwd': '${workspaceFolder}/cpp', + }]); adapter = createAdapterAndSubscribe(); const root = await load(adapter); @@ -192,19 +229,19 @@ describe('C2TestAdapter.cpp', function() { await waitFor(this, () => { return root.children.length == 1; - }); + }, 2000); await copy('../suite2.exe', 'out/sub/suite2X.exe'); await waitFor(this, () => { return root.children.length == 2; - }); + }, 2000); await copy('../suite2.exe', 'out/sub/suite2.exe'); await waitFor(this, () => { return root.children.length == 3; - }); + }, 2000); await fse.remove(inCpp('out/sub/suite2X.exe').fsPath); diff --git a/src/test/C2TestAdapter.test.ts b/src/test/C2TestAdapter.test.ts index 27928553..1c42f560 100644 --- a/src/test/C2TestAdapter.test.ts +++ b/src/test/C2TestAdapter.test.ts @@ -11,14 +11,14 @@ import * as fse from 'fs-extra'; import * as assert from 'assert'; import * as vscode from 'vscode'; import * as sinon from 'sinon'; -import {TestEvent, TestLoadFinishedEvent, TestLoadStartedEvent, TestRunFinishedEvent, TestRunStartedEvent, TestSuiteEvent, TestSuiteInfo, TestInfo, TestAdapter} from 'vscode-test-adapter-api'; -import {Log} from 'vscode-test-adapter-util'; -import {inspect, promisify} from 'util'; +import { TestEvent, TestLoadFinishedEvent, TestLoadStartedEvent, TestRunFinishedEvent, TestRunStartedEvent, TestSuiteEvent, TestSuiteInfo, TestInfo, TestAdapter } from 'vscode-test-adapter-api'; +import { Log } from 'vscode-test-adapter-util'; +import { inspect, promisify } from 'util'; import { EOL } from 'os'; -import {C2TestAdapter} from '../C2TestAdapter'; -import {example1} from './example1'; -import {ChildProcessStub, FileSystemWatcherStub} from './Helpers'; +import { C2TestAdapter } from '../C2TestAdapter'; +import { example1 } from './example1'; +import { ChildProcessStub, FileSystemWatcherStub } from './Helpers'; import * as Mocha from 'mocha'; assert.notStrictEqual(vscode.workspace.workspaceFolders, undefined); @@ -27,10 +27,10 @@ assert.equal(vscode.workspace.workspaceFolders!.length, 1); const workspaceFolderUri = vscode.workspace.workspaceFolders![0].uri; const workspaceFolder = - vscode.workspace.getWorkspaceFolder(workspaceFolderUri)!; + vscode.workspace.getWorkspaceFolder(workspaceFolderUri)!; const logger = - new Log('Catch2TestAdapter', workspaceFolder, 'Catch2TestAdapter'); + new Log('Catch2TestAdapter', workspaceFolder, 'Catch2TestAdapter'); const dotVscodePath = path.join(workspaceFolderUri.fsPath, '.vscode'); @@ -38,14 +38,14 @@ const sinonSandbox = sinon.createSandbox(); /// -describe('C2TestAdapter', function() { - let testsEvents: (TestLoadStartedEvent|TestLoadFinishedEvent)[] = []; - let testStatesEvents: (TestRunStartedEvent|TestRunFinishedEvent| - TestSuiteEvent|TestEvent)[] = []; +describe('C2TestAdapter', function () { + let testsEvents: (TestLoadStartedEvent | TestLoadFinishedEvent)[] = []; + let testStatesEvents: (TestRunStartedEvent | TestRunFinishedEvent | + TestSuiteEvent | TestEvent)[] = []; function getConfig() { return vscode.workspace.getConfiguration( - 'catch2TestExplorer', workspaceFolderUri); + 'catch2TestExplorer', workspaceFolderUri); } async function updateConfig(key: string, value: any) { @@ -55,9 +55,9 @@ describe('C2TestAdapter', function() { while (testsEvents.length < count--) testsEvents.pop(); } - let adapter: C2TestAdapter|undefined; - let testsEventsConnection: vscode.Disposable|undefined; - let testStatesEventsConnection: vscode.Disposable|undefined; + let adapter: C2TestAdapter | undefined; + let testsEventsConnection: vscode.Disposable | undefined; + let testStatesEventsConnection: vscode.Disposable | undefined; let spawnStub: sinon.SinonStub; let vsfsWatchStub: sinon.SinonStub; @@ -66,14 +66,14 @@ describe('C2TestAdapter', function() { function resetConfig(): Thenable { const packageJson = fse.readJSONSync( - path.join(workspaceFolderUri.fsPath, '../..', 'package.json')); - const properties: {[prop: string]: any}[] = - packageJson['contributes']['configuration']['properties']; + path.join(workspaceFolderUri.fsPath, '../..', 'package.json')); + const properties: { [prop: string]: any }[] = + packageJson['contributes']['configuration']['properties']; let t: Thenable = Promise.resolve(); Object.keys(properties).forEach(key => { assert.ok(key.startsWith('catch2TestExplorer.')); const k = key.replace('catch2TestExplorer.', ''); - t = t.then(function() { + t = t.then(function () { return getConfig().update(k, undefined); }); }); @@ -82,18 +82,18 @@ describe('C2TestAdapter', function() { function testStatesEvI(o: any) { const i = testStatesEvents.findIndex( - (v: TestRunStartedEvent|TestRunFinishedEvent|TestSuiteEvent| - TestEvent) => { - if (o.type == v.type) - if (o.type == 'suite' || o.type == 'test') - return o.state === (v).state && - o[o.type] === (v)[v.type]; - return deepStrictEqual(o, v); - }); + (v: TestRunStartedEvent | TestRunFinishedEvent | TestSuiteEvent | + TestEvent) => { + if (o.type == v.type) + if (o.type == 'suite' || o.type == 'test') + return o.state === (v).state && + o[o.type] === (v)[v.type]; + return deepStrictEqual(o, v); + }); assert.notStrictEqual( - i, -1, - 'testStatesEvI failed to find: ' + inspect(o) + '\n\nin\n\n' + - inspect(testStatesEvents)); + i, -1, + 'testStatesEvI failed to find: ' + inspect(o) + '\n\nin\n\n' + + inspect(testStatesEvents)); assert.deepStrictEqual(testStatesEvents[i], o); return i; } @@ -102,34 +102,34 @@ describe('C2TestAdapter', function() { adapter = new C2TestAdapter(workspaceFolder, logger); testsEventsConnection = - adapter.tests((e: TestLoadStartedEvent|TestLoadFinishedEvent) => { - testsEvents.push(e); - }); + adapter.tests((e: TestLoadStartedEvent | TestLoadFinishedEvent) => { + testsEvents.push(e); + }); testStatesEvents = []; testStatesEventsConnection = adapter.testStates( - (e: TestRunStartedEvent|TestRunFinishedEvent|TestSuiteEvent| - TestEvent) => { - testStatesEvents.push(e); - }); + (e: TestRunStartedEvent | TestRunFinishedEvent | TestSuiteEvent | + TestEvent) => { + testStatesEvents.push(e); + }); return adapter!; } async function waitFor( - test: Mocha.Context, condition: Function, - timeout: number = 1000): Promise { + test: Mocha.Context, condition: Function, + timeout: number = 1000): Promise { const start = Date.now(); let c = await condition(); while (!(c = await condition()) && - (Date.now() - start < timeout || !test.enableTimeouts())) + (Date.now() - start < timeout || !test.enableTimeouts())) await promisify(setTimeout)(10); assert.ok(c); } async function doAndWaitForReloadEvent( - test: Mocha.Context, action: Function, - timeout: number = 1000): Promise { + test: Mocha.Context, action: Function, + timeout: number = 1000): Promise { const origCount = testsEvents.length; await action(); await waitFor(test, () => { @@ -150,18 +150,18 @@ describe('C2TestAdapter', function() { if (check) { for (let i = 0; i < testsEvents.length; i++) { assert.deepStrictEqual( - {type: 'started'}, testsEvents[i], - inspect({index: i, testsEvents: testsEvents})); + { type: 'started' }, testsEvents[i], + inspect({ index: i, testsEvents: testsEvents })); i++; assert.ok( - i < testsEvents.length, - inspect({index: i, testsEvents: testsEvents})); + i < testsEvents.length, + inspect({ index: i, testsEvents: testsEvents })); assert.equal( - testsEvents[i].type, 'finished', - inspect({index: i, testsEvents: testsEvents})); + testsEvents[i].type, 'finished', + inspect({ index: i, testsEvents: testsEvents })); assert.ok( - (testsEvents[i]).suite, - inspect({index: i, testsEvents: testsEvents})); + (testsEvents[i]).suite, + inspect({ index: i, testsEvents: testsEvents })); } } testsEvents = []; @@ -178,17 +178,17 @@ describe('C2TestAdapter', function() { vsFindFilesStub.callThrough(); } - before(function() { + before(function () { fse.removeSync(dotVscodePath); adapter = undefined; spawnStub = sinonSandbox.stub(child_process, 'spawn').named('spawnStub'); vsfsWatchStub = - sinonSandbox.stub(vscode.workspace, 'createFileSystemWatcher') - .named('vscode.createFileSystemWatcher'); + sinonSandbox.stub(vscode.workspace, 'createFileSystemWatcher') + .named('vscode.createFileSystemWatcher'); fsStatStub = sinonSandbox.stub(fs, 'stat').named('fsStat'); vsFindFilesStub = sinonSandbox.stub(vscode.workspace, 'findFiles') - .named('vsFindFilesStub'); + .named('vsFindFilesStub'); stubsResetToMyDefault(); @@ -196,52 +196,52 @@ describe('C2TestAdapter', function() { return resetConfig(); }); - after(function() { + after(function () { disposeAdapterAndSubscribers(); sinonSandbox.restore(); }); - describe('detect config change', function() { + describe('detect config change', function () { this.slow(200); let adapter: C2TestAdapter; - before(function() { + before(function () { adapter = createAdapterAndSubscribe(); assert.deepStrictEqual(testsEvents, []); }) - after(function() { + after(function () { disposeAdapterAndSubscribers(); return resetConfig(); }) - it('defaultEnv', function() { + it('defaultEnv', function () { return doAndWaitForReloadEvent(this, () => { - return updateConfig('defaultEnv', {'APPLE': 'apple'}); + return updateConfig('defaultEnv', { 'APPLE': 'apple' }); }); }) - it('defaultCwd', function() { + it('defaultCwd', function () { return doAndWaitForReloadEvent(this, () => { return updateConfig('defaultCwd', 'apple/peach'); }); }); - it('enableSourceDecoration', function() { - return updateConfig('enableSourceDecoration', false).then(function() { + it('enableSourceDecoration', function () { + return updateConfig('enableSourceDecoration', false).then(function () { assert.ok(!adapter.getIsEnabledSourceDecoration()); }); }); - it('defaultRngSeed', function() { - return updateConfig('defaultRngSeed', 987).then(function() { + it('defaultRngSeed', function () { + return updateConfig('defaultRngSeed', 987).then(function () { assert.equal(adapter.getRngSeed(), 987); }); }) }) - it('load with empty config', async function() { + it('load with empty config', async function () { this.slow(500); const adapter = createAdapterAndSubscribe(); await adapter.load(); @@ -254,24 +254,24 @@ describe('C2TestAdapter', function() { disposeAdapterAndSubscribers(); }) - context('example1', function() { + context('example1', function () { const watchers: Map = new Map(); function handleCreateWatcherCb( - p: vscode.RelativePattern, ignoreCreateEvents: boolean, - ignoreChangeEvents: boolean, ignoreDeleteEvents: boolean) { + p: vscode.RelativePattern, ignoreCreateEvents: boolean, + ignoreChangeEvents: boolean, ignoreDeleteEvents: boolean) { const pp = path.join(p.base, p.pattern); const e = new FileSystemWatcherStub( - vscode.Uri.file(pp), ignoreCreateEvents, ignoreChangeEvents, - ignoreDeleteEvents); + vscode.Uri.file(pp), ignoreCreateEvents, ignoreChangeEvents, + ignoreDeleteEvents); watchers.set(pp, e); return e; } function handleStatExistsFile( - path: string, - cb: (err: NodeJS.ErrnoException|null, stats: fs.Stats|undefined) => - void) { + path: string, + cb: (err: NodeJS.ErrnoException | null, stats: fs.Stats | undefined) => + void) { cb(null, { isFile() { return true; @@ -283,9 +283,9 @@ describe('C2TestAdapter', function() { } function handleStatNotExists( - path: string, - cb: (err: NodeJS.ErrnoException|null|any, stats: fs.Stats|undefined) => - void) { + path: string, + cb: (err: NodeJS.ErrnoException | null | any, stats: fs.Stats | undefined) => + void) { cb({ code: 'ENOENT', errno: -2, @@ -293,22 +293,22 @@ describe('C2TestAdapter', function() { path: path, syscall: 'stat' }, - undefined); + undefined); } function matchRelativePattern(p: string) { return sinon.match((actual: vscode.RelativePattern) => { const required = new vscode.RelativePattern( - workspaceFolder, path.relative(workspaceFolderUri.fsPath, p)); + workspaceFolder, path.relative(workspaceFolderUri.fsPath, p)); return required.base == actual.base && - required.pattern == actual.pattern; + required.pattern == actual.pattern; }); } - before(function() { + before(function () { for (let suite of example1.outputs) { for (let scenario of suite[1]) { - spawnStub.withArgs(suite[0], scenario[0]).callsFake(function() { + spawnStub.withArgs(suite[0], scenario[0]).callsFake(function () { return new ChildProcessStub(scenario[1]); }); } @@ -316,7 +316,7 @@ describe('C2TestAdapter', function() { fsStatStub.withArgs(suite[0]).callsFake(handleStatExistsFile); vsfsWatchStub.withArgs(matchRelativePattern(suite[0])) - .callsFake(handleCreateWatcherCb); + .callsFake(handleCreateWatcherCb); } const dirContent: Map = new Map(); @@ -340,36 +340,36 @@ describe('C2TestAdapter', function() { }); }); - after(function() { + after(function () { stubsResetToMyDefault(); }); - afterEach(function() { + afterEach(function () { watchers.clear(); }); - describe('load', function() { + describe('load', function () { const uniqueIdC = new Set(); let adapter: TestAdapter; let root: TestSuiteInfo; - let suite1: TestSuiteInfo|any; - let s1t1: TestInfo|any; - let s1t2: TestInfo|any; - let suite2: TestSuiteInfo|any; - let s2t1: TestInfo|any; - let s2t2: TestInfo|any; - let s2t3: TestInfo|any; - - before(function() { + let suite1: TestSuiteInfo | any; + let s1t1: TestInfo | any; + let s1t2: TestInfo | any; + let suite2: TestSuiteInfo | any; + let s2t1: TestInfo | any; + let s2t2: TestInfo | any; + let s2t3: TestInfo | any; + + before(function () { return updateConfig('workerMaxNumber', 4); }); - after(function() { + after(function () { return updateConfig('workerMaxNumber', undefined); }); - beforeEach(async function() { + beforeEach(async function () { adapter = createAdapterAndSubscribe(); await adapter.load(); @@ -392,28 +392,28 @@ describe('C2TestAdapter', function() { assert.deepStrictEqual(testStatesEvents, []); }); - afterEach(function() { + afterEach(function () { uniqueIdC.clear(); disposeAdapterAndSubscribers(); }); - context('executables="execPath1"', function() { - before(function() { + context('executables="execPath1"', function () { + before(function () { return updateConfig('executables', 'execPath1'); }); - after(function() { + after(function () { return updateConfig('executables', undefined); }); - beforeEach(async function() { + beforeEach(async function () { assert.deepStrictEqual( - getConfig().get('executables'), 'execPath1'); + getConfig().get('executables'), 'execPath1'); assert.equal(root.children.length, 1); assert.equal(root.children[0].type, 'suite'); suite1 = root.children[0]; example1.suite1.assert( - 'execPath1', ['s1t1', 's1t2'], suite1, uniqueIdC); + 'execPath1', ['s1t1', 's1t2'], suite1, uniqueIdC); assert.equal(suite1.children.length, 2); assert.equal(suite1.children[0].type, 'test'); s1t1 = suite1.children[0]; @@ -421,21 +421,21 @@ describe('C2TestAdapter', function() { s1t2 = suite1.children[1]; }); - it('should run with not existing test id', async function() { + it('should run with not existing test id', async function () { await adapter.run(['not existing id']); assert.deepStrictEqual(testStatesEvents, [ - {type: 'started', tests: ['not existing id']}, {type: 'finished'} + { type: 'started', tests: ['not existing id'] }, { type: 'finished' } ]); }); - it('should run s1t1 with success', async function() { + it('should run s1t1 with success', async function () { assert.equal(getConfig().get('executables'), 'execPath1'); await adapter.run([s1t1.id]); const expected = [ - {type: 'started', tests: [s1t1.id]}, - {type: 'suite', state: 'running', suite: suite1}, - {type: 'test', state: 'running', test: s1t1}, + { type: 'started', tests: [s1t1.id] }, + { type: 'suite', state: 'running', suite: suite1 }, + { type: 'test', state: 'running', test: s1t1 }, { type: 'test', state: 'passed', @@ -443,8 +443,8 @@ describe('C2TestAdapter', function() { decorations: undefined, message: 'Duration: 0.000112 second(s)\n' }, - {type: 'suite', state: 'completed', suite: suite1}, - {type: 'finished'}, + { type: 'suite', state: 'completed', suite: suite1 }, + { type: 'finished' }, ]; assert.deepStrictEqual(testStatesEvents, expected); @@ -452,12 +452,12 @@ describe('C2TestAdapter', function() { assert.deepStrictEqual(testStatesEvents, [...expected, ...expected]); }); - it('should run suite1', async function() { + it('should run suite1', async function () { await adapter.run([suite1.id]); const expected = [ - {type: 'started', tests: [suite1.id]}, - {type: 'suite', state: 'running', suite: suite1}, - {type: 'test', state: 'running', test: s1t1}, + { type: 'started', tests: [suite1.id] }, + { type: 'suite', state: 'running', suite: suite1 }, + { type: 'test', state: 'running', test: s1t1 }, { type: 'test', state: 'passed', @@ -465,17 +465,17 @@ describe('C2TestAdapter', function() { decorations: undefined, message: 'Duration: 0.000132 second(s)\n' }, - {type: 'test', state: 'running', test: s1t2}, + { type: 'test', state: 'running', test: s1t2 }, { type: 'test', state: 'failed', test: s1t2, - decorations: [{line: 14, message: 'Expanded: false'}], + decorations: [{ line: 14, message: 'Expanded: false' }], message: - 'Duration: 0.000204 second(s)\n>>> s1t2(line: 13) REQUIRE (line: 15) \n Original:\n std::false_type::value\n Expanded:\n false\n<<<\n' + 'Duration: 0.000204 second(s)\n>>> s1t2(line: 13) REQUIRE (line: 15) \n Original:\n std::false_type::value\n Expanded:\n false\n<<<\n' }, - {type: 'suite', state: 'completed', suite: suite1}, - {type: 'finished'}, + { type: 'suite', state: 'completed', suite: suite1 }, + { type: 'finished' }, ]; assert.deepStrictEqual(testStatesEvents, expected); @@ -483,12 +483,12 @@ describe('C2TestAdapter', function() { assert.deepStrictEqual(testStatesEvents, [...expected, ...expected]); }); - it('should run all', async function() { + it('should run all', async function () { await adapter.run([root.id]); const expected = [ - {type: 'started', tests: [root.id]}, - {type: 'suite', state: 'running', suite: suite1}, - {type: 'test', state: 'running', test: s1t1}, + { type: 'started', tests: [root.id] }, + { type: 'suite', state: 'running', suite: suite1 }, + { type: 'test', state: 'running', test: s1t1 }, { type: 'test', state: 'passed', @@ -496,17 +496,17 @@ describe('C2TestAdapter', function() { decorations: undefined, message: 'Duration: 0.000132 second(s)\n' }, - {type: 'test', state: 'running', test: s1t2}, + { type: 'test', state: 'running', test: s1t2 }, { type: 'test', state: 'failed', test: s1t2, - decorations: [{line: 14, message: 'Expanded: false'}], + decorations: [{ line: 14, message: 'Expanded: false' }], message: - 'Duration: 0.000204 second(s)\n>>> s1t2(line: 13) REQUIRE (line: 15) \n Original:\n std::false_type::value\n Expanded:\n false\n<<<\n' + 'Duration: 0.000204 second(s)\n>>> s1t2(line: 13) REQUIRE (line: 15) \n Original:\n std::false_type::value\n Expanded:\n false\n<<<\n' }, - {type: 'suite', state: 'completed', suite: suite1}, - {type: 'finished'}, + { type: 'suite', state: 'completed', suite: suite1 }, + { type: 'finished' }, ]; assert.deepStrictEqual(testStatesEvents, expected); @@ -514,7 +514,7 @@ describe('C2TestAdapter', function() { assert.deepStrictEqual(testStatesEvents, [...expected, ...expected]); }); - it('cancels without any problem', async function() { + it('cancels without any problem', async function () { adapter.cancel(); assert.deepStrictEqual(testsEvents, []); assert.deepStrictEqual(testStatesEvents, []); @@ -525,17 +525,17 @@ describe('C2TestAdapter', function() { await adapter.run([s1t1.id]); const expected = [ - {type: 'started', tests: [s1t1.id]}, - {type: 'suite', state: 'running', suite: suite1}, - {type: 'test', state: 'running', test: s1t1}, { + { type: 'started', tests: [s1t1.id] }, + { type: 'suite', state: 'running', suite: suite1 }, + { type: 'test', state: 'running', test: s1t1 }, { type: 'test', state: 'passed', test: s1t1, decorations: undefined, message: 'Duration: 0.000112 second(s)\n' }, - {type: 'suite', state: 'completed', suite: suite1}, - {type: 'finished'} + { type: 'suite', state: 'completed', suite: suite1 }, + { type: 'finished' } ]; assert.deepStrictEqual(testStatesEvents, expected); @@ -544,42 +544,42 @@ describe('C2TestAdapter', function() { assert.deepStrictEqual(testStatesEvents, expected); }); - context('with config: defaultRngSeed=2', function() { - before(function() { + context('with config: defaultRngSeed=2', function () { + before(function () { return updateConfig('defaultRngSeed', 2); }); - after(function() { + after(function () { return updateConfig('defaultRngSeed', undefined); }); - it('should run s1t1 with success', async function() { + it('should run s1t1 with success', async function () { await adapter.run([s1t1.id]); const expected = [ - {type: 'started', tests: [s1t1.id]}, - {type: 'suite', state: 'running', suite: suite1}, - {type: 'test', state: 'running', test: s1t1}, { + { type: 'started', tests: [s1t1.id] }, + { type: 'suite', state: 'running', suite: suite1 }, + { type: 'test', state: 'running', test: s1t1 }, { type: 'test', state: 'passed', test: s1t1, decorations: undefined, message: - 'Randomness seeded to: 2\nDuration: 0.000327 second(s)\n' + 'Randomness seeded to: 2\nDuration: 0.000327 second(s)\n' }, - {type: 'suite', state: 'completed', suite: suite1}, - {type: 'finished'} + { type: 'suite', state: 'completed', suite: suite1 }, + { type: 'finished' } ]; assert.deepStrictEqual(testStatesEvents, expected); await adapter.run([s1t1.id]); assert.deepStrictEqual( - testStatesEvents, [...expected, ...expected]); + testStatesEvents, [...expected, ...expected]); }) }) }) - context('suite1 and suite2 are used', function() { - beforeEach(function() { + context('suite1 and suite2 are used', function () { + beforeEach(function () { assert.equal(root.children.length, 2); assert.equal(root.children[0].type, 'suite'); @@ -610,728 +610,728 @@ describe('C2TestAdapter', function() { const testsForAdapterWithSuite1AndSuite2: Mocha.Test[] = [ new Mocha.Test( - 'test variables are fine, suite1 and suite1 are loaded', - function() { - assert.equal(root.children.length, 2); - assert.ok(suite1 != undefined); - assert.ok(s1t1 != undefined); - assert.ok(s1t2 != undefined); - assert.ok(suite2 != undefined); - assert.ok(s2t1 != undefined); - assert.ok(s2t2 != undefined); - assert.ok(s2t3 != undefined); - }), + 'test variables are fine, suite1 and suite1 are loaded', + function () { + assert.equal(root.children.length, 2); + assert.ok(suite1 != undefined); + assert.ok(s1t1 != undefined); + assert.ok(s1t2 != undefined); + assert.ok(suite2 != undefined); + assert.ok(s2t1 != undefined); + assert.ok(s2t2 != undefined); + assert.ok(s2t3 != undefined); + }), new Mocha.Test( - 'should run all', - async function() { - assert.equal(root.children.length, 2); - await adapter.run([root.id]); - - const running = {type: 'started', tests: [root.id]}; - - const s1running = { - type: 'suite', - state: 'running', - suite: suite1 - }; - const s1finished = { - type: 'suite', - state: 'completed', - suite: suite1 - }; - assert.ok(testStatesEvI(running) < testStatesEvI(s1running)); - assert.ok(testStatesEvI(s1running) < testStatesEvI(s1finished)); - - const s2running = { - type: 'suite', - state: 'running', - suite: suite2 - }; - const s2finished = { - type: 'suite', - state: 'completed', - suite: suite2 - }; - assert.ok(testStatesEvI(running) < testStatesEvI(s1running)); - assert.ok(testStatesEvI(s2running) < testStatesEvI(s2finished)); - - const s1t1running = { - type: 'test', - state: 'running', - test: s1t1 - }; - assert.ok( - testStatesEvI(s1running) < testStatesEvI(s1t1running)); + 'should run all', + async function () { + assert.equal(root.children.length, 2); + await adapter.run([root.id]); + + const running = { type: 'started', tests: [root.id] }; + + const s1running = { + type: 'suite', + state: 'running', + suite: suite1 + }; + const s1finished = { + type: 'suite', + state: 'completed', + suite: suite1 + }; + assert.ok(testStatesEvI(running) < testStatesEvI(s1running)); + assert.ok(testStatesEvI(s1running) < testStatesEvI(s1finished)); + + const s2running = { + type: 'suite', + state: 'running', + suite: suite2 + }; + const s2finished = { + type: 'suite', + state: 'completed', + suite: suite2 + }; + assert.ok(testStatesEvI(running) < testStatesEvI(s1running)); + assert.ok(testStatesEvI(s2running) < testStatesEvI(s2finished)); + + const s1t1running = { + type: 'test', + state: 'running', + test: s1t1 + }; + assert.ok( + testStatesEvI(s1running) < testStatesEvI(s1t1running)); + + const s1t1finished = { + type: 'test', + state: 'passed', + test: s1t1, + decorations: undefined, + message: 'Duration: 0.000132 second(s)\n' + }; + assert.ok( + testStatesEvI(s1t1running) < testStatesEvI(s1t1finished)); + assert.ok( + testStatesEvI(s1t1finished) < testStatesEvI(s1finished)); + + const s1t2running = { + type: 'test', + state: 'running', + test: s1t2 + }; + assert.ok( + testStatesEvI(s1running) < testStatesEvI(s1t2running)); + + const s1t2finished = { + type: 'test', + state: 'failed', + test: s1t2, + decorations: [{ line: 14, message: 'Expanded: false' }], + message: + 'Duration: 0.000204 second(s)\n>>> s1t2(line: 13) REQUIRE (line: 15) \n Original:\n std::false_type::value\n Expanded:\n false\n<<<\n' + }; + assert.ok( + testStatesEvI(s1t2running) < testStatesEvI(s1t2finished)); + assert.ok( + testStatesEvI(s1t2finished) < testStatesEvI(s1finished)); + + const s2t1running = { + type: 'test', + state: 'running', + test: s2t1 + }; + assert.ok( + testStatesEvI(s2running) < testStatesEvI(s2t1running)); + + const s2t1finished = { + type: 'test', + state: 'passed', + test: s2t1, + decorations: undefined, + message: 'Duration: 0.00037 second(s)\n' + }; + assert.ok( + testStatesEvI(s2t1running) < testStatesEvI(s2t1finished)); + assert.ok( + testStatesEvI(s2t1finished) < testStatesEvI(s2finished)); - const s1t1finished = { + const s2t2running = { + type: 'test', + state: 'running', + test: s2t2 + }; + assert.ok( + testStatesEvI(s2running) < testStatesEvI(s2t2running)); + + const s2t2finished = { + type: 'test', + state: 'skipped', + test: s2t2 + }; + assert.ok( + testStatesEvI(s2t2running) < testStatesEvI(s2t2finished)); + assert.ok( + testStatesEvI(s2t2finished) < testStatesEvI(s2finished)); + + const s2t3running = { + type: 'test', + state: 'running', + test: s2t3 + }; + assert.ok( + testStatesEvI(s2running) < testStatesEvI(s2t3running)); + + const s2t3finished = { + type: 'test', + state: 'failed', + test: s2t3, + decorations: [{ line: 20, message: 'Expanded: false' }], + message: + 'Duration: 0.000178 second(s)\n>>> s2t3(line: 19) REQUIRE (line: 21) \n Original:\n std::false_type::value\n Expanded:\n false\n<<<\n' + }; + assert.ok( + testStatesEvI(s2t3running) < testStatesEvI(s2t3finished)); + assert.ok( + testStatesEvI(s2t3finished) < testStatesEvI(s2finished)); + + const finished = { type: 'finished' }; + assert.ok(testStatesEvI(s1finished) < testStatesEvI(finished)); + assert.ok(testStatesEvI(s2finished) < testStatesEvI(finished)); + + assert.equal( + testStatesEvents.length, 16, inspect(testStatesEvents)); + }), + new Mocha.Test( + 'should run with not existing test id', + async function () { + await adapter.run(['not existing id']); + + assert.deepStrictEqual(testStatesEvents, [ + { type: 'started', tests: ['not existing id'] }, + { type: 'finished' } + ]); + }), + new Mocha.Test( + 'should run s1t1', + async function () { + await adapter.run([s1t1.id]); + const expected = [ + { type: 'started', tests: [s1t1.id] }, + { type: 'suite', state: 'running', suite: suite1 }, + { type: 'test', state: 'running', test: s1t1 }, { type: 'test', state: 'passed', test: s1t1, decorations: undefined, - message: 'Duration: 0.000132 second(s)\n' - }; - assert.ok( - testStatesEvI(s1t1running) < testStatesEvI(s1t1finished)); - assert.ok( - testStatesEvI(s1t1finished) < testStatesEvI(s1finished)); - - const s1t2running = { + message: 'Duration: 0.000112 second(s)\n' + }, + { type: 'suite', state: 'completed', suite: suite1 }, + { type: 'finished' } + ]; + assert.deepStrictEqual(testStatesEvents, expected); + + await adapter.run([s1t1.id]); + assert.deepStrictEqual( + testStatesEvents, [...expected, ...expected]); + }), + new Mocha.Test( + 'should run skipped s2t2', + async function () { + await adapter.run([s2t2.id]); + const expected = [ + { type: 'started', tests: [s2t2.id] }, + { type: 'suite', state: 'running', suite: suite2 }, + { type: 'test', state: 'running', test: s2t2 }, { type: 'test', - state: 'running', - test: s1t2 - }; - assert.ok( - testStatesEvI(s1running) < testStatesEvI(s1t2running)); - - const s1t2finished = { + state: 'passed', + test: s2t2, + decorations: undefined, + message: 'Duration: 0.001294 second(s)\n' + }, + { type: 'suite', state: 'completed', suite: suite2 }, + { type: 'finished' } + ]; + assert.deepStrictEqual(testStatesEvents, expected); + + await adapter.run([s2t2.id]); + assert.deepStrictEqual( + testStatesEvents, [...expected, ...expected]); + }), + new Mocha.Test( + 'should run failing test s2t3', + async function () { + await adapter.run([s2t3.id]); + const expected = [ + { type: 'started', tests: [s2t3.id] }, + { type: 'suite', state: 'running', suite: suite2 }, + { type: 'test', state: 'running', test: s2t3 }, { type: 'test', state: 'failed', - test: s1t2, - decorations: [{line: 14, message: 'Expanded: false'}], + test: s2t3, + decorations: [{ line: 20, message: 'Expanded: false' }], message: - 'Duration: 0.000204 second(s)\n>>> s1t2(line: 13) REQUIRE (line: 15) \n Original:\n std::false_type::value\n Expanded:\n false\n<<<\n' - }; - assert.ok( - testStatesEvI(s1t2running) < testStatesEvI(s1t2finished)); - assert.ok( - testStatesEvI(s1t2finished) < testStatesEvI(s1finished)); - - const s2t1running = { + 'Duration: 0.000596 second(s)\n>>> s2t3(line: 19) REQUIRE (line: 21) \n Original:\n std::false_type::value\n Expanded:\n false\n<<<\n' + }, + { type: 'suite', state: 'completed', suite: suite2 }, + { type: 'finished' } + ]; + assert.deepStrictEqual(testStatesEvents, expected); + + await adapter.run([s2t3.id]); + assert.deepStrictEqual( + testStatesEvents, [...expected, ...expected]); + }), + new Mocha.Test( + 'should run failing test s2t3 with chunks', + async function () { + const withArgs = spawnStub.withArgs( + example1.suite2.execPath, example1.suite2.t3.outputs[0][0]); + withArgs.onCall(withArgs.callCount) + .returns( + new ChildProcessStub(example1.suite2.t3.outputs[0][1])); + + await adapter.run([s2t3.id]); + const expected = [ + { type: 'started', tests: [s2t3.id] }, + { type: 'suite', state: 'running', suite: suite2 }, + { type: 'test', state: 'running', test: s2t3 }, { type: 'test', - state: 'running', - test: s2t1 - }; - assert.ok( - testStatesEvI(s2running) < testStatesEvI(s2t1running)); - - const s2t1finished = { + state: 'failed', + test: s2t3, + decorations: [{ line: 20, message: 'Expanded: false' }], + message: + 'Duration: 0.000596 second(s)\n>>> s2t3(line: 19) REQUIRE (line: 21) \n Original:\n std::false_type::value\n Expanded:\n false\n<<<\n' + }, + { type: 'suite', state: 'completed', suite: suite2 }, + { type: 'finished' } + ]; + assert.deepStrictEqual(testStatesEvents, expected); + + await adapter.run([s2t3.id]); + assert.deepStrictEqual( + testStatesEvents, [...expected, ...expected]); + }), + new Mocha.Test( + 'should run suite1', + async function () { + await adapter.run([suite1.id]); + const expected = [ + { type: 'started', tests: [suite1.id] }, + { type: 'suite', state: 'running', suite: suite1 }, + { type: 'test', state: 'running', test: s1t1 }, { type: 'test', state: 'passed', - test: s2t1, + test: s1t1, decorations: undefined, - message: 'Duration: 0.00037 second(s)\n' - }; - assert.ok( - testStatesEvI(s2t1running) < testStatesEvI(s2t1finished)); - assert.ok( - testStatesEvI(s2t1finished) < testStatesEvI(s2finished)); - - const s2t2running = { + message: 'Duration: 0.000132 second(s)\n' + }, + { type: 'test', state: 'running', test: s1t2 }, { type: 'test', - state: 'running', - test: s2t2 - }; - assert.ok( - testStatesEvI(s2running) < testStatesEvI(s2t2running)); + state: 'failed', + test: s1t2, + decorations: [{ line: 14, message: 'Expanded: false' }], + message: + 'Duration: 0.000204 second(s)\n>>> s1t2(line: 13) REQUIRE (line: 15) \n Original:\n std::false_type::value\n Expanded:\n false\n<<<\n' + }, + { type: 'suite', state: 'completed', suite: suite1 }, + { type: 'finished' } + ]; + assert.deepStrictEqual(testStatesEvents, expected); - const s2t2finished = { - type: 'test', - state: 'skipped', - test: s2t2 - }; - assert.ok( - testStatesEvI(s2t2running) < testStatesEvI(s2t2finished)); - assert.ok( - testStatesEvI(s2t2finished) < testStatesEvI(s2finished)); - - const s2t3running = { - type: 'test', - state: 'running', - test: s2t3 - }; - assert.ok( - testStatesEvI(s2running) < testStatesEvI(s2t3running)); + await adapter.run([suite1.id]); + assert.deepStrictEqual( + testStatesEvents, [...expected, ...expected]); + }), + new Mocha.Test( + 'should run with wrong xml', + async function () { + const m = + example1.suite1.t1.outputs[0][1].match(']+>'); + assert.notStrictEqual(m, undefined); + assert.notStrictEqual(m!.input, undefined); + assert.notStrictEqual(m!.index, undefined); + const part = m!.input!.substr(0, m!.index! + m![0].length); + const withArgs = spawnStub.withArgs( + example1.suite1.execPath, example1.suite1.t1.outputs[0][0]); + withArgs.onCall(withArgs.callCount) + .returns(new ChildProcessStub(part)); + + await adapter.run([s1t1.id]); - const s2t3finished = { + const expected = [ + { type: 'started', tests: [s1t1.id] }, + { type: 'suite', state: 'running', suite: suite1 }, + { type: 'test', state: 'running', test: s1t1 }, { type: 'test', state: 'failed', - test: s2t3, - decorations: [{line: 20, message: 'Expanded: false'}], - message: - 'Duration: 0.000178 second(s)\n>>> s2t3(line: 19) REQUIRE (line: 21) \n Original:\n std::false_type::value\n Expanded:\n false\n<<<\n' - }; - assert.ok( - testStatesEvI(s2t3running) < testStatesEvI(s2t3finished)); - assert.ok( - testStatesEvI(s2t3finished) < testStatesEvI(s2finished)); - - const finished = {type: 'finished'}; - assert.ok(testStatesEvI(s1finished) < testStatesEvI(finished)); - assert.ok(testStatesEvI(s2finished) < testStatesEvI(finished)); - - assert.equal( - testStatesEvents.length, 16, inspect(testStatesEvents)); - }), - new Mocha.Test( - 'should run with not existing test id', - async function() { - await adapter.run(['not existing id']); - - assert.deepStrictEqual(testStatesEvents, [ - {type: 'started', tests: ['not existing id']}, - {type: 'finished'} - ]); - }), - new Mocha.Test( - 'should run s1t1', - async function() { - await adapter.run([s1t1.id]); - const expected = [ - {type: 'started', tests: [s1t1.id]}, - {type: 'suite', state: 'running', suite: suite1}, - {type: 'test', state: 'running', test: s1t1}, { - type: 'test', - state: 'passed', - test: s1t1, - decorations: undefined, - message: 'Duration: 0.000112 second(s)\n' - }, - {type: 'suite', state: 'completed', suite: suite1}, - {type: 'finished'} - ]; - assert.deepStrictEqual(testStatesEvents, expected); - - await adapter.run([s1t1.id]); - assert.deepStrictEqual( - testStatesEvents, [...expected, ...expected]); - }), - new Mocha.Test( - 'should run skipped s2t2', - async function() { - await adapter.run([s2t2.id]); - const expected = [ - {type: 'started', tests: [s2t2.id]}, - {type: 'suite', state: 'running', suite: suite2}, - {type: 'test', state: 'running', test: s2t2}, { - type: 'test', - state: 'passed', - test: s2t2, - decorations: undefined, - message: 'Duration: 0.001294 second(s)\n' - }, - {type: 'suite', state: 'completed', suite: suite2}, - {type: 'finished'} - ]; - assert.deepStrictEqual(testStatesEvents, expected); - - await adapter.run([s2t2.id]); - assert.deepStrictEqual( - testStatesEvents, [...expected, ...expected]); - }), + test: s1t1, + message: 'Unexpected test error. (Is Catch2 crashed?)\n' + }, + { type: 'suite', state: 'completed', suite: suite1 }, + { type: 'finished' } + ]; + assert.deepStrictEqual(testStatesEvents, expected); + + // this tests the sinon stubs too + await adapter.run([s1t1.id]); + assert.deepStrictEqual(testStatesEvents, [ + ...expected, { type: 'started', tests: [s1t1.id] }, + { type: 'suite', state: 'running', suite: suite1 }, + { type: 'test', state: 'running', test: s1t1 }, { + type: 'test', + state: 'passed', + test: s1t1, + decorations: undefined, + message: 'Duration: 0.000112 second(s)\n' + }, + { type: 'suite', state: 'completed', suite: suite1 }, + { type: 'finished' } + ]); + }), new Mocha.Test( - 'should run failing test s2t3', - async function() { - await adapter.run([s2t3.id]); - const expected = [ - {type: 'started', tests: [s2t3.id]}, - {type: 'suite', state: 'running', suite: suite2}, - {type: 'test', state: 'running', test: s2t3}, { - type: 'test', - state: 'failed', - test: s2t3, - decorations: [{line: 20, message: 'Expanded: false'}], - message: - 'Duration: 0.000596 second(s)\n>>> s2t3(line: 19) REQUIRE (line: 21) \n Original:\n std::false_type::value\n Expanded:\n false\n<<<\n' - }, - {type: 'suite', state: 'completed', suite: suite2}, - {type: 'finished'} - ]; - assert.deepStrictEqual(testStatesEvents, expected); - - await adapter.run([s2t3.id]); - assert.deepStrictEqual( - testStatesEvents, [...expected, ...expected]); - }), + 'should cancel without error', + function () { + adapter.cancel(); + }), new Mocha.Test( - 'should run failing test s2t3 with chunks', - async function() { + 'cancel', + async function () { + let spyKill1: sinon.SinonSpy; + let spyKill2: sinon.SinonSpy; + { + const spawnEvent = + new ChildProcessStub(example1.suite1.outputs[2][1]); + spyKill1 = sinon.spy(spawnEvent, 'kill'); const withArgs = spawnStub.withArgs( - example1.suite2.execPath, example1.suite2.t3.outputs[0][0]); - withArgs.onCall(withArgs.callCount) - .returns( - new ChildProcessStub(example1.suite2.t3.outputs[0][1])); - - await adapter.run([s2t3.id]); - const expected = [ - {type: 'started', tests: [s2t3.id]}, - {type: 'suite', state: 'running', suite: suite2}, - {type: 'test', state: 'running', test: s2t3}, { - type: 'test', - state: 'failed', - test: s2t3, - decorations: [{line: 20, message: 'Expanded: false'}], - message: - 'Duration: 0.000596 second(s)\n>>> s2t3(line: 19) REQUIRE (line: 21) \n Original:\n std::false_type::value\n Expanded:\n false\n<<<\n' - }, - {type: 'suite', state: 'completed', suite: suite2}, - {type: 'finished'} - ]; - assert.deepStrictEqual(testStatesEvents, expected); - - await adapter.run([s2t3.id]); - assert.deepStrictEqual( - testStatesEvents, [...expected, ...expected]); - }), - new Mocha.Test( - 'should run suite1', - async function() { - await adapter.run([suite1.id]); - const expected = [ - {type: 'started', tests: [suite1.id]}, - {type: 'suite', state: 'running', suite: suite1}, - {type: 'test', state: 'running', test: s1t1}, { - type: 'test', - state: 'passed', - test: s1t1, - decorations: undefined, - message: 'Duration: 0.000132 second(s)\n' - }, - {type: 'test', state: 'running', test: s1t2}, { - type: 'test', - state: 'failed', - test: s1t2, - decorations: [{line: 14, message: 'Expanded: false'}], - message: - 'Duration: 0.000204 second(s)\n>>> s1t2(line: 13) REQUIRE (line: 15) \n Original:\n std::false_type::value\n Expanded:\n false\n<<<\n' - }, - {type: 'suite', state: 'completed', suite: suite1}, - {type: 'finished'} - ]; - assert.deepStrictEqual(testStatesEvents, expected); - - await adapter.run([suite1.id]); - assert.deepStrictEqual( - testStatesEvents, [...expected, ...expected]); - }), - new Mocha.Test( - 'should run with wrong xml', - async function() { - const m = - example1.suite1.t1.outputs[0][1].match(']+>'); - assert.notStrictEqual(m, undefined); - assert.notStrictEqual(m!.input, undefined); - assert.notStrictEqual(m!.index, undefined); - const part = m!.input!.substr(0, m!.index! + m![0].length); + example1.suite1.execPath, example1.suite1.outputs[2][0]); + withArgs.onCall(withArgs.callCount).returns(spawnEvent); + } + { + const spawnEvent = + new ChildProcessStub(example1.suite2.outputs[2][1]); + spyKill2 = sinon.spy(spawnEvent, 'kill'); const withArgs = spawnStub.withArgs( - example1.suite1.execPath, example1.suite1.t1.outputs[0][0]); - withArgs.onCall(withArgs.callCount) - .returns(new ChildProcessStub(part)); - - await adapter.run([s1t1.id]); - - const expected = [ - {type: 'started', tests: [s1t1.id]}, - {type: 'suite', state: 'running', suite: suite1}, - {type: 'test', state: 'running', test: s1t1}, { - type: 'test', - state: 'failed', - test: s1t1, - message: 'Unexpected test error. (Is Catch2 crashed?)\n' - }, - {type: 'suite', state: 'completed', suite: suite1}, - {type: 'finished'} - ]; - assert.deepStrictEqual(testStatesEvents, expected); - - // this tests the sinon stubs too - await adapter.run([s1t1.id]); - assert.deepStrictEqual(testStatesEvents, [ - ...expected, {type: 'started', tests: [s1t1.id]}, - {type: 'suite', state: 'running', suite: suite1}, - {type: 'test', state: 'running', test: s1t1}, { - type: 'test', - state: 'passed', - test: s1t1, - decorations: undefined, - message: 'Duration: 0.000112 second(s)\n' - }, - {type: 'suite', state: 'completed', suite: suite1}, - {type: 'finished'} - ]); - }), - new Mocha.Test( - 'should cancel without error', - function() { - adapter.cancel(); - }), - new Mocha.Test( - 'cancel', - async function() { - let spyKill1: sinon.SinonSpy; - let spyKill2: sinon.SinonSpy; - { - const spawnEvent = - new ChildProcessStub(example1.suite1.outputs[2][1]); - spyKill1 = sinon.spy(spawnEvent, 'kill'); - const withArgs = spawnStub.withArgs( - example1.suite1.execPath, example1.suite1.outputs[2][0]); - withArgs.onCall(withArgs.callCount).returns(spawnEvent); - } - { - const spawnEvent = - new ChildProcessStub(example1.suite2.outputs[2][1]); - spyKill2 = sinon.spy(spawnEvent, 'kill'); - const withArgs = spawnStub.withArgs( - example1.suite2.execPath, example1.suite2.outputs[2][0]); - withArgs.onCall(withArgs.callCount).returns(spawnEvent); - } - const run = adapter.run([root.id]); - adapter.cancel(); - await run; - - assert.equal(spyKill1.callCount, 1); - assert.equal(spyKill2.callCount, 1); - - const running = {type: 'started', tests: [root.id]}; - - const s1running = { - type: 'suite', - state: 'running', - suite: suite1 - }; - const s1finished = { - type: 'suite', - state: 'completed', - suite: suite1 - }; - assert.ok(testStatesEvI(running) < testStatesEvI(s1running)); - assert.ok(testStatesEvI(s1running) < testStatesEvI(s1finished)); - - const s2running = { - type: 'suite', - state: 'running', - suite: suite2 - }; - const s2finished = { - type: 'suite', - state: 'completed', - suite: suite2 - }; - assert.ok(testStatesEvI(running) < testStatesEvI(s1running)); - assert.ok(testStatesEvI(s2running) < testStatesEvI(s2finished)); - - const s2t2running = { - type: 'test', - state: 'running', - test: s2t2 - }; - assert.ok( - testStatesEvI(s2running) < testStatesEvI(s2t2running)); + example1.suite2.execPath, example1.suite2.outputs[2][0]); + withArgs.onCall(withArgs.callCount).returns(spawnEvent); + } + const run = adapter.run([root.id]); + adapter.cancel(); + await run; - const s2t2finished = { - type: 'test', - state: 'skipped', - test: s2t2 - }; - assert.ok( - testStatesEvI(s2t2running) < testStatesEvI(s2t2finished)); - assert.ok( - testStatesEvI(s2t2finished) < testStatesEvI(s2finished)); - - const finished = {type: 'finished'}; - assert.ok(testStatesEvI(s1finished) < testStatesEvI(finished)); - assert.ok(testStatesEvI(s2finished) < testStatesEvI(finished)); - - assert.equal( - testStatesEvents.length, 8, inspect(testStatesEvents)); - }), + assert.equal(spyKill1.callCount, 1); + assert.equal(spyKill2.callCount, 1); + + const running = { type: 'started', tests: [root.id] }; + + const s1running = { + type: 'suite', + state: 'running', + suite: suite1 + }; + const s1finished = { + type: 'suite', + state: 'completed', + suite: suite1 + }; + assert.ok(testStatesEvI(running) < testStatesEvI(s1running)); + assert.ok(testStatesEvI(s1running) < testStatesEvI(s1finished)); + + const s2running = { + type: 'suite', + state: 'running', + suite: suite2 + }; + const s2finished = { + type: 'suite', + state: 'completed', + suite: suite2 + }; + assert.ok(testStatesEvI(running) < testStatesEvI(s1running)); + assert.ok(testStatesEvI(s2running) < testStatesEvI(s2finished)); + + const s2t2running = { + type: 'test', + state: 'running', + test: s2t2 + }; + assert.ok( + testStatesEvI(s2running) < testStatesEvI(s2t2running)); + + const s2t2finished = { + type: 'test', + state: 'skipped', + test: s2t2 + }; + assert.ok( + testStatesEvI(s2t2running) < testStatesEvI(s2t2finished)); + assert.ok( + testStatesEvI(s2t2finished) < testStatesEvI(s2finished)); + + const finished = { type: 'finished' }; + assert.ok(testStatesEvI(s1finished) < testStatesEvI(finished)); + assert.ok(testStatesEvI(s2finished) < testStatesEvI(finished)); + + assert.equal( + testStatesEvents.length, 8, inspect(testStatesEvents)); + }), new Mocha.Test( - 'cancel after run finished', - function() { - let spyKill1: sinon.SinonSpy; - let spyKill2: sinon.SinonSpy; - { - const spawnEvent = - new ChildProcessStub(example1.suite1.outputs[2][1]); - spyKill1 = sinon.spy(spawnEvent, 'kill'); - const withArgs = spawnStub.withArgs( - example1.suite1.execPath, example1.suite1.outputs[2][0]); - withArgs.onCall(withArgs.callCount).returns(spawnEvent); - } - { - const spawnEvent = - new ChildProcessStub(example1.suite2.outputs[2][1]); - spyKill2 = sinon.spy(spawnEvent, 'kill'); - const withArgs = spawnStub.withArgs( - example1.suite2.execPath, example1.suite2.outputs[2][0]); - withArgs.onCall(withArgs.callCount).returns(spawnEvent); - } - const run = adapter.run([root.id]); - return run.then(function() { - adapter.cancel(); - assert.equal(spyKill1.callCount, 0); - assert.equal(spyKill2.callCount, 0); - }); - }) + 'cancel after run finished', + function () { + let spyKill1: sinon.SinonSpy; + let spyKill2: sinon.SinonSpy; + { + const spawnEvent = + new ChildProcessStub(example1.suite1.outputs[2][1]); + spyKill1 = sinon.spy(spawnEvent, 'kill'); + const withArgs = spawnStub.withArgs( + example1.suite1.execPath, example1.suite1.outputs[2][0]); + withArgs.onCall(withArgs.callCount).returns(spawnEvent); + } + { + const spawnEvent = + new ChildProcessStub(example1.suite2.outputs[2][1]); + spyKill2 = sinon.spy(spawnEvent, 'kill'); + const withArgs = spawnStub.withArgs( + example1.suite2.execPath, example1.suite2.outputs[2][0]); + withArgs.onCall(withArgs.callCount).returns(spawnEvent); + } + const run = adapter.run([root.id]); + return run.then(function () { + adapter.cancel(); + assert.equal(spyKill1.callCount, 0); + assert.equal(spyKill2.callCount, 0); + }); + }) ]; - context('executables=["execPath1", "./execPath2"]', function() { - before(function() { - return updateConfig('executables', ['execPath1', './execPath2']); + context('executables=["execPath1", "execPath2"]', function () { + before(function () { + return updateConfig('executables', ['execPath1', 'execPath2']); }); - after(function() { + after(function () { return updateConfig('executables', undefined); }); let suite1Watcher: FileSystemWatcherStub; - beforeEach(async function() { + beforeEach(async function () { assert.equal(watchers.size, 2); assert.ok(watchers.has(example1.suite1.execPath)); suite1Watcher = watchers.get(example1.suite1.execPath)!; example1.suite1.assert( - 'execPath1', ['s1t1', 's1t2'], suite1, uniqueIdC); + 'execPath1', ['s1t1', 's1t2'], suite1, uniqueIdC); example1.suite2.assert( - './execPath2', ['s2t1', 's2t2 [.]', 's2t3'], suite2, uniqueIdC); + 'execPath2', ['s2t1', 's2t2 [.]', 's2t3'], suite2, uniqueIdC); }); for (let t of testsForAdapterWithSuite1AndSuite2) this.addTest(t.clone()); it('reload because of fswatcher event: touch(changed)', - async function() { - this.slow(200); - const newRoot = await doAndWaitForReloadEvent(this, async () => { - suite1Watcher.sendChange(); - }); - assert.deepStrictEqual(newRoot, root); - assert.deepStrictEqual( - testsEvents, - [{type: 'started'}, {type: 'finished', suite: root}]); - }); + async function () { + this.slow(200); + const newRoot = await doAndWaitForReloadEvent(this, async () => { + suite1Watcher.sendChange(); + }); + assert.deepStrictEqual(newRoot, root); + assert.deepStrictEqual( + testsEvents, + [{ type: 'started' }, { type: 'finished', suite: root }]); + }); it('reload because of fswatcher event: double touch(changed)', - async function() { - this.slow(300); - const oldRoot = root; - suite1Watcher.sendChange(); - suite1Watcher.sendChange(); - await waitFor(this, async () => { - return testsEvents.length >= 2; - }); - await promisify(setTimeout)(100); - assert.deepStrictEqual( - testsEvents, - [{type: 'started'}, {type: 'finished', suite: oldRoot}]); - testsEvents.pop(); - testsEvents.pop(); - }); + async function () { + this.slow(300); + const oldRoot = root; + suite1Watcher.sendChange(); + suite1Watcher.sendChange(); + await waitFor(this, async () => { + return testsEvents.length >= 2; + }); + await promisify(setTimeout)(100); + assert.deepStrictEqual( + testsEvents, + [{ type: 'started' }, { type: 'finished', suite: oldRoot }]); + testsEvents.pop(); + testsEvents.pop(); + }); it('reload because of fswatcher event: double touch(changed) with delay', - async function() { - this.slow(300); - const oldRoot = root; - suite1Watcher.sendChange(); - setTimeout(() => { - suite1Watcher.sendChange(); - }, 20); - await waitFor(this, async () => { - return testsEvents.length >= 2; - }); - await promisify(setTimeout)(100); - assert.deepStrictEqual( - testsEvents, - [{type: 'started'}, {type: 'finished', suite: oldRoot}]); - testsEvents.pop(); - testsEvents.pop(); - }); + async function () { + this.slow(300); + const oldRoot = root; + suite1Watcher.sendChange(); + setTimeout(() => { + suite1Watcher.sendChange(); + }, 20); + await waitFor(this, async () => { + return testsEvents.length >= 2; + }); + await promisify(setTimeout)(100); + assert.deepStrictEqual( + testsEvents, + [{ type: 'started' }, { type: 'finished', suite: oldRoot }]); + testsEvents.pop(); + testsEvents.pop(); + }); it('reload because of fswatcher event: touch(delete,create)', - async function() { - this.slow(200); - const newRoot = await doAndWaitForReloadEvent(this, async () => { - suite1Watcher.sendDelete(); - suite1Watcher.sendCreate(); - }); - assert.deepStrictEqual(newRoot, root); - assert.deepStrictEqual( - testsEvents, - [{type: 'started'}, {type: 'finished', suite: root}]); - }); + async function () { + this.slow(200); + const newRoot = await doAndWaitForReloadEvent(this, async () => { + suite1Watcher.sendDelete(); + suite1Watcher.sendCreate(); + }); + assert.deepStrictEqual(newRoot, root); + assert.deepStrictEqual( + testsEvents, + [{ type: 'started' }, { type: 'finished', suite: root }]); + }); it('reload because of fswatcher event: double touch(delete,create)', - async function() { - this.slow(300); - const oldRoot = root; - suite1Watcher.sendChange(); - suite1Watcher.sendChange(); - await waitFor(this, async () => { - return testsEvents.length >= 2; - }); - await promisify(setTimeout)(100); - assert.deepStrictEqual( - testsEvents, - [{type: 'started'}, {type: 'finished', suite: oldRoot}]); - testsEvents.pop(); - testsEvents.pop(); - }); + async function () { + this.slow(300); + const oldRoot = root; + suite1Watcher.sendChange(); + suite1Watcher.sendChange(); + await waitFor(this, async () => { + return testsEvents.length >= 2; + }); + await promisify(setTimeout)(100); + assert.deepStrictEqual( + testsEvents, + [{ type: 'started' }, { type: 'finished', suite: oldRoot }]); + testsEvents.pop(); + testsEvents.pop(); + }); it('reload because of fswatcher event: double touch(delete,create) with delay', - async function() { - this.slow(300); - const oldRoot = root; - suite1Watcher.sendChange(); - setTimeout(() => { - suite1Watcher.sendChange(); - }, 20); - await waitFor(this, async () => { - return testsEvents.length >= 2; - }); - await promisify(setTimeout)(100); - assert.deepStrictEqual( - testsEvents, - [{type: 'started'}, {type: 'finished', suite: oldRoot}]); - testsEvents.pop(); - testsEvents.pop(); - }); + async function () { + this.slow(300); + const oldRoot = root; + suite1Watcher.sendChange(); + setTimeout(() => { + suite1Watcher.sendChange(); + }, 20); + await waitFor(this, async () => { + return testsEvents.length >= 2; + }); + await promisify(setTimeout)(100); + assert.deepStrictEqual( + testsEvents, + [{ type: 'started' }, { type: 'finished', suite: oldRoot }]); + testsEvents.pop(); + testsEvents.pop(); + }); it('reload because of fswatcher event: test added', - async function(this: Mocha.Context) { - this.slow(200); - const testListOutput = example1.suite1.outputs[1][1].split('\n'); - assert.equal(testListOutput.length, 10); - testListOutput.splice( - 1, 0, ' s1t0', ' suite1.cpp:6', ' tag1'); - const withArgs = spawnStub.withArgs( - example1.suite1.execPath, example1.suite1.outputs[1][0]); - withArgs.onCall(withArgs.callCount) - .returns(new ChildProcessStub( - testListOutput.join(EOL))); - - const oldRootChildren = [...root.children]; - const oldSuite1Children = [...suite1.children]; - const oldSuite2Children = [...suite2.children]; - - const newRoot = await doAndWaitForReloadEvent(this, async () => { - suite1Watcher.sendDelete(); - suite1Watcher.sendCreate(); - }); - - assert.equal(newRoot, root); - assert.equal(root.children.length, oldRootChildren.length); - for (let i = 0; i < oldRootChildren.length; i++) { - assert.equal(root.children[i], oldRootChildren[i]); - } - - assert.equal( - suite1.children.length, oldSuite1Children.length + 1); - for (let i = 0; i < suite1.children.length; i++) { - assert.equal(suite1.children[i + 1], oldSuite1Children[i]); - } - const newTest = suite1.children[0]; - assert.ok(!uniqueIdC.has(newTest.id)); - assert.equal(newTest.label, 's1t0'); - - assert.equal(suite2.children.length, oldSuite2Children.length); - for (let i = 0; i < suite2.children.length; i++) { - assert.equal(suite2.children[i], oldSuite2Children[i]); - } - }); + async function (this: Mocha.Context) { + this.slow(200); + const testListOutput = example1.suite1.outputs[1][1].split('\n'); + assert.equal(testListOutput.length, 10); + testListOutput.splice( + 1, 0, ' s1t0', ' suite1.cpp:6', ' tag1'); + const withArgs = spawnStub.withArgs( + example1.suite1.execPath, example1.suite1.outputs[1][0]); + withArgs.onCall(withArgs.callCount) + .returns(new ChildProcessStub( + testListOutput.join(EOL))); + + const oldRootChildren = [...root.children]; + const oldSuite1Children = [...suite1.children]; + const oldSuite2Children = [...suite2.children]; + + const newRoot = await doAndWaitForReloadEvent(this, async () => { + suite1Watcher.sendDelete(); + suite1Watcher.sendCreate(); + }); + + assert.equal(newRoot, root); + assert.equal(root.children.length, oldRootChildren.length); + for (let i = 0; i < oldRootChildren.length; i++) { + assert.equal(root.children[i], oldRootChildren[i]); + } + + assert.equal( + suite1.children.length, oldSuite1Children.length + 1); + for (let i = 0; i < suite1.children.length; i++) { + assert.equal(suite1.children[i + 1], oldSuite1Children[i]); + } + const newTest = suite1.children[0]; + assert.ok(!uniqueIdC.has(newTest.id)); + assert.equal(newTest.label, 's1t0'); + + assert.equal(suite2.children.length, oldSuite2Children.length); + for (let i = 0; i < suite2.children.length; i++) { + assert.equal(suite2.children[i], oldSuite2Children[i]); + } + }); it('reload because of fswatcher event: test deleted', - async function(this: Mocha.Context) { - this.slow(200); - const testListOutput = example1.suite1.outputs[1][1].split('\n'); - assert.equal(testListOutput.length, 10); - testListOutput.splice(1, 3); - const withArgs = spawnStub.withArgs( - example1.suite1.execPath, example1.suite1.outputs[1][0]); - withArgs.onCall(withArgs.callCount) - .returns(new ChildProcessStub(testListOutput.join('\n'))); - - const oldRootChildren = [...root.children]; - const oldSuite1Children = [...suite1.children]; - const oldSuite2Children = [...suite2.children]; - - const newRoot = await doAndWaitForReloadEvent(this, async () => { - suite1Watcher.sendDelete(); - suite1Watcher.sendCreate(); - }); - - assert.equal(newRoot, root); - assert.equal(root.children.length, oldRootChildren.length); - for (let i = 0; i < oldRootChildren.length; i++) { - assert.equal(root.children[i], oldRootChildren[i]); - } - - assert.equal( - suite1.children.length + 1, oldSuite1Children.length); - for (let i = 0; i < suite1.children.length; i++) { - assert.equal(suite1.children[i], oldSuite1Children[i + 1]); - } - - assert.equal(suite2.children.length, oldSuite2Children.length); - for (let i = 0; i < suite2.children.length; i++) { - assert.equal(suite2.children[i], oldSuite2Children[i]); - } - }); + async function (this: Mocha.Context) { + this.slow(200); + const testListOutput = example1.suite1.outputs[1][1].split('\n'); + assert.equal(testListOutput.length, 10); + testListOutput.splice(1, 3); + const withArgs = spawnStub.withArgs( + example1.suite1.execPath, example1.suite1.outputs[1][0]); + withArgs.onCall(withArgs.callCount) + .returns(new ChildProcessStub(testListOutput.join('\n'))); + + const oldRootChildren = [...root.children]; + const oldSuite1Children = [...suite1.children]; + const oldSuite2Children = [...suite2.children]; + + const newRoot = await doAndWaitForReloadEvent(this, async () => { + suite1Watcher.sendDelete(); + suite1Watcher.sendCreate(); + }); + + assert.equal(newRoot, root); + assert.equal(root.children.length, oldRootChildren.length); + for (let i = 0; i < oldRootChildren.length; i++) { + assert.equal(root.children[i], oldRootChildren[i]); + } + + assert.equal( + suite1.children.length + 1, oldSuite1Children.length); + for (let i = 0; i < suite1.children.length; i++) { + assert.equal(suite1.children[i], oldSuite1Children[i + 1]); + } + + assert.equal(suite2.children.length, oldSuite2Children.length); + for (let i = 0; i < suite2.children.length; i++) { + assert.equal(suite2.children[i], oldSuite2Children[i]); + } + }); }); - context('executables=[{}] and env={...}', function() { - before(async function() { + context('executables=[{}] and env={...}', function () { + before(async function () { await updateConfig( - 'executables', [{ - name: '${relDirpath}/${filename} (${absDirpath})', - path: 'execPath{1,2}', - cwd: '${workspaceFolder}/cwd', - env: { - C2LOCALTESTENV: 'c2localtestenv', - C2OVERRIDETESTENV: 'c2overridetestenv-l' - } - }]); + 'executables', [{ + name: '${relDirpath}/${filename} (${absDirpath})', + path: 'execPath{1,2}', + cwd: '${workspaceFolder}/cwd', + env: { + C2LOCALTESTENV: 'c2localtestenv', + C2OVERRIDETESTENV: 'c2overridetestenv-l' + } + }]); vsfsWatchStub - .withArgs(matchRelativePattern( - path.join(workspaceFolderUri.fsPath, 'execPath{1,2}'))) - .callsFake(handleCreateWatcherCb); + .withArgs(matchRelativePattern( + path.join(workspaceFolderUri.fsPath, 'execPath{1,2}'))) + .callsFake(handleCreateWatcherCb); vsFindFilesStub - .withArgs(matchRelativePattern( - path.join(workspaceFolderUri.fsPath, 'execPath{1,2}'))) - .returns([ - vscode.Uri.file(example1.suite1.execPath), - vscode.Uri.file(example1.suite2.execPath), - ]); + .withArgs(matchRelativePattern( + path.join(workspaceFolderUri.fsPath, 'execPath{1,2}'))) + .returns([ + vscode.Uri.file(example1.suite1.execPath), + vscode.Uri.file(example1.suite2.execPath), + ]); await updateConfig('defaultEnv', { 'C2GLOBALTESTENV': 'c2globaltestenv', 'C2OVERRIDETESTENV': 'c2overridetestenv-g', }); }); - after(async function() { + after(async function () { await updateConfig('executables', undefined); await updateConfig('defaultEnv', undefined); }); - beforeEach(async function() { + beforeEach(async function () { example1.suite1.assert( - './execPath1 (' + workspaceFolderUri.fsPath + ')', - ['s1t1', 's1t2'], suite1, uniqueIdC); + './execPath1 (' + workspaceFolderUri.fsPath + ')', + ['s1t1', 's1t2'], suite1, uniqueIdC); example1.suite2.assert( - './execPath2 (' + workspaceFolderUri.fsPath + ')', - ['s2t1', 's2t2 [.]', 's2t3'], suite2, uniqueIdC); + './execPath2 (' + workspaceFolderUri.fsPath + ')', + ['s2t1', 's2t2 [.]', 's2t3'], suite2, uniqueIdC); }) for (let t of testsForAdapterWithSuite1AndSuite2) this.addTest( - t.clone()); + t.clone()); - it('should get execution options', async function() { + it('should get execution options', async function () { { const withArgs = spawnStub.withArgs( - example1.suite1.execPath, example1.suite1.outputs[2][0]); + example1.suite1.execPath, example1.suite1.outputs[2][0]); withArgs.onCall(withArgs.callCount) - .callsFake((p: string, args: string[], ops: any) => { - assert.equal( - ops.cwd, path.join(workspaceFolderUri.fsPath, 'cwd')); - assert.equal(ops.env.C2LOCALTESTENV, 'c2localtestenv'); - assert.ok(!ops.env.hasOwnProperty('C2GLOBALTESTENV')); - assert.equal( - ops.env.C2OVERRIDETESTENV, 'c2overridetestenv-l'); - return new ChildProcessStub(example1.suite1.outputs[2][1]); - }); + .callsFake((p: string, args: string[], ops: any) => { + assert.equal( + ops.cwd, path.join(workspaceFolderUri.fsPath, 'cwd')); + assert.equal(ops.env.C2LOCALTESTENV, 'c2localtestenv'); + assert.ok(!ops.env.hasOwnProperty('C2GLOBALTESTENV')); + assert.equal( + ops.env.C2OVERRIDETESTENV, 'c2overridetestenv-l'); + return new ChildProcessStub(example1.suite1.outputs[2][1]); + }); const cc = withArgs.callCount; await adapter.run([suite1.id]); @@ -1339,17 +1339,17 @@ describe('C2TestAdapter', function() { } { const withArgs = spawnStub.withArgs( - example1.suite2.execPath, example1.suite2.outputs[2][0]); + example1.suite2.execPath, example1.suite2.outputs[2][0]); withArgs.onCall(withArgs.callCount) - .callsFake((p: string, args: string[], ops: any) => { - assert.equal( - ops.cwd, path.join(workspaceFolderUri.fsPath, 'cwd')); - assert.equal(ops.env.C2LOCALTESTENV, 'c2localtestenv'); - assert.ok(!ops.env.hasOwnProperty('C2GLOBALTESTENV')); - assert.equal( - ops.env.C2OVERRIDETESTENV, 'c2overridetestenv-l'); - return new ChildProcessStub(example1.suite2.outputs[2][1]); - }); + .callsFake((p: string, args: string[], ops: any) => { + assert.equal( + ops.cwd, path.join(workspaceFolderUri.fsPath, 'cwd')); + assert.equal(ops.env.C2LOCALTESTENV, 'c2localtestenv'); + assert.ok(!ops.env.hasOwnProperty('C2GLOBALTESTENV')); + assert.equal( + ops.env.C2OVERRIDETESTENV, 'c2overridetestenv-l'); + return new ChildProcessStub(example1.suite2.outputs[2][1]); + }); const cc = withArgs.callCount; await adapter.run([suite2.id]); assert.equal(withArgs.callCount, cc + 1); @@ -1359,67 +1359,67 @@ describe('C2TestAdapter', function() { }); context( - 'executables=["execPath1", "execPath2", "execPath3"]', - async function() { - before(function() { - return updateConfig( - 'executables', ['execPath1', 'execPath2', 'execPath3']); - }); + 'executables=["execPath1", "execPath2", "execPath3"]', + async function () { + before(function () { + return updateConfig( + 'executables', ['execPath1', 'execPath2', 'execPath3']); + }); - after(function() { - return updateConfig('executables', undefined); - }); + after(function () { + return updateConfig('executables', undefined); + }); - it('run suite3 one-by-one', async function() { - this.slow(300); - assert.equal(root.children.length, 3); - assert.equal(root.children[0].type, 'suite'); - const suite3 = root.children[2]; - assert.equal(suite3.children.length, 33); + it('run suite3 one-by-one', async function () { + this.slow(300); + assert.equal(root.children.length, 3); + assert.equal(root.children[0].type, 'suite'); + const suite3 = root.children[2]; + assert.equal(suite3.children.length, 33); - spawnStub.withArgs(example1.suite3.execPath).throwsArg(1); + spawnStub.withArgs(example1.suite3.execPath).throwsArg(1); - const runAndCheckEvents = async (test: TestInfo) => { - assert.equal(testStatesEvents.length, 0); + const runAndCheckEvents = async (test: TestInfo) => { + assert.equal(testStatesEvents.length, 0); - await adapter.run([test.id]); + await adapter.run([test.id]); - assert.equal(testStatesEvents.length, 6, inspect(test)); + assert.equal(testStatesEvents.length, 6, inspect(test)); - assert.deepStrictEqual( - {type: 'started', tests: [test.id]}, testStatesEvents[0]); - assert.deepStrictEqual( - {type: 'suite', state: 'running', suite: suite3}, - testStatesEvents[1]); + assert.deepStrictEqual( + { type: 'started', tests: [test.id] }, testStatesEvents[0]); + assert.deepStrictEqual( + { type: 'suite', state: 'running', suite: suite3 }, + testStatesEvents[1]); - assert.equal(testStatesEvents[2].type, 'test'); - assert.equal((testStatesEvents[2]).state, 'running'); - assert.equal((testStatesEvents[2]).test, test); + assert.equal(testStatesEvents[2].type, 'test'); + assert.equal((testStatesEvents[2]).state, 'running'); + assert.equal((testStatesEvents[2]).test, test); - assert.equal(testStatesEvents[3].type, 'test'); - assert.ok( - (testStatesEvents[3]).state == 'passed' || - (testStatesEvents[3]).state == 'skipped' || - (testStatesEvents[3]).state == 'failed'); - assert.equal((testStatesEvents[3]).test, test); + assert.equal(testStatesEvents[3].type, 'test'); + assert.ok( + (testStatesEvents[3]).state == 'passed' || + (testStatesEvents[3]).state == 'skipped' || + (testStatesEvents[3]).state == 'failed'); + assert.equal((testStatesEvents[3]).test, test); - assert.deepStrictEqual( - {type: 'suite', state: 'completed', suite: suite3}, - testStatesEvents[4]); - assert.deepStrictEqual({type: 'finished'}, testStatesEvents[5]); + assert.deepStrictEqual( + { type: 'suite', state: 'completed', suite: suite3 }, + testStatesEvents[4]); + assert.deepStrictEqual({ type: 'finished' }, testStatesEvents[5]); - while (testStatesEvents.length) testStatesEvents.pop(); - }; + while (testStatesEvents.length) testStatesEvents.pop(); + }; - for (let test of suite3.children) { - assert.equal(test.type, 'test'); - await runAndCheckEvents(test); - } - }) + for (let test of suite3.children) { + assert.equal(test.type, 'test'); + await runAndCheckEvents(test); + } }) + }) }) - specify('load executables=', async function() { + specify('load executables=', async function () { this.slow(300); await updateConfig('executables', example1.suite1.execPath); adapter = createAdapterAndSubscribe(); @@ -1427,9 +1427,9 @@ describe('C2TestAdapter', function() { await adapter.load(); assert.equal(testsEvents.length, 2); assert.equal( - (testsEvents[testsEvents.length - 1]) - .suite!.children.length, - 1); + (testsEvents[testsEvents.length - 1]) + .suite!.children.length, + 1); testsEvents.pop(); testsEvents.pop(); @@ -1438,197 +1438,197 @@ describe('C2TestAdapter', function() { }); specify( - 'load executables=["execPath1", "execPath2"] with error', - async function() { - this.slow(300); - await updateConfig('executables', ['execPath1', 'execPath2']); - adapter = createAdapterAndSubscribe(); - - const withArgs = spawnStub.withArgs( - example1.suite2.execPath, example1.suite2.outputs[1][0]); - withArgs.onCall(withArgs.callCount).throws( - 'dummy error for testing (should be handled)'); - - await adapter.load(); - testsEvents.pop(); - testsEvents.pop(); - - disposeAdapterAndSubscribers(); - await updateConfig('executables', undefined); - }) + 'load executables=["execPath1", "execPath2"] with error', + async function () { + this.slow(300); + await updateConfig('executables', ['execPath1', 'execPath2']); + adapter = createAdapterAndSubscribe(); + + const withArgs = spawnStub.withArgs( + example1.suite2.execPath, example1.suite2.outputs[1][0]); + withArgs.onCall(withArgs.callCount).throws( + 'dummy error for testing (should be handled)'); + + await adapter.load(); + testsEvents.pop(); + testsEvents.pop(); + + disposeAdapterAndSubscribers(); + await updateConfig('executables', undefined); + }) specify( - 'load executables=["execPath1", "execPath2Copy"]; delete; sleep 3; create', - async function() { - const watchTimeout = 6; - await updateConfig('defaultWatchTimeoutSec', watchTimeout); - this.timeout(watchTimeout * 1000 + 2500 /* because of 'delay' */); - this.slow(watchTimeout * 1000 + 2500 /* because of 'delay' */); - const execPath2CopyPath = - path.join(workspaceFolderUri.fsPath, 'execPath2Copy'); - - for (let scenario of example1.suite2.outputs) { - spawnStub.withArgs(execPath2CopyPath, scenario[0]) - .callsFake(function() { - return new ChildProcessStub(scenario[1]); - }); - } + 'load executables=["execPath1", "execPath2Copy"]; delete; sleep 3; create', + async function () { + const watchTimeout = 6; + await updateConfig('defaultWatchTimeoutSec', watchTimeout); + this.timeout(watchTimeout * 1000 + 2500 /* because of 'delay' */); + this.slow(watchTimeout * 1000 + 2500 /* because of 'delay' */); + const execPath2CopyPath = + path.join(workspaceFolderUri.fsPath, 'execPath2Copy'); + + for (let scenario of example1.suite2.outputs) { + spawnStub.withArgs(execPath2CopyPath, scenario[0]) + .callsFake(function () { + return new ChildProcessStub(scenario[1]); + }); + } - fsStatStub.withArgs(execPath2CopyPath) - .callsFake(handleStatExistsFile); + fsStatStub.withArgs(execPath2CopyPath) + .callsFake(handleStatExistsFile); - vsfsWatchStub.withArgs(matchRelativePattern(execPath2CopyPath)) - .callsFake(handleCreateWatcherCb); + vsfsWatchStub.withArgs(matchRelativePattern(execPath2CopyPath)) + .callsFake(handleCreateWatcherCb); - vsFindFilesStub.withArgs(matchRelativePattern(execPath2CopyPath)) - .returns([vscode.Uri.file(execPath2CopyPath)]); + vsFindFilesStub.withArgs(matchRelativePattern(execPath2CopyPath)) + .returns([vscode.Uri.file(execPath2CopyPath)]); - await updateConfig('executables', ['execPath1', 'execPath2Copy']); - adapter = createAdapterAndSubscribe(); + await updateConfig('executables', ['execPath1', 'execPath2Copy']); + adapter = createAdapterAndSubscribe(); - await adapter.load(); + await adapter.load(); - assert.equal( - (testsEvents[testsEvents.length - 1]) - .suite!.children.length, - 2); - testsEvents.pop(); - testsEvents.pop(); + assert.equal( + (testsEvents[testsEvents.length - 1]) + .suite!.children.length, + 2); + testsEvents.pop(); + testsEvents.pop(); - assert.ok(watchers.has(execPath2CopyPath)); - const watcher = watchers.get(execPath2CopyPath)!; + assert.ok(watchers.has(execPath2CopyPath)); + const watcher = watchers.get(execPath2CopyPath)!; - let start: number = 0; - const newRoot = await doAndWaitForReloadEvent(this, async () => { + let start: number = 0; + const newRoot = await doAndWaitForReloadEvent(this, async () => { + fsStatStub.withArgs(execPath2CopyPath) + .callsFake(handleStatNotExists); + start = Date.now(); + watcher.sendDelete(); + setTimeout(() => { + assert.equal(testsEvents.length, 0); + }, 1500); + setTimeout(() => { fsStatStub.withArgs(execPath2CopyPath) - .callsFake(handleStatNotExists); - start = Date.now(); - watcher.sendDelete(); - setTimeout(() => { - assert.equal(testsEvents.length, 0); - }, 1500); - setTimeout(() => { - fsStatStub.withArgs(execPath2CopyPath) - .callsFake(handleStatExistsFile); - watcher.sendCreate(); - }, 3000); - }, 40000); - const elapsed = Date.now() - start; - - assert.equal(testsEvents.length, 2); - testsEvents.pop(); - testsEvents.pop(); - for (let scenario of example1.suite2.outputs) { - spawnStub.withArgs(execPath2CopyPath, scenario[0]).callsFake(() => { - throw Error('restore'); - }); - } - fsStatStub.withArgs(execPath2CopyPath).callsFake(() => { + .callsFake(handleStatExistsFile); + watcher.sendCreate(); + }, 3000); + }, 40000); + const elapsed = Date.now() - start; + + assert.equal(testsEvents.length, 2); + testsEvents.pop(); + testsEvents.pop(); + for (let scenario of example1.suite2.outputs) { + spawnStub.withArgs(execPath2CopyPath, scenario[0]).callsFake(() => { throw Error('restore'); }); - vsfsWatchStub.withArgs(matchRelativePattern(execPath2CopyPath)) - .callsFake(() => { - throw Error('restore'); - }); - vsFindFilesStub.withArgs(matchRelativePattern(execPath2CopyPath)) - .callsFake(() => { - throw Error('restore'); - }); - disposeAdapterAndSubscribers(); - await updateConfig('executables', undefined); - await updateConfig('defaultWatchTimeoutSec', undefined); - - assert.equal(newRoot.children.length, 2); - assert.ok(3000 < elapsed, inspect(elapsed)); - assert.ok(elapsed < watchTimeout * 1000 + 2400, inspect(elapsed)); + } + fsStatStub.withArgs(execPath2CopyPath).callsFake(() => { + throw Error('restore'); }); + vsfsWatchStub.withArgs(matchRelativePattern(execPath2CopyPath)) + .callsFake(() => { + throw Error('restore'); + }); + vsFindFilesStub.withArgs(matchRelativePattern(execPath2CopyPath)) + .callsFake(() => { + throw Error('restore'); + }); + disposeAdapterAndSubscribers(); + await updateConfig('executables', undefined); + await updateConfig('defaultWatchTimeoutSec', undefined); + + assert.equal(newRoot.children.length, 2); + assert.ok(3000 < elapsed, inspect(elapsed)); + assert.ok(elapsed < watchTimeout * 1000 + 2400, inspect(elapsed)); + }); specify( - 'load executables=["execPath1", "execPath2Copy"]; delete second', - async function() { - const watchTimeout = 5; - await updateConfig('defaultWatchTimeoutSec', watchTimeout); - this.timeout(watchTimeout * 1000 + 2500 /* because of 'delay' */); - this.slow(watchTimeout * 1000 + 2500 /* because of 'delay' */); - const execPath2CopyPath = - path.join(workspaceFolderUri.fsPath, 'execPath2Copy'); - - for (let scenario of example1.suite2.outputs) { - spawnStub.withArgs(execPath2CopyPath, scenario[0]) - .callsFake(function() { - return new ChildProcessStub(scenario[1]); - }); - } + 'load executables=["execPath1", "execPath2Copy"]; delete second', + async function () { + const watchTimeout = 5; + await updateConfig('defaultWatchTimeoutSec', watchTimeout); + this.timeout(watchTimeout * 1000 + 2500 /* because of 'delay' */); + this.slow(watchTimeout * 1000 + 2500 /* because of 'delay' */); + const execPath2CopyPath = + path.join(workspaceFolderUri.fsPath, 'execPath2Copy'); + + for (let scenario of example1.suite2.outputs) { + spawnStub.withArgs(execPath2CopyPath, scenario[0]) + .callsFake(function () { + return new ChildProcessStub(scenario[1]); + }); + } - fsStatStub.withArgs(execPath2CopyPath) - .callsFake(handleStatExistsFile); + fsStatStub.withArgs(execPath2CopyPath) + .callsFake(handleStatExistsFile); - vsfsWatchStub.withArgs(matchRelativePattern(execPath2CopyPath)) - .callsFake(handleCreateWatcherCb); + vsfsWatchStub.withArgs(matchRelativePattern(execPath2CopyPath)) + .callsFake(handleCreateWatcherCb); - vsFindFilesStub.withArgs(matchRelativePattern(execPath2CopyPath)) - .returns([vscode.Uri.file(execPath2CopyPath)]); + vsFindFilesStub.withArgs(matchRelativePattern(execPath2CopyPath)) + .returns([vscode.Uri.file(execPath2CopyPath)]); - await updateConfig('executables', ['execPath1', 'execPath2Copy']); - adapter = createAdapterAndSubscribe(); + await updateConfig('executables', ['execPath1', 'execPath2Copy']); + adapter = createAdapterAndSubscribe(); - await adapter.load(); + await adapter.load(); - assert.equal( - (testsEvents[testsEvents.length - 1]) - .suite!.children.length, - 2); - testsEvents.pop(); - testsEvents.pop(); + assert.equal( + (testsEvents[testsEvents.length - 1]) + .suite!.children.length, + 2); + testsEvents.pop(); + testsEvents.pop(); - assert.ok(watchers.has(execPath2CopyPath)); - const watcher = watchers.get(execPath2CopyPath)!; + assert.ok(watchers.has(execPath2CopyPath)); + const watcher = watchers.get(execPath2CopyPath)!; - let start: number = 0; - const newRoot = await doAndWaitForReloadEvent(this, async () => { - fsStatStub.withArgs(execPath2CopyPath) - .callsFake(handleStatNotExists); - start = Date.now(); - watcher.sendDelete(); - }, 40000); - const elapsed = Date.now() - start; - testsEvents.pop(); - testsEvents.pop(); - for (let scenario of example1.suite2.outputs) { - spawnStub.withArgs(execPath2CopyPath, scenario[0]).callsFake(() => { - throw Error('restore'); - }); - } - fsStatStub.withArgs(execPath2CopyPath).callsFake(() => { + let start: number = 0; + const newRoot = await doAndWaitForReloadEvent(this, async () => { + fsStatStub.withArgs(execPath2CopyPath) + .callsFake(handleStatNotExists); + start = Date.now(); + watcher.sendDelete(); + }, 40000); + const elapsed = Date.now() - start; + testsEvents.pop(); + testsEvents.pop(); + for (let scenario of example1.suite2.outputs) { + spawnStub.withArgs(execPath2CopyPath, scenario[0]).callsFake(() => { throw Error('restore'); }); - vsfsWatchStub.withArgs(matchRelativePattern(execPath2CopyPath)) - .callsFake(() => { - throw Error('restore'); - }); - vsFindFilesStub.withArgs(matchRelativePattern(execPath2CopyPath)) - .callsFake(() => { - throw Error('restore'); - }); - disposeAdapterAndSubscribers(); - await updateConfig('executables', undefined); - await updateConfig('defaultWatchTimeoutSec', undefined); + } + fsStatStub.withArgs(execPath2CopyPath).callsFake(() => { + throw Error('restore'); + }); + vsfsWatchStub.withArgs(matchRelativePattern(execPath2CopyPath)) + .callsFake(() => { + throw Error('restore'); + }); + vsFindFilesStub.withArgs(matchRelativePattern(execPath2CopyPath)) + .callsFake(() => { + throw Error('restore'); + }); + disposeAdapterAndSubscribers(); + await updateConfig('executables', undefined); + await updateConfig('defaultWatchTimeoutSec', undefined); - assert.equal(newRoot.children.length, 1); - assert.ok(watchTimeout * 1000 < elapsed, inspect(elapsed)); - assert.ok(elapsed < watchTimeout * 1000 + 2400, inspect(elapsed)); - }) + assert.equal(newRoot.children.length, 1); + assert.ok(watchTimeout * 1000 < elapsed, inspect(elapsed)); + assert.ok(elapsed < watchTimeout * 1000 + 2400, inspect(elapsed)); + }) - specify('wrong executables format', async function() { + specify('wrong executables format', async function () { this.slow(300); - await updateConfig('executables', {name: ''}); + await updateConfig('executables', { name: '' }); adapter = createAdapterAndSubscribe(); await adapter.load(); const root = - (testsEvents[testsEvents.length - 1]).suite!; + (testsEvents[testsEvents.length - 1]).suite!; assert.equal(root.children.length, 0); testsEvents.pop(); testsEvents.pop(); @@ -1637,12 +1637,12 @@ describe('C2TestAdapter', function() { await updateConfig('executables', undefined); }) - specify('variable substitution with executables={...}', async function() { + specify('variable substitution with executables={...}', async function () { this.slow(300); const wsPath = workspaceFolderUri.fsPath; const execPath2CopyRelPath = path.normalize('foo/bar/base.second.first'); const execPath2CopyPath = - vscode.Uri.file(path.join(wsPath, execPath2CopyRelPath)).fsPath; + vscode.Uri.file(path.join(wsPath, execPath2CopyRelPath)).fsPath; const envArray: [string, string][] = [ ['${absPath}', execPath2CopyPath], @@ -1659,44 +1659,44 @@ describe('C2TestAdapter', function() { ['${workspaceDirectory}', wsPath], ['${workspaceFolder}', wsPath], ]; - const envsStr = envArray.map(v => {return v[0]}).join(' , '); - const expectStr = envArray.map(v => {return v[1]}).join(' , '); + const envsStr = envArray.map(v => { return v[0] }).join(' , '); + const expectStr = envArray.map(v => { return v[1] }).join(' , '); await updateConfig('executables', { name: envsStr, pattern: execPath2CopyRelPath, cwd: envsStr, - env: {C2TESTVARS: envsStr} + env: { C2TESTVARS: envsStr } }); for (let scenario of example1.suite2.outputs) { spawnStub.withArgs(execPath2CopyPath, scenario[0]) - .callsFake(function() { - return new ChildProcessStub(scenario[1]); - }); + .callsFake(function () { + return new ChildProcessStub(scenario[1]); + }); } spawnStub.withArgs(execPath2CopyPath, example1.suite2.t1.outputs[0][0]) - .callsFake(function(p: string, args: string[], ops: any) { - assert.equal(ops.cwd, expectStr); - assert.ok(ops.env.hasOwnProperty('C2TESTVARS')); - assert.equal(ops.env.C2TESTVARS, expectStr); - return new ChildProcessStub(example1.suite2.t1.outputs[0][1]); - }); + .callsFake(function (p: string, args: string[], ops: any) { + assert.equal(ops.cwd, expectStr); + assert.ok(ops.env.hasOwnProperty('C2TESTVARS')); + assert.equal(ops.env.C2TESTVARS, expectStr); + return new ChildProcessStub(example1.suite2.t1.outputs[0][1]); + }); fsStatStub.withArgs(execPath2CopyPath).callsFake(handleStatExistsFile); vsfsWatchStub.withArgs(matchRelativePattern(execPath2CopyPath)) - .callsFake(handleCreateWatcherCb); + .callsFake(handleCreateWatcherCb); vsFindFilesStub.withArgs(matchRelativePattern(execPath2CopyPath)) - .returns([vscode.Uri.file(execPath2CopyPath)]); + .returns([vscode.Uri.file(execPath2CopyPath)]); adapter = createAdapterAndSubscribe(); await adapter.load(); const root = - (testsEvents[testsEvents.length - 1]).suite!; + (testsEvents[testsEvents.length - 1]).suite!; testsEvents.pop(); testsEvents.pop(); @@ -1718,13 +1718,13 @@ describe('C2TestAdapter', function() { throw Error('restore'); }); vsfsWatchStub.withArgs(matchRelativePattern(execPath2CopyPath)) - .callsFake(() => { - throw Error('restore'); - }); + .callsFake(() => { + throw Error('restore'); + }); vsFindFilesStub.withArgs(matchRelativePattern(execPath2CopyPath)) - .callsFake(() => { - throw Error('restore'); - }); + .callsFake(() => { + throw Error('restore'); + }); disposeAdapterAndSubscribers(); await updateConfig('executables', undefined); }) From 4544d77b27dfc5f6fdf1340910de72f92dfa2140 Mon Sep 17 00:00:00 2001 From: Mate Pek Date: Sun, 28 Oct 2018 10:08:08 +0100 Subject: [PATCH 26/49] src/test/C2TestAdapter.test.ts OK --- .vscode/launch.json | 15 +- src/C2ExecutableInfo.ts | 140 +- src/C2TestAdapter.ts | 149 +- src/test/C2TestAdapter.cpp.test.ts | 116 +- src/test/C2TestAdapter.test.ts | 2047 ++++++++++++++-------------- 5 files changed, 1233 insertions(+), 1234 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index d128583f..27a98be5 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -6,12 +6,9 @@ "request": "launch", "name": "Catch2 adapter", "runtimeExecutable": "${execPath}", - "args": [ - "--extensionDevelopmentPath=${workspaceFolder}" - ], - "outFiles": [ - "${workspaceFolder}/out" - ] + "args": ["--extensionDevelopmentPath=${workspaceFolder}"], + "outFiles": ["${workspaceFolder}/out"], + "preLaunchTask": "npm: watch" }, { "name": "Extension Tests", @@ -24,13 +21,11 @@ "--extensionDevelopmentPath=${workspaceFolder}", "--extensionTestsPath=${workspaceFolder}/out/test" ], - "outFiles": [ - "${workspaceFolder}/out/test/**/*.js" - ], + "outFiles": ["${workspaceFolder}/out/test/**/*.js"], "preLaunchTask": "npm: watch", "env": { "C2AVCVA": "c:\\Program Files (x86)\\Microsoft Visual Studio\\2017\\Community\\VC\\Auxiliary\\Build\\vcvarsall.bat" } } ] -} \ No newline at end of file +} diff --git a/src/C2ExecutableInfo.ts b/src/C2ExecutableInfo.ts index c3620e5a..d06f2d78 100644 --- a/src/C2ExecutableInfo.ts +++ b/src/C2ExecutableInfo.ts @@ -3,28 +3,28 @@ // public domain. The author hereby disclaims copyright to this source code. import * as path from 'path'; -import { inspect, promisify } from 'util'; +import {inspect, promisify} from 'util'; import * as vscode from 'vscode'; -import { C2AllTestSuiteInfo } from './C2AllTestSuiteInfo'; -import { C2TestAdapter } from './C2TestAdapter'; -import { C2TestSuiteInfo } from './C2TestSuiteInfo'; +import {C2AllTestSuiteInfo} from './C2AllTestSuiteInfo'; +import {C2TestAdapter} from './C2TestAdapter'; +import {C2TestSuiteInfo} from './C2TestSuiteInfo'; import * as c2fs from './FsWrapper'; -import { resolveVariables } from './Helpers'; +import {resolveVariables} from './Helpers'; export class C2ExecutableInfo implements vscode.Disposable { constructor( - private _adapter: C2TestAdapter, - private readonly _allTests: C2AllTestSuiteInfo, - public readonly name: string, public readonly pattern: string, - public readonly cwd: string, public readonly env: { [prop: string]: any }) { + private _adapter: C2TestAdapter, + private readonly _allTests: C2AllTestSuiteInfo, + public readonly name: string, public readonly pattern: string, + public readonly cwd: string, public readonly env: {[prop: string]: any}) { } private _disposables: vscode.Disposable[] = []; - private _watcher: vscode.FileSystemWatcher | undefined = undefined; + private _watcher: vscode.FileSystemWatcher|undefined = undefined; private readonly _executables: Map = - new Map(); + new Map(); dispose() { if (this._watcher) this._watcher.dispose(); @@ -36,29 +36,31 @@ export class C2ExecutableInfo implements vscode.Disposable { const isAbsolute = path.isAbsolute(this.pattern); const absPattern = isAbsolute ? path.normalize(this.pattern) : - path.resolve(wsUri.fsPath, this.pattern); + path.resolve(wsUri.fsPath, this.pattern); const absPatternAsUri = vscode.Uri.file(absPattern); - const relativeToWs = path.relative(wsUri.fsPath, absPatternAsUri.fsPath); - const isPartOfWs = !relativeToWs.startsWith('..'); + // const relativeToWs = path.relative(wsUri.fsPath, absPatternAsUri.fsPath); + // const isPartOfWs = !relativeToWs.startsWith('..'); // TODO let fileUris: vscode.Uri[] = []; - if (!isAbsolute || (isAbsolute && isPartOfWs)) { + if (!isAbsolute) { const relativePattern = new vscode.RelativePattern( - this._adapter.workspaceFolder, this.pattern); + this._adapter.workspaceFolder, + this.pattern); // TODO pattern backslash + fileUris = - await vscode.workspace.findFiles(relativePattern, undefined, 1000); + await vscode.workspace.findFiles(relativePattern, undefined, 1000); try { // abs path is required this._watcher = vscode.workspace.createFileSystemWatcher( - relativePattern, false, false, false); + relativePattern, false, false, false); this._disposables.push(this._watcher); this._disposables.push( - this._watcher.onDidCreate(this._handleCreate, this)); + this._watcher.onDidCreate(this._handleCreate, this)); this._disposables.push( - this._watcher.onDidChange(this._handleChange, this)); + this._watcher.onDidChange(this._handleChange, this)); this._disposables.push( - this._watcher.onDidDelete(this._handleDelete, this)); + this._watcher.onDidDelete(this._handleDelete, this)); } catch (e) { this._adapter.log.error(inspect([e, this])); } @@ -85,7 +87,7 @@ export class C2ExecutableInfo implements vscode.Disposable { let resolvedName = this.name; let resolvedCwd = this.cwd; - let resolvedEnv: { [prop: string]: string } = this.env; + let resolvedEnv: {[prop: string]: string} = this.env; try { const relPath = path.relative(wsUri.fsPath, file.fsPath); @@ -119,7 +121,7 @@ export class C2ExecutableInfo implements vscode.Disposable { } const suite = this._allTests.createChildSuite( - resolvedName, file.fsPath, { cwd: resolvedCwd, env: resolvedEnv }); + resolvedName, file.fsPath, {cwd: resolvedCwd, env: resolvedEnv}); this._executables.set(file.fsPath, suite); @@ -127,8 +129,8 @@ export class C2ExecutableInfo implements vscode.Disposable { } private readonly _lastEventArrivedAt: - Map = new Map(); + Map = new Map(); private _handleEverything(uri: vscode.Uri) { let suite = this._executables.get(uri.fsPath); @@ -147,44 +149,44 @@ export class C2ExecutableInfo implements vscode.Disposable { this._lastEventArrivedAt.set(uri.fsPath, Date.now()); const x = - (exists: boolean, timeout: number, delay: number): Promise => { - let lastEventArrivedAt = this._lastEventArrivedAt.get(uri.fsPath); - if (lastEventArrivedAt === undefined) { - this._adapter.log.error('assert in ' + __filename); - debugger; - return Promise.resolve(); - } - if (Date.now() - lastEventArrivedAt! > timeout) { - this._lastEventArrivedAt.delete(uri.fsPath); - this._executables.delete(uri.fsPath); - this._adapter.testsEmitter.fire({ type: 'started' }); - this._allTests.removeChild(suite!); - this._adapter.testsEmitter.fire( - { type: 'finished', suite: this._allTests }); - return Promise.resolve(); - } else if (exists) { - return this._adapter.queue.then(() => { - this._adapter.testsEmitter.fire({ type: 'started' }); - return suite!.reloadChildren().then( - () => { - this._adapter.testsEmitter.fire( - { type: 'finished', suite: this._allTests }); - this._lastEventArrivedAt.delete(uri.fsPath); - }, - (err: any) => { - this._adapter.testsEmitter.fire( - { type: 'finished', suite: this._allTests }); - this._adapter.log.warn(inspect(err)); - 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)); + (exists: boolean, timeout: number, delay: number): Promise => { + let lastEventArrivedAt = this._lastEventArrivedAt.get(uri.fsPath); + if (lastEventArrivedAt === undefined) { + this._adapter.log.error('assert in ' + __filename); + debugger; + return Promise.resolve(); + } + if (Date.now() - lastEventArrivedAt! > timeout) { + this._lastEventArrivedAt.delete(uri.fsPath); + this._executables.delete(uri.fsPath); + this._adapter.testsEmitter.fire({type: 'started'}); + this._allTests.removeChild(suite!); + this._adapter.testsEmitter.fire( + {type: 'finished', suite: this._allTests}); + return Promise.resolve(); + } else if (exists) { + return this._adapter.queue.then(() => { + this._adapter.testsEmitter.fire({type: 'started'}); + return suite!.reloadChildren().then( + () => { + this._adapter.testsEmitter.fire( + {type: 'finished', suite: this._allTests}); + this._lastEventArrivedAt.delete(uri.fsPath); + }, + (err: any) => { + this._adapter.testsEmitter.fire( + {type: 'finished', suite: this._allTests}); + this._adapter.log.warn(inspect(err)); + 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)); + }); }); - }); - }; + }; // change event can arrive during debug session on osx (why?) // if (!this.isDebugging) { x(false, this._adapter.getExecWatchTimeout(), 64); @@ -226,12 +228,12 @@ export class C2ExecutableInfo implements vscode.Disposable { private _verifyIsCatch2TestExecutable(path: string): Promise { return c2fs.spawnAsync(path, ['--help']) - .then(res => { - return res.stdout.indexOf('Catch v2.') != -1; - }) - .catch(e => { - this._adapter.log.error(inspect(e)); - return false; - }); + .then(res => { + return res.stdout.indexOf('Catch v2.') != -1; + }) + .catch(e => { + this._adapter.log.error(inspect(e)); + return false; + }); } } diff --git a/src/C2TestAdapter.ts b/src/C2TestAdapter.ts index 22a15e47..3a114804 100644 --- a/src/C2TestAdapter.ts +++ b/src/C2TestAdapter.ts @@ -3,24 +3,23 @@ // public domain. The author hereby disclaims copyright to this source code. import * as path from 'path'; -import { inspect } from 'util'; +import {inspect} from 'util'; import * as vscode from 'vscode'; -import { TestAdapter, TestEvent, TestLoadFinishedEvent, TestLoadStartedEvent, TestRunFinishedEvent, TestRunStartedEvent, TestSuiteEvent } from 'vscode-test-adapter-api'; +import {TestAdapter, TestEvent, TestLoadFinishedEvent, TestLoadStartedEvent, TestRunFinishedEvent, TestRunStartedEvent, TestSuiteEvent} from 'vscode-test-adapter-api'; import * as util from 'vscode-test-adapter-util'; -import { C2AllTestSuiteInfo } from './C2AllTestSuiteInfo'; -import { C2ExecutableInfo } from './C2ExecutableInfo'; -import { C2TestInfo } from './C2TestInfo'; -import { resolveVariables } from './Helpers'; -import { QueueGraphNode } from './QueueGraph'; -import { generateUniqueId } from './IdGenerator'; +import {C2AllTestSuiteInfo} from './C2AllTestSuiteInfo'; +import {C2ExecutableInfo} from './C2ExecutableInfo'; +import {C2TestInfo} from './C2TestInfo'; +import {resolveVariables} from './Helpers'; +import {QueueGraphNode} from './QueueGraph'; export class C2TestAdapter implements TestAdapter, vscode.Disposable { readonly testsEmitter = - new vscode.EventEmitter(); + new vscode.EventEmitter(); readonly testStatesEmitter = - new vscode.EventEmitter(); + new vscode.EventEmitter(); private readonly autorunEmitter = new vscode.EventEmitter(); readonly variableToValue: [string, string][] = [ @@ -35,38 +34,38 @@ export class C2TestAdapter implements TestAdapter, vscode.Disposable { private readonly disposables: Array = new Array(); constructor( - public readonly workspaceFolder: vscode.WorkspaceFolder, - public readonly log: util.Log) { + public readonly workspaceFolder: vscode.WorkspaceFolder, + public readonly log: util.Log) { this.disposables.push(this.testsEmitter); this.disposables.push(this.testStatesEmitter); this.disposables.push(this.autorunEmitter); this.disposables.push( - vscode.workspace.onDidChangeConfiguration(configChange => { - if (configChange.affectsConfiguration( - 'catch2TestExplorer.defaultEnv', this.workspaceFolder.uri) || - configChange.affectsConfiguration( - 'catch2TestExplorer.defaultCwd', this.workspaceFolder.uri) || - configChange.affectsConfiguration( - 'catch2TestExplorer.executables', this.workspaceFolder.uri)) { - this.load(); - } - })); + vscode.workspace.onDidChangeConfiguration(configChange => { + if (configChange.affectsConfiguration( + 'catch2TestExplorer.defaultEnv', this.workspaceFolder.uri) || + configChange.affectsConfiguration( + 'catch2TestExplorer.defaultCwd', this.workspaceFolder.uri) || + configChange.affectsConfiguration( + 'catch2TestExplorer.executables', this.workspaceFolder.uri)) { + this.load(); + } + })); this.disposables.push( - vscode.workspace.onDidChangeConfiguration(configChange => { - if (configChange.affectsConfiguration( - 'catch2TestExplorer.enableSourceDecoration', - this.workspaceFolder.uri)) { - this.isEnabledSourceDecoration = - this.getEnableSourceDecoration(this.getConfiguration()); - } - if (configChange.affectsConfiguration( - 'catch2TestExplorer.defaultRngSeed', - this.workspaceFolder.uri)) { - this.autorunEmitter.fire(); - } - })); + vscode.workspace.onDidChangeConfiguration(configChange => { + if (configChange.affectsConfiguration( + 'catch2TestExplorer.enableSourceDecoration', + this.workspaceFolder.uri)) { + this.isEnabledSourceDecoration = + this.getEnableSourceDecoration(this.getConfiguration()); + } + if (configChange.affectsConfiguration( + 'catch2TestExplorer.defaultRngSeed', + this.workspaceFolder.uri)) { + this.autorunEmitter.fire(); + } + })); this.allTests = new C2AllTestSuiteInfo(this); } @@ -78,12 +77,12 @@ export class C2TestAdapter implements TestAdapter, vscode.Disposable { this.allTests.dispose(); } - get testStates(): vscode.Event { + get testStates(): vscode.Event { return this.testStatesEmitter.event; } - get tests(): vscode.Event { + get tests(): vscode.Event { return this.testsEmitter.event; } @@ -95,7 +94,7 @@ export class C2TestAdapter implements TestAdapter, vscode.Disposable { return this.isEnabledSourceDecoration; } - getRngSeed(): string | number | null { + getRngSeed(): string|number|null { return this.getDefaultRngSeed(this.getConfiguration()); } @@ -113,16 +112,16 @@ export class C2TestAdapter implements TestAdapter, vscode.Disposable { this.allTests.dispose(); this.allTests = new C2AllTestSuiteInfo(this); - this.testsEmitter.fire({ type: 'started', uid: generateUniqueId(), allTests: this.allTests }); + this.testsEmitter.fire({type: 'started'}); const config = this.getConfiguration(); await this.allTests.load(this.getExecutables(config, this.allTests)); - this.testsEmitter.fire({ type: 'finished', suite: this.allTests }); + this.testsEmitter.fire({type: 'finished', suite: this.allTests}); } catch (e) { this.testsEmitter.fire( - { type: 'finished', suite: undefined, errorMessage: e.message }); + {type: 'finished', suite: undefined, errorMessage: e.message}); } } @@ -141,8 +140,8 @@ export class C2TestAdapter implements TestAdapter, vscode.Disposable { this.isRunning -= 1; }; return this.allTests - .run(tests, this.getWorkerMaxNumber(this.getConfiguration())) - .then(always, always); + .run(tests, this.getWorkerMaxNumber(this.getConfiguration())) + .then(always, always); } throw Error('Catch2 Test Adapter: Test(s) are currently being run.'); @@ -175,7 +174,7 @@ export class C2TestAdapter implements TestAdapter, vscode.Disposable { }; const template = - this.getDebugConfigurationTemplate(this.getConfiguration()); + this.getDebugConfigurationTemplate(this.getConfiguration()); let resolveDebugVariables: [string, any][] = this.variableToValue; resolveDebugVariables = resolveDebugVariables.concat([ ['${label}', testInfo.label], ['${exec}', testInfo.parent.execPath], @@ -209,7 +208,7 @@ export class C2TestAdapter implements TestAdapter, vscode.Disposable { this.isDebugging = true; const debugSessionStarted = - await vscode.debug.startDebugging(this.workspaceFolder, debugConfig); + await vscode.debug.startDebugging(this.workspaceFolder, debugConfig); if (!debugSessionStarted) { console.error('Failed starting the debug session - aborting'); @@ -240,15 +239,16 @@ export class C2TestAdapter implements TestAdapter, vscode.Disposable { private getConfiguration(): vscode.WorkspaceConfiguration { return vscode.workspace.getConfiguration( - 'catch2TestExplorer', this.workspaceFolder.uri); + 'catch2TestExplorer', this.workspaceFolder.uri); } - private getDebugConfigurationTemplate(config: vscode.WorkspaceConfiguration): { [prop: string]: string } | null { + private getDebugConfigurationTemplate(config: vscode.WorkspaceConfiguration): + {[prop: string]: string}|null { const o = config.get('debugConfigTemplate', null); if (o === null) return null; - const result: { [prop: string]: string } = {}; + const result: {[prop: string]: string} = {}; for (const prop in o) { const val = o[prop]; @@ -262,11 +262,12 @@ export class C2TestAdapter implements TestAdapter, vscode.Disposable { } private getGlobalAndDefaultEnvironmentVariables( - config: vscode.WorkspaceConfiguration): { [prop: string]: string | undefined } { + config: vscode.WorkspaceConfiguration): + {[prop: string]: string|undefined} { const processEnv = process.env; - const configEnv: { [prop: string]: any } = config.get('defaultEnv') || {}; + const configEnv: {[prop: string]: any} = config.get('defaultEnv') || {}; - const resultEnv = { ...processEnv }; + const resultEnv = {...processEnv}; for (const prop in configEnv) { const val = configEnv[prop]; @@ -283,7 +284,7 @@ export class C2TestAdapter implements TestAdapter, vscode.Disposable { private getDefaultCwd(config: vscode.WorkspaceConfiguration): string { const dirname = this.workspaceFolder.uri.fsPath; const cwd = resolveVariables( - config.get('defaultCwd', dirname), this.variableToValue); + config.get('defaultCwd', dirname), this.variableToValue); if (path.isAbsolute(cwd)) { return cwd; } else { @@ -292,8 +293,8 @@ export class C2TestAdapter implements TestAdapter, vscode.Disposable { } private getDefaultRngSeed(config: vscode.WorkspaceConfiguration): string - | number | null { - return config.get('defaultRngSeed', null); + |number|null { + return config.get('defaultRngSeed', null); } getDefaultExecWatchTimeout(config: vscode.WorkspaceConfiguration): number { @@ -305,10 +306,10 @@ export class C2TestAdapter implements TestAdapter, vscode.Disposable { } private getGlobalAndCurrentEnvironmentVariables( - config: vscode.WorkspaceConfiguration, - configEnv: { [prop: string]: any }): { [prop: string]: any } { + config: vscode.WorkspaceConfiguration, + configEnv: {[prop: string]: any}): {[prop: string]: any} { const processEnv = process.env; - const resultEnv = { ...processEnv }; + const resultEnv = {...processEnv}; for (const prop in configEnv) { const val = configEnv[prop]; @@ -323,23 +324,23 @@ export class C2TestAdapter implements TestAdapter, vscode.Disposable { } private getEnableSourceDecoration(config: vscode.WorkspaceConfiguration): - boolean { + boolean { return config.get('enableSourceDecoration', true); } private getExecutables( - config: vscode.WorkspaceConfiguration, - allTests: C2AllTestSuiteInfo): C2ExecutableInfo[] { + config: vscode.WorkspaceConfiguration, + allTests: C2AllTestSuiteInfo): C2ExecutableInfo[] { const globalWorkingDirectory = this.getDefaultCwd(config); let executables: C2ExecutableInfo[] = []; - const configExecs: |undefined | string | string[] | { [prop: string]: any } | - { [prop: string]: any }[] = config.get('executables'); + const configExecs:|undefined|string|string[]|{[prop: string]: any}| + {[prop: string]: any}[] = config.get('executables'); - const createFromObject = (obj: { [prop: string]: any }): C2ExecutableInfo => { + const createFromObject = (obj: {[prop: string]: any}): C2ExecutableInfo => { const name: string = - obj.hasOwnProperty('name') ? obj.name : '${relName} (${relDirname}/)'; + obj.hasOwnProperty('name') ? obj.name : '${relName} (${relDirname}/)'; let pattern: string = ''; if (obj.hasOwnProperty('pattern') && typeof obj.pattern == 'string') @@ -350,11 +351,11 @@ export class C2TestAdapter implements TestAdapter, vscode.Disposable { throw Error('Error: pattern or path property is required.'); const cwd: string = - obj.hasOwnProperty('cwd') ? obj.cwd : globalWorkingDirectory; + obj.hasOwnProperty('cwd') ? obj.cwd : globalWorkingDirectory; - const env: { [prop: string]: any } = obj.hasOwnProperty('env') ? - this.getGlobalAndCurrentEnvironmentVariables(config, obj.env) : - this.getGlobalAndDefaultEnvironmentVariables(config); + const env: {[prop: string]: any} = obj.hasOwnProperty('env') ? + this.getGlobalAndCurrentEnvironmentVariables(config, obj.env) : + this.getGlobalAndDefaultEnvironmentVariables(config); return new C2ExecutableInfo(this, allTests, name, pattern, cwd, env); }; @@ -362,8 +363,8 @@ export class C2TestAdapter implements TestAdapter, vscode.Disposable { if (typeof configExecs === 'string') { if (configExecs.length == 0) return []; executables.push(new C2ExecutableInfo( - this, allTests, configExecs, configExecs, globalWorkingDirectory, - {})); + this, allTests, configExecs, configExecs, globalWorkingDirectory, + {})); } else if (Array.isArray(configExecs)) { for (var i = 0; i < configExecs.length; ++i) { const configExe = configExecs[i]; @@ -371,8 +372,8 @@ export class C2TestAdapter implements TestAdapter, vscode.Disposable { const configExecsName = String(configExe); if (configExecsName.length > 0) { executables.push(new C2ExecutableInfo( - this, allTests, configExecsName, configExecsName, - globalWorkingDirectory, {})); + this, allTests, configExecsName, configExecsName, + globalWorkingDirectory, {})); } } else { try { diff --git a/src/test/C2TestAdapter.cpp.test.ts b/src/test/C2TestAdapter.cpp.test.ts index f2aa528a..be080ab1 100644 --- a/src/test/C2TestAdapter.cpp.test.ts +++ b/src/test/C2TestAdapter.cpp.test.ts @@ -6,12 +6,12 @@ import * as assert from 'assert'; import * as cp from 'child_process'; import * as fse from 'fs-extra'; import * as path from 'path'; -import { inspect, promisify } from 'util'; +import {inspect, promisify} from 'util'; import * as vscode from 'vscode'; -import { TestAdapter, TestEvent, TestLoadFinishedEvent, TestLoadStartedEvent, TestRunFinishedEvent, TestRunStartedEvent, TestSuiteEvent, TestSuiteInfo } from 'vscode-test-adapter-api'; -import { Log } from 'vscode-test-adapter-util'; +import {TestAdapter, TestEvent, TestLoadFinishedEvent, TestLoadStartedEvent, TestRunFinishedEvent, TestRunStartedEvent, TestSuiteEvent, TestSuiteInfo} from 'vscode-test-adapter-api'; +import {Log} from 'vscode-test-adapter-util'; -import { C2TestAdapter } from '../C2TestAdapter'; +import {C2TestAdapter} from '../C2TestAdapter'; import * as c2fs from '../FsWrapper'; assert.notStrictEqual(vscode.workspace.workspaceFolders, undefined); @@ -20,10 +20,10 @@ assert.equal(vscode.workspace.workspaceFolders!.length, 1); const workspaceFolderUri = vscode.workspace.workspaceFolders![0].uri; const workspaceFolder = - vscode.workspace.getWorkspaceFolder(workspaceFolderUri)!; + vscode.workspace.getWorkspaceFolder(workspaceFolderUri)!; const logger = - new Log('Catch2TestAdapter', workspaceFolder, 'Catch2TestAdapter'); + new Log('Catch2TestAdapter', workspaceFolder, 'Catch2TestAdapter'); const cppUri = vscode.Uri.file(path.join(workspaceFolderUri.fsPath, 'cpp')); @@ -35,11 +35,13 @@ const isWin = process.platform === 'win32'; /// -describe.skip('C2TestAdapter.cpp', function () { - this.enableTimeouts(false);//TODO +// TODO skip +describe.skip('C2TestAdapter.cpp', function() { + this.enableTimeouts(false); // TODO async function compile(source: vscode.Uri, output: vscode.Uri) { if (isWin) { - assert.notStrictEqual(process.env['C2AVCVA'], undefined, inspect(process.env)); + assert.notStrictEqual( + process.env['C2AVCVA'], undefined, inspect(process.env)); const vcvarsall = vscode.Uri.file(process.env['C2AVCVA']!); const command = '"' + vcvarsall.fsPath + '" x86 && ' + [ 'cl.exe', @@ -62,7 +64,7 @@ describe.skip('C2TestAdapter.cpp', function () { assert.ok(await c2fs.existsAsync(output.fsPath)); } - before(async function () { + before(async function() { this.timeout(50000); await fse.remove(cppUri.fsPath); @@ -70,18 +72,18 @@ describe.skip('C2TestAdapter.cpp', function () { if (!await c2fs.existsAsync(inCpp('../suite1.exe').fsPath)) await compile( - inCpp('../../../src/test/cpp/suite1.cpp'), inCpp('../suite1.exe')); + inCpp('../../../src/test/cpp/suite1.cpp'), inCpp('../suite1.exe')); if (!await c2fs.existsAsync(inCpp('../suite2.exe').fsPath)) await compile( - inCpp('../../../src/test/cpp/suite2.cpp'), inCpp('../suite2.exe')); + inCpp('../../../src/test/cpp/suite2.cpp'), inCpp('../suite2.exe')); if (!await c2fs.existsAsync(inCpp('../suite3.exe').fsPath)) await compile( - inCpp('../../../src/test/cpp/suite3.cpp'), inCpp('../suite3.exe')); + inCpp('../../../src/test/cpp/suite3.cpp'), inCpp('../suite3.exe')); }) - after(async function () { + after(async function() { await fse.remove(cppUri.fsPath); await fse.remove(inCpp('../suite1.exe').fsPath); await fse.remove(inCpp('../suite2.exe').fsPath); @@ -89,45 +91,45 @@ describe.skip('C2TestAdapter.cpp', function () { }) async function waitFor( - test: Mocha.Context, condition: Function, - timeout: number = 1000): Promise { + test: Mocha.Context, condition: Function, + timeout: number = 1000): Promise { const start = Date.now(); let c; while (!(c = await condition()) && - (Date.now() - start < timeout || !test.enableTimeouts())) + (Date.now() - start < timeout || !test.enableTimeouts())) await promisify(setTimeout)(10); assert.ok(c); } function copy(from: string, to: string) { return fse.copy( - vscode.Uri.file(path.join(cppUri.fsPath, from)).fsPath, - vscode.Uri.file(path.join(cppUri.fsPath, to)).fsPath); + vscode.Uri.file(path.join(cppUri.fsPath, from)).fsPath, + vscode.Uri.file(path.join(cppUri.fsPath, to)).fsPath); } - let adapter: C2TestAdapter | undefined; - let testsEventsConnection: vscode.Disposable | undefined; - let testStatesEventsConnection: vscode.Disposable | undefined; - let testsEvents: (TestLoadStartedEvent | TestLoadFinishedEvent)[] = []; - let testStatesEvents: (TestRunStartedEvent | TestRunFinishedEvent | - TestSuiteEvent | TestEvent)[] = []; + let adapter: C2TestAdapter|undefined; + let testsEventsConnection: vscode.Disposable|undefined; + let testStatesEventsConnection: vscode.Disposable|undefined; + let testsEvents: (TestLoadStartedEvent|TestLoadFinishedEvent)[] = []; + let testStatesEvents: (TestRunStartedEvent|TestRunFinishedEvent| + TestSuiteEvent|TestEvent)[] = []; function createAdapterAndSubscribe() { adapter = new C2TestAdapter(workspaceFolder, logger); testsEvents = []; testsEventsConnection = - adapter.tests((e: TestLoadStartedEvent | TestLoadFinishedEvent) => { - if (testsEvents.length % 2 == 1 && e.type == 'started') debugger; - testsEvents.push(e); - }); + adapter.tests((e: TestLoadStartedEvent|TestLoadFinishedEvent) => { + if (testsEvents.length % 2 == 1 && e.type == 'started') debugger; + testsEvents.push(e); + }); testStatesEvents = []; testStatesEventsConnection = adapter.testStates( - (e: TestRunStartedEvent | TestRunFinishedEvent | TestSuiteEvent | - TestEvent) => { - testStatesEvents.push(e); - }); + (e: TestRunStartedEvent|TestRunFinishedEvent|TestSuiteEvent| + TestEvent) => { + testStatesEvents.push(e); + }); return adapter!; } @@ -137,7 +139,7 @@ describe.skip('C2TestAdapter.cpp', function () { await adapter.load(); if (testsEvents.length != eventCount + 2) debugger; assert.strictEqual( - testsEvents.length, eventCount + 2, inspect(testsEvents)); + testsEvents.length, eventCount + 2, inspect(testsEvents)); const finished = testsEvents.pop()!; assert.strictEqual(finished.type, 'finished'); assert.strictEqual(testsEvents.pop()!.type, 'started'); @@ -153,18 +155,18 @@ describe.skip('C2TestAdapter.cpp', function () { if (check) { for (let i = 0; i < testsEvents.length; i++) { assert.deepStrictEqual( - { type: 'started' }, testsEvents[i], - inspect({ index: i, testsEvents: testsEvents })); + {type: 'started'}, testsEvents[i], + inspect({index: i, testsEvents: testsEvents})); i++; assert.ok( - i < testsEvents.length, - inspect({ index: i, testsEvents: testsEvents })); + i < testsEvents.length, + inspect({index: i, testsEvents: testsEvents})); assert.equal( - testsEvents[i].type, 'finished', - inspect({ index: i, testsEvents: testsEvents })); + testsEvents[i].type, 'finished', + inspect({index: i, testsEvents: testsEvents})); assert.ok( - (testsEvents[i]).suite, - inspect({ index: i, testsEvents: testsEvents })); + (testsEvents[i]).suite, + inspect({index: i, testsEvents: testsEvents})); } } testsEvents = []; @@ -172,7 +174,7 @@ describe.skip('C2TestAdapter.cpp', function () { function getConfig() { return vscode.workspace.getConfiguration( - 'catch2TestExplorer', workspaceFolderUri); + 'catch2TestExplorer', workspaceFolderUri); } async function updateConfig(key: string, value: any) { @@ -182,18 +184,18 @@ describe.skip('C2TestAdapter.cpp', function () { while (testsEvents.length < count--) testsEvents.pop(); } - context('example1', function () { - afterEach(async function () { + context('example1', function() { + afterEach(async function() { await fse.remove(cppUri.fsPath); }) - it('shoud be found and run withouth error', async function () { + it('shoud be found and run withouth error', async function() { await updateConfig( - 'executables', [{ - 'name': '${baseFilename}', - 'pattern': 'cpp/{build,Build,BUILD,out,Out,OUT}/**/*suite[0-9]*', - 'cwd': '${workspaceFolder}/cpp', - }]); + 'executables', [{ + 'name': '${baseFilename}', + 'pattern': 'cpp/{build,Build,BUILD,out,Out,OUT}/**/*suite[0-9]*', + 'cwd': '${workspaceFolder}/cpp', + }]); await copy('../suite1.exe', 'out/suite1.exe'); await copy('../suite2.exe', 'out/suite2.exe'); @@ -211,15 +213,15 @@ describe.skip('C2TestAdapter.cpp', function () { await updateConfig('executables', undefined); }) - it('shoud be notified by watcher', async function () { + it('shoud be notified by watcher', async function() { this.timeout(10000); this.slow(3500); await updateConfig( - 'executables', [{ - 'name': '${baseFilename}', - 'pattern': 'cpp/{build,Build,BUILD,out,Out,OUT}/**/*suite[0-9]*', - 'cwd': '${workspaceFolder}/cpp', - }]); + 'executables', [{ + 'name': '${baseFilename}', + 'pattern': 'cpp/{build,Build,BUILD,out,Out,OUT}/**/*suite[0-9]*', + 'cwd': '${workspaceFolder}/cpp', + }]); adapter = createAdapterAndSubscribe(); const root = await load(adapter); diff --git a/src/test/C2TestAdapter.test.ts b/src/test/C2TestAdapter.test.ts index 1c42f560..98863159 100644 --- a/src/test/C2TestAdapter.test.ts +++ b/src/test/C2TestAdapter.test.ts @@ -11,14 +11,14 @@ import * as fse from 'fs-extra'; import * as assert from 'assert'; import * as vscode from 'vscode'; import * as sinon from 'sinon'; -import { TestEvent, TestLoadFinishedEvent, TestLoadStartedEvent, TestRunFinishedEvent, TestRunStartedEvent, TestSuiteEvent, TestSuiteInfo, TestInfo, TestAdapter } from 'vscode-test-adapter-api'; -import { Log } from 'vscode-test-adapter-util'; -import { inspect, promisify } from 'util'; -import { EOL } from 'os'; - -import { C2TestAdapter } from '../C2TestAdapter'; -import { example1 } from './example1'; -import { ChildProcessStub, FileSystemWatcherStub } from './Helpers'; +import {TestEvent, TestLoadFinishedEvent, TestLoadStartedEvent, TestRunFinishedEvent, TestRunStartedEvent, TestSuiteEvent, TestSuiteInfo, TestInfo, TestAdapter} from 'vscode-test-adapter-api'; +import {Log} from 'vscode-test-adapter-util'; +import {inspect, promisify} from 'util'; +import {EOL} from 'os'; + +import {C2TestAdapter} from '../C2TestAdapter'; +import {example1} from './example1'; +import {ChildProcessStub, FileSystemWatcherStub} from './Helpers'; import * as Mocha from 'mocha'; assert.notStrictEqual(vscode.workspace.workspaceFolders, undefined); @@ -27,10 +27,10 @@ assert.equal(vscode.workspace.workspaceFolders!.length, 1); const workspaceFolderUri = vscode.workspace.workspaceFolders![0].uri; const workspaceFolder = - vscode.workspace.getWorkspaceFolder(workspaceFolderUri)!; + vscode.workspace.getWorkspaceFolder(workspaceFolderUri)!; const logger = - new Log('Catch2TestAdapter', workspaceFolder, 'Catch2TestAdapter'); + new Log('Catch2TestAdapter', workspaceFolder, 'Catch2TestAdapter'); const dotVscodePath = path.join(workspaceFolderUri.fsPath, '.vscode'); @@ -38,14 +38,14 @@ const sinonSandbox = sinon.createSandbox(); /// -describe('C2TestAdapter', function () { - let testsEvents: (TestLoadStartedEvent | TestLoadFinishedEvent)[] = []; - let testStatesEvents: (TestRunStartedEvent | TestRunFinishedEvent | - TestSuiteEvent | TestEvent)[] = []; +describe('C2TestAdapter', function() { + let testsEvents: (TestLoadStartedEvent|TestLoadFinishedEvent)[] = []; + let testStatesEvents: (TestRunStartedEvent|TestRunFinishedEvent| + TestSuiteEvent|TestEvent)[] = []; function getConfig() { return vscode.workspace.getConfiguration( - 'catch2TestExplorer', workspaceFolderUri); + 'catch2TestExplorer', workspaceFolderUri); } async function updateConfig(key: string, value: any) { @@ -55,9 +55,9 @@ describe('C2TestAdapter', function () { while (testsEvents.length < count--) testsEvents.pop(); } - let adapter: C2TestAdapter | undefined; - let testsEventsConnection: vscode.Disposable | undefined; - let testStatesEventsConnection: vscode.Disposable | undefined; + let adapter: C2TestAdapter|undefined; + let testsEventsConnection: vscode.Disposable|undefined; + let testStatesEventsConnection: vscode.Disposable|undefined; let spawnStub: sinon.SinonStub; let vsfsWatchStub: sinon.SinonStub; @@ -66,14 +66,14 @@ describe('C2TestAdapter', function () { function resetConfig(): Thenable { const packageJson = fse.readJSONSync( - path.join(workspaceFolderUri.fsPath, '../..', 'package.json')); - const properties: { [prop: string]: any }[] = - packageJson['contributes']['configuration']['properties']; + path.join(workspaceFolderUri.fsPath, '../..', 'package.json')); + const properties: {[prop: string]: any}[] = + packageJson['contributes']['configuration']['properties']; let t: Thenable = Promise.resolve(); Object.keys(properties).forEach(key => { assert.ok(key.startsWith('catch2TestExplorer.')); const k = key.replace('catch2TestExplorer.', ''); - t = t.then(function () { + t = t.then(function() { return getConfig().update(k, undefined); }); }); @@ -82,18 +82,18 @@ describe('C2TestAdapter', function () { function testStatesEvI(o: any) { const i = testStatesEvents.findIndex( - (v: TestRunStartedEvent | TestRunFinishedEvent | TestSuiteEvent | - TestEvent) => { - if (o.type == v.type) - if (o.type == 'suite' || o.type == 'test') - return o.state === (v).state && - o[o.type] === (v)[v.type]; - return deepStrictEqual(o, v); - }); + (v: TestRunStartedEvent|TestRunFinishedEvent|TestSuiteEvent| + TestEvent) => { + if (o.type == v.type) + if (o.type == 'suite' || o.type == 'test') + return o.state === (v).state && + o[o.type] === (v)[v.type]; + return deepStrictEqual(o, v); + }); assert.notStrictEqual( - i, -1, - 'testStatesEvI failed to find: ' + inspect(o) + '\n\nin\n\n' + - inspect(testStatesEvents)); + i, -1, + 'testStatesEvI failed to find: ' + inspect(o) + '\n\nin\n\n' + + inspect(testStatesEvents)); assert.deepStrictEqual(testStatesEvents[i], o); return i; } @@ -102,34 +102,34 @@ describe('C2TestAdapter', function () { adapter = new C2TestAdapter(workspaceFolder, logger); testsEventsConnection = - adapter.tests((e: TestLoadStartedEvent | TestLoadFinishedEvent) => { - testsEvents.push(e); - }); + adapter.tests((e: TestLoadStartedEvent|TestLoadFinishedEvent) => { + testsEvents.push(e); + }); testStatesEvents = []; testStatesEventsConnection = adapter.testStates( - (e: TestRunStartedEvent | TestRunFinishedEvent | TestSuiteEvent | - TestEvent) => { - testStatesEvents.push(e); - }); + (e: TestRunStartedEvent|TestRunFinishedEvent|TestSuiteEvent| + TestEvent) => { + testStatesEvents.push(e); + }); return adapter!; } async function waitFor( - test: Mocha.Context, condition: Function, - timeout: number = 1000): Promise { + test: Mocha.Context, condition: Function, + timeout: number = 1000): Promise { const start = Date.now(); let c = await condition(); while (!(c = await condition()) && - (Date.now() - start < timeout || !test.enableTimeouts())) + (Date.now() - start < timeout || !test.enableTimeouts())) await promisify(setTimeout)(10); assert.ok(c); } async function doAndWaitForReloadEvent( - test: Mocha.Context, action: Function, - timeout: number = 1000): Promise { + test: Mocha.Context, action: Function, + timeout: number = 1000): Promise { const origCount = testsEvents.length; await action(); await waitFor(test, () => { @@ -150,18 +150,18 @@ describe('C2TestAdapter', function () { if (check) { for (let i = 0; i < testsEvents.length; i++) { assert.deepStrictEqual( - { type: 'started' }, testsEvents[i], - inspect({ index: i, testsEvents: testsEvents })); + {type: 'started'}, testsEvents[i], + inspect({index: i, testsEvents: testsEvents})); i++; assert.ok( - i < testsEvents.length, - inspect({ index: i, testsEvents: testsEvents })); + i < testsEvents.length, + inspect({index: i, testsEvents: testsEvents})); assert.equal( - testsEvents[i].type, 'finished', - inspect({ index: i, testsEvents: testsEvents })); + testsEvents[i].type, 'finished', + inspect({index: i, testsEvents: testsEvents})); assert.ok( - (testsEvents[i]).suite, - inspect({ index: i, testsEvents: testsEvents })); + (testsEvents[i]).suite, + inspect({index: i, testsEvents: testsEvents})); } } testsEvents = []; @@ -178,17 +178,17 @@ describe('C2TestAdapter', function () { vsFindFilesStub.callThrough(); } - before(function () { + before(function() { fse.removeSync(dotVscodePath); adapter = undefined; spawnStub = sinonSandbox.stub(child_process, 'spawn').named('spawnStub'); vsfsWatchStub = - sinonSandbox.stub(vscode.workspace, 'createFileSystemWatcher') - .named('vscode.createFileSystemWatcher'); + sinonSandbox.stub(vscode.workspace, 'createFileSystemWatcher') + .named('vscode.createFileSystemWatcher'); fsStatStub = sinonSandbox.stub(fs, 'stat').named('fsStat'); vsFindFilesStub = sinonSandbox.stub(vscode.workspace, 'findFiles') - .named('vsFindFilesStub'); + .named('vsFindFilesStub'); stubsResetToMyDefault(); @@ -196,52 +196,52 @@ describe('C2TestAdapter', function () { return resetConfig(); }); - after(function () { + after(function() { disposeAdapterAndSubscribers(); sinonSandbox.restore(); }); - describe('detect config change', function () { + describe('detect config change', function() { this.slow(200); let adapter: C2TestAdapter; - before(function () { + before(function() { adapter = createAdapterAndSubscribe(); assert.deepStrictEqual(testsEvents, []); }) - after(function () { + after(function() { disposeAdapterAndSubscribers(); return resetConfig(); }) - it('defaultEnv', function () { + it('defaultEnv', function() { return doAndWaitForReloadEvent(this, () => { - return updateConfig('defaultEnv', { 'APPLE': 'apple' }); + return updateConfig('defaultEnv', {'APPLE': 'apple'}); }); }) - it('defaultCwd', function () { + it('defaultCwd', function() { return doAndWaitForReloadEvent(this, () => { return updateConfig('defaultCwd', 'apple/peach'); }); }); - it('enableSourceDecoration', function () { - return updateConfig('enableSourceDecoration', false).then(function () { + it('enableSourceDecoration', function() { + return updateConfig('enableSourceDecoration', false).then(function() { assert.ok(!adapter.getIsEnabledSourceDecoration()); }); }); - it('defaultRngSeed', function () { - return updateConfig('defaultRngSeed', 987).then(function () { + it('defaultRngSeed', function() { + return updateConfig('defaultRngSeed', 987).then(function() { assert.equal(adapter.getRngSeed(), 987); }); }) }) - it('load with empty config', async function () { + it('load with empty config', async function() { this.slow(500); const adapter = createAdapterAndSubscribe(); await adapter.load(); @@ -254,24 +254,24 @@ describe('C2TestAdapter', function () { disposeAdapterAndSubscribers(); }) - context('example1', function () { + context('example1', function() { const watchers: Map = new Map(); function handleCreateWatcherCb( - p: vscode.RelativePattern, ignoreCreateEvents: boolean, - ignoreChangeEvents: boolean, ignoreDeleteEvents: boolean) { + p: vscode.RelativePattern, ignoreCreateEvents: boolean, + ignoreChangeEvents: boolean, ignoreDeleteEvents: boolean) { const pp = path.join(p.base, p.pattern); const e = new FileSystemWatcherStub( - vscode.Uri.file(pp), ignoreCreateEvents, ignoreChangeEvents, - ignoreDeleteEvents); + vscode.Uri.file(pp), ignoreCreateEvents, ignoreChangeEvents, + ignoreDeleteEvents); watchers.set(pp, e); return e; } function handleStatExistsFile( - path: string, - cb: (err: NodeJS.ErrnoException | null, stats: fs.Stats | undefined) => - void) { + path: string, + cb: (err: NodeJS.ErrnoException|null, stats: fs.Stats|undefined) => + void) { cb(null, { isFile() { return true; @@ -283,9 +283,9 @@ describe('C2TestAdapter', function () { } function handleStatNotExists( - path: string, - cb: (err: NodeJS.ErrnoException | null | any, stats: fs.Stats | undefined) => - void) { + path: string, + cb: (err: NodeJS.ErrnoException|null|any, stats: fs.Stats|undefined) => + void) { cb({ code: 'ENOENT', errno: -2, @@ -293,22 +293,22 @@ describe('C2TestAdapter', function () { path: path, syscall: 'stat' }, - undefined); + undefined); } function matchRelativePattern(p: string) { return sinon.match((actual: vscode.RelativePattern) => { const required = new vscode.RelativePattern( - workspaceFolder, path.relative(workspaceFolderUri.fsPath, p)); + workspaceFolder, path.relative(workspaceFolderUri.fsPath, p)); return required.base == actual.base && - required.pattern == actual.pattern; + required.pattern == actual.pattern; }); } - before(function () { + before(function() { for (let suite of example1.outputs) { for (let scenario of suite[1]) { - spawnStub.withArgs(suite[0], scenario[0]).callsFake(function () { + spawnStub.withArgs(suite[0], scenario[0]).callsFake(function() { return new ChildProcessStub(scenario[1]); }); } @@ -316,7 +316,7 @@ describe('C2TestAdapter', function () { fsStatStub.withArgs(suite[0]).callsFake(handleStatExistsFile); vsfsWatchStub.withArgs(matchRelativePattern(suite[0])) - .callsFake(handleCreateWatcherCb); + .callsFake(handleCreateWatcherCb); } const dirContent: Map = new Map(); @@ -340,36 +340,36 @@ describe('C2TestAdapter', function () { }); }); - after(function () { + after(function() { stubsResetToMyDefault(); }); - afterEach(function () { + afterEach(function() { watchers.clear(); }); - describe('load', function () { + describe('load', function() { const uniqueIdC = new Set(); let adapter: TestAdapter; let root: TestSuiteInfo; - let suite1: TestSuiteInfo | any; - let s1t1: TestInfo | any; - let s1t2: TestInfo | any; - let suite2: TestSuiteInfo | any; - let s2t1: TestInfo | any; - let s2t2: TestInfo | any; - let s2t3: TestInfo | any; - - before(function () { + let suite1: TestSuiteInfo|any; + let s1t1: TestInfo|any; + let s1t2: TestInfo|any; + let suite2: TestSuiteInfo|any; + let s2t1: TestInfo|any; + let s2t2: TestInfo|any; + let s2t3: TestInfo|any; + + before(function() { return updateConfig('workerMaxNumber', 4); }); - after(function () { + after(function() { return updateConfig('workerMaxNumber', undefined); }); - beforeEach(async function () { + beforeEach(async function() { adapter = createAdapterAndSubscribe(); await adapter.load(); @@ -392,28 +392,28 @@ describe('C2TestAdapter', function () { assert.deepStrictEqual(testStatesEvents, []); }); - afterEach(function () { + afterEach(function() { uniqueIdC.clear(); disposeAdapterAndSubscribers(); }); - context('executables="execPath1"', function () { - before(function () { + context('executables="execPath1"', function() { + before(function() { return updateConfig('executables', 'execPath1'); }); - after(function () { + after(function() { return updateConfig('executables', undefined); }); - beforeEach(async function () { + beforeEach(async function() { assert.deepStrictEqual( - getConfig().get('executables'), 'execPath1'); + getConfig().get('executables'), 'execPath1'); assert.equal(root.children.length, 1); assert.equal(root.children[0].type, 'suite'); suite1 = root.children[0]; example1.suite1.assert( - 'execPath1', ['s1t1', 's1t2'], suite1, uniqueIdC); + 'execPath1', ['s1t1', 's1t2'], suite1, uniqueIdC); assert.equal(suite1.children.length, 2); assert.equal(suite1.children[0].type, 'test'); s1t1 = suite1.children[0]; @@ -421,21 +421,21 @@ describe('C2TestAdapter', function () { s1t2 = suite1.children[1]; }); - it('should run with not existing test id', async function () { + it('should run with not existing test id', async function() { await adapter.run(['not existing id']); assert.deepStrictEqual(testStatesEvents, [ - { type: 'started', tests: ['not existing id'] }, { type: 'finished' } + {type: 'started', tests: ['not existing id']}, {type: 'finished'} ]); }); - it('should run s1t1 with success', async function () { + it('should run s1t1 with success', async function() { assert.equal(getConfig().get('executables'), 'execPath1'); await adapter.run([s1t1.id]); const expected = [ - { type: 'started', tests: [s1t1.id] }, - { type: 'suite', state: 'running', suite: suite1 }, - { type: 'test', state: 'running', test: s1t1 }, + {type: 'started', tests: [s1t1.id]}, + {type: 'suite', state: 'running', suite: suite1}, + {type: 'test', state: 'running', test: s1t1}, { type: 'test', state: 'passed', @@ -443,8 +443,8 @@ describe('C2TestAdapter', function () { decorations: undefined, message: 'Duration: 0.000112 second(s)\n' }, - { type: 'suite', state: 'completed', suite: suite1 }, - { type: 'finished' }, + {type: 'suite', state: 'completed', suite: suite1}, + {type: 'finished'}, ]; assert.deepStrictEqual(testStatesEvents, expected); @@ -452,12 +452,12 @@ describe('C2TestAdapter', function () { assert.deepStrictEqual(testStatesEvents, [...expected, ...expected]); }); - it('should run suite1', async function () { + it('should run suite1', async function() { await adapter.run([suite1.id]); const expected = [ - { type: 'started', tests: [suite1.id] }, - { type: 'suite', state: 'running', suite: suite1 }, - { type: 'test', state: 'running', test: s1t1 }, + {type: 'started', tests: [suite1.id]}, + {type: 'suite', state: 'running', suite: suite1}, + {type: 'test', state: 'running', test: s1t1}, { type: 'test', state: 'passed', @@ -465,17 +465,17 @@ describe('C2TestAdapter', function () { decorations: undefined, message: 'Duration: 0.000132 second(s)\n' }, - { type: 'test', state: 'running', test: s1t2 }, + {type: 'test', state: 'running', test: s1t2}, { type: 'test', state: 'failed', test: s1t2, - decorations: [{ line: 14, message: 'Expanded: false' }], + decorations: [{line: 14, message: 'Expanded: false'}], message: - 'Duration: 0.000204 second(s)\n>>> s1t2(line: 13) REQUIRE (line: 15) \n Original:\n std::false_type::value\n Expanded:\n false\n<<<\n' + 'Duration: 0.000204 second(s)\n>>> s1t2(line: 13) REQUIRE (line: 15) \n Original:\n std::false_type::value\n Expanded:\n false\n<<<\n' }, - { type: 'suite', state: 'completed', suite: suite1 }, - { type: 'finished' }, + {type: 'suite', state: 'completed', suite: suite1}, + {type: 'finished'}, ]; assert.deepStrictEqual(testStatesEvents, expected); @@ -483,12 +483,12 @@ describe('C2TestAdapter', function () { assert.deepStrictEqual(testStatesEvents, [...expected, ...expected]); }); - it('should run all', async function () { + it('should run all', async function() { await adapter.run([root.id]); const expected = [ - { type: 'started', tests: [root.id] }, - { type: 'suite', state: 'running', suite: suite1 }, - { type: 'test', state: 'running', test: s1t1 }, + {type: 'started', tests: [root.id]}, + {type: 'suite', state: 'running', suite: suite1}, + {type: 'test', state: 'running', test: s1t1}, { type: 'test', state: 'passed', @@ -496,17 +496,17 @@ describe('C2TestAdapter', function () { decorations: undefined, message: 'Duration: 0.000132 second(s)\n' }, - { type: 'test', state: 'running', test: s1t2 }, + {type: 'test', state: 'running', test: s1t2}, { type: 'test', state: 'failed', test: s1t2, - decorations: [{ line: 14, message: 'Expanded: false' }], + decorations: [{line: 14, message: 'Expanded: false'}], message: - 'Duration: 0.000204 second(s)\n>>> s1t2(line: 13) REQUIRE (line: 15) \n Original:\n std::false_type::value\n Expanded:\n false\n<<<\n' + 'Duration: 0.000204 second(s)\n>>> s1t2(line: 13) REQUIRE (line: 15) \n Original:\n std::false_type::value\n Expanded:\n false\n<<<\n' }, - { type: 'suite', state: 'completed', suite: suite1 }, - { type: 'finished' }, + {type: 'suite', state: 'completed', suite: suite1}, + {type: 'finished'}, ]; assert.deepStrictEqual(testStatesEvents, expected); @@ -514,7 +514,7 @@ describe('C2TestAdapter', function () { assert.deepStrictEqual(testStatesEvents, [...expected, ...expected]); }); - it('cancels without any problem', async function () { + it('cancels without any problem', async function() { adapter.cancel(); assert.deepStrictEqual(testsEvents, []); assert.deepStrictEqual(testStatesEvents, []); @@ -525,17 +525,17 @@ describe('C2TestAdapter', function () { await adapter.run([s1t1.id]); const expected = [ - { type: 'started', tests: [s1t1.id] }, - { type: 'suite', state: 'running', suite: suite1 }, - { type: 'test', state: 'running', test: s1t1 }, { + {type: 'started', tests: [s1t1.id]}, + {type: 'suite', state: 'running', suite: suite1}, + {type: 'test', state: 'running', test: s1t1}, { type: 'test', state: 'passed', test: s1t1, decorations: undefined, message: 'Duration: 0.000112 second(s)\n' }, - { type: 'suite', state: 'completed', suite: suite1 }, - { type: 'finished' } + {type: 'suite', state: 'completed', suite: suite1}, + {type: 'finished'} ]; assert.deepStrictEqual(testStatesEvents, expected); @@ -544,42 +544,42 @@ describe('C2TestAdapter', function () { assert.deepStrictEqual(testStatesEvents, expected); }); - context('with config: defaultRngSeed=2', function () { - before(function () { + context('with config: defaultRngSeed=2', function() { + before(function() { return updateConfig('defaultRngSeed', 2); }); - after(function () { + after(function() { return updateConfig('defaultRngSeed', undefined); }); - it('should run s1t1 with success', async function () { + it('should run s1t1 with success', async function() { await adapter.run([s1t1.id]); const expected = [ - { type: 'started', tests: [s1t1.id] }, - { type: 'suite', state: 'running', suite: suite1 }, - { type: 'test', state: 'running', test: s1t1 }, { + {type: 'started', tests: [s1t1.id]}, + {type: 'suite', state: 'running', suite: suite1}, + {type: 'test', state: 'running', test: s1t1}, { type: 'test', state: 'passed', test: s1t1, decorations: undefined, message: - 'Randomness seeded to: 2\nDuration: 0.000327 second(s)\n' + 'Randomness seeded to: 2\nDuration: 0.000327 second(s)\n' }, - { type: 'suite', state: 'completed', suite: suite1 }, - { type: 'finished' } + {type: 'suite', state: 'completed', suite: suite1}, + {type: 'finished'} ]; assert.deepStrictEqual(testStatesEvents, expected); await adapter.run([s1t1.id]); assert.deepStrictEqual( - testStatesEvents, [...expected, ...expected]); + testStatesEvents, [...expected, ...expected]); }) }) }) - context('suite1 and suite2 are used', function () { - beforeEach(function () { + context('suite1 and suite2 are used', function() { + beforeEach(function() { assert.equal(root.children.length, 2); assert.equal(root.children[0].type, 'suite'); @@ -610,728 +610,727 @@ describe('C2TestAdapter', function () { const testsForAdapterWithSuite1AndSuite2: Mocha.Test[] = [ new Mocha.Test( - 'test variables are fine, suite1 and suite1 are loaded', - function () { - assert.equal(root.children.length, 2); - assert.ok(suite1 != undefined); - assert.ok(s1t1 != undefined); - assert.ok(s1t2 != undefined); - assert.ok(suite2 != undefined); - assert.ok(s2t1 != undefined); - assert.ok(s2t2 != undefined); - assert.ok(s2t3 != undefined); - }), + 'test variables are fine, suite1 and suite1 are loaded', + function() { + assert.equal(root.children.length, 2); + assert.ok(suite1 != undefined); + assert.ok(s1t1 != undefined); + assert.ok(s1t2 != undefined); + assert.ok(suite2 != undefined); + assert.ok(s2t1 != undefined); + assert.ok(s2t2 != undefined); + assert.ok(s2t3 != undefined); + }), new Mocha.Test( - 'should run all', - async function () { - assert.equal(root.children.length, 2); - await adapter.run([root.id]); - - const running = { type: 'started', tests: [root.id] }; - - const s1running = { - type: 'suite', - state: 'running', - suite: suite1 - }; - const s1finished = { - type: 'suite', - state: 'completed', - suite: suite1 - }; - assert.ok(testStatesEvI(running) < testStatesEvI(s1running)); - assert.ok(testStatesEvI(s1running) < testStatesEvI(s1finished)); - - const s2running = { - type: 'suite', - state: 'running', - suite: suite2 - }; - const s2finished = { - type: 'suite', - state: 'completed', - suite: suite2 - }; - assert.ok(testStatesEvI(running) < testStatesEvI(s1running)); - assert.ok(testStatesEvI(s2running) < testStatesEvI(s2finished)); - - const s1t1running = { - type: 'test', - state: 'running', - test: s1t1 - }; - assert.ok( - testStatesEvI(s1running) < testStatesEvI(s1t1running)); - - const s1t1finished = { - type: 'test', - state: 'passed', - test: s1t1, - decorations: undefined, - message: 'Duration: 0.000132 second(s)\n' - }; - assert.ok( - testStatesEvI(s1t1running) < testStatesEvI(s1t1finished)); - assert.ok( - testStatesEvI(s1t1finished) < testStatesEvI(s1finished)); - - const s1t2running = { - type: 'test', - state: 'running', - test: s1t2 - }; - assert.ok( - testStatesEvI(s1running) < testStatesEvI(s1t2running)); - - const s1t2finished = { - type: 'test', - state: 'failed', - test: s1t2, - decorations: [{ line: 14, message: 'Expanded: false' }], - message: - 'Duration: 0.000204 second(s)\n>>> s1t2(line: 13) REQUIRE (line: 15) \n Original:\n std::false_type::value\n Expanded:\n false\n<<<\n' - }; - assert.ok( - testStatesEvI(s1t2running) < testStatesEvI(s1t2finished)); - assert.ok( - testStatesEvI(s1t2finished) < testStatesEvI(s1finished)); - - const s2t1running = { - type: 'test', - state: 'running', - test: s2t1 - }; - assert.ok( - testStatesEvI(s2running) < testStatesEvI(s2t1running)); - - const s2t1finished = { - type: 'test', - state: 'passed', - test: s2t1, - decorations: undefined, - message: 'Duration: 0.00037 second(s)\n' - }; - assert.ok( - testStatesEvI(s2t1running) < testStatesEvI(s2t1finished)); - assert.ok( - testStatesEvI(s2t1finished) < testStatesEvI(s2finished)); - - const s2t2running = { - type: 'test', - state: 'running', - test: s2t2 - }; - assert.ok( - testStatesEvI(s2running) < testStatesEvI(s2t2running)); - - const s2t2finished = { - type: 'test', - state: 'skipped', - test: s2t2 - }; - assert.ok( - testStatesEvI(s2t2running) < testStatesEvI(s2t2finished)); - assert.ok( - testStatesEvI(s2t2finished) < testStatesEvI(s2finished)); - - const s2t3running = { - type: 'test', - state: 'running', - test: s2t3 - }; - assert.ok( - testStatesEvI(s2running) < testStatesEvI(s2t3running)); + 'should run all', + async function() { + assert.equal(root.children.length, 2); + await adapter.run([root.id]); + + const running = {type: 'started', tests: [root.id]}; + + const s1running = { + type: 'suite', + state: 'running', + suite: suite1 + }; + const s1finished = { + type: 'suite', + state: 'completed', + suite: suite1 + }; + assert.ok(testStatesEvI(running) < testStatesEvI(s1running)); + assert.ok(testStatesEvI(s1running) < testStatesEvI(s1finished)); + + const s2running = { + type: 'suite', + state: 'running', + suite: suite2 + }; + const s2finished = { + type: 'suite', + state: 'completed', + suite: suite2 + }; + assert.ok(testStatesEvI(running) < testStatesEvI(s1running)); + assert.ok(testStatesEvI(s2running) < testStatesEvI(s2finished)); + + const s1t1running = { + type: 'test', + state: 'running', + test: s1t1 + }; + assert.ok( + testStatesEvI(s1running) < testStatesEvI(s1t1running)); - const s2t3finished = { - type: 'test', - state: 'failed', - test: s2t3, - decorations: [{ line: 20, message: 'Expanded: false' }], - message: - 'Duration: 0.000178 second(s)\n>>> s2t3(line: 19) REQUIRE (line: 21) \n Original:\n std::false_type::value\n Expanded:\n false\n<<<\n' - }; - assert.ok( - testStatesEvI(s2t3running) < testStatesEvI(s2t3finished)); - assert.ok( - testStatesEvI(s2t3finished) < testStatesEvI(s2finished)); - - const finished = { type: 'finished' }; - assert.ok(testStatesEvI(s1finished) < testStatesEvI(finished)); - assert.ok(testStatesEvI(s2finished) < testStatesEvI(finished)); - - assert.equal( - testStatesEvents.length, 16, inspect(testStatesEvents)); - }), - new Mocha.Test( - 'should run with not existing test id', - async function () { - await adapter.run(['not existing id']); - - assert.deepStrictEqual(testStatesEvents, [ - { type: 'started', tests: ['not existing id'] }, - { type: 'finished' } - ]); - }), - new Mocha.Test( - 'should run s1t1', - async function () { - await adapter.run([s1t1.id]); - const expected = [ - { type: 'started', tests: [s1t1.id] }, - { type: 'suite', state: 'running', suite: suite1 }, - { type: 'test', state: 'running', test: s1t1 }, { + const s1t1finished = { type: 'test', state: 'passed', test: s1t1, decorations: undefined, - message: 'Duration: 0.000112 second(s)\n' - }, - { type: 'suite', state: 'completed', suite: suite1 }, - { type: 'finished' } - ]; - assert.deepStrictEqual(testStatesEvents, expected); - - await adapter.run([s1t1.id]); - assert.deepStrictEqual( - testStatesEvents, [...expected, ...expected]); - }), - new Mocha.Test( - 'should run skipped s2t2', - async function () { - await adapter.run([s2t2.id]); - const expected = [ - { type: 'started', tests: [s2t2.id] }, - { type: 'suite', state: 'running', suite: suite2 }, - { type: 'test', state: 'running', test: s2t2 }, { + message: 'Duration: 0.000132 second(s)\n' + }; + assert.ok( + testStatesEvI(s1t1running) < testStatesEvI(s1t1finished)); + assert.ok( + testStatesEvI(s1t1finished) < testStatesEvI(s1finished)); + + const s1t2running = { type: 'test', - state: 'passed', - test: s2t2, - decorations: undefined, - message: 'Duration: 0.001294 second(s)\n' - }, - { type: 'suite', state: 'completed', suite: suite2 }, - { type: 'finished' } - ]; - assert.deepStrictEqual(testStatesEvents, expected); - - await adapter.run([s2t2.id]); - assert.deepStrictEqual( - testStatesEvents, [...expected, ...expected]); - }), - new Mocha.Test( - 'should run failing test s2t3', - async function () { - await adapter.run([s2t3.id]); - const expected = [ - { type: 'started', tests: [s2t3.id] }, - { type: 'suite', state: 'running', suite: suite2 }, - { type: 'test', state: 'running', test: s2t3 }, { + state: 'running', + test: s1t2 + }; + assert.ok( + testStatesEvI(s1running) < testStatesEvI(s1t2running)); + + const s1t2finished = { type: 'test', state: 'failed', - test: s2t3, - decorations: [{ line: 20, message: 'Expanded: false' }], + test: s1t2, + decorations: [{line: 14, message: 'Expanded: false'}], message: - 'Duration: 0.000596 second(s)\n>>> s2t3(line: 19) REQUIRE (line: 21) \n Original:\n std::false_type::value\n Expanded:\n false\n<<<\n' - }, - { type: 'suite', state: 'completed', suite: suite2 }, - { type: 'finished' } - ]; - assert.deepStrictEqual(testStatesEvents, expected); - - await adapter.run([s2t3.id]); - assert.deepStrictEqual( - testStatesEvents, [...expected, ...expected]); - }), - new Mocha.Test( - 'should run failing test s2t3 with chunks', - async function () { - const withArgs = spawnStub.withArgs( - example1.suite2.execPath, example1.suite2.t3.outputs[0][0]); - withArgs.onCall(withArgs.callCount) - .returns( - new ChildProcessStub(example1.suite2.t3.outputs[0][1])); - - await adapter.run([s2t3.id]); - const expected = [ - { type: 'started', tests: [s2t3.id] }, - { type: 'suite', state: 'running', suite: suite2 }, - { type: 'test', state: 'running', test: s2t3 }, { + 'Duration: 0.000204 second(s)\n>>> s1t2(line: 13) REQUIRE (line: 15) \n Original:\n std::false_type::value\n Expanded:\n false\n<<<\n' + }; + assert.ok( + testStatesEvI(s1t2running) < testStatesEvI(s1t2finished)); + assert.ok( + testStatesEvI(s1t2finished) < testStatesEvI(s1finished)); + + const s2t1running = { type: 'test', - state: 'failed', - test: s2t3, - decorations: [{ line: 20, message: 'Expanded: false' }], - message: - 'Duration: 0.000596 second(s)\n>>> s2t3(line: 19) REQUIRE (line: 21) \n Original:\n std::false_type::value\n Expanded:\n false\n<<<\n' - }, - { type: 'suite', state: 'completed', suite: suite2 }, - { type: 'finished' } - ]; - assert.deepStrictEqual(testStatesEvents, expected); - - await adapter.run([s2t3.id]); - assert.deepStrictEqual( - testStatesEvents, [...expected, ...expected]); - }), - new Mocha.Test( - 'should run suite1', - async function () { - await adapter.run([suite1.id]); - const expected = [ - { type: 'started', tests: [suite1.id] }, - { type: 'suite', state: 'running', suite: suite1 }, - { type: 'test', state: 'running', test: s1t1 }, { + state: 'running', + test: s2t1 + }; + assert.ok( + testStatesEvI(s2running) < testStatesEvI(s2t1running)); + + const s2t1finished = { type: 'test', state: 'passed', - test: s1t1, + test: s2t1, decorations: undefined, - message: 'Duration: 0.000132 second(s)\n' - }, - { type: 'test', state: 'running', test: s1t2 }, { + message: 'Duration: 0.00037 second(s)\n' + }; + assert.ok( + testStatesEvI(s2t1running) < testStatesEvI(s2t1finished)); + assert.ok( + testStatesEvI(s2t1finished) < testStatesEvI(s2finished)); + + const s2t2running = { type: 'test', - state: 'failed', - test: s1t2, - decorations: [{ line: 14, message: 'Expanded: false' }], - message: - 'Duration: 0.000204 second(s)\n>>> s1t2(line: 13) REQUIRE (line: 15) \n Original:\n std::false_type::value\n Expanded:\n false\n<<<\n' - }, - { type: 'suite', state: 'completed', suite: suite1 }, - { type: 'finished' } - ]; - assert.deepStrictEqual(testStatesEvents, expected); + state: 'running', + test: s2t2 + }; + assert.ok( + testStatesEvI(s2running) < testStatesEvI(s2t2running)); - await adapter.run([suite1.id]); - assert.deepStrictEqual( - testStatesEvents, [...expected, ...expected]); - }), - new Mocha.Test( - 'should run with wrong xml', - async function () { - const m = - example1.suite1.t1.outputs[0][1].match(']+>'); - assert.notStrictEqual(m, undefined); - assert.notStrictEqual(m!.input, undefined); - assert.notStrictEqual(m!.index, undefined); - const part = m!.input!.substr(0, m!.index! + m![0].length); - const withArgs = spawnStub.withArgs( - example1.suite1.execPath, example1.suite1.t1.outputs[0][0]); - withArgs.onCall(withArgs.callCount) - .returns(new ChildProcessStub(part)); - - await adapter.run([s1t1.id]); + const s2t2finished = { + type: 'test', + state: 'skipped', + test: s2t2 + }; + assert.ok( + testStatesEvI(s2t2running) < testStatesEvI(s2t2finished)); + assert.ok( + testStatesEvI(s2t2finished) < testStatesEvI(s2finished)); + + const s2t3running = { + type: 'test', + state: 'running', + test: s2t3 + }; + assert.ok( + testStatesEvI(s2running) < testStatesEvI(s2t3running)); - const expected = [ - { type: 'started', tests: [s1t1.id] }, - { type: 'suite', state: 'running', suite: suite1 }, - { type: 'test', state: 'running', test: s1t1 }, { + const s2t3finished = { type: 'test', state: 'failed', - test: s1t1, - message: 'Unexpected test error. (Is Catch2 crashed?)\n' - }, - { type: 'suite', state: 'completed', suite: suite1 }, - { type: 'finished' } - ]; - assert.deepStrictEqual(testStatesEvents, expected); - - // this tests the sinon stubs too - await adapter.run([s1t1.id]); - assert.deepStrictEqual(testStatesEvents, [ - ...expected, { type: 'started', tests: [s1t1.id] }, - { type: 'suite', state: 'running', suite: suite1 }, - { type: 'test', state: 'running', test: s1t1 }, { - type: 'test', - state: 'passed', - test: s1t1, - decorations: undefined, - message: 'Duration: 0.000112 second(s)\n' - }, - { type: 'suite', state: 'completed', suite: suite1 }, - { type: 'finished' } - ]); - }), + test: s2t3, + decorations: [{line: 20, message: 'Expanded: false'}], + message: + 'Duration: 0.000178 second(s)\n>>> s2t3(line: 19) REQUIRE (line: 21) \n Original:\n std::false_type::value\n Expanded:\n false\n<<<\n' + }; + assert.ok( + testStatesEvI(s2t3running) < testStatesEvI(s2t3finished)); + assert.ok( + testStatesEvI(s2t3finished) < testStatesEvI(s2finished)); + + const finished = {type: 'finished'}; + assert.ok(testStatesEvI(s1finished) < testStatesEvI(finished)); + assert.ok(testStatesEvI(s2finished) < testStatesEvI(finished)); + + assert.equal( + testStatesEvents.length, 16, inspect(testStatesEvents)); + }), new Mocha.Test( - 'should cancel without error', - function () { - adapter.cancel(); - }), + 'should run with not existing test id', + async function() { + await adapter.run(['not existing id']); + + assert.deepStrictEqual(testStatesEvents, [ + {type: 'started', tests: ['not existing id']}, + {type: 'finished'} + ]); + }), new Mocha.Test( - 'cancel', - async function () { - let spyKill1: sinon.SinonSpy; - let spyKill2: sinon.SinonSpy; - { - const spawnEvent = - new ChildProcessStub(example1.suite1.outputs[2][1]); - spyKill1 = sinon.spy(spawnEvent, 'kill'); - const withArgs = spawnStub.withArgs( - example1.suite1.execPath, example1.suite1.outputs[2][0]); - withArgs.onCall(withArgs.callCount).returns(spawnEvent); - } - { - const spawnEvent = - new ChildProcessStub(example1.suite2.outputs[2][1]); - spyKill2 = sinon.spy(spawnEvent, 'kill'); - const withArgs = spawnStub.withArgs( - example1.suite2.execPath, example1.suite2.outputs[2][0]); - withArgs.onCall(withArgs.callCount).returns(spawnEvent); - } - const run = adapter.run([root.id]); - adapter.cancel(); - await run; - - assert.equal(spyKill1.callCount, 1); - assert.equal(spyKill2.callCount, 1); - - const running = { type: 'started', tests: [root.id] }; - - const s1running = { - type: 'suite', - state: 'running', - suite: suite1 - }; - const s1finished = { - type: 'suite', - state: 'completed', - suite: suite1 - }; - assert.ok(testStatesEvI(running) < testStatesEvI(s1running)); - assert.ok(testStatesEvI(s1running) < testStatesEvI(s1finished)); - - const s2running = { - type: 'suite', - state: 'running', - suite: suite2 - }; - const s2finished = { - type: 'suite', - state: 'completed', - suite: suite2 - }; - assert.ok(testStatesEvI(running) < testStatesEvI(s1running)); - assert.ok(testStatesEvI(s2running) < testStatesEvI(s2finished)); - - const s2t2running = { - type: 'test', - state: 'running', - test: s2t2 - }; - assert.ok( - testStatesEvI(s2running) < testStatesEvI(s2t2running)); - - const s2t2finished = { - type: 'test', - state: 'skipped', - test: s2t2 - }; - assert.ok( - testStatesEvI(s2t2running) < testStatesEvI(s2t2finished)); - assert.ok( - testStatesEvI(s2t2finished) < testStatesEvI(s2finished)); - - const finished = { type: 'finished' }; - assert.ok(testStatesEvI(s1finished) < testStatesEvI(finished)); - assert.ok(testStatesEvI(s2finished) < testStatesEvI(finished)); - - assert.equal( - testStatesEvents.length, 8, inspect(testStatesEvents)); - }), + 'should run s1t1', + async function() { + await adapter.run([s1t1.id]); + const expected = [ + {type: 'started', tests: [s1t1.id]}, + {type: 'suite', state: 'running', suite: suite1}, + {type: 'test', state: 'running', test: s1t1}, { + type: 'test', + state: 'passed', + test: s1t1, + decorations: undefined, + message: 'Duration: 0.000112 second(s)\n' + }, + {type: 'suite', state: 'completed', suite: suite1}, + {type: 'finished'} + ]; + assert.deepStrictEqual(testStatesEvents, expected); + + await adapter.run([s1t1.id]); + assert.deepStrictEqual( + testStatesEvents, [...expected, ...expected]); + }), + new Mocha.Test( + 'should run skipped s2t2', + async function() { + await adapter.run([s2t2.id]); + const expected = [ + {type: 'started', tests: [s2t2.id]}, + {type: 'suite', state: 'running', suite: suite2}, + {type: 'test', state: 'running', test: s2t2}, { + type: 'test', + state: 'passed', + test: s2t2, + decorations: undefined, + message: 'Duration: 0.001294 second(s)\n' + }, + {type: 'suite', state: 'completed', suite: suite2}, + {type: 'finished'} + ]; + assert.deepStrictEqual(testStatesEvents, expected); + + await adapter.run([s2t2.id]); + assert.deepStrictEqual( + testStatesEvents, [...expected, ...expected]); + }), + new Mocha.Test( + 'should run failing test s2t3', + async function() { + await adapter.run([s2t3.id]); + const expected = [ + {type: 'started', tests: [s2t3.id]}, + {type: 'suite', state: 'running', suite: suite2}, + {type: 'test', state: 'running', test: s2t3}, { + type: 'test', + state: 'failed', + test: s2t3, + decorations: [{line: 20, message: 'Expanded: false'}], + message: + 'Duration: 0.000596 second(s)\n>>> s2t3(line: 19) REQUIRE (line: 21) \n Original:\n std::false_type::value\n Expanded:\n false\n<<<\n' + }, + {type: 'suite', state: 'completed', suite: suite2}, + {type: 'finished'} + ]; + assert.deepStrictEqual(testStatesEvents, expected); + + await adapter.run([s2t3.id]); + assert.deepStrictEqual( + testStatesEvents, [...expected, ...expected]); + }), new Mocha.Test( - 'cancel after run finished', - function () { - let spyKill1: sinon.SinonSpy; - let spyKill2: sinon.SinonSpy; - { - const spawnEvent = - new ChildProcessStub(example1.suite1.outputs[2][1]); - spyKill1 = sinon.spy(spawnEvent, 'kill'); + 'should run failing test s2t3 with chunks', + async function() { const withArgs = spawnStub.withArgs( - example1.suite1.execPath, example1.suite1.outputs[2][0]); - withArgs.onCall(withArgs.callCount).returns(spawnEvent); - } - { - const spawnEvent = - new ChildProcessStub(example1.suite2.outputs[2][1]); - spyKill2 = sinon.spy(spawnEvent, 'kill'); + example1.suite2.execPath, example1.suite2.t3.outputs[0][0]); + withArgs.onCall(withArgs.callCount) + .returns( + new ChildProcessStub(example1.suite2.t3.outputs[0][1])); + + await adapter.run([s2t3.id]); + const expected = [ + {type: 'started', tests: [s2t3.id]}, + {type: 'suite', state: 'running', suite: suite2}, + {type: 'test', state: 'running', test: s2t3}, { + type: 'test', + state: 'failed', + test: s2t3, + decorations: [{line: 20, message: 'Expanded: false'}], + message: + 'Duration: 0.000596 second(s)\n>>> s2t3(line: 19) REQUIRE (line: 21) \n Original:\n std::false_type::value\n Expanded:\n false\n<<<\n' + }, + {type: 'suite', state: 'completed', suite: suite2}, + {type: 'finished'} + ]; + assert.deepStrictEqual(testStatesEvents, expected); + + await adapter.run([s2t3.id]); + assert.deepStrictEqual( + testStatesEvents, [...expected, ...expected]); + }), + new Mocha.Test( + 'should run suite1', + async function() { + await adapter.run([suite1.id]); + const expected = [ + {type: 'started', tests: [suite1.id]}, + {type: 'suite', state: 'running', suite: suite1}, + {type: 'test', state: 'running', test: s1t1}, { + type: 'test', + state: 'passed', + test: s1t1, + decorations: undefined, + message: 'Duration: 0.000132 second(s)\n' + }, + {type: 'test', state: 'running', test: s1t2}, { + type: 'test', + state: 'failed', + test: s1t2, + decorations: [{line: 14, message: 'Expanded: false'}], + message: + 'Duration: 0.000204 second(s)\n>>> s1t2(line: 13) REQUIRE (line: 15) \n Original:\n std::false_type::value\n Expanded:\n false\n<<<\n' + }, + {type: 'suite', state: 'completed', suite: suite1}, + {type: 'finished'} + ]; + assert.deepStrictEqual(testStatesEvents, expected); + + await adapter.run([suite1.id]); + assert.deepStrictEqual( + testStatesEvents, [...expected, ...expected]); + }), + new Mocha.Test( + 'should run with wrong xml', + async function() { + const m = + example1.suite1.t1.outputs[0][1].match(']+>'); + assert.notStrictEqual(m, undefined); + assert.notStrictEqual(m!.input, undefined); + assert.notStrictEqual(m!.index, undefined); + const part = m!.input!.substr(0, m!.index! + m![0].length); const withArgs = spawnStub.withArgs( - example1.suite2.execPath, example1.suite2.outputs[2][0]); - withArgs.onCall(withArgs.callCount).returns(spawnEvent); - } - const run = adapter.run([root.id]); - return run.then(function () { + example1.suite1.execPath, example1.suite1.t1.outputs[0][0]); + withArgs.onCall(withArgs.callCount) + .returns(new ChildProcessStub(part)); + + await adapter.run([s1t1.id]); + + const expected = [ + {type: 'started', tests: [s1t1.id]}, + {type: 'suite', state: 'running', suite: suite1}, + {type: 'test', state: 'running', test: s1t1}, { + type: 'test', + state: 'failed', + test: s1t1, + message: 'Unexpected test error. (Is Catch2 crashed?)\n' + }, + {type: 'suite', state: 'completed', suite: suite1}, + {type: 'finished'} + ]; + assert.deepStrictEqual(testStatesEvents, expected); + + // this tests the sinon stubs too + await adapter.run([s1t1.id]); + assert.deepStrictEqual(testStatesEvents, [ + ...expected, {type: 'started', tests: [s1t1.id]}, + {type: 'suite', state: 'running', suite: suite1}, + {type: 'test', state: 'running', test: s1t1}, { + type: 'test', + state: 'passed', + test: s1t1, + decorations: undefined, + message: 'Duration: 0.000112 second(s)\n' + }, + {type: 'suite', state: 'completed', suite: suite1}, + {type: 'finished'} + ]); + }), + new Mocha.Test( + 'should cancel without error', + function() { adapter.cancel(); - assert.equal(spyKill1.callCount, 0); - assert.equal(spyKill2.callCount, 0); - }); - }) + }), + new Mocha.Test( + 'cancel', + async function() { + let spyKill1: sinon.SinonSpy; + let spyKill2: sinon.SinonSpy; + { + const spawnEvent = + new ChildProcessStub(example1.suite1.outputs[2][1]); + spyKill1 = sinon.spy(spawnEvent, 'kill'); + const withArgs = spawnStub.withArgs( + example1.suite1.execPath, example1.suite1.outputs[2][0]); + withArgs.onCall(withArgs.callCount).returns(spawnEvent); + } + { + const spawnEvent = + new ChildProcessStub(example1.suite2.outputs[2][1]); + spyKill2 = sinon.spy(spawnEvent, 'kill'); + const withArgs = spawnStub.withArgs( + example1.suite2.execPath, example1.suite2.outputs[2][0]); + withArgs.onCall(withArgs.callCount).returns(spawnEvent); + } + const run = adapter.run([root.id]); + adapter.cancel(); + await run; + + assert.equal(spyKill1.callCount, 1); + assert.equal(spyKill2.callCount, 1); + + const running = {type: 'started', tests: [root.id]}; + + const s1running = { + type: 'suite', + state: 'running', + suite: suite1 + }; + const s1finished = { + type: 'suite', + state: 'completed', + suite: suite1 + }; + assert.ok(testStatesEvI(running) < testStatesEvI(s1running)); + assert.ok(testStatesEvI(s1running) < testStatesEvI(s1finished)); + + const s2running = { + type: 'suite', + state: 'running', + suite: suite2 + }; + const s2finished = { + type: 'suite', + state: 'completed', + suite: suite2 + }; + assert.ok(testStatesEvI(running) < testStatesEvI(s1running)); + assert.ok(testStatesEvI(s2running) < testStatesEvI(s2finished)); + + const s2t2running = { + type: 'test', + state: 'running', + test: s2t2 + }; + assert.ok( + testStatesEvI(s2running) < testStatesEvI(s2t2running)); + + const s2t2finished = { + type: 'test', + state: 'skipped', + test: s2t2 + }; + assert.ok( + testStatesEvI(s2t2running) < testStatesEvI(s2t2finished)); + assert.ok( + testStatesEvI(s2t2finished) < testStatesEvI(s2finished)); + + const finished = {type: 'finished'}; + assert.ok(testStatesEvI(s1finished) < testStatesEvI(finished)); + assert.ok(testStatesEvI(s2finished) < testStatesEvI(finished)); + + assert.equal( + testStatesEvents.length, 8, inspect(testStatesEvents)); + }), + new Mocha.Test( + 'cancel after run finished', + function() { + let spyKill1: sinon.SinonSpy; + let spyKill2: sinon.SinonSpy; + { + const spawnEvent = + new ChildProcessStub(example1.suite1.outputs[2][1]); + spyKill1 = sinon.spy(spawnEvent, 'kill'); + const withArgs = spawnStub.withArgs( + example1.suite1.execPath, example1.suite1.outputs[2][0]); + withArgs.onCall(withArgs.callCount).returns(spawnEvent); + } + { + const spawnEvent = + new ChildProcessStub(example1.suite2.outputs[2][1]); + spyKill2 = sinon.spy(spawnEvent, 'kill'); + const withArgs = spawnStub.withArgs( + example1.suite2.execPath, example1.suite2.outputs[2][0]); + withArgs.onCall(withArgs.callCount).returns(spawnEvent); + } + const run = adapter.run([root.id]); + return run.then(function() { + adapter.cancel(); + assert.equal(spyKill1.callCount, 0); + assert.equal(spyKill2.callCount, 0); + }); + }) ]; - context('executables=["execPath1", "execPath2"]', function () { - before(function () { + context('executables=["execPath1", "execPath2"]', function() { + before(function() { return updateConfig('executables', ['execPath1', 'execPath2']); }); - after(function () { + after(function() { return updateConfig('executables', undefined); }); let suite1Watcher: FileSystemWatcherStub; - beforeEach(async function () { + beforeEach(async function() { assert.equal(watchers.size, 2); assert.ok(watchers.has(example1.suite1.execPath)); suite1Watcher = watchers.get(example1.suite1.execPath)!; example1.suite1.assert( - 'execPath1', ['s1t1', 's1t2'], suite1, uniqueIdC); + 'execPath1', ['s1t1', 's1t2'], suite1, uniqueIdC); example1.suite2.assert( - 'execPath2', ['s2t1', 's2t2 [.]', 's2t3'], suite2, uniqueIdC); + 'execPath2', ['s2t1', 's2t2 [.]', 's2t3'], suite2, uniqueIdC); }); for (let t of testsForAdapterWithSuite1AndSuite2) this.addTest(t.clone()); it('reload because of fswatcher event: touch(changed)', - async function () { - this.slow(200); - const newRoot = await doAndWaitForReloadEvent(this, async () => { - suite1Watcher.sendChange(); - }); - assert.deepStrictEqual(newRoot, root); - assert.deepStrictEqual( - testsEvents, - [{ type: 'started' }, { type: 'finished', suite: root }]); - }); + async function() { + this.slow(200); + const newRoot = await doAndWaitForReloadEvent(this, async () => { + suite1Watcher.sendChange(); + }); + assert.deepStrictEqual(newRoot, root); + assert.deepStrictEqual( + testsEvents, + [{type: 'started'}, {type: 'finished', suite: root}]); + }); it('reload because of fswatcher event: double touch(changed)', - async function () { - this.slow(300); - const oldRoot = root; - suite1Watcher.sendChange(); - suite1Watcher.sendChange(); - await waitFor(this, async () => { - return testsEvents.length >= 2; - }); - await promisify(setTimeout)(100); - assert.deepStrictEqual( - testsEvents, - [{ type: 'started' }, { type: 'finished', suite: oldRoot }]); - testsEvents.pop(); - testsEvents.pop(); - }); + async function() { + this.slow(300); + const oldRoot = root; + suite1Watcher.sendChange(); + suite1Watcher.sendChange(); + await waitFor(this, async () => { + return testsEvents.length >= 2; + }); + await promisify(setTimeout)(100); + assert.deepStrictEqual( + testsEvents, + [{type: 'started'}, {type: 'finished', suite: oldRoot}]); + testsEvents.pop(); + testsEvents.pop(); + }); it('reload because of fswatcher event: double touch(changed) with delay', - async function () { - this.slow(300); - const oldRoot = root; - suite1Watcher.sendChange(); - setTimeout(() => { - suite1Watcher.sendChange(); - }, 20); - await waitFor(this, async () => { - return testsEvents.length >= 2; - }); - await promisify(setTimeout)(100); - assert.deepStrictEqual( - testsEvents, - [{ type: 'started' }, { type: 'finished', suite: oldRoot }]); - testsEvents.pop(); - testsEvents.pop(); - }); + async function() { + this.slow(300); + const oldRoot = root; + suite1Watcher.sendChange(); + setTimeout(() => { + suite1Watcher.sendChange(); + }, 20); + await waitFor(this, async () => { + return testsEvents.length >= 2; + }); + await promisify(setTimeout)(100); + assert.deepStrictEqual( + testsEvents, + [{type: 'started'}, {type: 'finished', suite: oldRoot}]); + testsEvents.pop(); + testsEvents.pop(); + }); it('reload because of fswatcher event: touch(delete,create)', - async function () { - this.slow(200); - const newRoot = await doAndWaitForReloadEvent(this, async () => { - suite1Watcher.sendDelete(); - suite1Watcher.sendCreate(); - }); - assert.deepStrictEqual(newRoot, root); - assert.deepStrictEqual( - testsEvents, - [{ type: 'started' }, { type: 'finished', suite: root }]); - }); + async function() { + this.slow(200); + const newRoot = await doAndWaitForReloadEvent(this, async () => { + suite1Watcher.sendDelete(); + suite1Watcher.sendCreate(); + }); + assert.deepStrictEqual(newRoot, root); + assert.deepStrictEqual( + testsEvents, + [{type: 'started'}, {type: 'finished', suite: root}]); + }); it('reload because of fswatcher event: double touch(delete,create)', - async function () { - this.slow(300); - const oldRoot = root; - suite1Watcher.sendChange(); - suite1Watcher.sendChange(); - await waitFor(this, async () => { - return testsEvents.length >= 2; - }); - await promisify(setTimeout)(100); - assert.deepStrictEqual( - testsEvents, - [{ type: 'started' }, { type: 'finished', suite: oldRoot }]); - testsEvents.pop(); - testsEvents.pop(); - }); + async function() { + this.slow(300); + const oldRoot = root; + suite1Watcher.sendChange(); + suite1Watcher.sendChange(); + await waitFor(this, async () => { + return testsEvents.length >= 2; + }); + await promisify(setTimeout)(100); + assert.deepStrictEqual( + testsEvents, + [{type: 'started'}, {type: 'finished', suite: oldRoot}]); + testsEvents.pop(); + testsEvents.pop(); + }); it('reload because of fswatcher event: double touch(delete,create) with delay', - async function () { - this.slow(300); - const oldRoot = root; - suite1Watcher.sendChange(); - setTimeout(() => { - suite1Watcher.sendChange(); - }, 20); - await waitFor(this, async () => { - return testsEvents.length >= 2; - }); - await promisify(setTimeout)(100); - assert.deepStrictEqual( - testsEvents, - [{ type: 'started' }, { type: 'finished', suite: oldRoot }]); - testsEvents.pop(); - testsEvents.pop(); - }); + async function() { + this.slow(300); + const oldRoot = root; + suite1Watcher.sendChange(); + setTimeout(() => { + suite1Watcher.sendChange(); + }, 20); + await waitFor(this, async () => { + return testsEvents.length >= 2; + }); + await promisify(setTimeout)(100); + assert.deepStrictEqual( + testsEvents, + [{type: 'started'}, {type: 'finished', suite: oldRoot}]); + testsEvents.pop(); + testsEvents.pop(); + }); it('reload because of fswatcher event: test added', - async function (this: Mocha.Context) { - this.slow(200); - const testListOutput = example1.suite1.outputs[1][1].split('\n'); - assert.equal(testListOutput.length, 10); - testListOutput.splice( - 1, 0, ' s1t0', ' suite1.cpp:6', ' tag1'); - const withArgs = spawnStub.withArgs( - example1.suite1.execPath, example1.suite1.outputs[1][0]); - withArgs.onCall(withArgs.callCount) - .returns(new ChildProcessStub( - testListOutput.join(EOL))); - - const oldRootChildren = [...root.children]; - const oldSuite1Children = [...suite1.children]; - const oldSuite2Children = [...suite2.children]; - - const newRoot = await doAndWaitForReloadEvent(this, async () => { - suite1Watcher.sendDelete(); - suite1Watcher.sendCreate(); - }); - - assert.equal(newRoot, root); - assert.equal(root.children.length, oldRootChildren.length); - for (let i = 0; i < oldRootChildren.length; i++) { - assert.equal(root.children[i], oldRootChildren[i]); - } - - assert.equal( - suite1.children.length, oldSuite1Children.length + 1); - for (let i = 0; i < suite1.children.length; i++) { - assert.equal(suite1.children[i + 1], oldSuite1Children[i]); - } - const newTest = suite1.children[0]; - assert.ok(!uniqueIdC.has(newTest.id)); - assert.equal(newTest.label, 's1t0'); - - assert.equal(suite2.children.length, oldSuite2Children.length); - for (let i = 0; i < suite2.children.length; i++) { - assert.equal(suite2.children[i], oldSuite2Children[i]); - } - }); + async function(this: Mocha.Context) { + this.slow(200); + const testListOutput = example1.suite1.outputs[1][1].split('\n'); + assert.equal(testListOutput.length, 10); + testListOutput.splice( + 1, 0, ' s1t0', ' suite1.cpp:6', ' tag1'); + const withArgs = spawnStub.withArgs( + example1.suite1.execPath, example1.suite1.outputs[1][0]); + withArgs.onCall(withArgs.callCount) + .returns(new ChildProcessStub(testListOutput.join(EOL))); + + const oldRootChildren = [...root.children]; + const oldSuite1Children = [...suite1.children]; + const oldSuite2Children = [...suite2.children]; + + const newRoot = await doAndWaitForReloadEvent(this, async () => { + suite1Watcher.sendDelete(); + suite1Watcher.sendCreate(); + }); + + assert.equal(newRoot, root); + assert.equal(root.children.length, oldRootChildren.length); + for (let i = 0; i < oldRootChildren.length; i++) { + assert.equal(root.children[i], oldRootChildren[i]); + } + + assert.equal( + suite1.children.length, oldSuite1Children.length + 1); + for (let i = 0; i < suite1.children.length; i++) { + assert.equal(suite1.children[i + 1], oldSuite1Children[i]); + } + const newTest = suite1.children[0]; + assert.ok(!uniqueIdC.has(newTest.id)); + assert.equal(newTest.label, 's1t0'); + + assert.equal(suite2.children.length, oldSuite2Children.length); + for (let i = 0; i < suite2.children.length; i++) { + assert.equal(suite2.children[i], oldSuite2Children[i]); + } + }); it('reload because of fswatcher event: test deleted', - async function (this: Mocha.Context) { - this.slow(200); - const testListOutput = example1.suite1.outputs[1][1].split('\n'); - assert.equal(testListOutput.length, 10); - testListOutput.splice(1, 3); - const withArgs = spawnStub.withArgs( - example1.suite1.execPath, example1.suite1.outputs[1][0]); - withArgs.onCall(withArgs.callCount) - .returns(new ChildProcessStub(testListOutput.join('\n'))); - - const oldRootChildren = [...root.children]; - const oldSuite1Children = [...suite1.children]; - const oldSuite2Children = [...suite2.children]; - - const newRoot = await doAndWaitForReloadEvent(this, async () => { - suite1Watcher.sendDelete(); - suite1Watcher.sendCreate(); - }); - - assert.equal(newRoot, root); - assert.equal(root.children.length, oldRootChildren.length); - for (let i = 0; i < oldRootChildren.length; i++) { - assert.equal(root.children[i], oldRootChildren[i]); - } - - assert.equal( - suite1.children.length + 1, oldSuite1Children.length); - for (let i = 0; i < suite1.children.length; i++) { - assert.equal(suite1.children[i], oldSuite1Children[i + 1]); - } - - assert.equal(suite2.children.length, oldSuite2Children.length); - for (let i = 0; i < suite2.children.length; i++) { - assert.equal(suite2.children[i], oldSuite2Children[i]); - } - }); + async function(this: Mocha.Context) { + this.slow(200); + const testListOutput = example1.suite1.outputs[1][1].split('\n'); + assert.equal(testListOutput.length, 10); + testListOutput.splice(1, 3); + const withArgs = spawnStub.withArgs( + example1.suite1.execPath, example1.suite1.outputs[1][0]); + withArgs.onCall(withArgs.callCount) + .returns(new ChildProcessStub(testListOutput.join('\n'))); + + const oldRootChildren = [...root.children]; + const oldSuite1Children = [...suite1.children]; + const oldSuite2Children = [...suite2.children]; + + const newRoot = await doAndWaitForReloadEvent(this, async () => { + suite1Watcher.sendDelete(); + suite1Watcher.sendCreate(); + }); + + assert.equal(newRoot, root); + assert.equal(root.children.length, oldRootChildren.length); + for (let i = 0; i < oldRootChildren.length; i++) { + assert.equal(root.children[i], oldRootChildren[i]); + } + + assert.equal( + suite1.children.length + 1, oldSuite1Children.length); + for (let i = 0; i < suite1.children.length; i++) { + assert.equal(suite1.children[i], oldSuite1Children[i + 1]); + } + + assert.equal(suite2.children.length, oldSuite2Children.length); + for (let i = 0; i < suite2.children.length; i++) { + assert.equal(suite2.children[i], oldSuite2Children[i]); + } + }); }); - context('executables=[{}] and env={...}', function () { - before(async function () { + context('executables=[{}] and env={...}', function() { + before(async function() { await updateConfig( - 'executables', [{ - name: '${relDirpath}/${filename} (${absDirpath})', - path: 'execPath{1,2}', - cwd: '${workspaceFolder}/cwd', - env: { - C2LOCALTESTENV: 'c2localtestenv', - C2OVERRIDETESTENV: 'c2overridetestenv-l' - } - }]); + 'executables', [{ + name: '${relDirpath}/${filename} (${absDirpath})', + path: 'execPath{1,2}', + cwd: '${workspaceFolder}/cwd', + env: { + C2LOCALTESTENV: 'c2localtestenv', + C2OVERRIDETESTENV: 'c2overridetestenv-l' + } + }]); vsfsWatchStub - .withArgs(matchRelativePattern( - path.join(workspaceFolderUri.fsPath, 'execPath{1,2}'))) - .callsFake(handleCreateWatcherCb); + .withArgs(matchRelativePattern( + path.join(workspaceFolderUri.fsPath, 'execPath{1,2}'))) + .callsFake(handleCreateWatcherCb); vsFindFilesStub - .withArgs(matchRelativePattern( - path.join(workspaceFolderUri.fsPath, 'execPath{1,2}'))) - .returns([ - vscode.Uri.file(example1.suite1.execPath), - vscode.Uri.file(example1.suite2.execPath), - ]); + .withArgs(matchRelativePattern( + path.join(workspaceFolderUri.fsPath, 'execPath{1,2}'))) + .returns([ + vscode.Uri.file(example1.suite1.execPath), + vscode.Uri.file(example1.suite2.execPath), + ]); await updateConfig('defaultEnv', { 'C2GLOBALTESTENV': 'c2globaltestenv', 'C2OVERRIDETESTENV': 'c2overridetestenv-g', }); }); - after(async function () { + after(async function() { await updateConfig('executables', undefined); await updateConfig('defaultEnv', undefined); }); - beforeEach(async function () { + beforeEach(async function() { example1.suite1.assert( - './execPath1 (' + workspaceFolderUri.fsPath + ')', - ['s1t1', 's1t2'], suite1, uniqueIdC); + './execPath1 (' + workspaceFolderUri.fsPath + ')', + ['s1t1', 's1t2'], suite1, uniqueIdC); example1.suite2.assert( - './execPath2 (' + workspaceFolderUri.fsPath + ')', - ['s2t1', 's2t2 [.]', 's2t3'], suite2, uniqueIdC); + './execPath2 (' + workspaceFolderUri.fsPath + ')', + ['s2t1', 's2t2 [.]', 's2t3'], suite2, uniqueIdC); }) for (let t of testsForAdapterWithSuite1AndSuite2) this.addTest( - t.clone()); + t.clone()); - it('should get execution options', async function () { + it('should get execution options', async function() { { const withArgs = spawnStub.withArgs( - example1.suite1.execPath, example1.suite1.outputs[2][0]); + example1.suite1.execPath, example1.suite1.outputs[2][0]); withArgs.onCall(withArgs.callCount) - .callsFake((p: string, args: string[], ops: any) => { - assert.equal( - ops.cwd, path.join(workspaceFolderUri.fsPath, 'cwd')); - assert.equal(ops.env.C2LOCALTESTENV, 'c2localtestenv'); - assert.ok(!ops.env.hasOwnProperty('C2GLOBALTESTENV')); - assert.equal( - ops.env.C2OVERRIDETESTENV, 'c2overridetestenv-l'); - return new ChildProcessStub(example1.suite1.outputs[2][1]); - }); + .callsFake((p: string, args: string[], ops: any) => { + assert.equal( + ops.cwd, path.join(workspaceFolderUri.fsPath, 'cwd')); + assert.equal(ops.env.C2LOCALTESTENV, 'c2localtestenv'); + assert.ok(!ops.env.hasOwnProperty('C2GLOBALTESTENV')); + assert.equal( + ops.env.C2OVERRIDETESTENV, 'c2overridetestenv-l'); + return new ChildProcessStub(example1.suite1.outputs[2][1]); + }); const cc = withArgs.callCount; await adapter.run([suite1.id]); @@ -1339,17 +1338,17 @@ describe('C2TestAdapter', function () { } { const withArgs = spawnStub.withArgs( - example1.suite2.execPath, example1.suite2.outputs[2][0]); + example1.suite2.execPath, example1.suite2.outputs[2][0]); withArgs.onCall(withArgs.callCount) - .callsFake((p: string, args: string[], ops: any) => { - assert.equal( - ops.cwd, path.join(workspaceFolderUri.fsPath, 'cwd')); - assert.equal(ops.env.C2LOCALTESTENV, 'c2localtestenv'); - assert.ok(!ops.env.hasOwnProperty('C2GLOBALTESTENV')); - assert.equal( - ops.env.C2OVERRIDETESTENV, 'c2overridetestenv-l'); - return new ChildProcessStub(example1.suite2.outputs[2][1]); - }); + .callsFake((p: string, args: string[], ops: any) => { + assert.equal( + ops.cwd, path.join(workspaceFolderUri.fsPath, 'cwd')); + assert.equal(ops.env.C2LOCALTESTENV, 'c2localtestenv'); + assert.ok(!ops.env.hasOwnProperty('C2GLOBALTESTENV')); + assert.equal( + ops.env.C2OVERRIDETESTENV, 'c2overridetestenv-l'); + return new ChildProcessStub(example1.suite2.outputs[2][1]); + }); const cc = withArgs.callCount; await adapter.run([suite2.id]); assert.equal(withArgs.callCount, cc + 1); @@ -1359,67 +1358,67 @@ describe('C2TestAdapter', function () { }); context( - 'executables=["execPath1", "execPath2", "execPath3"]', - async function () { - before(function () { - return updateConfig( - 'executables', ['execPath1', 'execPath2', 'execPath3']); - }); + 'executables=["execPath1", "execPath2", "execPath3"]', + async function() { + before(function() { + return updateConfig( + 'executables', ['execPath1', 'execPath2', 'execPath3']); + }); - after(function () { - return updateConfig('executables', undefined); - }); + after(function() { + return updateConfig('executables', undefined); + }); - it('run suite3 one-by-one', async function () { - this.slow(300); - assert.equal(root.children.length, 3); - assert.equal(root.children[0].type, 'suite'); - const suite3 = root.children[2]; - assert.equal(suite3.children.length, 33); + it('run suite3 one-by-one', async function() { + this.slow(300); + assert.equal(root.children.length, 3); + assert.equal(root.children[0].type, 'suite'); + const suite3 = root.children[2]; + assert.equal(suite3.children.length, 33); - spawnStub.withArgs(example1.suite3.execPath).throwsArg(1); + spawnStub.withArgs(example1.suite3.execPath).throwsArg(1); - const runAndCheckEvents = async (test: TestInfo) => { - assert.equal(testStatesEvents.length, 0); + const runAndCheckEvents = async (test: TestInfo) => { + assert.equal(testStatesEvents.length, 0); - await adapter.run([test.id]); + await adapter.run([test.id]); - assert.equal(testStatesEvents.length, 6, inspect(test)); + assert.equal(testStatesEvents.length, 6, inspect(test)); - assert.deepStrictEqual( - { type: 'started', tests: [test.id] }, testStatesEvents[0]); - assert.deepStrictEqual( - { type: 'suite', state: 'running', suite: suite3 }, - testStatesEvents[1]); + assert.deepStrictEqual( + {type: 'started', tests: [test.id]}, testStatesEvents[0]); + assert.deepStrictEqual( + {type: 'suite', state: 'running', suite: suite3}, + testStatesEvents[1]); - assert.equal(testStatesEvents[2].type, 'test'); - assert.equal((testStatesEvents[2]).state, 'running'); - assert.equal((testStatesEvents[2]).test, test); + assert.equal(testStatesEvents[2].type, 'test'); + assert.equal((testStatesEvents[2]).state, 'running'); + assert.equal((testStatesEvents[2]).test, test); - assert.equal(testStatesEvents[3].type, 'test'); - assert.ok( - (testStatesEvents[3]).state == 'passed' || - (testStatesEvents[3]).state == 'skipped' || - (testStatesEvents[3]).state == 'failed'); - assert.equal((testStatesEvents[3]).test, test); + assert.equal(testStatesEvents[3].type, 'test'); + assert.ok( + (testStatesEvents[3]).state == 'passed' || + (testStatesEvents[3]).state == 'skipped' || + (testStatesEvents[3]).state == 'failed'); + assert.equal((testStatesEvents[3]).test, test); - assert.deepStrictEqual( - { type: 'suite', state: 'completed', suite: suite3 }, - testStatesEvents[4]); - assert.deepStrictEqual({ type: 'finished' }, testStatesEvents[5]); + assert.deepStrictEqual( + {type: 'suite', state: 'completed', suite: suite3}, + testStatesEvents[4]); + assert.deepStrictEqual({type: 'finished'}, testStatesEvents[5]); - while (testStatesEvents.length) testStatesEvents.pop(); - }; + while (testStatesEvents.length) testStatesEvents.pop(); + }; - for (let test of suite3.children) { - assert.equal(test.type, 'test'); - await runAndCheckEvents(test); - } + for (let test of suite3.children) { + assert.equal(test.type, 'test'); + await runAndCheckEvents(test); + } + }) }) - }) }) - specify('load executables=', async function () { + specify('load executables=', async function() { this.slow(300); await updateConfig('executables', example1.suite1.execPath); adapter = createAdapterAndSubscribe(); @@ -1427,9 +1426,9 @@ describe('C2TestAdapter', function () { await adapter.load(); assert.equal(testsEvents.length, 2); assert.equal( - (testsEvents[testsEvents.length - 1]) - .suite!.children.length, - 1); + (testsEvents[testsEvents.length - 1]) + .suite!.children.length, + 1); testsEvents.pop(); testsEvents.pop(); @@ -1438,197 +1437,197 @@ describe('C2TestAdapter', function () { }); specify( - 'load executables=["execPath1", "execPath2"] with error', - async function () { - this.slow(300); - await updateConfig('executables', ['execPath1', 'execPath2']); - adapter = createAdapterAndSubscribe(); - - const withArgs = spawnStub.withArgs( - example1.suite2.execPath, example1.suite2.outputs[1][0]); - withArgs.onCall(withArgs.callCount).throws( - 'dummy error for testing (should be handled)'); - - await adapter.load(); - testsEvents.pop(); - testsEvents.pop(); - - disposeAdapterAndSubscribers(); - await updateConfig('executables', undefined); - }) + 'load executables=["execPath1", "execPath2"] with error', + async function() { + this.slow(300); + await updateConfig('executables', ['execPath1', 'execPath2']); + adapter = createAdapterAndSubscribe(); + + const withArgs = spawnStub.withArgs( + example1.suite2.execPath, example1.suite2.outputs[1][0]); + withArgs.onCall(withArgs.callCount).throws( + 'dummy error for testing (should be handled)'); + + await adapter.load(); + testsEvents.pop(); + testsEvents.pop(); + + disposeAdapterAndSubscribers(); + await updateConfig('executables', undefined); + }) specify( - 'load executables=["execPath1", "execPath2Copy"]; delete; sleep 3; create', - async function () { - const watchTimeout = 6; - await updateConfig('defaultWatchTimeoutSec', watchTimeout); - this.timeout(watchTimeout * 1000 + 2500 /* because of 'delay' */); - this.slow(watchTimeout * 1000 + 2500 /* because of 'delay' */); - const execPath2CopyPath = - path.join(workspaceFolderUri.fsPath, 'execPath2Copy'); - - for (let scenario of example1.suite2.outputs) { - spawnStub.withArgs(execPath2CopyPath, scenario[0]) - .callsFake(function () { - return new ChildProcessStub(scenario[1]); - }); - } + 'load executables=["execPath1", "execPath2Copy"]; delete; sleep 3; create', + async function() { + const watchTimeout = 6; + await updateConfig('defaultWatchTimeoutSec', watchTimeout); + this.timeout(watchTimeout * 1000 + 2500 /* because of 'delay' */); + this.slow(watchTimeout * 1000 + 2500 /* because of 'delay' */); + const execPath2CopyPath = + path.join(workspaceFolderUri.fsPath, 'execPath2Copy'); + + for (let scenario of example1.suite2.outputs) { + spawnStub.withArgs(execPath2CopyPath, scenario[0]) + .callsFake(function() { + return new ChildProcessStub(scenario[1]); + }); + } - fsStatStub.withArgs(execPath2CopyPath) - .callsFake(handleStatExistsFile); + fsStatStub.withArgs(execPath2CopyPath) + .callsFake(handleStatExistsFile); - vsfsWatchStub.withArgs(matchRelativePattern(execPath2CopyPath)) - .callsFake(handleCreateWatcherCb); + vsfsWatchStub.withArgs(matchRelativePattern(execPath2CopyPath)) + .callsFake(handleCreateWatcherCb); - vsFindFilesStub.withArgs(matchRelativePattern(execPath2CopyPath)) - .returns([vscode.Uri.file(execPath2CopyPath)]); + vsFindFilesStub.withArgs(matchRelativePattern(execPath2CopyPath)) + .returns([vscode.Uri.file(execPath2CopyPath)]); - await updateConfig('executables', ['execPath1', 'execPath2Copy']); - adapter = createAdapterAndSubscribe(); + await updateConfig('executables', ['execPath1', 'execPath2Copy']); + adapter = createAdapterAndSubscribe(); - await adapter.load(); + await adapter.load(); - assert.equal( - (testsEvents[testsEvents.length - 1]) - .suite!.children.length, - 2); - testsEvents.pop(); - testsEvents.pop(); + assert.equal( + (testsEvents[testsEvents.length - 1]) + .suite!.children.length, + 2); + testsEvents.pop(); + testsEvents.pop(); - assert.ok(watchers.has(execPath2CopyPath)); - const watcher = watchers.get(execPath2CopyPath)!; + assert.ok(watchers.has(execPath2CopyPath)); + const watcher = watchers.get(execPath2CopyPath)!; - let start: number = 0; - const newRoot = await doAndWaitForReloadEvent(this, async () => { - fsStatStub.withArgs(execPath2CopyPath) - .callsFake(handleStatNotExists); - start = Date.now(); - watcher.sendDelete(); - setTimeout(() => { - assert.equal(testsEvents.length, 0); - }, 1500); - setTimeout(() => { + let start: number = 0; + const newRoot = await doAndWaitForReloadEvent(this, async () => { fsStatStub.withArgs(execPath2CopyPath) - .callsFake(handleStatExistsFile); - watcher.sendCreate(); - }, 3000); - }, 40000); - const elapsed = Date.now() - start; - - assert.equal(testsEvents.length, 2); - testsEvents.pop(); - testsEvents.pop(); - for (let scenario of example1.suite2.outputs) { - spawnStub.withArgs(execPath2CopyPath, scenario[0]).callsFake(() => { - throw Error('restore'); - }); - } - fsStatStub.withArgs(execPath2CopyPath).callsFake(() => { - throw Error('restore'); - }); - vsfsWatchStub.withArgs(matchRelativePattern(execPath2CopyPath)) - .callsFake(() => { - throw Error('restore'); - }); - vsFindFilesStub.withArgs(matchRelativePattern(execPath2CopyPath)) - .callsFake(() => { + .callsFake(handleStatNotExists); + start = Date.now(); + watcher.sendDelete(); + setTimeout(() => { + assert.equal(testsEvents.length, 0); + }, 1500); + setTimeout(() => { + fsStatStub.withArgs(execPath2CopyPath) + .callsFake(handleStatExistsFile); + watcher.sendCreate(); + }, 3000); + }, 40000); + const elapsed = Date.now() - start; + + assert.equal(testsEvents.length, 2); + testsEvents.pop(); + testsEvents.pop(); + for (let scenario of example1.suite2.outputs) { + spawnStub.withArgs(execPath2CopyPath, scenario[0]).callsFake(() => { + throw Error('restore'); + }); + } + fsStatStub.withArgs(execPath2CopyPath).callsFake(() => { throw Error('restore'); }); - disposeAdapterAndSubscribers(); - await updateConfig('executables', undefined); - await updateConfig('defaultWatchTimeoutSec', undefined); + vsfsWatchStub.withArgs(matchRelativePattern(execPath2CopyPath)) + .callsFake(() => { + throw Error('restore'); + }); + vsFindFilesStub.withArgs(matchRelativePattern(execPath2CopyPath)) + .callsFake(() => { + throw Error('restore'); + }); + disposeAdapterAndSubscribers(); + await updateConfig('executables', undefined); + await updateConfig('defaultWatchTimeoutSec', undefined); - assert.equal(newRoot.children.length, 2); - assert.ok(3000 < elapsed, inspect(elapsed)); - assert.ok(elapsed < watchTimeout * 1000 + 2400, inspect(elapsed)); - }); + assert.equal(newRoot.children.length, 2); + assert.ok(3000 < elapsed, inspect(elapsed)); + assert.ok(elapsed < watchTimeout * 1000 + 2400, inspect(elapsed)); + }); specify( - 'load executables=["execPath1", "execPath2Copy"]; delete second', - async function () { - const watchTimeout = 5; - await updateConfig('defaultWatchTimeoutSec', watchTimeout); - this.timeout(watchTimeout * 1000 + 2500 /* because of 'delay' */); - this.slow(watchTimeout * 1000 + 2500 /* because of 'delay' */); - const execPath2CopyPath = - path.join(workspaceFolderUri.fsPath, 'execPath2Copy'); - - for (let scenario of example1.suite2.outputs) { - spawnStub.withArgs(execPath2CopyPath, scenario[0]) - .callsFake(function () { - return new ChildProcessStub(scenario[1]); - }); - } + 'load executables=["execPath1", "execPath2Copy"]; delete second', + async function() { + const watchTimeout = 5; + await updateConfig('defaultWatchTimeoutSec', watchTimeout); + this.timeout(watchTimeout * 1000 + 2500 /* because of 'delay' */); + this.slow(watchTimeout * 1000 + 2500 /* because of 'delay' */); + const execPath2CopyPath = + path.join(workspaceFolderUri.fsPath, 'execPath2Copy'); + + for (let scenario of example1.suite2.outputs) { + spawnStub.withArgs(execPath2CopyPath, scenario[0]) + .callsFake(function() { + return new ChildProcessStub(scenario[1]); + }); + } - fsStatStub.withArgs(execPath2CopyPath) - .callsFake(handleStatExistsFile); + fsStatStub.withArgs(execPath2CopyPath) + .callsFake(handleStatExistsFile); - vsfsWatchStub.withArgs(matchRelativePattern(execPath2CopyPath)) - .callsFake(handleCreateWatcherCb); + vsfsWatchStub.withArgs(matchRelativePattern(execPath2CopyPath)) + .callsFake(handleCreateWatcherCb); - vsFindFilesStub.withArgs(matchRelativePattern(execPath2CopyPath)) - .returns([vscode.Uri.file(execPath2CopyPath)]); + vsFindFilesStub.withArgs(matchRelativePattern(execPath2CopyPath)) + .returns([vscode.Uri.file(execPath2CopyPath)]); - await updateConfig('executables', ['execPath1', 'execPath2Copy']); - adapter = createAdapterAndSubscribe(); + await updateConfig('executables', ['execPath1', 'execPath2Copy']); + adapter = createAdapterAndSubscribe(); - await adapter.load(); + await adapter.load(); - assert.equal( - (testsEvents[testsEvents.length - 1]) - .suite!.children.length, - 2); - testsEvents.pop(); - testsEvents.pop(); + assert.equal( + (testsEvents[testsEvents.length - 1]) + .suite!.children.length, + 2); + testsEvents.pop(); + testsEvents.pop(); - assert.ok(watchers.has(execPath2CopyPath)); - const watcher = watchers.get(execPath2CopyPath)!; + assert.ok(watchers.has(execPath2CopyPath)); + const watcher = watchers.get(execPath2CopyPath)!; - let start: number = 0; - const newRoot = await doAndWaitForReloadEvent(this, async () => { - fsStatStub.withArgs(execPath2CopyPath) - .callsFake(handleStatNotExists); - start = Date.now(); - watcher.sendDelete(); - }, 40000); - const elapsed = Date.now() - start; - testsEvents.pop(); - testsEvents.pop(); - for (let scenario of example1.suite2.outputs) { - spawnStub.withArgs(execPath2CopyPath, scenario[0]).callsFake(() => { - throw Error('restore'); - }); - } - fsStatStub.withArgs(execPath2CopyPath).callsFake(() => { - throw Error('restore'); - }); - vsfsWatchStub.withArgs(matchRelativePattern(execPath2CopyPath)) - .callsFake(() => { - throw Error('restore'); - }); - vsFindFilesStub.withArgs(matchRelativePattern(execPath2CopyPath)) - .callsFake(() => { + let start: number = 0; + const newRoot = await doAndWaitForReloadEvent(this, async () => { + fsStatStub.withArgs(execPath2CopyPath) + .callsFake(handleStatNotExists); + start = Date.now(); + watcher.sendDelete(); + }, 40000); + const elapsed = Date.now() - start; + testsEvents.pop(); + testsEvents.pop(); + for (let scenario of example1.suite2.outputs) { + spawnStub.withArgs(execPath2CopyPath, scenario[0]).callsFake(() => { + throw Error('restore'); + }); + } + fsStatStub.withArgs(execPath2CopyPath).callsFake(() => { throw Error('restore'); }); - disposeAdapterAndSubscribers(); - await updateConfig('executables', undefined); - await updateConfig('defaultWatchTimeoutSec', undefined); + vsfsWatchStub.withArgs(matchRelativePattern(execPath2CopyPath)) + .callsFake(() => { + throw Error('restore'); + }); + vsFindFilesStub.withArgs(matchRelativePattern(execPath2CopyPath)) + .callsFake(() => { + throw Error('restore'); + }); + disposeAdapterAndSubscribers(); + await updateConfig('executables', undefined); + await updateConfig('defaultWatchTimeoutSec', undefined); - assert.equal(newRoot.children.length, 1); - assert.ok(watchTimeout * 1000 < elapsed, inspect(elapsed)); - assert.ok(elapsed < watchTimeout * 1000 + 2400, inspect(elapsed)); - }) + assert.equal(newRoot.children.length, 1); + assert.ok(watchTimeout * 1000 < elapsed, inspect(elapsed)); + assert.ok(elapsed < watchTimeout * 1000 + 2400, inspect(elapsed)); + }) - specify('wrong executables format', async function () { + specify('wrong executables format', async function() { this.slow(300); - await updateConfig('executables', { name: '' }); + await updateConfig('executables', {name: ''}); adapter = createAdapterAndSubscribe(); await adapter.load(); const root = - (testsEvents[testsEvents.length - 1]).suite!; + (testsEvents[testsEvents.length - 1]).suite!; assert.equal(root.children.length, 0); testsEvents.pop(); testsEvents.pop(); @@ -1637,12 +1636,12 @@ describe('C2TestAdapter', function () { await updateConfig('executables', undefined); }) - specify('variable substitution with executables={...}', async function () { + specify('variable substitution with executables={...}', async function() { this.slow(300); const wsPath = workspaceFolderUri.fsPath; const execPath2CopyRelPath = path.normalize('foo/bar/base.second.first'); const execPath2CopyPath = - vscode.Uri.file(path.join(wsPath, execPath2CopyRelPath)).fsPath; + vscode.Uri.file(path.join(wsPath, execPath2CopyRelPath)).fsPath; const envArray: [string, string][] = [ ['${absPath}', execPath2CopyPath], @@ -1659,44 +1658,44 @@ describe('C2TestAdapter', function () { ['${workspaceDirectory}', wsPath], ['${workspaceFolder}', wsPath], ]; - const envsStr = envArray.map(v => { return v[0] }).join(' , '); - const expectStr = envArray.map(v => { return v[1] }).join(' , '); + const envsStr = envArray.map(v => {return v[0]}).join(' , '); + const expectStr = envArray.map(v => {return v[1]}).join(' , '); await updateConfig('executables', { name: envsStr, pattern: execPath2CopyRelPath, cwd: envsStr, - env: { C2TESTVARS: envsStr } + env: {C2TESTVARS: envsStr} }); for (let scenario of example1.suite2.outputs) { spawnStub.withArgs(execPath2CopyPath, scenario[0]) - .callsFake(function () { - return new ChildProcessStub(scenario[1]); - }); + .callsFake(function() { + return new ChildProcessStub(scenario[1]); + }); } spawnStub.withArgs(execPath2CopyPath, example1.suite2.t1.outputs[0][0]) - .callsFake(function (p: string, args: string[], ops: any) { - assert.equal(ops.cwd, expectStr); - assert.ok(ops.env.hasOwnProperty('C2TESTVARS')); - assert.equal(ops.env.C2TESTVARS, expectStr); - return new ChildProcessStub(example1.suite2.t1.outputs[0][1]); - }); + .callsFake(function(p: string, args: string[], ops: any) { + assert.equal(ops.cwd, expectStr); + assert.ok(ops.env.hasOwnProperty('C2TESTVARS')); + assert.equal(ops.env.C2TESTVARS, expectStr); + return new ChildProcessStub(example1.suite2.t1.outputs[0][1]); + }); fsStatStub.withArgs(execPath2CopyPath).callsFake(handleStatExistsFile); vsfsWatchStub.withArgs(matchRelativePattern(execPath2CopyPath)) - .callsFake(handleCreateWatcherCb); + .callsFake(handleCreateWatcherCb); vsFindFilesStub.withArgs(matchRelativePattern(execPath2CopyPath)) - .returns([vscode.Uri.file(execPath2CopyPath)]); + .returns([vscode.Uri.file(execPath2CopyPath)]); adapter = createAdapterAndSubscribe(); await adapter.load(); const root = - (testsEvents[testsEvents.length - 1]).suite!; + (testsEvents[testsEvents.length - 1]).suite!; testsEvents.pop(); testsEvents.pop(); @@ -1718,13 +1717,13 @@ describe('C2TestAdapter', function () { throw Error('restore'); }); vsfsWatchStub.withArgs(matchRelativePattern(execPath2CopyPath)) - .callsFake(() => { - throw Error('restore'); - }); + .callsFake(() => { + throw Error('restore'); + }); vsFindFilesStub.withArgs(matchRelativePattern(execPath2CopyPath)) - .callsFake(() => { - throw Error('restore'); - }); + .callsFake(() => { + throw Error('restore'); + }); disposeAdapterAndSubscribers(); await updateConfig('executables', undefined); }) From ed987ebc7a0d85ad81f61cf6ebabfb25158a0220 Mon Sep 17 00:00:00 2001 From: Mate Pek Date: Sun, 28 Oct 2018 10:27:28 +0100 Subject: [PATCH 27/49] src/test/C2TestAdapter.cpp.test.ts OK --- src/C2ExecutableInfo.ts | 13 +++++++------ src/test/C2TestAdapter.cpp.test.ts | 18 +++++++----------- 2 files changed, 14 insertions(+), 17 deletions(-) diff --git a/src/C2ExecutableInfo.ts b/src/C2ExecutableInfo.ts index d06f2d78..e2aa8586 100644 --- a/src/C2ExecutableInfo.ts +++ b/src/C2ExecutableInfo.ts @@ -39,19 +39,20 @@ export class C2ExecutableInfo implements vscode.Disposable { path.resolve(wsUri.fsPath, this.pattern); const absPatternAsUri = vscode.Uri.file(absPattern); // const relativeToWs = path.relative(wsUri.fsPath, absPatternAsUri.fsPath); - // const isPartOfWs = !relativeToWs.startsWith('..'); // TODO + // const isPartOfWs = !relativeToWs.startsWith('..'); + // TODO, TODO patern backlash logging let fileUris: vscode.Uri[] = []; if (!isAbsolute) { const relativePattern = new vscode.RelativePattern( - this._adapter.workspaceFolder, - this.pattern); // TODO pattern backslash + this._adapter.workspaceFolder, this.pattern); - fileUris = - await vscode.workspace.findFiles(relativePattern, undefined, 1000); try { - // abs path is required + fileUris = + await vscode.workspace.findFiles(relativePattern, undefined, 1000); + + // abs path string or vscode.RelativePattern is required. this._watcher = vscode.workspace.createFileSystemWatcher( relativePattern, false, false, false); this._disposables.push(this._watcher); diff --git a/src/test/C2TestAdapter.cpp.test.ts b/src/test/C2TestAdapter.cpp.test.ts index be080ab1..788428c5 100644 --- a/src/test/C2TestAdapter.cpp.test.ts +++ b/src/test/C2TestAdapter.cpp.test.ts @@ -35,9 +35,7 @@ const isWin = process.platform === 'win32'; /// -// TODO skip -describe.skip('C2TestAdapter.cpp', function() { - this.enableTimeouts(false); // TODO +describe('C2TestAdapter.cpp', function() { async function compile(source: vscode.Uri, output: vscode.Uri) { if (isWin) { assert.notStrictEqual( @@ -85,9 +83,6 @@ describe.skip('C2TestAdapter.cpp', function() { after(async function() { await fse.remove(cppUri.fsPath); - await fse.remove(inCpp('../suite1.exe').fsPath); - await fse.remove(inCpp('../suite2.exe').fsPath); - await fse.remove(inCpp('../suite3.exe').fsPath); }) async function waitFor( @@ -190,6 +185,7 @@ describe.skip('C2TestAdapter.cpp', function() { }) it('shoud be found and run withouth error', async function() { + this.slow(1500); await updateConfig( 'executables', [{ 'name': '${baseFilename}', @@ -207,15 +203,15 @@ describe.skip('C2TestAdapter.cpp', function() { const eventCount = testStatesEvents.length; await adapter.run([root.id]); - assert.strictEqual(testStatesEvents.length, eventCount + 24); + assert.strictEqual(testStatesEvents.length, eventCount + 86); disposeAdapterAndSubscribers(); await updateConfig('executables', undefined); }) it('shoud be notified by watcher', async function() { - this.timeout(10000); - this.slow(3500); + this.timeout(5000); + this.slow(4000); await updateConfig( 'executables', [{ 'name': '${baseFilename}', @@ -245,10 +241,10 @@ describe.skip('C2TestAdapter.cpp', function() { return root.children.length == 3; }, 2000); - await fse.remove(inCpp('out/sub/suite2X.exe').fsPath); - await updateConfig('defaultWatchTimeoutSec', 1); + await fse.remove(inCpp('out/sub/suite2X.exe').fsPath); + await waitFor(this, () => { return root.children.length == 2; }, 3100); From 23d29975aef0869accc72cf5586ec6ba1bba233e Mon Sep 17 00:00:00 2001 From: Mate Pek Date: Sun, 28 Oct 2018 17:21:06 +0700 Subject: [PATCH 28/49] all tests are ok on win too --- README.md | 2 +- appveyor.yml | 7 +++---- src/C2ExecutableInfo.ts | 12 ++++++++---- src/test/C2TestAdapter.cpp.test.ts | 10 +++++++--- 4 files changed, 19 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index a525d5ce..931a8cac 100644 --- a/README.md +++ b/README.md @@ -43,7 +43,7 @@ If it is an object it can contains the following properties: | Property | | Description | | --------- | ---------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `name` | (optional) | The name of the test suite (file). Can contains variables. | -| `pattern` | (requierd) | A relative pattern (to workspace) or an absolute file-path. ([Details](https://code.visualstudio.com/docs/editor/codebasics#_advanced-search-options)). Example: `{build,out}/**/test[1-9]*` | +| `pattern` | (requierd) | A relative pattern (to workspace) or an absolute file-path. ⚠️__Avoid backslash!__ ([Details](https://code.visualstudio.com/docs/editor/codebasics#_advanced-search-options)). Example: `{build,out}/**/test[1-9]*` | | `path` | (alias) | Alias of `pattern`. | | `cwd` | (optional) | The current working directory for the test executable. If it isn't provided and `defaultCwd` does, then that will be used. Can contains variables. | | `env` | (optional) | Environment variables for the test executable. If it isn't provided and `defaultEnv` does, then that will be used. Can contains variables. | diff --git a/appveyor.yml b/appveyor.yml index 1abb24ec..b528f1cf 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -9,7 +9,7 @@ environment: matrix: - nodejs_version: LTS - nodejs_version: Current - - C2AVCVA: "C:\\Program Files (x86)\\Microsoft Visual Studio\\2017\\Community\\VC\\Auxiliary\\Build\\vcvarsall.bat" + - C2AVCVA: C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Auxiliary\Build\vcvarsall.bat install: - ps: Install-Product node $env:nodejs_version @@ -17,9 +17,8 @@ install: - npm --version - npm install +build: off + test_script: - npm run compile - npm test --silent - -# Don't actually build. -build: off diff --git a/src/C2ExecutableInfo.ts b/src/C2ExecutableInfo.ts index e2aa8586..396c3109 100644 --- a/src/C2ExecutableInfo.ts +++ b/src/C2ExecutableInfo.ts @@ -38,10 +38,14 @@ export class C2ExecutableInfo implements vscode.Disposable { const absPattern = isAbsolute ? path.normalize(this.pattern) : path.resolve(wsUri.fsPath, this.pattern); const absPatternAsUri = vscode.Uri.file(absPattern); - // const relativeToWs = path.relative(wsUri.fsPath, absPatternAsUri.fsPath); - // const isPartOfWs = !relativeToWs.startsWith('..'); - // TODO, TODO patern backlash logging - + const relativeToWs = path.relative(wsUri.fsPath, absPatternAsUri.fsPath); + const isPartOfWs = !relativeToWs.startsWith('..'); + + if(isAbsolute && isPartOfWs) + this._adapter.log.info('Absolute path is used for workspace directory: ' + inspect([this])); + if(this.pattern.indexOf('\\') != -1) + this._adapter.log.warn('Pattern contains backslash character: ' + this.pattern); + let fileUris: vscode.Uri[] = []; if (!isAbsolute) { diff --git a/src/test/C2TestAdapter.cpp.test.ts b/src/test/C2TestAdapter.cpp.test.ts index 788428c5..3136cbb2 100644 --- a/src/test/C2TestAdapter.cpp.test.ts +++ b/src/test/C2TestAdapter.cpp.test.ts @@ -43,6 +43,7 @@ describe('C2TestAdapter.cpp', function() { const vcvarsall = vscode.Uri.file(process.env['C2AVCVA']!); const command = '"' + vcvarsall.fsPath + '" x86 && ' + [ 'cl.exe', + '/EHsc', '/I"' + path.dirname(source.fsPath) + '"', '/Fe"' + output.fsPath + '"', '"' + source.fsPath + '"', @@ -115,7 +116,9 @@ describe('C2TestAdapter.cpp', function() { testsEvents = []; testsEventsConnection = adapter.tests((e: TestLoadStartedEvent|TestLoadFinishedEvent) => { - if (testsEvents.length % 2 == 1 && e.type == 'started') debugger; + if (testsEvents.length % 2 == 1 && e.type == 'started') { + const i = 0;i; + } testsEvents.push(e); }); @@ -132,7 +135,7 @@ describe('C2TestAdapter.cpp', function() { async function load(adapter: TestAdapter): Promise { const eventCount = testsEvents.length; await adapter.load(); - if (testsEvents.length != eventCount + 2) debugger; + if (testsEvents.length != eventCount + 2) debugger; //TODO bug on win assert.strictEqual( testsEvents.length, eventCount + 2, inspect(testsEvents)); const finished = testsEvents.pop()!; @@ -185,7 +188,8 @@ describe('C2TestAdapter.cpp', function() { }) it('shoud be found and run withouth error', async function() { - this.slow(1500); + this.timeout(5000); + this.slow(2000); await updateConfig( 'executables', [{ 'name': '${baseFilename}', From 2cfca6139ad47ab44459a5a641121cffd65eb648 Mon Sep 17 00:00:00 2001 From: Mate Pek Date: Sun, 28 Oct 2018 11:31:58 +0100 Subject: [PATCH 29/49] another try to fix appveyor vcvarsall problem --- appveyor.yml | 1 - src/test/C2TestAdapter.cpp.test.ts | 19 +++++++++++++------ 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index b528f1cf..4f6ff56c 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -9,7 +9,6 @@ environment: matrix: - nodejs_version: LTS - nodejs_version: Current - - C2AVCVA: C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Auxiliary\Build\vcvarsall.bat install: - ps: Install-Product node $env:nodejs_version diff --git a/src/test/C2TestAdapter.cpp.test.ts b/src/test/C2TestAdapter.cpp.test.ts index 3136cbb2..cb0d8fc3 100644 --- a/src/test/C2TestAdapter.cpp.test.ts +++ b/src/test/C2TestAdapter.cpp.test.ts @@ -38,10 +38,16 @@ const isWin = process.platform === 'win32'; describe('C2TestAdapter.cpp', function() { async function compile(source: vscode.Uri, output: vscode.Uri) { if (isWin) { - assert.notStrictEqual( - process.env['C2AVCVA'], undefined, inspect(process.env)); - const vcvarsall = vscode.Uri.file(process.env['C2AVCVA']!); - const command = '"' + vcvarsall.fsPath + '" x86 && ' + [ + let vcvarsall: vscode.Uri|undefined; + if (process.env['C2AVCVA']) { + vcvarsall = vscode.Uri.file(process.env['C2AVCVA']!); + } else if ( + process.env['APPVEYOR_BUILD_WORKER_IMAGE'] == 'Visual Studio 2017') + vcvarsall = vscode.Uri.file( + 'C:\\Program Files (x86)\\Microsoft Visual Studio\\2017\\Community\\VC\\Auxiliary\\Build\\vcvarsall.bat'); + + assert.notStrictEqual(vcvarsall, undefined, inspect(process.env)); + const command = '"' + vcvarsall!.fsPath + '" x86 && ' + [ 'cl.exe', '/EHsc', '/I"' + path.dirname(source.fsPath) + '"', @@ -117,7 +123,8 @@ describe('C2TestAdapter.cpp', function() { testsEventsConnection = adapter.tests((e: TestLoadStartedEvent|TestLoadFinishedEvent) => { if (testsEvents.length % 2 == 1 && e.type == 'started') { - const i = 0;i; + const i = 0; + i; } testsEvents.push(e); }); @@ -135,7 +142,7 @@ describe('C2TestAdapter.cpp', function() { async function load(adapter: TestAdapter): Promise { const eventCount = testsEvents.length; await adapter.load(); - if (testsEvents.length != eventCount + 2) debugger; //TODO bug on win + if (testsEvents.length != eventCount + 2) debugger; // TODO bug on win assert.strictEqual( testsEvents.length, eventCount + 2, inspect(testsEvents)); const finished = testsEvents.pop()!; From 4192eead53c60a216d7f7b7c9f2e0264408c0859 Mon Sep 17 00:00:00 2001 From: Mate Pek Date: Sun, 28 Oct 2018 11:53:08 +0100 Subject: [PATCH 30/49] vscode prettier extension made this mess -.- --- .travis.yml | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 43d977b5..6f47ec0a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,7 @@ sudo: false + language: node_js + node_js: - "8" - "11" @@ -10,9 +12,9 @@ os: before_install: - if [ $TRAVIS_OS_NAME == "linux" ]; then - export CXX="g++-4.9" CC="gcc-4.9" DISPLAY=:99.0; - sh -e /etc/init.d/xvfb start; - sleep 3; + export CXX="g++-4.9" CC="gcc-4.9" DISPLAY=:99.0; + sh -e /etc/init.d/xvfb start; + sleep 3; fi install: From 35adbf2a0496fc33da2039ba0136edc6ac24da0c Mon Sep 17 00:00:00 2001 From: Mate Pek Date: Sun, 28 Oct 2018 11:57:25 +0100 Subject: [PATCH 31/49] move appveyor.yml -> .appveyor.yml --- appveyor.yml => .appveyor.yml | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename appveyor.yml => .appveyor.yml (100%) diff --git a/appveyor.yml b/.appveyor.yml similarity index 100% rename from appveyor.yml rename to .appveyor.yml From 789b7305e2fba95f09d0168b70d01b25e961dd8e Mon Sep 17 00:00:00 2001 From: Mate Pek Date: Sun, 28 Oct 2018 12:27:13 +0100 Subject: [PATCH 32/49] releasetest begins --- .travis.yml | 36 +++++++++++++++---- package-lock.json | 55 ++++++++++++++++++++---------- src/test/C2TestAdapter.cpp.test.ts | 2 ++ 3 files changed, 69 insertions(+), 24 deletions(-) diff --git a/.travis.yml b/.travis.yml index 6f47ec0a..10c01a94 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,12 +3,12 @@ sudo: false language: node_js node_js: - - "8" - - "11" +- '8' +- '11' os: - - osx - - linux +- osx +- linux before_install: - if [ $TRAVIS_OS_NAME == "linux" ]; then @@ -18,8 +18,32 @@ before_install: fi install: +- npm install +- npm run compile +script: +- npm test --silent + +before_deploy: + - echo Whooho I will deploy on one day! - npm install - npm run compile + - npm install vsce + - node ./node_modules/vsce/out/vsce --version || true + - npx vsce --version || true + - echo node ./node_modules/vsce/out/vsce package -o vscode-catch2-test-adapter.vsix + - echo test > vscode-catch2-test-adapter.vsix + +deploy: + provider: releases + api_key: + secure: O5j1kUme6hcXrVUK7X3fKbMu77zVkAkVLfEV2pprVD7Qy7VX83oJmER4qVZHlq47IICmumspSlCAlSi2C8GjB6SGjqzX5+9InCQDn9haPuGP7G9iniGLVXXMpaqFYZeoj/YxGobOiO2GueajB2dqwGucc/WPIiXqPTHOkr6rhfEv8vGzo9mKEmQFS8OVnVu/nMCfON/14XcYn3gICUH5GqcHmyRrd5uiLJr0090G2XSzNDKXINLUFodTkHS+Ov0bKUtlob7Q6bf9+qxokSv1HThduczVT5gdAvhfe1OvnVmroJ3OdFyenyCOW4Nqb72NDWwcGyy1Sh7WiJEq/AgC8a3aRIe1yYKM/Y1iesupyC5rg1aG5OH7N7P6vU3p9wBMyECwCZ2zYH2kZG5tuv3ct1vDgwoFklv7l63VMN4oBs4uG+nt/CWbk30+U/GOwqni0Rnh5Qe+AeNToR0Ex+l1b6843inxg6XM4L/PF1Mcj4ClmxIXzXRWnhgSNljE5OUAreckNOYcSmNOvlVLE1cAYS0+g7x8FkUF2mz7s+AhNw4xKHfoweZ9yH18HpQtrAIHQ7l+NnadGy5i29EgNv5i3U7EFqvBzsvjjlvwRzDlvOzixYRkzjmav44VTYJCK35kA2VUHEAlmwjRnCzlcF6vg8DSc2oBoDfPeUkKC7w5mR4= + file: vscode-catch2-test-adapter.vsix + skip_cleanup: true + on: + repo: matepek/vscode-catch2-test-adapter + #tags: true //TODO this will be better condition + #branch: master + branch: releasetest -script: - - npm test --silent +after_deploy: + - echo node ./node_modules/vsce/out/vsce publish --packagePath vscode-catch2-test-adapter.vsix --pat TODO diff --git a/package-lock.json b/package-lock.json index b75b121f..2457e648 100644 --- a/package-lock.json +++ b/package-lock.json @@ -413,7 +413,7 @@ }, "css-select": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/css-select/-/css-select-1.2.0.tgz", + "resolved": "http://registry.npmjs.org/css-select/-/css-select-1.2.0.tgz", "integrity": "sha1-KzoRBTnFNV8c2NMUYj6HCxIeyFg=", "dev": true, "requires": { @@ -424,9 +424,9 @@ } }, "css-what": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/css-what/-/css-what-2.1.0.tgz", - "integrity": "sha1-lGfQMsOM+u+58teVASUwYvh/ob0=", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-2.1.2.tgz", + "integrity": "sha512-wan8dMWQ0GUeF7DGEPVjhHemVW/vy6xUYmFzRY8RYqgA0JtXC9rJmbScBjqSu6dg9q0lwPQy6ZAmJVr3PPTvqQ==", "dev": true }, "dashdash": { @@ -492,16 +492,16 @@ "dependencies": { "domelementtype": { "version": "1.1.3", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.1.3.tgz", + "resolved": "http://registry.npmjs.org/domelementtype/-/domelementtype-1.1.3.tgz", "integrity": "sha1-vSh3PiZCiBrsUVRJJCmcXNgiGFs=", "dev": true } } }, "domelementtype": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.0.tgz", - "integrity": "sha1-sXrtguirWeUt2cGbF1bg/BhyBMI=", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.2.1.tgz", + "integrity": "sha512-SQVCLFS2E7G5CRCMdn6K9bIhRj1bS6QBWZfF0TUPh4V/BbqrQ619IdSS3/izn0FZ+9l+uODzaZjb08fjOfablA==", "dev": true }, "domhandler": { @@ -1174,9 +1174,9 @@ "dev": true }, "htmlparser2": { - "version": "3.9.2", - "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.9.2.tgz", - "integrity": "sha1-G9+HrMoPP55T+k/M6w9LTLsAszg=", + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.10.0.tgz", + "integrity": "sha512-J1nEUGv+MkXS0weHNWVKJJ+UrLfePxRWpN3C9bEi9fLxL2+ggW94DQvgYVXsaT30PGwYRIZKNZXuyMhp3Di4bQ==", "dev": true, "requires": { "domelementtype": "^1.3.0", @@ -1184,7 +1184,26 @@ "domutils": "^1.5.1", "entities": "^1.1.1", "inherits": "^2.0.1", - "readable-stream": "^2.0.2" + "readable-stream": "^3.0.6" + }, + "dependencies": { + "domelementtype": { + "version": "1.3.0", + "resolved": "http://registry.npmjs.org/domelementtype/-/domelementtype-1.3.0.tgz", + "integrity": "sha1-sXrtguirWeUt2cGbF1bg/BhyBMI=", + "dev": true + }, + "readable-stream": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.0.6.tgz", + "integrity": "sha512-9E1oLoOWfhSXHGv6QlwXJim7uNzd9EVlWK+21tCU9Ju/kR0/p2AZYPz4qSchgO8PlLIH4FpZYfzwS+rEksZjIg==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + } } }, "http-signature": { @@ -1733,9 +1752,9 @@ } }, "nth-check": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.1.tgz", - "integrity": "sha1-mSms32KPwsQQmN6rgqxYDPFJquQ=", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.2.tgz", + "integrity": "sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg==", "dev": true, "requires": { "boolbase": "~1.0.0" @@ -2534,9 +2553,9 @@ } }, "vsce": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/vsce/-/vsce-1.52.0.tgz", - "integrity": "sha512-k+KYoTx1sacpYf2BHTA7GN82MNSlf2N4EuppFWwtTN/Sh6fWzIJafxxCNBCDK0H+5NDWfRGZheBY8C3/HOE2ZA==", + "version": "1.51.1", + "resolved": "https://registry.npmjs.org/vsce/-/vsce-1.51.1.tgz", + "integrity": "sha512-Hf2HE9O/MRQHxUUgWHAm7mOkz0K5swuF2smaE/sP7+OWp/5DdIPFwmLEYCCZHxG25l3GBRoO0dAL8S5w//et+g==", "dev": true, "requires": { "cheerio": "^1.0.0-rc.1", diff --git a/src/test/C2TestAdapter.cpp.test.ts b/src/test/C2TestAdapter.cpp.test.ts index cb0d8fc3..4559db98 100644 --- a/src/test/C2TestAdapter.cpp.test.ts +++ b/src/test/C2TestAdapter.cpp.test.ts @@ -56,6 +56,7 @@ describe('C2TestAdapter.cpp', function() { ].join(' '); await promisify(cp.exec)(command); } else { + console.log('compiling ' + source.fsPath); await promisify(cp.exec)('"' + [ 'c++', '-x', @@ -65,6 +66,7 @@ describe('C2TestAdapter.cpp', function() { output.fsPath, source.fsPath, ].join('" "') + '"'); + console.log('compiling finished' + output.fsPath); } assert.ok(await c2fs.existsAsync(output.fsPath)); } From 99370de1e03ab93755a18112d8c3ec3d38faa77d Mon Sep 17 00:00:00 2001 From: Mate Pek Date: Sun, 28 Oct 2018 13:50:21 +0100 Subject: [PATCH 33/49] prettier is suck --- .travis.yml | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/.travis.yml b/.travis.yml index 10c01a94..74eadd97 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,25 +3,26 @@ sudo: false language: node_js node_js: -- '8' -- '11' + - "8" + - "11" os: -- osx -- linux + - osx + - linux before_install: - if [ $TRAVIS_OS_NAME == "linux" ]; then - export CXX="g++-4.9" CC="gcc-4.9" DISPLAY=:99.0; - sh -e /etc/init.d/xvfb start; - sleep 3; + export CXX="g++-4.9" CC="gcc-4.9" DISPLAY=:99.0; + sh -e /etc/init.d/xvfb start; + sleep 3; fi install: -- npm install -- npm run compile + - npm install + - npm run compile + script: -- npm test --silent + - npm test --silent before_deploy: - echo Whooho I will deploy on one day! From e112ed097fb27e284131b024699a7751a19ea9a8 Mon Sep 17 00:00:00 2001 From: Mate Pek Date: Sun, 28 Oct 2018 14:10:45 +0100 Subject: [PATCH 34/49] unix like test fix --- src/test/C2TestAdapter.cpp.test.ts | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/test/C2TestAdapter.cpp.test.ts b/src/test/C2TestAdapter.cpp.test.ts index 4559db98..49c9bbbf 100644 --- a/src/test/C2TestAdapter.cpp.test.ts +++ b/src/test/C2TestAdapter.cpp.test.ts @@ -31,8 +31,17 @@ function inCpp(relPath: string) { return vscode.Uri.file(path.join(cppUri.fsPath, relPath)); } + const isWin = process.platform === 'win32'; +async function removeDir(p: vscode.Uri) { + if (isWin) { + await promisify(cp.exec)('rd /s /q "' + p.fsPath + '"'); + } else { + await promisify(cp.exec)('rm -r "' + p.fsPath + '"'); + } +} + /// describe('C2TestAdapter.cpp', function() { @@ -66,7 +75,7 @@ describe('C2TestAdapter.cpp', function() { output.fsPath, source.fsPath, ].join('" "') + '"'); - console.log('compiling finished' + output.fsPath); + console.log('compiled ' + output.fsPath); } assert.ok(await c2fs.existsAsync(output.fsPath)); } @@ -193,7 +202,7 @@ describe('C2TestAdapter.cpp', function() { context('example1', function() { afterEach(async function() { - await fse.remove(cppUri.fsPath); + await removeDir(cppUri); }) it('shoud be found and run withouth error', async function() { @@ -256,7 +265,7 @@ describe('C2TestAdapter.cpp', function() { await updateConfig('defaultWatchTimeoutSec', 1); - await fse.remove(inCpp('out/sub/suite2X.exe').fsPath); + await fse.unlink(inCpp('out/sub/suite2X.exe').fsPath); await waitFor(this, () => { return root.children.length == 2; From 81ec860ea934a242dac248e44293463d3dc11b13 Mon Sep 17 00:00:00 2001 From: Mate Pek Date: Sun, 28 Oct 2018 14:18:28 +0100 Subject: [PATCH 35/49] skip failing tests on unix-like --- src/test/C2TestAdapter.cpp.test.ts | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/src/test/C2TestAdapter.cpp.test.ts b/src/test/C2TestAdapter.cpp.test.ts index 49c9bbbf..cc9ad42b 100644 --- a/src/test/C2TestAdapter.cpp.test.ts +++ b/src/test/C2TestAdapter.cpp.test.ts @@ -31,17 +31,8 @@ function inCpp(relPath: string) { return vscode.Uri.file(path.join(cppUri.fsPath, relPath)); } - const isWin = process.platform === 'win32'; -async function removeDir(p: vscode.Uri) { - if (isWin) { - await promisify(cp.exec)('rd /s /q "' + p.fsPath + '"'); - } else { - await promisify(cp.exec)('rm -r "' + p.fsPath + '"'); - } -} - /// describe('C2TestAdapter.cpp', function() { @@ -202,10 +193,12 @@ describe('C2TestAdapter.cpp', function() { context('example1', function() { afterEach(async function() { - await removeDir(cppUri); + await fse.remove(cppUri.fsPath); }) it('shoud be found and run withouth error', async function() { + if (process.env['TRAVIS'] == 'true') this.skip(); + this.timeout(5000); this.slow(2000); await updateConfig( @@ -232,6 +225,9 @@ describe('C2TestAdapter.cpp', function() { }) it('shoud be notified by watcher', async function() { + if (process.env['TRAVIS'] == 'true') + this.skip(); // TODO something is wrong wit it + this.timeout(5000); this.slow(4000); await updateConfig( From 199cecdbf5a9d418c44649cb9212aeca3d31bf54 Mon Sep 17 00:00:00 2001 From: Mate Pek Date: Sun, 28 Oct 2018 14:44:23 +0100 Subject: [PATCH 36/49] deployment testing --- .travis.yml | 13 ++++++------- src/test/C2TestAdapter.test.ts | 2 +- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/.travis.yml b/.travis.yml index 74eadd97..8c777dce 100644 --- a/.travis.yml +++ b/.travis.yml @@ -25,14 +25,11 @@ script: - npm test --silent before_deploy: - - echo Whooho I will deploy on one day! - npm install - npm run compile - npm install vsce - - node ./node_modules/vsce/out/vsce --version || true - - npx vsce --version || true - - echo node ./node_modules/vsce/out/vsce package -o vscode-catch2-test-adapter.vsix - - echo test > vscode-catch2-test-adapter.vsix + - node ./node_modules/vsce/out/vsce --version + - node ./node_modules/vsce/out/vsce package -o vscode-catch2-test-adapter.vsix deploy: provider: releases @@ -41,9 +38,11 @@ deploy: file: vscode-catch2-test-adapter.vsix skip_cleanup: true on: + os: osx + node_js: "8" repo: matepek/vscode-catch2-test-adapter - #tags: true //TODO this will be better condition - #branch: master + tags: true + #branch: master TODO branch: releasetest after_deploy: diff --git a/src/test/C2TestAdapter.test.ts b/src/test/C2TestAdapter.test.ts index 98863159..3c8fec96 100644 --- a/src/test/C2TestAdapter.test.ts +++ b/src/test/C2TestAdapter.test.ts @@ -1547,7 +1547,7 @@ describe('C2TestAdapter', function() { async function() { const watchTimeout = 5; await updateConfig('defaultWatchTimeoutSec', watchTimeout); - this.timeout(watchTimeout * 1000 + 2500 /* because of 'delay' */); + this.timeout(watchTimeout * 1000 + 5500 /* because of 'delay' */); this.slow(watchTimeout * 1000 + 2500 /* because of 'delay' */); const execPath2CopyPath = path.join(workspaceFolderUri.fsPath, 'execPath2Copy'); From c4531c016d4d87d8c3459b4769b7b6a306f2d4bc Mon Sep 17 00:00:00 2001 From: Mate Pek Date: Sun, 28 Oct 2018 20:17:47 +0100 Subject: [PATCH 37/49] automatic deployment on progress --- .travis.yml | 11 ++--- .vscode/launch.json | 6 +++ resources/deploy.ts | 105 ++++++++++++++++++++++++++++++++++++++++++++ tsconfig.json | 11 ++--- 4 files changed, 118 insertions(+), 15 deletions(-) create mode 100644 resources/deploy.ts diff --git a/.travis.yml b/.travis.yml index 8c777dce..bdfe2335 100644 --- a/.travis.yml +++ b/.travis.yml @@ -26,24 +26,21 @@ script: before_deploy: - npm install - - npm run compile - npm install vsce + - npm run compile - node ./node_modules/vsce/out/vsce --version - - node ./node_modules/vsce/out/vsce package -o vscode-catch2-test-adapter.vsix + - node ./out/resources/deploy.js deploy: provider: releases api_key: secure: O5j1kUme6hcXrVUK7X3fKbMu77zVkAkVLfEV2pprVD7Qy7VX83oJmER4qVZHlq47IICmumspSlCAlSi2C8GjB6SGjqzX5+9InCQDn9haPuGP7G9iniGLVXXMpaqFYZeoj/YxGobOiO2GueajB2dqwGucc/WPIiXqPTHOkr6rhfEv8vGzo9mKEmQFS8OVnVu/nMCfON/14XcYn3gICUH5GqcHmyRrd5uiLJr0090G2XSzNDKXINLUFodTkHS+Ov0bKUtlob7Q6bf9+qxokSv1HThduczVT5gdAvhfe1OvnVmroJ3OdFyenyCOW4Nqb72NDWwcGyy1Sh7WiJEq/AgC8a3aRIe1yYKM/Y1iesupyC5rg1aG5OH7N7P6vU3p9wBMyECwCZ2zYH2kZG5tuv3ct1vDgwoFklv7l63VMN4oBs4uG+nt/CWbk30+U/GOwqni0Rnh5Qe+AeNToR0Ex+l1b6843inxg6XM4L/PF1Mcj4ClmxIXzXRWnhgSNljE5OUAreckNOYcSmNOvlVLE1cAYS0+g7x8FkUF2mz7s+AhNw4xKHfoweZ9yH18HpQtrAIHQ7l+NnadGy5i29EgNv5i3U7EFqvBzsvjjlvwRzDlvOzixYRkzjmav44VTYJCK35kA2VUHEAlmwjRnCzlcF6vg8DSc2oBoDfPeUkKC7w5mR4= - file: vscode-catch2-test-adapter.vsix + file_glob: true + file: vscode-catch2-test-adapter-*.vsix skip_cleanup: true on: os: osx node_js: "8" repo: matepek/vscode-catch2-test-adapter - tags: true #branch: master TODO branch: releasetest - -after_deploy: - - echo node ./node_modules/vsce/out/vsce publish --packagePath vscode-catch2-test-adapter.vsix --pat TODO diff --git a/.vscode/launch.json b/.vscode/launch.json index 27a98be5..62928280 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -1,6 +1,12 @@ { "version": "0.2.0", "configurations": [ + { + "type": "node", + "request": "launch", + "name": "Deploy", + "program": "${workspaceFolder}/out/resources/deploy.js" + }, { "type": "extensionHost", "request": "launch", diff --git a/resources/deploy.ts b/resources/deploy.ts new file mode 100644 index 00000000..bdd1b920 --- /dev/null +++ b/resources/deploy.ts @@ -0,0 +1,105 @@ + +// create vsix +// check changelog with date + +// kell pull rqeuest checkhez changelog check + +import * as assert from 'assert'; +import * as cp from 'child_process'; +import * as fse from 'fs-extra'; +import {inspect, promisify} from 'util'; +import * as vsce from 'vsce'; + +try { + main() +} catch (e) { + console.log(inspect(e)); + process.exit(1); +} + +const repoId = 'matepek-vscode-catch2-test-adapter'; + +async function main() { + const version = await updateChangelog(); + await gitCommitAndTag(version); + await publishPackage(version) +} + +/// + +async function updateChangelog() { + console.log('Parsing CHANGELOG.md'); + const changelogBuffer = await promisify(fse.readFile)('CHANGELOG.md'); + + const changelog = changelogBuffer.toString(); + // example:'## [0.1.0-beta] - 2018-04-12' + const re = new RegExp( + /## \[(([0-9]+)\.([0-9]+)\.([0-9]+)(?:|(?:-([^\]]+))))\](?: - (\S+))?/); + + const match: RegExpMatchArray|null = changelog.match(re); + assert.notStrictEqual(match, null); + if (match === null) + throw Error('Release error: Couldn\'t find version entry.'); + + assert.strictEqual(match.length, 7); + + if (match[6] != undefined) { + throw Error( + 'Release error: Most recent version has release date: ' + match[0] + + '\n For deploy it should be a version without date.'); + } + + const now = new Date(); + const month = now.getUTCMonth() + 1 < 10 ? '0' + now.getUTCMonth() + 1 : + now.getUTCMonth() + 1; + const day = now.getUTCDate() < 10 ? '0' + now.getUTCDate() : now.getUTCDate(); + const date = now.getUTCFullYear() + '-' + month + '-' + day; + + const changelogWithReleaseDate = + changelog.substr(0, match.index! + match[0].length) + ' - ' + date + + changelog.substr(match.index! + match[0].length); + + console.log('Updating CHANGELOG.md'); + // TODO + // await promisify(fse.writeFile)('CHANGELOG.md', changelogWithReleaseDate); + + return { + 'version': match[1], + 'major': match[2], + 'minor': match[3], + 'patch': match[4], + 'label': match[5], + 'date': date, + 'full': match[0].substr(3) + ' - ' + date + }; +} + +async function gitCommitAndTag(version: {[prop: string]: string|undefined}) { + console.log('Creating signed tag and pushing to origin'); + + // TODO + // await promisify(cp.exec)( + // 'git config --local user.name "matepek+vscode-catch2-test-adapter"'); + // await promisify(cp.exec)( + // 'git config --local user.email + // "matepek+vscode-catch2-test-adapter@gmail.com"'); + + await promisify(cp.exec)('git add -- CHANGELOG.md'); + await promisify(cp.exec)( + 'git commit -m "[Updated] Release info in CHANGELOG.md: ' + version.full + + '"'); + + const tagName = 'v' + version.version; + await promisify(cp.exec)('git tag -a ' + tagName); + // await promisify(cp.exec)('git push origin ' + tagName); TODO +} + +async function publishPackage(version: {[prop: string]: string|undefined}) { + console.log('Creating vsce package'); + const packagePath = './out/' + repoId + '-' + version.version + '.vsix'; + await vsce.createVSIX({'cwd': '.', 'packagePath': packagePath}); + + // TODO + process.env['something']; + // await vsce.publishVSIX(packagePath, {'pat': 'TODO'}); +} \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json index ff7a12a6..bc44bbfd 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,9 +1,7 @@ { "compilerOptions": { "target": "es2018", - "lib": [ - "es2018", - ], + "lib": ["es2018"], "module": "commonjs", "outDir": "out", "importHelpers": true, @@ -16,8 +14,5 @@ "removeComments": true, "skipLibCheck": true }, - "include": [ - "src/main.ts", - "src/test/**/*.ts", - ] -} \ No newline at end of file + "include": ["src/main.ts", "src/test/**/*.ts", "resources/*.ts"] +} From 5ece5b65ddc1e268b472381ba196b0c78415f550 Mon Sep 17 00:00:00 2001 From: Mate Pek Date: Sun, 28 Oct 2018 21:25:33 +0100 Subject: [PATCH 38/49] automatic deployment on progress --- resources/deploy.ts | 50 ++++++++++++++++++++++++++++++--------------- 1 file changed, 34 insertions(+), 16 deletions(-) diff --git a/resources/deploy.ts b/resources/deploy.ts index bdd1b920..479145cd 100644 --- a/resources/deploy.ts +++ b/resources/deploy.ts @@ -6,7 +6,7 @@ import * as assert from 'assert'; import * as cp from 'child_process'; -import * as fse from 'fs-extra'; +import * as fs from 'fs'; import {inspect, promisify} from 'util'; import * as vsce from 'vsce'; @@ -29,7 +29,7 @@ async function main() { async function updateChangelog() { console.log('Parsing CHANGELOG.md'); - const changelogBuffer = await promisify(fse.readFile)('CHANGELOG.md'); + const changelogBuffer = await promisify(fs.readFile)('CHANGELOG.md'); const changelog = changelogBuffer.toString(); // example:'## [0.1.0-beta] - 2018-04-12' @@ -61,7 +61,7 @@ async function updateChangelog() { console.log('Updating CHANGELOG.md'); // TODO - // await promisify(fse.writeFile)('CHANGELOG.md', changelogWithReleaseDate); + await promisify(fs.writeFile)('CHANGELOG.md', changelogWithReleaseDate); return { 'version': match[1], @@ -78,20 +78,27 @@ async function gitCommitAndTag(version: {[prop: string]: string|undefined}) { console.log('Creating signed tag and pushing to origin'); // TODO - // await promisify(cp.exec)( - // 'git config --local user.name "matepek+vscode-catch2-test-adapter"'); - // await promisify(cp.exec)( - // 'git config --local user.email - // "matepek+vscode-catch2-test-adapter@gmail.com"'); + await spawn( + 'git', 'config', '--local', 'user.name', + 'matepek/vscode-catch2-test-adapter bot'); + await spawn( + 'git', 'config', '--local', 'user.email', + 'matepek+vscode-catch2-test-adapter@gmail.com'); + // await spawn( + // 'git', 'config', '--global', 'user.signingkey', '107C10A2C50AA905'); + + await spawn('git', 'status'); + await spawn('git', 'add', '--', 'CHANGELOG.md'); + await spawn( + 'git', 'commit', '-m', + '[Updated] Release info in CHANGELOG.md: ' + version.full, '--amend'); - await promisify(cp.exec)('git add -- CHANGELOG.md'); - await promisify(cp.exec)( - 'git commit -m "[Updated] Release info in CHANGELOG.md: ' + version.full + - '"'); const tagName = 'v' + version.version; - await promisify(cp.exec)('git tag -a ' + tagName); - // await promisify(cp.exec)('git push origin ' + tagName); TODO + await spawn( + 'git', 'tag', '-a', tagName, '-u', '107C10A2C50AA905', '-m', + 'Version v' + version.version); + await spawn('git push origin ' + tagName); // TODO } async function publishPackage(version: {[prop: string]: string|undefined}) { @@ -100,6 +107,17 @@ async function publishPackage(version: {[prop: string]: string|undefined}) { await vsce.createVSIX({'cwd': '.', 'packagePath': packagePath}); // TODO - process.env['something']; + // console.log('Publishing vsce package'); + // process.env['something']; // await vsce.publishVSIX(packagePath, {'pat': 'TODO'}); -} \ No newline at end of file +} + +async function spawn(command: string, ...args: string[]) { + console.log('$ ' + command + ' "' + args.join('" "') + '"'); + return new Promise((resolve, reject) => { + const c = cp.spawn(command, args, {stdio: 'inherit'}); + c.on('exit', (code: number) => { + code == 0 ? resolve() : reject(); + }); + }); +} From 861457e66f3295d2ba5b6efcd8ece2767e3ef163 Mon Sep 17 00:00:00 2001 From: Mate Pek Date: Mon, 29 Oct 2018 00:23:14 +0100 Subject: [PATCH 39/49] test fixes --- .travis.yml | 4 +-- .vscode/launch.json | 2 +- package.json | 2 +- {resources => src/repo_util}/deploy.ts | 35 ++++++++++++++++++++++---- tsconfig.json | 2 +- 5 files changed, 35 insertions(+), 10 deletions(-) rename {resources => src/repo_util}/deploy.ts (73%) diff --git a/.travis.yml b/.travis.yml index bdfe2335..8a0aa9fc 100644 --- a/.travis.yml +++ b/.travis.yml @@ -29,7 +29,7 @@ before_deploy: - npm install vsce - npm run compile - node ./node_modules/vsce/out/vsce --version - - node ./out/resources/deploy.js + - node ./out/repo_util/deploy.js deploy: provider: releases @@ -39,7 +39,7 @@ deploy: file: vscode-catch2-test-adapter-*.vsix skip_cleanup: true on: - os: osx + condition: "$TRAVIS_OS_NAME = osx" node_js: "8" repo: matepek/vscode-catch2-test-adapter #branch: master TODO diff --git a/.vscode/launch.json b/.vscode/launch.json index 62928280..c05f47c2 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -5,7 +5,7 @@ "type": "node", "request": "launch", "name": "Deploy", - "program": "${workspaceFolder}/out/resources/deploy.js" + "program": "${workspaceFolder}/out/repo_util/deploy.js" }, { "type": "extensionHost", diff --git a/package.json b/package.json index 65659baf..45657bf5 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "icon": "resources/icon.png", "author": "Mate Pek", "publisher": "matepek", - "version": "2.0.0", + "version": "1.2.3", "license": "Unlicense", "homepage": "https://github.com/matepek/vscode-catch2-test-adapter", "repository": { diff --git a/resources/deploy.ts b/src/repo_util/deploy.ts similarity index 73% rename from resources/deploy.ts rename to src/repo_util/deploy.ts index 479145cd..3b62ef57 100644 --- a/resources/deploy.ts +++ b/src/repo_util/deploy.ts @@ -21,6 +21,7 @@ const repoId = 'matepek-vscode-catch2-test-adapter'; async function main() { const version = await updateChangelog(); + await updatePackageJson(version); await gitCommitAndTag(version); await publishPackage(version) } @@ -74,6 +75,32 @@ async function updateChangelog() { }; } +async function updatePackageJson(version: {[prop: string]: string|undefined}) { + console.log('Parsing package.json'); + const packageJsonBuffer = await promisify(fs.readFile)('package.json'); + + const packageJson = packageJsonBuffer.toString(); + // example:'## [0.1.0-beta] - 2018-04-12' + const re = new RegExp(/(['"]version['"]\s*:\s*['"])([^'"]*)(['"])/); + + const match: RegExpMatchArray|null = packageJson.match(re); + assert.notStrictEqual(match, null); + if (match === null) + throw Error('Release error: Couldn\'t find version entry.'); + + assert.strictEqual(match.length, 4); + assert.notStrictEqual(match[1], undefined); + assert.notStrictEqual(match[2], undefined); + assert.notStrictEqual(match[3], undefined); + + const packageJsonWithVer = + packageJson.substr(0, match.index! + match[1].length) + version.version + + packageJson.substr(match.index! + match[1].length + match[2].length); + + console.log('Updating package.json'); + await promisify(fs.writeFile)('package.json', packageJsonWithVer); +} + async function gitCommitAndTag(version: {[prop: string]: string|undefined}) { console.log('Creating signed tag and pushing to origin'); @@ -95,10 +122,8 @@ async function gitCommitAndTag(version: {[prop: string]: string|undefined}) { const tagName = 'v' + version.version; - await spawn( - 'git', 'tag', '-a', tagName, '-u', '107C10A2C50AA905', '-m', - 'Version v' + version.version); - await spawn('git push origin ' + tagName); // TODO + await spawn('git', 'tag', '-a', tagName, '-m', 'Version v' + version.version); + await spawn('git', 'push', 'origin', '--tags'); // TODO } async function publishPackage(version: {[prop: string]: string|undefined}) { @@ -117,7 +142,7 @@ async function spawn(command: string, ...args: string[]) { return new Promise((resolve, reject) => { const c = cp.spawn(command, args, {stdio: 'inherit'}); c.on('exit', (code: number) => { - code == 0 ? resolve() : reject(); + code == 0 ? resolve() : reject(new Error('Process exited with: ' + code)); }); }); } diff --git a/tsconfig.json b/tsconfig.json index bc44bbfd..e6841a45 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -14,5 +14,5 @@ "removeComments": true, "skipLibCheck": true }, - "include": ["src/main.ts", "src/test/**/*.ts", "resources/*.ts"] + "include": ["src/main.ts", "src/repo_util/deploy.ts", "src/test/**/*.ts"] } From 4bd6477f61c4689474064cc751f8b09906c428b9 Mon Sep 17 00:00:00 2001 From: Mate Pek Date: Mon, 29 Oct 2018 18:13:43 +0100 Subject: [PATCH 40/49] repo_util -> repo_scripts --- .travis.yml | 30 ++++++++++++++--------- .vscode/launch.json | 2 +- package-lock.json | 2 +- src/{repo_util => repo_scripts}/deploy.ts | 29 +++++++++++++++------- tsconfig.json | 3 ++- 5 files changed, 43 insertions(+), 23 deletions(-) rename src/{repo_util => repo_scripts}/deploy.ts (80%) diff --git a/.travis.yml b/.travis.yml index 8a0aa9fc..ebac583a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,6 +2,9 @@ sudo: false language: node_js +env: + - secure: O5j1kUme6hcXrVUK7X3fKbMu77zVkAkVLfEV2pprVD7Qy7VX83oJmER4qVZHlq47IICmumspSlCAlSi2C8GjB6SGjqzX5+9InCQDn9haPuGP7G9iniGLVXXMpaqFYZeoj/YxGobOiO2GueajB2dqwGucc/WPIiXqPTHOkr6rhfEv8vGzo9mKEmQFS8OVnVu/nMCfON/14XcYn3gICUH5GqcHmyRrd5uiLJr0090G2XSzNDKXINLUFodTkHS+Ov0bKUtlob7Q6bf9+qxokSv1HThduczVT5gdAvhfe1OvnVmroJ3OdFyenyCOW4Nqb72NDWwcGyy1Sh7WiJEq/AgC8a3aRIe1yYKM/Y1iesupyC5rg1aG5OH7N7P6vU3p9wBMyECwCZ2zYH2kZG5tuv3ct1vDgwoFklv7l63VMN4oBs4uG+nt/CWbk30+U/GOwqni0Rnh5Qe+AeNToR0Ex+l1b6843inxg6XM4L/PF1Mcj4ClmxIXzXRWnhgSNljE5OUAreckNOYcSmNOvlVLE1cAYS0+g7x8FkUF2mz7s+AhNw4xKHfoweZ9yH18HpQtrAIHQ7l+NnadGy5i29EgNv5i3U7EFqvBzsvjjlvwRzDlvOzixYRkzjmav44VTYJCK35kA2VUHEAlmwjRnCzlcF6vg8DSc2oBoDfPeUkKC7w5mR4= + node_js: - "8" - "11" @@ -12,9 +15,9 @@ os: before_install: - if [ $TRAVIS_OS_NAME == "linux" ]; then - export CXX="g++-4.9" CC="gcc-4.9" DISPLAY=:99.0; - sh -e /etc/init.d/xvfb start; - sleep 3; + export CXX="g++-4.9" CC="gcc-4.9" DISPLAY=:99.0; + sh -e /etc/init.d/xvfb start; + sleep 3; fi install: @@ -24,13 +27,17 @@ install: script: - npm test --silent -before_deploy: - - npm install - - npm install vsce - - npm run compile - - node ./node_modules/vsce/out/vsce --version - - node ./out/repo_util/deploy.js - +# https://stackoverflow.com/questions/51925941/travis-ci-how-to-push-to-master-branch +# https://stackoverflow.com/questions/23277391/how-to-publish-to-github-pages-from-travis-ci + +after_success: + - if [ "$TRAVIS_BRANCH" = "releasetest" ] && [ "$TRAVIS_OS_NAME" = "osx" ]; then + npm install vsce + npm run compile; + node ./node_modules/vsce/out/vsce --version; + node ./out/repo_scripts/deploy.js; + fi + deploy: provider: releases api_key: @@ -39,8 +46,9 @@ deploy: file: vscode-catch2-test-adapter-*.vsix skip_cleanup: true on: - condition: "$TRAVIS_OS_NAME = osx" + condition: "$TRAVIS_OS_NAME = osxNEVER" #TODO osx node_js: "8" repo: matepek/vscode-catch2-test-adapter #branch: master TODO branch: releasetest + tags: true diff --git a/.vscode/launch.json b/.vscode/launch.json index c05f47c2..5a11e7eb 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -5,7 +5,7 @@ "type": "node", "request": "launch", "name": "Deploy", - "program": "${workspaceFolder}/out/repo_util/deploy.js" + "program": "${workspaceFolder}/out/deploy.js" }, { "type": "extensionHost", diff --git a/package-lock.json b/package-lock.json index 2457e648..dade2c30 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "vscode-catch2-test-adapter", - "version": "2.0.0", + "version": "1.2.3", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/src/repo_util/deploy.ts b/src/repo_scripts/deploy.ts similarity index 80% rename from src/repo_util/deploy.ts rename to src/repo_scripts/deploy.ts index 3b62ef57..33e2e42c 100644 --- a/src/repo_util/deploy.ts +++ b/src/repo_scripts/deploy.ts @@ -1,8 +1,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. -// create vsix -// check changelog with date - -// kell pull rqeuest checkhez changelog check +// https://stackoverflow.com/questions/51925941/travis-ci-how-to-push-to-master-branch +// https://stackoverflow.com/questions/23277391/how-to-publish-to-github-pages-from-travis-ci import * as assert from 'assert'; import * as cp from 'child_process'; @@ -111,19 +112,29 @@ async function gitCommitAndTag(version: {[prop: string]: string|undefined}) { await spawn( 'git', 'config', '--local', 'user.email', 'matepek+vscode-catch2-test-adapter@gmail.com'); + // TODO signing // await spawn( // 'git', 'config', '--global', 'user.signingkey', '107C10A2C50AA905'); await spawn('git', 'status'); - await spawn('git', 'add', '--', 'CHANGELOG.md'); + assert.ok(process.env['TRAVIS_BRANCH'] != undefined); + const branch = process.env['TRAVIS_BRANCH']!; + await spawn('git', 'checkout', branch); + await spawn( + 'git', 'add', '--', 'CHANGELOG.md', 'package.json', 'package-lock.json'); + await spawn('git', 'status'); await spawn( 'git', 'commit', '-m', - '[Updated] Release info in CHANGELOG.md: ' + version.full, '--amend'); - + '[Updated] Release info in CHANGELOG.md: ' + version.full); const tagName = 'v' + version.version; await spawn('git', 'tag', '-a', tagName, '-m', 'Version v' + version.version); - await spawn('git', 'push', 'origin', '--tags'); // TODO + assert.ok(process.env['GITHUB_API_KEY'] != undefined); + await spawn( + 'git', 'push', '--follow-tags', + 'https://matepek:' + process.env['GITHUB_API_KEY']! + + '@github.com/matepek/vscode-catch2-test-adapter.git', + branch + ':' + branch); } async function publishPackage(version: {[prop: string]: string|undefined}) { @@ -139,7 +150,7 @@ async function publishPackage(version: {[prop: string]: string|undefined}) { async function spawn(command: string, ...args: string[]) { console.log('$ ' + command + ' "' + args.join('" "') + '"'); - return new Promise((resolve, reject) => { + await new Promise((resolve, reject) => { const c = cp.spawn(command, args, {stdio: 'inherit'}); c.on('exit', (code: number) => { code == 0 ? resolve() : reject(new Error('Process exited with: ' + code)); diff --git a/tsconfig.json b/tsconfig.json index e6841a45..75128a45 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -4,6 +4,7 @@ "lib": ["es2018"], "module": "commonjs", "outDir": "out", + "rootDir": "src", "importHelpers": true, "sourceMap": true, "strict": true, @@ -14,5 +15,5 @@ "removeComments": true, "skipLibCheck": true }, - "include": ["src/main.ts", "src/repo_util/deploy.ts", "src/test/**/*.ts"] + "include": ["src/main.ts", "src/repo_scripts/*.ts", "src/test/**/*.ts"] } From 6a1e30942b81c8f8cd960d5c4676249344eb8623 Mon Sep 17 00:00:00 2001 From: Mate Pek Date: Mon, 29 Oct 2018 20:46:31 +0100 Subject: [PATCH 41/49] using GITHUB_API_KEY --- .travis.yml | 13 ++++++------- src/repo_scripts/deploy.ts | 6 ++++-- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/.travis.yml b/.travis.yml index ebac583a..e33ad840 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,7 +3,9 @@ sudo: false language: node_js env: - - secure: O5j1kUme6hcXrVUK7X3fKbMu77zVkAkVLfEV2pprVD7Qy7VX83oJmER4qVZHlq47IICmumspSlCAlSi2C8GjB6SGjqzX5+9InCQDn9haPuGP7G9iniGLVXXMpaqFYZeoj/YxGobOiO2GueajB2dqwGucc/WPIiXqPTHOkr6rhfEv8vGzo9mKEmQFS8OVnVu/nMCfON/14XcYn3gICUH5GqcHmyRrd5uiLJr0090G2XSzNDKXINLUFodTkHS+Ov0bKUtlob7Q6bf9+qxokSv1HThduczVT5gdAvhfe1OvnVmroJ3OdFyenyCOW4Nqb72NDWwcGyy1Sh7WiJEq/AgC8a3aRIe1yYKM/Y1iesupyC5rg1aG5OH7N7P6vU3p9wBMyECwCZ2zYH2kZG5tuv3ct1vDgwoFklv7l63VMN4oBs4uG+nt/CWbk30+U/GOwqni0Rnh5Qe+AeNToR0Ex+l1b6843inxg6XM4L/PF1Mcj4ClmxIXzXRWnhgSNljE5OUAreckNOYcSmNOvlVLE1cAYS0+g7x8FkUF2mz7s+AhNw4xKHfoweZ9yH18HpQtrAIHQ7l+NnadGy5i29EgNv5i3U7EFqvBzsvjjlvwRzDlvOzixYRkzjmav44VTYJCK35kA2VUHEAlmwjRnCzlcF6vg8DSc2oBoDfPeUkKC7w5mR4= + global: + - GITHUB_API_KEY: + secure: O5j1kUme6hcXrVUK7X3fKbMu77zVkAkVLfEV2pprVD7Qy7VX83oJmER4qVZHlq47IICmumspSlCAlSi2C8GjB6SGjqzX5+9InCQDn9haPuGP7G9iniGLVXXMpaqFYZeoj/YxGobOiO2GueajB2dqwGucc/WPIiXqPTHOkr6rhfEv8vGzo9mKEmQFS8OVnVu/nMCfON/14XcYn3gICUH5GqcHmyRrd5uiLJr0090G2XSzNDKXINLUFodTkHS+Ov0bKUtlob7Q6bf9+qxokSv1HThduczVT5gdAvhfe1OvnVmroJ3OdFyenyCOW4Nqb72NDWwcGyy1Sh7WiJEq/AgC8a3aRIe1yYKM/Y1iesupyC5rg1aG5OH7N7P6vU3p9wBMyECwCZ2zYH2kZG5tuv3ct1vDgwoFklv7l63VMN4oBs4uG+nt/CWbk30+U/GOwqni0Rnh5Qe+AeNToR0Ex+l1b6843inxg6XM4L/PF1Mcj4ClmxIXzXRWnhgSNljE5OUAreckNOYcSmNOvlVLE1cAYS0+g7x8FkUF2mz7s+AhNw4xKHfoweZ9yH18HpQtrAIHQ7l+NnadGy5i29EgNv5i3U7EFqvBzsvjjlvwRzDlvOzixYRkzjmav44VTYJCK35kA2VUHEAlmwjRnCzlcF6vg8DSc2oBoDfPeUkKC7w5mR4= node_js: - "8" @@ -24,18 +26,15 @@ install: - npm install - npm run compile -script: - - npm test --silent - # https://stackoverflow.com/questions/51925941/travis-ci-how-to-push-to-master-branch # https://stackoverflow.com/questions/23277391/how-to-publish-to-github-pages-from-travis-ci -after_success: - - if [ "$TRAVIS_BRANCH" = "releasetest" ] && [ "$TRAVIS_OS_NAME" = "osx" ]; then +script: # TODO releasetest->master + - npm test --silent && if [ "$TRAVIS_BRANCH" = "releasetest" ] && [ "$TRAVIS_OS_NAME" = "osx" ]; then npm install vsce npm run compile; node ./node_modules/vsce/out/vsce --version; - node ./out/repo_scripts/deploy.js; + node ./out/repo_scripts/deploy.js "${GITHUB_API_KEY}"; fi deploy: diff --git a/src/repo_scripts/deploy.ts b/src/repo_scripts/deploy.ts index 33e2e42c..a260bee2 100644 --- a/src/repo_scripts/deploy.ts +++ b/src/repo_scripts/deploy.ts @@ -12,7 +12,7 @@ import {inspect, promisify} from 'util'; import * as vsce from 'vsce'; try { - main() + main(process.argv) } catch (e) { console.log(inspect(e)); process.exit(1); @@ -20,7 +20,9 @@ try { const repoId = 'matepek-vscode-catch2-test-adapter'; -async function main() { +async function main(argv: string[]) { + assert.strictEqual(argv.length, 1); + assert.ok(argv[0].length > 0); const version = await updateChangelog(); await updatePackageJson(version); await gitCommitAndTag(version); From 2bb64639b0eadc5e4cb59573c0dc0ab82b05c150 Mon Sep 17 00:00:00 2001 From: Mate Pek Date: Mon, 29 Oct 2018 21:24:06 +0100 Subject: [PATCH 42/49] using GITHUB_API_KEY as parameter --- src/repo_scripts/deploy.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/repo_scripts/deploy.ts b/src/repo_scripts/deploy.ts index a260bee2..7483f2d0 100644 --- a/src/repo_scripts/deploy.ts +++ b/src/repo_scripts/deploy.ts @@ -12,7 +12,7 @@ import {inspect, promisify} from 'util'; import * as vsce from 'vsce'; try { - main(process.argv) + main(process.argv.slice(2)) } catch (e) { console.log(inspect(e)); process.exit(1); From 18a2f8d71658b6b0dbc915c4ffda717587f5e6ae Mon Sep 17 00:00:00 2001 From: Mate Pek Date: Mon, 29 Oct 2018 21:40:33 +0100 Subject: [PATCH 43/49] new access token --- .travis.yml | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index e33ad840..6bc13cfb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,11 +1,10 @@ sudo: false -language: node_js - env: global: - - GITHUB_API_KEY: - secure: O5j1kUme6hcXrVUK7X3fKbMu77zVkAkVLfEV2pprVD7Qy7VX83oJmER4qVZHlq47IICmumspSlCAlSi2C8GjB6SGjqzX5+9InCQDn9haPuGP7G9iniGLVXXMpaqFYZeoj/YxGobOiO2GueajB2dqwGucc/WPIiXqPTHOkr6rhfEv8vGzo9mKEmQFS8OVnVu/nMCfON/14XcYn3gICUH5GqcHmyRrd5uiLJr0090G2XSzNDKXINLUFodTkHS+Ov0bKUtlob7Q6bf9+qxokSv1HThduczVT5gdAvhfe1OvnVmroJ3OdFyenyCOW4Nqb72NDWwcGyy1Sh7WiJEq/AgC8a3aRIe1yYKM/Y1iesupyC5rg1aG5OH7N7P6vU3p9wBMyECwCZ2zYH2kZG5tuv3ct1vDgwoFklv7l63VMN4oBs4uG+nt/CWbk30+U/GOwqni0Rnh5Qe+AeNToR0Ex+l1b6843inxg6XM4L/PF1Mcj4ClmxIXzXRWnhgSNljE5OUAreckNOYcSmNOvlVLE1cAYS0+g7x8FkUF2mz7s+AhNw4xKHfoweZ9yH18HpQtrAIHQ7l+NnadGy5i29EgNv5i3U7EFqvBzsvjjlvwRzDlvOzixYRkzjmav44VTYJCK35kA2VUHEAlmwjRnCzlcF6vg8DSc2oBoDfPeUkKC7w5mR4= + secure: GnwXBT/tIhfb7dCu2fYOjWxGJ9t9T16Oc36w+vgAPKJ43boDO3xWWUuqb6CL9yQpzH1ZO8M5+twwCt12B0RTeAqIfqKzfFq5V3v9/4DhXi8huUNB3Acv79GbFNSReXyfz+KXth7fA4zO35Rru+/reSXnJZDCrb7bMMN30z5Whe8CnqlINbHocI2UEcb+cjx6J8EGNzzR9OBZbpNdy2kGmq/5buzomZorneiNrI6ap6fZg97ljuwh4VATOqBBhkGWLYOlBVBVWs1Nc7JwwYrEloMp1mB3LxGtn1Ut1zjDPDX4SLOYujAcsuzWcOp0DAwtPi1oc4atnPmCNfPFCtYzCt4hXwSswQkYkJ0jD9jjZomkOSxbq47zTULisj1chGrZiMwJ295cZ7vjnh8FvN5e7KNd4AczUIMIxf0mYGAQYqCajMTTp3S4AzdVd6mXj8x0CJ1p0y1cFHCsK2BLuGbAANfR7oQNfTsChBMhi2uKxgVt0V+ndZ4y+JMTxBkvPoUrdm5ghkTvYdk4mEKDnvxAPmqsstJRaCBBnLNZtkLfrvQW4WB3Z9N0DnnV1z1j6DnceM4GkYOZekOjM8jm5ea/bpLlYm43vW8M5073nM3IfgS+DkZeysSnn0QTvUx96PPSlWCXypo1GVsbKyyKoGkFGhT7Y6FOKJqeMR3M2WHa928= + +language: node_js node_js: - "8" From aa354d17d0a09c8e2ba4dd186c997ac272d09c99 Mon Sep 17 00:00:00 2001 From: Mate Pek Date: Mon, 29 Oct 2018 22:48:09 +0100 Subject: [PATCH 44/49] one step from publish --- .travis.yml | 18 ++++++++++++++---- src/repo_scripts/deploy.ts | 14 ++++++-------- 2 files changed, 20 insertions(+), 12 deletions(-) diff --git a/.travis.yml b/.travis.yml index 6bc13cfb..98374f10 100644 --- a/.travis.yml +++ b/.travis.yml @@ -29,13 +29,23 @@ install: # https://stackoverflow.com/questions/23277391/how-to-publish-to-github-pages-from-travis-ci script: # TODO releasetest->master - - npm test --silent && if [ "$TRAVIS_BRANCH" = "releasetest" ] && [ "$TRAVIS_OS_NAME" = "osx" ]; then - npm install vsce + - npm test --silent + +after_success: + - if [ "$TRAVIS_TAG" != "$TRAVIS_BRANCH" ] && [ "$TRAVIS_BRANCH" = "releasetest" ] && [ "$TRAVIS_OS_NAME" = "osx" ]; then + npm install --no-save vsce; npm run compile; node ./node_modules/vsce/out/vsce --version; - node ./out/repo_scripts/deploy.js "${GITHUB_API_KEY}"; + # update CHANGELOG.md, package.json; git commit; git tag; vsce package; vsce publish + node ./out/repo_scripts/deploy.js; fi +before_deploy: + - npm install --no-save vsce; + - npm run compile; + - node ./node_modules/vsce/out/vsce --version; + - node ./node_modules/vsce/out/vsce package # for adding to github releases + deploy: provider: releases api_key: @@ -44,7 +54,7 @@ deploy: file: vscode-catch2-test-adapter-*.vsix skip_cleanup: true on: - condition: "$TRAVIS_OS_NAME = osxNEVER" #TODO osx + condition: "$TRAVIS_OS_NAME = osx" #TODO osx node_js: "8" repo: matepek/vscode-catch2-test-adapter #branch: master TODO diff --git a/src/repo_scripts/deploy.ts b/src/repo_scripts/deploy.ts index 7483f2d0..29311fcb 100644 --- a/src/repo_scripts/deploy.ts +++ b/src/repo_scripts/deploy.ts @@ -21,8 +21,6 @@ try { const repoId = 'matepek-vscode-catch2-test-adapter'; async function main(argv: string[]) { - assert.strictEqual(argv.length, 1); - assert.ok(argv[0].length > 0); const version = await updateChangelog(); await updatePackageJson(version); await gitCommitAndTag(version); @@ -122,12 +120,12 @@ async function gitCommitAndTag(version: {[prop: string]: string|undefined}) { assert.ok(process.env['TRAVIS_BRANCH'] != undefined); const branch = process.env['TRAVIS_BRANCH']!; await spawn('git', 'checkout', branch); - await spawn( - 'git', 'add', '--', 'CHANGELOG.md', 'package.json', 'package-lock.json'); + await spawn('git', 'add', '--', 'CHANGELOG.md', 'package.json'); await spawn('git', 'status'); await spawn( 'git', 'commit', '-m', - '[Updated] Release info in CHANGELOG.md: ' + version.full); + '[Updated] Release info in CHANGELOG.md: ' + version.full + + ' [skip travis-ci]'); const tagName = 'v' + version.version; await spawn('git', 'tag', '-a', tagName, '-m', 'Version v' + version.version); @@ -144,10 +142,10 @@ async function publishPackage(version: {[prop: string]: string|undefined}) { const packagePath = './out/' + repoId + '-' + version.version + '.vsix'; await vsce.createVSIX({'cwd': '.', 'packagePath': packagePath}); + console.log('Publishing vsce package'); + // assert.ok(process.env['VSCE_PAT'] != undefined); // TODO - // console.log('Publishing vsce package'); - // process.env['something']; - // await vsce.publishVSIX(packagePath, {'pat': 'TODO'}); + // await vsce.publishVSIX(packagePath, {'pat': process.env['VSCE_PAT']!}); } async function spawn(command: string, ...args: string[]) { From c3504feb23691a07836e4c54dd7b14ccfe585848 Mon Sep 17 00:00:00 2001 From: Mate Pek Date: Tue, 30 Oct 2018 00:34:42 +0100 Subject: [PATCH 45/49] trying to pulish with vsce on master --- .travis.yml | 27 ++++++++++----------------- .vscodeignore | 5 +++++ package.json | 1 + resources/{ => src}/logo.xcf | Bin src/repo_scripts/deploy.ts | 16 ---------------- 5 files changed, 16 insertions(+), 33 deletions(-) rename resources/{ => src}/logo.xcf (100%) diff --git a/.travis.yml b/.travis.yml index 98374f10..dbf45ab4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,8 +2,9 @@ sudo: false env: global: - secure: GnwXBT/tIhfb7dCu2fYOjWxGJ9t9T16Oc36w+vgAPKJ43boDO3xWWUuqb6CL9yQpzH1ZO8M5+twwCt12B0RTeAqIfqKzfFq5V3v9/4DhXi8huUNB3Acv79GbFNSReXyfz+KXth7fA4zO35Rru+/reSXnJZDCrb7bMMN30z5Whe8CnqlINbHocI2UEcb+cjx6J8EGNzzR9OBZbpNdy2kGmq/5buzomZorneiNrI6ap6fZg97ljuwh4VATOqBBhkGWLYOlBVBVWs1Nc7JwwYrEloMp1mB3LxGtn1Ut1zjDPDX4SLOYujAcsuzWcOp0DAwtPi1oc4atnPmCNfPFCtYzCt4hXwSswQkYkJ0jD9jjZomkOSxbq47zTULisj1chGrZiMwJ295cZ7vjnh8FvN5e7KNd4AczUIMIxf0mYGAQYqCajMTTp3S4AzdVd6mXj8x0CJ1p0y1cFHCsK2BLuGbAANfR7oQNfTsChBMhi2uKxgVt0V+ndZ4y+JMTxBkvPoUrdm5ghkTvYdk4mEKDnvxAPmqsstJRaCBBnLNZtkLfrvQW4WB3Z9N0DnnV1z1j6DnceM4GkYOZekOjM8jm5ea/bpLlYm43vW8M5073nM3IfgS+DkZeysSnn0QTvUx96PPSlWCXypo1GVsbKyyKoGkFGhT7Y6FOKJqeMR3M2WHa928= - + - secure: GnwXBT/tIhfb7dCu2fYOjWxGJ9t9T16Oc36w+vgAPKJ43boDO3xWWUuqb6CL9yQpzH1ZO8M5+twwCt12B0RTeAqIfqKzfFq5V3v9/4DhXi8huUNB3Acv79GbFNSReXyfz+KXth7fA4zO35Rru+/reSXnJZDCrb7bMMN30z5Whe8CnqlINbHocI2UEcb+cjx6J8EGNzzR9OBZbpNdy2kGmq/5buzomZorneiNrI6ap6fZg97ljuwh4VATOqBBhkGWLYOlBVBVWs1Nc7JwwYrEloMp1mB3LxGtn1Ut1zjDPDX4SLOYujAcsuzWcOp0DAwtPi1oc4atnPmCNfPFCtYzCt4hXwSswQkYkJ0jD9jjZomkOSxbq47zTULisj1chGrZiMwJ295cZ7vjnh8FvN5e7KNd4AczUIMIxf0mYGAQYqCajMTTp3S4AzdVd6mXj8x0CJ1p0y1cFHCsK2BLuGbAANfR7oQNfTsChBMhi2uKxgVt0V+ndZ4y+JMTxBkvPoUrdm5ghkTvYdk4mEKDnvxAPmqsstJRaCBBnLNZtkLfrvQW4WB3Z9N0DnnV1z1j6DnceM4GkYOZekOjM8jm5ea/bpLlYm43vW8M5073nM3IfgS+DkZeysSnn0QTvUx96PPSlWCXypo1GVsbKyyKoGkFGhT7Y6FOKJqeMR3M2WHa928= + - secure: K3woraK3efvVQXzn5xtOfAsimcbrXD1HzSNzDQX6gDZazvRN+vhQqNAD6fFg0JA4cSo7+BzQi2rIRqcDxaPjeWflgbhGhyFBc21xxOchIDVBGXhWhdbHuZ+KS7Qn4xQyzJPr6qRHIcPwlJW8c40q/AXqPkhiVgWJZNYNlpuCNNM+xqyM+FKz2jPmHt0mcyv6vcXMP0eeORu5UC2txMIY2Kcpf9xm486tUStB31mzb4zZzOHjYtr3iAkmp8DiypPWepTKm2Eg8dYiAhqaLqQtZjx5HayBGhAD0HQyuiMiHmzqkCJU0xTvSf+Ky3xOMGtb9WboTIotGeh6+8lIZMRrUQn1bFHF0rQ/Ge4aU9KhAmugd1ycppslU7BtHpasa9vgWV9CqGpIg8F8fihOE0ygEaOAwU41hoI9LisH5YHKNqtBOrfM6Pdm+urQC2hL5wA64nOxLTRZZkaDpFWdXN+etaiJ9bmlMrM8qTZ7dz+Rfrpora9DvuMSmm+RX+ETIb1tqtSS3tO7SVhufnqefRoRwBA8EBmhTRnmWyRVqhwQaqeYzVPWTi6s5Rt2uz3d/7h0NdTpEb2YHcd7V3TC9k+c2nALDFPP1LgFP6AScPVoAYmoDSovOwaxfO9LHPgNzikf50Sz5/zeSariib4iYKSFt/waJ6NU2FIvvP8qVlYO568= + language: node_js node_js: @@ -28,23 +29,17 @@ install: # https://stackoverflow.com/questions/51925941/travis-ci-how-to-push-to-master-branch # https://stackoverflow.com/questions/23277391/how-to-publish-to-github-pages-from-travis-ci -script: # TODO releasetest->master - - npm test --silent - -after_success: - - if [ "$TRAVIS_TAG" != "$TRAVIS_BRANCH" ] && [ "$TRAVIS_BRANCH" = "releasetest" ] && [ "$TRAVIS_OS_NAME" = "osx" ]; then - npm install --no-save vsce; - npm run compile; - node ./node_modules/vsce/out/vsce --version; - # update CHANGELOG.md, package.json; git commit; git tag; vsce package; vsce publish - node ./out/repo_scripts/deploy.js; +script: + - npm test --silent && if [ "$TRAVIS_TAG" != "$TRAVIS_BRANCH" ] && [ "$TRAVIS_BRANCH" = "master" ] && [ "$TRAVIS_OS_NAME" = "osx" ]; then + node ./out/repo_scripts/deploy.js fi before_deploy: + - npm install --no-save; - npm install --no-save vsce; - - npm run compile; - node ./node_modules/vsce/out/vsce --version; - node ./node_modules/vsce/out/vsce package # for adding to github releases + - node ./node_modules/vsce/out/vsce publish -p $VSCE_PAT deploy: provider: releases @@ -52,11 +47,9 @@ deploy: secure: O5j1kUme6hcXrVUK7X3fKbMu77zVkAkVLfEV2pprVD7Qy7VX83oJmER4qVZHlq47IICmumspSlCAlSi2C8GjB6SGjqzX5+9InCQDn9haPuGP7G9iniGLVXXMpaqFYZeoj/YxGobOiO2GueajB2dqwGucc/WPIiXqPTHOkr6rhfEv8vGzo9mKEmQFS8OVnVu/nMCfON/14XcYn3gICUH5GqcHmyRrd5uiLJr0090G2XSzNDKXINLUFodTkHS+Ov0bKUtlob7Q6bf9+qxokSv1HThduczVT5gdAvhfe1OvnVmroJ3OdFyenyCOW4Nqb72NDWwcGyy1Sh7WiJEq/AgC8a3aRIe1yYKM/Y1iesupyC5rg1aG5OH7N7P6vU3p9wBMyECwCZ2zYH2kZG5tuv3ct1vDgwoFklv7l63VMN4oBs4uG+nt/CWbk30+U/GOwqni0Rnh5Qe+AeNToR0Ex+l1b6843inxg6XM4L/PF1Mcj4ClmxIXzXRWnhgSNljE5OUAreckNOYcSmNOvlVLE1cAYS0+g7x8FkUF2mz7s+AhNw4xKHfoweZ9yH18HpQtrAIHQ7l+NnadGy5i29EgNv5i3U7EFqvBzsvjjlvwRzDlvOzixYRkzjmav44VTYJCK35kA2VUHEAlmwjRnCzlcF6vg8DSc2oBoDfPeUkKC7w5mR4= file_glob: true file: vscode-catch2-test-adapter-*.vsix - skip_cleanup: true on: - condition: "$TRAVIS_OS_NAME = osx" #TODO osx + condition: "$TRAVIS_OS_NAME = osx" node_js: "8" repo: matepek/vscode-catch2-test-adapter - #branch: master TODO - branch: releasetest + branch: master tags: true diff --git a/.vscodeignore b/.vscodeignore index 584d5ae7..2a543a87 100644 --- a/.vscodeignore +++ b/.vscodeignore @@ -1,6 +1,11 @@ +.appveyor.yml +.travis.yml +.github src/** **/*.map out/test/** +out/repo_scripts +resources/src package-lock.json tsconfig.json .vscode/** diff --git a/package.json b/package.json index 45657bf5..a5828fba 100644 --- a/package.json +++ b/package.json @@ -35,6 +35,7 @@ "rebuild": "npm run clean && npm run build", "package": "vsce package", "publish": "vsce publish", + "vscode:prepublish": "tsc", "test": "node ./node_modules/vscode/bin/test" }, "extensionDependencies": [ diff --git a/resources/logo.xcf b/resources/src/logo.xcf similarity index 100% rename from resources/logo.xcf rename to resources/src/logo.xcf diff --git a/src/repo_scripts/deploy.ts b/src/repo_scripts/deploy.ts index 29311fcb..55e5a551 100644 --- a/src/repo_scripts/deploy.ts +++ b/src/repo_scripts/deploy.ts @@ -9,7 +9,6 @@ import * as assert from 'assert'; import * as cp from 'child_process'; import * as fs from 'fs'; import {inspect, promisify} from 'util'; -import * as vsce from 'vsce'; try { main(process.argv.slice(2)) @@ -18,13 +17,10 @@ try { process.exit(1); } -const repoId = 'matepek-vscode-catch2-test-adapter'; - async function main(argv: string[]) { const version = await updateChangelog(); await updatePackageJson(version); await gitCommitAndTag(version); - await publishPackage(version) } /// @@ -105,7 +101,6 @@ async function updatePackageJson(version: {[prop: string]: string|undefined}) { async function gitCommitAndTag(version: {[prop: string]: string|undefined}) { console.log('Creating signed tag and pushing to origin'); - // TODO await spawn( 'git', 'config', '--local', 'user.name', 'matepek/vscode-catch2-test-adapter bot'); @@ -137,17 +132,6 @@ async function gitCommitAndTag(version: {[prop: string]: string|undefined}) { branch + ':' + branch); } -async function publishPackage(version: {[prop: string]: string|undefined}) { - console.log('Creating vsce package'); - const packagePath = './out/' + repoId + '-' + version.version + '.vsix'; - await vsce.createVSIX({'cwd': '.', 'packagePath': packagePath}); - - console.log('Publishing vsce package'); - // assert.ok(process.env['VSCE_PAT'] != undefined); - // TODO - // await vsce.publishVSIX(packagePath, {'pat': process.env['VSCE_PAT']!}); -} - async function spawn(command: string, ...args: string[]) { console.log('$ ' + command + ' "' + args.join('" "') + '"'); await new Promise((resolve, reject) => { From 7564415d2c6b72e1b2ac24986cd1cdd57d210b0e Mon Sep 17 00:00:00 2001 From: Mate Pek Date: Tue, 30 Oct 2018 01:27:23 +0100 Subject: [PATCH 46/49] travis ci fix: ; --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index dbf45ab4..fbb4165e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -31,7 +31,7 @@ install: script: - npm test --silent && if [ "$TRAVIS_TAG" != "$TRAVIS_BRANCH" ] && [ "$TRAVIS_BRANCH" = "master" ] && [ "$TRAVIS_OS_NAME" = "osx" ]; then - node ./out/repo_scripts/deploy.js + node ./out/repo_scripts/deploy.js; fi before_deploy: From 5697680d0e345b830a0b6bb629d2c1675cf7325a Mon Sep 17 00:00:00 2001 From: Mate Pek Date: Tue, 30 Oct 2018 09:26:01 +0100 Subject: [PATCH 47/49] fix wrong email --- src/repo_scripts/deploy.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/repo_scripts/deploy.ts b/src/repo_scripts/deploy.ts index 55e5a551..279b662f 100644 --- a/src/repo_scripts/deploy.ts +++ b/src/repo_scripts/deploy.ts @@ -103,7 +103,7 @@ async function gitCommitAndTag(version: {[prop: string]: string|undefined}) { await spawn( 'git', 'config', '--local', 'user.name', - 'matepek/vscode-catch2-test-adapter bot'); + 'matepek/vscode-catch2-test-adapter'); await spawn( 'git', 'config', '--local', 'user.email', 'matepek+vscode-catch2-test-adapter@gmail.com'); From 6e04d7b4adb4b3fd6205cebdfee481e07a226f10 Mon Sep 17 00:00:00 2001 From: Mate Pek Date: Tue, 30 Oct 2018 16:58:52 +0100 Subject: [PATCH 48/49] new publish script --- .travis.yml | 23 ++----- .vscode/launch.json | 2 +- package-lock.json | 82 +++++++++++++++++++++++ package.json | 4 +- src/repo_scripts/deploy.ts | 132 ++++++++++++++++++++++++++++++++----- 5 files changed, 208 insertions(+), 35 deletions(-) diff --git a/.travis.yml b/.travis.yml index fbb4165e..52728378 100644 --- a/.travis.yml +++ b/.travis.yml @@ -26,30 +26,19 @@ install: - npm install - npm run compile -# https://stackoverflow.com/questions/51925941/travis-ci-how-to-push-to-master-branch -# https://stackoverflow.com/questions/23277391/how-to-publish-to-github-pages-from-travis-ci - script: - - npm test --silent && if [ "$TRAVIS_TAG" != "$TRAVIS_BRANCH" ] && [ "$TRAVIS_BRANCH" = "master" ] && [ "$TRAVIS_OS_NAME" = "osx" ]; then - node ./out/repo_scripts/deploy.js; - fi + - npm test --silent before_deploy: - - npm install --no-save; - - npm install --no-save vsce; - - node ./node_modules/vsce/out/vsce --version; - - node ./node_modules/vsce/out/vsce package # for adding to github releases - - node ./node_modules/vsce/out/vsce publish -p $VSCE_PAT + - npm install + - npm run compile deploy: - provider: releases - api_key: - secure: O5j1kUme6hcXrVUK7X3fKbMu77zVkAkVLfEV2pprVD7Qy7VX83oJmER4qVZHlq47IICmumspSlCAlSi2C8GjB6SGjqzX5+9InCQDn9haPuGP7G9iniGLVXXMpaqFYZeoj/YxGobOiO2GueajB2dqwGucc/WPIiXqPTHOkr6rhfEv8vGzo9mKEmQFS8OVnVu/nMCfON/14XcYn3gICUH5GqcHmyRrd5uiLJr0090G2XSzNDKXINLUFodTkHS+Ov0bKUtlob7Q6bf9+qxokSv1HThduczVT5gdAvhfe1OvnVmroJ3OdFyenyCOW4Nqb72NDWwcGyy1Sh7WiJEq/AgC8a3aRIe1yYKM/Y1iesupyC5rg1aG5OH7N7P6vU3p9wBMyECwCZ2zYH2kZG5tuv3ct1vDgwoFklv7l63VMN4oBs4uG+nt/CWbk30+U/GOwqni0Rnh5Qe+AeNToR0Ex+l1b6843inxg6XM4L/PF1Mcj4ClmxIXzXRWnhgSNljE5OUAreckNOYcSmNOvlVLE1cAYS0+g7x8FkUF2mz7s+AhNw4xKHfoweZ9yH18HpQtrAIHQ7l+NnadGy5i29EgNv5i3U7EFqvBzsvjjlvwRzDlvOzixYRkzjmav44VTYJCK35kA2VUHEAlmwjRnCzlcF6vg8DSc2oBoDfPeUkKC7w5mR4= - file_glob: true - file: vscode-catch2-test-adapter-*.vsix + provider: script + script: node ./out/repo_scripts/deploy.js on: condition: "$TRAVIS_OS_NAME = osx" node_js: "8" repo: matepek/vscode-catch2-test-adapter branch: master - tags: true + tags: false diff --git a/.vscode/launch.json b/.vscode/launch.json index 5a11e7eb..47fdffd9 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -5,7 +5,7 @@ "type": "node", "request": "launch", "name": "Deploy", - "program": "${workspaceFolder}/out/deploy.js" + "program": "${workspaceFolder}/out/repo_scripts/deploy.js" }, { "type": "extensionHost", diff --git a/package-lock.json b/package-lock.json index dade2c30..780b3436 100644 --- a/package-lock.json +++ b/package-lock.json @@ -39,6 +39,18 @@ "integrity": "sha512-ZwTHAlC9akprWDinwEPD4kOuwaYZlyMwVJIANsKNC3QVp0AHB04m7RnB4eqeWfgmxw8MGTzS9uMaw93Z3QcZbw==", "dev": true }, + "@types/bluebird": { + "version": "3.5.24", + "resolved": "https://registry.npmjs.org/@types/bluebird/-/bluebird-3.5.24.tgz", + "integrity": "sha512-YeQoDpq4Lm8ppSBqAnAeF/xy1cYp/dMTif2JFcvmAbETMRlvKHT2iLcWu+WyYiJO3b3Ivokwo7EQca/xfLVJmg==", + "dev": true + }, + "@types/caseless": { + "version": "0.12.1", + "resolved": "https://registry.npmjs.org/@types/caseless/-/caseless-0.12.1.tgz", + "integrity": "sha512-FhlMa34NHp9K5MY1Uz8yb+ZvuX0pnvn3jScRSNAb75KHGB8d3rEU6hqMs3Z2vjuytcMfRg6c5CHMc3wtYyD2/A==", + "dev": true + }, "@types/chai": { "version": "4.1.6", "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.1.6.tgz", @@ -63,6 +75,15 @@ "integrity": "sha512-KEIlhXnIutzKwRbQkGWb/I4HFqBuUykAdHgDED6xqwXJfONCjF5VoE0cXEiurh3XauygxzeDzgtXUqvLkxFzzA==", "dev": true }, + "@types/form-data": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@types/form-data/-/form-data-2.2.1.tgz", + "integrity": "sha512-JAMFhOaHIciYVh8fb5/83nmuO/AHwmto+Hq7a9y8FzLDcC1KCU344XDOMEmahnrTFlHjgh4L0WJFczNIX2GxnQ==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "@types/fs-extra": { "version": "5.0.4", "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-5.0.4.tgz", @@ -84,12 +105,40 @@ "integrity": "sha512-3TUHC3jsBAB7qVRGxT6lWyYo2v96BMmD2PTcl47H25Lu7UXtFH/2qqmKiVrnel6Ne//0TFYf6uvNX+HW2FRkLQ==", "dev": true }, + "@types/request": { + "version": "2.48.0", + "resolved": "https://registry.npmjs.org/@types/request/-/request-2.48.0.tgz", + "integrity": "sha512-KnfoOtqXKllSqfXSEvGTd8KDkNlpHs+PWr6I6XiEIWk/jckH3pNmWDXNFZyPkB9wApb8vzDq2wMByM/0GFSmXg==", + "dev": true, + "requires": { + "@types/caseless": "*", + "@types/form-data": "*", + "@types/node": "*", + "@types/tough-cookie": "*" + } + }, + "@types/request-promise": { + "version": "4.1.42", + "resolved": "https://registry.npmjs.org/@types/request-promise/-/request-promise-4.1.42.tgz", + "integrity": "sha512-b8li55sEZ00BXZstZ3d8WOi48dnapTqB1VufEG9Qox0nVI2JVnTVT1Mw4JbBa1j+1sGVX/qJ0R4WDv4v2GjT0w==", + "dev": true, + "requires": { + "@types/bluebird": "*", + "@types/request": "*" + } + }, "@types/sinon": { "version": "5.0.5", "resolved": "https://registry.npmjs.org/@types/sinon/-/sinon-5.0.5.tgz", "integrity": "sha512-Wnuv66VhvAD2LEJfZkq8jowXGxe+gjVibeLCYcVBp7QLdw0BFx2sRkKzoiiDkYEPGg5VyqO805Rcj0stVjQwCQ==", "dev": true }, + "@types/tough-cookie": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-2.3.3.tgz", + "integrity": "sha512-MDQLxNFRLasqS4UlkWMSACMKeSm1x4Q3TxzUC7KQUsh6RK1ZrQ0VEyE3yzXcBu+K8ejVj4wuX32eUG02yNp+YQ==", + "dev": true + }, "@types/xml-parser": { "version": "1.2.29", "resolved": "https://registry.npmjs.org/@types/xml-parser/-/xml-parser-1.2.29.tgz", @@ -275,6 +324,12 @@ "inherits": "~2.0.0" } }, + "bluebird": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.2.tgz", + "integrity": "sha512-dhHTWMI7kMx5whMQntl7Vr9C6BvV10lFXDAasnqnrMYhXVCzzk6IO9Fo2L75jXHT07WrOngL1WDXOp+yYS91Yg==", + "dev": true + }, "boolbase": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", @@ -2094,6 +2149,27 @@ "uuid": "^3.3.2" } }, + "request-promise": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/request-promise/-/request-promise-4.2.2.tgz", + "integrity": "sha1-0epG1lSm7k+O5qT+oQGMIpEZBLQ=", + "dev": true, + "requires": { + "bluebird": "^3.5.0", + "request-promise-core": "1.1.1", + "stealthy-require": "^1.1.0", + "tough-cookie": ">=2.3.3" + } + }, + "request-promise-core": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.1.tgz", + "integrity": "sha1-Pu4AssWqgyOc+wTFcA2jb4HNCLY=", + "dev": true, + "requires": { + "lodash": "^4.13.1" + } + }, "requires-port": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", @@ -2203,6 +2279,12 @@ "integrity": "sha1-5sgLYjEj19gM8TLOU480YokHJQI=", "dev": true }, + "stealthy-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz", + "integrity": "sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks=", + "dev": true + }, "stream-combiner": { "version": "0.2.2", "resolved": "http://registry.npmjs.org/stream-combiner/-/stream-combiner-0.2.2.tgz", diff --git a/package.json b/package.json index a5828fba..458c2f52 100644 --- a/package.json +++ b/package.json @@ -62,7 +62,9 @@ "sinon": "^7.1.0", "typescript": "^2.9.2", "vsce": "^1.51.1", - "vscode": "^1.1.21" + "vscode": "^1.1.21", + "request-promise": "4.2.2", + "@types/request-promise": "4.1.42" }, "engines": { "vscode": "^1.23.0" diff --git a/src/repo_scripts/deploy.ts b/src/repo_scripts/deploy.ts index 279b662f..4dace16a 100644 --- a/src/repo_scripts/deploy.ts +++ b/src/repo_scripts/deploy.ts @@ -5,22 +5,50 @@ // https://stackoverflow.com/questions/51925941/travis-ci-how-to-push-to-master-branch // https://stackoverflow.com/questions/23277391/how-to-publish-to-github-pages-from-travis-ci +// TODO +/* +ez igy nagyon nem jo +kell csinalni egy kommitot [skip travis] taggel +ezt meg kell tagelni +ehhez kell github releases apit hasznalva publisholni, travis relelases cucc +mehet a kukaba, not good enough sign-elni kell publusholni kell mindezt csak +masteren +*/ + import * as assert from 'assert'; import * as cp from 'child_process'; import * as fs from 'fs'; +import * as path from 'path'; +import * as request from 'request-promise'; import {inspect, promisify} from 'util'; +import * as vsce from 'vsce'; + + +const repoId = 'matepek-vscode-catch2-test-adapter'; + try { - main(process.argv.slice(2)) + process.env['GITHUB_API_KEY'] = 'a395a14cca36c15c2e62064895a3bb8d06720547'; + createGithubRelease({version: '2.0.0'}, 'tmp'); + main; + // main(process.argv.slice(2)) } catch (e) { console.log(inspect(e)); process.exit(1); } async function main(argv: string[]) { - const version = await updateChangelog(); - await updatePackageJson(version); - await gitCommitAndTag(version); + assert.strictEqual( + path.basename(path.dirname(process.cwd())), 'vscode-catch2-test-adapter'); + assert.strictEqual(process.env['TRAVIS_BRANCH'], 'master'); + + const info = await updateChangelog(); + await updatePackageJson(info); + await gitCommitAndTag(info); + const packagePath = await createPackage(info); + await gitPush(); + await createGithubRelease(info, packagePath); + await publishPackage(packagePath); } /// @@ -72,7 +100,7 @@ async function updateChangelog() { }; } -async function updatePackageJson(version: {[prop: string]: string|undefined}) { +async function updatePackageJson(info: {[prop: string]: string|undefined}) { console.log('Parsing package.json'); const packageJsonBuffer = await promisify(fs.readFile)('package.json'); @@ -91,15 +119,15 @@ async function updatePackageJson(version: {[prop: string]: string|undefined}) { assert.notStrictEqual(match[3], undefined); const packageJsonWithVer = - packageJson.substr(0, match.index! + match[1].length) + version.version + + packageJson.substr(0, match.index! + match[1].length) + info.version + packageJson.substr(match.index! + match[1].length + match[2].length); console.log('Updating package.json'); await promisify(fs.writeFile)('package.json', packageJsonWithVer); } -async function gitCommitAndTag(version: {[prop: string]: string|undefined}) { - console.log('Creating signed tag and pushing to origin'); +async function gitCommitAndTag(info: {[prop: string]: string|undefined}) { + console.log('Creating commit and signed tag'); await spawn( 'git', 'config', '--local', 'user.name', @@ -113,23 +141,95 @@ async function gitCommitAndTag(version: {[prop: string]: string|undefined}) { await spawn('git', 'status'); assert.ok(process.env['TRAVIS_BRANCH'] != undefined); - const branch = process.env['TRAVIS_BRANCH']!; - await spawn('git', 'checkout', branch); - await spawn('git', 'add', '--', 'CHANGELOG.md', 'package.json'); + await spawn( + 'git', 'add', '--', 'CHANGELOG.md', 'package.json', 'package-lock.json'); await spawn('git', 'status'); + // [skip travis-ci]: because we dont want to build the new commit it again await spawn( 'git', 'commit', '-m', - '[Updated] Release info in CHANGELOG.md: ' + version.full + + '[Updated] Release info in CHANGELOG.md: ' + info.full + ' [skip travis-ci]'); - const tagName = 'v' + version.version; - await spawn('git', 'tag', '-a', tagName, '-m', 'Version v' + version.version); + // const logOutput = cp.execSync('git log -n 1', {encoding: 'utf8'}); + // const match = logOutput.match(/^commit ([^ ]+)/); + // assert.ok(match != null); + // assert.strictEqual(match!.length, 2); + // info.commitHash = match![1]; + + const tagName = 'v' + info.version; + await spawn('git', 'tag', '-a', tagName, '-m', 'Version v' + info.version); +} + +async function gitPush() { + console.log('Pushing to origin'); + + const branch = process.env['TRAVIS_BRANCH']!; assert.ok(process.env['GITHUB_API_KEY'] != undefined); + await spawn('git', 'checkout', branch); await spawn( 'git', 'push', '--follow-tags', 'https://matepek:' + process.env['GITHUB_API_KEY']! + - '@github.com/matepek/vscode-catch2-test-adapter.git', - branch + ':' + branch); + '@github.com/matepek/vscode-catch2-test-adapter.git'); +} + +async function createPackage(version: {[prop: string]: string|undefined}) { + console.log('Creating vsce package'); + const packagePath = './out/' + repoId + '-' + version.version + '.vsix'; + await vsce.createVSIX({'cwd': '.', 'packagePath': packagePath}); + return packagePath; +} + +async function publishPackage(packagePath: string) { + console.log('Publishing vsce package'); + assert.ok(process.env['VSCE_PAT'] != undefined); + // TODO + // await vsce.publishVSIX(packagePath, {'pat': process.env['VSCE_PAT']!}); +} + +async function createGithubRelease( + info: {[prop: string]: string|undefined}, packagePath: string) { + console.log('Publishing to github releases'); + assert.ok(process.env['GITHUB_API_KEY'] != undefined); + const key = process.env['GITHUB_API_KEY']!; + + const latestRes = JSON.parse( + (await request + .get({ + url: + 'https://api.github.com/repos/matepek/vscode-catch2-test-adapter/releases/latest', + headers: {'User-Agent': 'matepek'} + }) + .auth('matepek', key)) + .toString()); + assert.notStrictEqual(latestRes.tag_name, 'v' + info.version); + + const createReleaseRes = JSON.parse( + (await request + .post({ + url: + 'https://api.github.com/repos/matepek/vscode-catch2-test-adapter/releases', + headers: {'User-Agent': 'matepek'} + }) + .auth('matepek', key) + .json(JSON.stringify({ + 'tag_name': 'testtag' //'v' + info.version, + }))) + .toString()); + + const uploadAssetRequest = + request + .post({ + url: createReleaseRes.upload_url.replace( + '{?name,label}', + '?name=vscode-catch2-test-adapter-' + info.version + '.vsix'), + headers: + {'User-Agent': 'matepek', 'Content-Type': 'application/zip'} + }) + .auth('matepek', key); + + fs.createReadStream(packagePath).pipe(uploadAssetRequest); + + uploadAssetRequest; } async function spawn(command: string, ...args: string[]) { From 9bfc5c720026115ffc409c8e1f059e5b33bbf726 Mon Sep 17 00:00:00 2001 From: Mate Pek Date: Tue, 30 Oct 2018 23:07:15 +0100 Subject: [PATCH 49/49] had to made an aearly release because of an error --- .travis.yml | 26 +++++++++++++------------- package.json | 2 +- src/repo_scripts/deploy.ts | 11 ++++++----- 3 files changed, 20 insertions(+), 19 deletions(-) diff --git a/.travis.yml b/.travis.yml index 52728378..50620317 100644 --- a/.travis.yml +++ b/.travis.yml @@ -29,16 +29,16 @@ install: script: - npm test --silent -before_deploy: - - npm install - - npm run compile - -deploy: - provider: script - script: node ./out/repo_scripts/deploy.js - on: - condition: "$TRAVIS_OS_NAME = osx" - node_js: "8" - repo: matepek/vscode-catch2-test-adapter - branch: master - tags: false +#before_deploy: +# - npm install +# - npm run compile +# +#deploy: +# provider: script +# script: node ./out/repo_scripts/deploy.js +# on: +# condition: "$TRAVIS_OS_NAME = osx" +# node_js: "8" +# repo: matepek/vscode-catch2-test-adapter +# branch: master +# tags: false diff --git a/package.json b/package.json index 458c2f52..68be24c8 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "icon": "resources/icon.png", "author": "Mate Pek", "publisher": "matepek", - "version": "1.2.3", + "version": "2.0.0", "license": "Unlicense", "homepage": "https://github.com/matepek/vscode-catch2-test-adapter", "repository": { diff --git a/src/repo_scripts/deploy.ts b/src/repo_scripts/deploy.ts index 4dace16a..36c9fc69 100644 --- a/src/repo_scripts/deploy.ts +++ b/src/repo_scripts/deploy.ts @@ -208,12 +208,13 @@ async function createGithubRelease( .post({ url: 'https://api.github.com/repos/matepek/vscode-catch2-test-adapter/releases', - headers: {'User-Agent': 'matepek'} + headers: {'User-Agent': 'matepek'}, + form: { + 'tag_name': 'testtag2', //'v' + info.version, + 'target_commitish': 'development' + } }) - .auth('matepek', key) - .json(JSON.stringify({ - 'tag_name': 'testtag' //'v' + info.version, - }))) + .auth('matepek', key)) .toString()); const uploadAssetRequest =