diff --git a/CHANGELOG.md b/CHANGELOG.md index 7533ecba..1532ee5e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [1.1.2] + +Bugfix release. + ## [1.1.1] ### Added diff --git a/README.md b/README.md index 10cc2f9f..9c077ca9 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,11 @@ # 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) +[![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) +[![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). @@ -19,6 +24,8 @@ This adapter doesn't support everything. | `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.) | ### catch2TestExplorer.executables @@ -115,11 +122,8 @@ Example: ## TODOs - Implement more [Catch command line options](https://github.com/catchorg/Catch2/blob/master/docs/command-line.md#specifying-which-tests-to-run), such as: - - `--nothrow` -- Tests - ## Contribution Any contribution is welcome. diff --git a/package-lock.json b/package-lock.json index ec851d2c..f21a8291 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "vscode-catch2-test-adapter", - "version": "1.1.1", + "version": "1.1.2", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -39,6 +39,12 @@ "integrity": "sha512-ZwTHAlC9akprWDinwEPD4kOuwaYZlyMwVJIANsKNC3QVp0AHB04m7RnB4eqeWfgmxw8MGTzS9uMaw93Z3QcZbw==", "dev": true }, + "@types/chai": { + "version": "4.1.6", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.1.6.tgz", + "integrity": "sha512-CBk7KTZt3FhPsEkYioG6kuCIpWISw+YI8o+3op4+NXwTpvAPxE1ES8+PY8zfaK2L98b1z5oq03UHa4VYpeUxnw==", + "dev": true + }, "@types/deep-equal": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@types/deep-equal/-/deep-equal-1.0.1.tgz", @@ -78,6 +84,12 @@ "integrity": "sha512-3TUHC3jsBAB7qVRGxT6lWyYo2v96BMmD2PTcl47H25Lu7UXtFH/2qqmKiVrnel6Ne//0TFYf6uvNX+HW2FRkLQ==", "dev": true }, + "@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/xml-parser": { "version": "1.2.29", "resolved": "https://registry.npmjs.org/@types/xml-parser/-/xml-parser-1.2.29.tgz", @@ -549,9 +561,9 @@ } }, "entities": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.1.tgz", - "integrity": "sha1-blwtClYhtdra7O+AuQ7ftc13cvA=" + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz", + "integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==" }, "escape-string-regexp": { "version": "1.0.5", diff --git a/package.json b/package.json index 5f1f0e91..a172578f 100644 --- a/package.json +++ b/package.json @@ -41,7 +41,7 @@ "hbenl.vscode-test-explorer" ], "dependencies": { - "entities": "^1.1.1", + "entities": "^1.1.2", "tslib": "^1.9.3", "vscode-test-adapter-api": "^1.1.0", "vscode-test-adapter-util": "^0.5.0", @@ -52,10 +52,12 @@ "@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", "typescript": "^2.9.2", "vsce": "^1.51.1", @@ -150,4 +152,4 @@ } } } -} \ No newline at end of file +} diff --git a/src/C2TestAdapter.ts b/src/C2TestAdapter.ts index cd901b4c..6e3a7692 100644 --- a/src/C2TestAdapter.ts +++ b/src/C2TestAdapter.ts @@ -2,7 +2,6 @@ // 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 {execFile} from 'child_process'; import * as fs from 'fs'; import * as path from 'path'; import * as vscode from 'vscode'; @@ -11,6 +10,7 @@ import * as util from 'vscode-test-adapter-util'; import {C2AllTestSuiteInfo} from './C2AllTestSuiteInfo'; import {C2TestInfo} from './C2TestInfo'; +import * as c2fs from './FsWrapper'; export class C2TestAdapter implements TestAdapter, vscode.Disposable { private readonly testsEmitter = @@ -80,8 +80,6 @@ export class C2TestAdapter implements TestAdapter, vscode.Disposable { this.disposables.forEach(d => { d.dispose(); }); - while (this.disposables.shift() !== undefined) - ; } get testStates(): vscode.Event { + loadSuite(exe: ExecutableConfig): Promise { const suite = this.allTests.createChildSuite( exe.name, exe.path, {cwd: exe.cwd, env: exe.env}); @@ -106,39 +104,43 @@ export class C2TestAdapter implements TestAdapter, vscode.Disposable { if (watcher != undefined) { watcher.close(); } - - watcher = fs.watch(suite.execPath); - this.watchers.set(suite.execPath, watcher); - const allTests = this.allTests; // alltest may has changed - - watcher.on('change', (eventType: string, filename: string) => { - // need some time here: - const waitAndThenTry = (remainingIteration: number, delay: number) => { - if (remainingIteration == 0) { - watcher!.close(); - this.watchers.delete(suite.execPath); - this.testsEmitter.fire({type: 'started'}); - allTests.removeChild(suite); - this.testsEmitter.fire({type: 'finished', suite: this.allTests}); - } else if (!fs.existsSync(suite.execPath)) { - setTimeout( - waitAndThenTry, delay, - [remainingIteration - 1, Math.max(delay * 2, 2000)]); - } else { - this.testsEmitter.fire({type: 'started'}); - suite.reloadChildren().then(() => { + try { + watcher = fs.watch(suite.execPath); + this.watchers.set(suite.execPath, watcher); + const allTests = this.allTests; // alltest may has changed + + watcher.on('change', (eventType: string, filename: string) => { + // need some time here: + const waitAndThenTry = (remainingIteration: number, delay: number) => { + if (remainingIteration == 0) { + watcher!.close(); + this.watchers.delete(suite.execPath); + this.testsEmitter.fire({type: 'started'}); + allTests.removeChild(suite); this.testsEmitter.fire({type: 'finished', suite: this.allTests}); - }); - } - }; + } else if (!fs.existsSync(suite.execPath)) { + setTimeout( + waitAndThenTry, delay, + [remainingIteration - 1, Math.max(delay * 2, 2000)]); + } else { + this.testsEmitter.fire({type: 'started'}); + suite.reloadChildren().then(() => { + this.testsEmitter.fire({type: 'finished', suite: this.allTests}); + }); + } + }; - // change event can arrive during debug session on osx (why?) - if (!this.isDebugging) { - waitAndThenTry(10, 128); - } + // change event can arrive during debug session on osx (why?) + if (!this.isDebugging) { + waitAndThenTry(10, 128); + } + }); + } catch (e) { + this.log.warn('watcher couldn\'t watch: ' + suite.execPath); + } + return suite.reloadChildren().catch((e) => { + this.allTests.removeChild(suite); }); - - return suite.reloadChildren(); } load(): Promise { @@ -172,13 +174,17 @@ export class C2TestAdapter implements TestAdapter, vscode.Disposable { return testListReaders; }) - .then(() => { - this.testsEmitter.fire({type: 'finished', suite: this.allTests}); - }) - .catch((err: Error) => { - this.testsEmitter.fire( - {type: 'finished', suite: undefined, errorMessage: err.message}); - }); + .then( + () => { + this.testsEmitter.fire({type: 'finished', suite: this.allTests}); + }, + (err: Error) => { + this.testsEmitter.fire({ + type: 'finished', + suite: undefined, + errorMessage: err.message + }); + }); } cancel(): void { @@ -416,10 +422,10 @@ export class C2TestAdapter implements TestAdapter, vscode.Disposable { if (regex.length > 0) { const recursiveAdd = (directory: string): void => { - const children = fs.readdirSync(directory, 'utf8'); + const children = c2fs.readdirSync(directory); children.forEach(child => { const childPath = path.resolve(directory, child); - const childStat = fs.statSync(childPath); + const childStat = c2fs.statSync(childPath); if (childPath.match(regex) && childStat.isFile()) { let resolvedName = name + ' : ' + child; let resolvedCwd = cwd; @@ -450,7 +456,7 @@ export class C2TestAdapter implements TestAdapter, vscode.Disposable { }); }; try { - const stat = fs.statSync(p); + const stat = c2fs.statSync(p); if (stat.isDirectory()) { recursiveAdd(p); } else if (stat.isFile()) { @@ -498,21 +504,9 @@ export class C2TestAdapter implements TestAdapter, vscode.Disposable { return this.filterVerifiedCatch2TestExecutables(executables); } - private verifyIsCatch2TestExecutable(path: string): Promise { - return new Promise((resolve, reject) => { - try { - execFile( - path, ['--help'], - (error: Error|null, stdout: string, stderr: string) => { - if (stdout.indexOf('Catch v2.') != -1) { - resolve(true); - } else { - resolve(false); - } - }); - } catch (e) { - resolve(false); - } + verifyIsCatch2TestExecutable(path: string): Promise { + return c2fs.spawnAsync(path, ['--help']).then((res) => { + return res.stdout.indexOf('Catch v2.') != -1; }); } diff --git a/src/C2TestSuiteInfo.ts b/src/C2TestSuiteInfo.ts index 448e36e4..22a7c7f1 100644 --- a/src/C2TestSuiteInfo.ts +++ b/src/C2TestSuiteInfo.ts @@ -2,7 +2,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 {ChildProcess, execFile, spawn, SpawnOptions} from 'child_process'; +import {ChildProcess, spawn, SpawnOptions} from 'child_process'; import * as fs from 'fs'; import * as path from 'path'; import {promisify} from 'util'; @@ -11,6 +11,7 @@ import * as xml2js from 'xml2js'; import {C2TestAdapter} from './C2TestAdapter'; import {C2TestInfo} from './C2TestInfo'; +import * as c2fs from './FsWrapper'; import {generateUniqueId} from './IdGenerator'; import {TaskPool} from './TaskPool'; @@ -256,8 +257,11 @@ export class C2TestSuiteInfo implements TestSuiteInfo { message: 'Unexpected test error. (Is Catch2 crashed?)\n' }); } - this.adapter.log.error(err.message); suiteFinally(); + try { + this.adapter.log.error(err.message); + } catch (e) { + } }); } @@ -266,74 +270,75 @@ export class C2TestSuiteInfo implements TestSuiteInfo { if (!exists) throw Error('reloadSuiteChildren: Should exists: ' + this.execPath); - return new Promise((resolve, reject) => { - execFile( - this.execPath, - [ - '[.],*', '--verbosity', 'high', '--list-tests', '--use-colour', - 'no' - ], - (error: Error|null, stdout: string, stderr: string) => { - const oldChildren = this.children; - this.children = []; - - let lines = stdout.split(/\r?\n/); - - if (lines.length == 0) this.adapter.log.error('Empty test list.'); - - while (lines[lines.length - 1].length == 0) lines.pop(); - - let i = 1; - while (i < lines.length - 1) { - if (lines[i][0] != ' ') - this.adapter.log.error( - 'Wrong test list output format: ' + lines.toString()); - - const testNameFull = lines[i++].substr(2); - - let filePath = ''; - let line = 0; - { - const fileLine = lines[i++].substr(4); - const match = - fileLine.match(/(?:(.+):([0-9]+)|(.+)\(([0-9]+)\))/); - if (match && match.length == 5) { - filePath = match[1] ? match[1] : match[3]; - if (this.execOptions.cwd) - filePath = path.resolve(this.execOptions.cwd, filePath); - line = Number(match[2] ? match[2] : match[4]); + return c2fs + .spawnAsync( + this.execPath, + [ + '[.],*', '--verbosity', 'high', '--list-tests', '--use-colour', + 'no' + ], + this.execOptions) + .then((r) => { + const oldChildren = this.children; + this.children = []; + + let lines = r.stdout.split(/\r?\n/); + + if (lines.length == 0) this.adapter.log.error('Empty test list.'); + + while (lines[lines.length - 1].trim().length == 0) lines.pop(); + + let i = 1; + while (i < lines.length - 1) { + if (lines[i][0] != ' ') + this.adapter.log.error( + 'Wrong test list output format: ' + lines.toString()); + + const testNameFull = lines[i++].substr(2); + + let filePath = ''; + let line = 0; + { + const fileLine = lines[i++].substr(4); + const match = + fileLine.match(/(?:(.+):([0-9]+)|(.+)\(([0-9]+)\))/); + if (match && match.length == 5) { + filePath = match[1] ? match[1] : match[3]; + filePath = + path.resolve(path.dirname(this.execPath), filePath); + if (!c2fs.existsSync(filePath) && this.execOptions.cwd) { + const r = path.resolve(this.execOptions.cwd, filePath); + if (c2fs.existsSync(r)) filePath = r; } + line = Number(match[2] ? match[2] : match[4]); } + } - let description = lines[i++].substr(4); - if (description.startsWith('(NO DESCRIPTION)')) - description = ''; - - let tags: string[] = []; - if (lines[i].length > 6 && lines[i][6] === '[') { - tags = lines[i].trim().split(']'); - tags.pop(); - for (let j = 0; j < tags.length; ++j) tags[j] += ']'; - ++i; - } + let description = lines[i++].substr(4); + if (description.startsWith('(NO DESCRIPTION)')) description = ''; - const index = oldChildren.findIndex( - (c: C2TestInfo): boolean => {return c.testNameFull == - testNameFull}); - if (index != -1 && - oldChildren[index].label == - C2TestInfo.generateLabel( - testNameFull, description, tags)) { - this.children.push(oldChildren[index]); - } else { - this.createChildTest( - testNameFull, description, tags, filePath, line); - } + let tags: string[] = []; + if (lines[i].length > 6 && lines[i][6] === '[') { + tags = lines[i].trim().split(']'); + tags.pop(); + for (let j = 0; j < tags.length; ++j) tags[j] += ']'; + ++i; } - resolve(); - }); - }); + const index = oldChildren.findIndex( + (c: C2TestInfo): boolean => {return c.testNameFull == + testNameFull}); + if (index != -1 && + oldChildren[index].label == + C2TestInfo.generateLabel( + testNameFull, description, tags)) { + this.children.push(oldChildren[index]); + } else { + this.createChildTest( + testNameFull, description, tags, filePath, line); + } + } + }); }); } } diff --git a/src/FsWrapper.ts b/src/FsWrapper.ts new file mode 100644 index 00000000..d4c8b66a --- /dev/null +++ b/src/FsWrapper.ts @@ -0,0 +1,47 @@ +//----------------------------------------------------------------------------- +// 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 cp from 'child_process'; +import * as fs from 'fs'; + +export function spawnAsync( + cmd: string, args?: string[], + options?: cp.SpawnOptions): Promise> { + return new Promise((resolve) => { + const command = cp.spawn(cmd, args, options); + const ret: cp.SpawnSyncReturns = { + pid: command.pid, + output: [], + stdout: '', + stderr: '', + status: 0, + signal: '', + error: new Error() + }; + command.stdout.on('data', function(data) { + ret.stdout += data; + ret.output.push(data); + }); + command.on('close', function(code) { + ret.status = code; + resolve(ret) + }); + command.on('error', function(err) { + ret.error = err; + resolve(ret); + }); + }) +} + +export function statSync(path: string): fs.Stats { + return fs.statSync(path); +} + +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/test/C2TestAdapter.test.ts b/src/test/C2TestAdapter.test.ts index 3b421e11..dd4099d7 100644 --- a/src/test/C2TestAdapter.test.ts +++ b/src/test/C2TestAdapter.test.ts @@ -1,19 +1,26 @@ -const sinon = require('sinon'); +//----------------------------------------------------------------------------- +// vscode-catch2-test-adapter was written by Mate Pek, and is placed in the +// public domain. The author hereby disclaims copyright to this source code. + const child_process = require('child_process'); -const deepEqual = require('deep-equal'); +const deepStrictEqual = require('deep-equal'); import * as path from 'path'; -import * as fs from 'fs-extra'; +import * as fs from 'fs'; +import * as fse from 'fs-extra'; import * as assert from 'assert'; -import {EventEmitter} from 'events'; import * as vscode from 'vscode'; -import {TestEvent, TestLoadFinishedEvent, TestLoadStartedEvent, TestRunFinishedEvent, TestRunStartedEvent, TestSuiteEvent, TestSuiteInfo} from 'vscode-test-adapter-api'; +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} from 'util'; import {C2AllTestSuiteInfo} from '../C2AllTestSuiteInfo'; -import * as myExtension from '../C2TestAdapter'; -import {Stream} from 'stream'; -import {inspect} from 'util'; +import {C2TestAdapter} from '../C2TestAdapter'; +import {example1} from './example1'; +import {ChildProcessStub} from './Helpers'; +import * as c2fs from '../FsWrapper'; assert.notEqual(vscode.workspace.workspaceFolders, undefined); assert.equal(vscode.workspace.workspaceFolders!.length, 1); @@ -22,28 +29,45 @@ const workspaceFolderUri = vscode.workspace.workspaceFolders![0].uri; const workspaceFolder = vscode.workspace.getWorkspaceFolder(workspaceFolderUri)!; + +const workspaceFolderMatcher = + sinon.match(new RegExp('out(/|\\\\)test')) + .and(sinon.match(new RegExp('^((?!\\.vscode).)*$'))); + const logger = new Log('Catch2TestAdapter', workspaceFolder, 'Catch2TestAdapter'); -const spawnStub = sinon.stub(child_process, 'spawn'); - const dotVscodePath = path.join(workspaceFolderUri.path, '.vscode'); +const sinonSandbox = sinon.createSandbox(); + /// describe('C2TestAdapter', function() { - const config = vscode.workspace.getConfiguration( - 'catch2TestExplorer', workspaceFolderUri); + function getConfig() { + return vscode.workspace.getConfiguration( + 'catch2TestExplorer', workspaceFolderUri) + }; - const disposable: vscode.Disposable[] = []; + function updateConfig(key: string, value: any) { + return getConfig().update(key, value) + } - let adapter: myExtension.C2TestAdapter; + let adapter: C2TestAdapter|undefined; let testsEvents: (TestLoadStartedEvent|TestLoadFinishedEvent)[]; + let testsEventsConnection: vscode.Disposable|undefined; let testStatesEvents: (TestRunStartedEvent|TestRunFinishedEvent| TestSuiteEvent|TestEvent)[]; + let testStatesEventsConnection: vscode.Disposable|undefined; - const resetConfig = function(): Thenable { - const packageJson = fs.readJSONSync( + let spawnStub: sinon.SinonStub; + let fsWatchStub: sinon.SinonStub; + let fsExistsStub: sinon.SinonStub; + let c2fsReaddirSyncStub: sinon.SinonStub; + let c2fsStatSyncStub: sinon.SinonStub; + + function resetConfig(): Thenable { + const packageJson = fse.readJSONSync( path.join(workspaceFolderUri.path, '../..', 'package.json')); const properties: {[prop: string]: any}[] = packageJson['contributes']['configuration']['properties']; @@ -52,104 +76,181 @@ describe('C2TestAdapter', function() { assert.ok(key.startsWith('catch2TestExplorer.')); const k = key.replace('catch2TestExplorer.', '') t = t.then(() => { - return config.update(k, undefined); + return getConfig().update(k, undefined); }); }); return t; - }; + } - const createAdapterAndSubscribe = function() { - adapter = new myExtension.C2TestAdapter(workspaceFolder, logger); - testsEvents = []; - testStatesEvents = []; + 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; + }; - spawnStub.throws(); + function createAdapterAndSubscribe() { + adapter = new C2TestAdapter(workspaceFolder, logger); - disposable.push( + testsEvents = []; + testsEventsConnection = adapter.tests((e: TestLoadStartedEvent|TestLoadFinishedEvent) => { testsEvents.push(e); - })); - disposable.push(adapter.testStates( + }); + + testStatesEvents = []; + testStatesEventsConnection = adapter.testStates( (e: TestRunStartedEvent|TestRunFinishedEvent|TestSuiteEvent| TestEvent) => { testStatesEvents.push(e); - })); - }; + }); - beforeEach(() => { - fs.removeSync(dotVscodePath); + return adapter!; + } + + function disposeAdapterAndSubscribers() { + adapter && adapter.dispose(); + testsEventsConnection && testsEventsConnection.dispose(); + testStatesEventsConnection && testStatesEventsConnection.dispose(); + testsEvents = []; + testStatesEvents = []; + } + + function stubsThrowByDefault() { + spawnStub.withArgs(workspaceFolderMatcher).callsFake((...args: any[]) => { + throw new Error(inspect(['spawnStub', args])); + }); + fsWatchStub.withArgs(workspaceFolderMatcher).callsFake((...args: any[]) => { + throw new Error(inspect(['fsWatchStub', args])); + }); + fsExistsStub.withArgs(workspaceFolderMatcher) + .callsFake((...args: any[]) => { + throw new Error(inspect(['fsExistsStub', args])); + }); + } + + function stubsResetToThrow() { + spawnStub.reset(); + fsWatchStub.reset(); + fsExistsStub.reset(); + c2fsReaddirSyncStub.reset(); + c2fsStatSyncStub.reset(); + stubsThrowByDefault(); + // TODO stub.callThrough(); + } + + before(() => { + fse.removeSync(dotVscodePath); + adapter = undefined; + + spawnStub = sinonSandbox.stub(child_process, 'spawn'); + fsWatchStub = sinonSandbox.stub(fs, 'watch'); + fsExistsStub = sinonSandbox.stub(fs, 'exists'); + c2fsReaddirSyncStub = sinonSandbox.stub(c2fs, 'readdirSync'); + c2fsStatSyncStub = sinonSandbox.stub(c2fs, 'statSync'); + + stubsResetToThrow(); + + // reset config can cause problem with fse.removeSync(dotVscodePath); return resetConfig(); }); - afterEach(() => { - while (disposable.length > 0) disposable.pop()!.dispose(); + after(() => { + disposeAdapterAndSubscribers(); + sinonSandbox.restore(); }); describe('detect config change', function() { - const waitForReloadAndAssert = () => { - const waitForReloadAndAssertInner = - (tryCount: number = 20): Promise => { - if (testsEvents.length < 2) - return new Promise(r => setTimeout(r, 20)) - .then(() => {waitForReloadAndAssertInner(tryCount - 1)}); - else { - 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); - return Promise.resolve(); - } - }; - return waitForReloadAndAssertInner(); + this.slow(150); + + const waitForReloadAndAssert = (): Promise => { + const waitForReloadAndAssertInner = (tryCount: number): Promise => { + if (testsEvents.length < 2) + return new Promise(r => setTimeout(r, 10)) + .then(() => {waitForReloadAndAssertInner(tryCount - 1)}); + else { + 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); + return Promise.resolve(); + } + }; + return waitForReloadAndAssertInner(20); }; + afterEach(() => { + disposeAdapterAndSubscribers(); + return resetConfig(); + }); + it('workerMaxNumber', () => { createAdapterAndSubscribe(); - assert.deepEqual(testsEvents, []); - return config.update('workerMaxNumber', 42).then(waitForReloadAndAssert); + assert.deepStrictEqual(testsEvents, []); + return getConfig() + .update('workerMaxNumber', 42) + .then(waitForReloadAndAssert); }); it('defaultEnv', () => { createAdapterAndSubscribe(); - assert.deepEqual(testsEvents, []); - return config.update('defaultEnv', {'APPLE': 'apple'}) + assert.deepStrictEqual(testsEvents, []); + return getConfig() + .update('defaultEnv', {'APPLE': 'apple'}) .then(waitForReloadAndAssert); }); it('defaultCwd', () => { createAdapterAndSubscribe(); - assert.deepEqual(testsEvents, []); - return config.update('defaultCwd', 'apple/peach') + assert.deepStrictEqual(testsEvents, []); + return getConfig() + .update('defaultCwd', 'apple/peach') .then(waitForReloadAndAssert); }); it('enableSourceDecoration', () => { - createAdapterAndSubscribe(); - assert.deepEqual(testsEvents, []); - return config.update('enableSourceDecoration', false).then(() => { + const adapter = createAdapterAndSubscribe(); + assert.deepStrictEqual(testsEvents, []); + return getConfig().update('enableSourceDecoration', false).then(() => { assert.ok(!adapter.getIsEnabledSourceDecoration()); }); }); it('defaultRngSeed', () => { - createAdapterAndSubscribe(); - assert.deepEqual(testsEvents, []); - return config.update('defaultRngSeed', 987).then(() => { + const adapter = createAdapterAndSubscribe(); + assert.deepStrictEqual(testsEvents, []); + return getConfig().update('defaultRngSeed', 987).then(() => { assert.equal(adapter.getRngSeed(), 987); }); }); }); - - // describe('example1' + // describe('detect config change' describe('adapter:', () => { + let adapter: C2TestAdapter; + beforeEach(() => { - createAdapterAndSubscribe(); + adapter = createAdapterAndSubscribe(); + }); + + afterEach(() => { + disposeAdapterAndSubscribers(); }); - it('load: empty config', function() { + it('fill with empty config', function() { return adapter.load().then(() => { assert.equal(testsEvents.length, 2); assert.equal(testsEvents[0].type, 'started'); @@ -160,74 +261,22 @@ describe('C2TestAdapter', function() { }); }); - describe('load: example1', function() { + describe('example1', function() { let tests: any; - const randomnessXml = ``; - - const s1t1Xml = ` - - - `; - - const s1t2Xml = ` - - - - std::false_type::value - - - false - - - - ;`; - - const s1HeaderXml = ` - - - `; - - const s1Xml = s1HeaderXml + s1t1Xml + s1t2Xml + ` - - - - `; - - const s2t1Xml = ` - - - `; - - const s2t2Xml = ` - - - `; - - const s2t3Xml = ` - - - - std::false_type::value - - - false - - - - `; - - const s2HeaderXml = ` - - ` + - randomnessXml + ` - `; - - const s2Xml = s2HeaderXml + s2t1Xml + /* s2t2 is skipped */ s2t3Xml + ` - - - - `; + before(() => { + for (let suite of example1.outputs) { + for (let scenario of suite[1]) { + spawnStub.withArgs(suite[0], scenario[0]).callsFake(() => { + return new ChildProcessStub(scenario[1]); + }); + } + } + }) + + after(() => { + stubsResetToThrow(); + }); beforeEach(() => { return adapter.load() @@ -238,18 +287,20 @@ describe('C2TestAdapter', function() { }) .then((suite: TestSuiteInfo) => { const root = suite; - const s1 = root.createChildSuite('s1', 'execPath1', {}); - const s1t1 = - s1.createChildTest('s1t1', 'd', ['tag1'], 'suite1.cpp', 1); - const s1t2 = - s1.createChildTest('s1t2', 'd', ['tag1'], 'suite1.cpp', 2); - const s2 = root.createChildSuite('s2', 'execPath2', {}); - const s2t1 = - s2.createChildTest('s2t1', 'd', ['tag1'], 'suite2.cpp', 1); - const s2t2 = - s2.createChildTest('s2t2', 'd', ['[.]'], 'suite2.cpp', 2); - const s2t3 = - s2.createChildTest('s2t3', 'd', ['tag1'], 'suite2.cpp', 3); + const s1 = + root.createChildSuite('s1', example1.suite1.execPath, {}); + const s1t1 = s1.createChildTest( + 's1t1', 'd', ['tag1'], example1.suite1.execPath, 1); + const s1t2 = s1.createChildTest( + 's1t2', 'd', ['tag1'], example1.suite1.execPath, 2); + const s2 = + root.createChildSuite('s2', example1.suite2.execPath, {}); + const s2t1 = s2.createChildTest( + 's2t1', 'd', ['tag1'], example1.suite2.execPath, 1); + const s2t2 = s2.createChildTest( + 's2t2', 'd', ['[.]'], example1.suite2.execPath, 2); + const s2t3 = s2.createChildTest( + 's2t3', 'd', ['tag1'], example1.suite2.execPath, 3); tests = { root: root, @@ -262,35 +313,11 @@ describe('C2TestAdapter', function() { s2t3: s2t3 } }); - }); + }) it('run: 1 test (succ)', function() { - const stdout = new Stream.Readable(); - const spawnEvent: any = new EventEmitter(); - spawnEvent.stdout = stdout; - stdout.on('end', () => { - spawnEvent.emit('close', 1); - }); - stdout.push( - s1HeaderXml + s1t1Xml + - ` - - - `); - stdout.push(null); - - spawnStub - .withArgs( - tests.s1.execPath, - [ - tests.s1t1.testNameFull, '--reporter', 'xml', '--durations', - 'yes' - ], - tests.s1.execOptions) - .returns(spawnEvent); - return adapter.run([tests.s1t1.id]).then(() => { - assert.deepEqual(testStatesEvents, [ + assert.deepStrictEqual(testStatesEvents, [ {type: 'started', tests: [tests.s1t1.id]}, {type: 'suite', state: 'running', suite: tests.s1}, {type: 'test', state: 'running', test: tests.s1t1}, @@ -299,77 +326,17 @@ describe('C2TestAdapter', function() { state: 'passed', test: tests.s1t1, decorations: undefined, - message: 'Randomness seeded to: 2\nDuration: 0.000174 second(s)\n' + message: 'Duration: 0.000112 second(s)\n' }, {type: 'suite', state: 'completed', suite: tests.s1}, {type: 'finished'}, ]); }); - }); - // it('run: 1 test (succ)' - - it('run: 1 test (missing)', function() { - const stdout = new Stream.Readable(); - const spawnEvent: any = new EventEmitter(); - spawnEvent.stdout = stdout; - stdout.on('end', () => { - spawnEvent.emit('close', 1); - }); - stdout.push( - s1HeaderXml + - ` - - - `); - stdout.push(null); - - spawnStub - .withArgs( - tests.s1.execPath, - [ - tests.s1t1.testNameFull, '--reporter', 'xml', '--durations', - 'yes' - ], - tests.s1.execOptions) - .returns(spawnEvent); - - return adapter.run([tests.s1t1.id]).then(() => { - assert.deepEqual(testStatesEvents, [ - {type: 'started', tests: [tests.s1t1.id]}, - {type: 'suite', state: 'running', suite: tests.s1}, - {type: 'suite', state: 'completed', suite: tests.s1}, - {type: 'finished'}, - ]); - }); - }); - // it('run: 1 test (missing)' + }) it('run: 1 test (skipped)', function() { - const stdout = new Stream.Readable(); - const spawnEvent: any = new EventEmitter(); - spawnEvent.stdout = stdout; - stdout.on('end', () => { - spawnEvent.emit('close', 1); - }); - stdout.push(s2HeaderXml + s2t2Xml + ` - - - - `); - stdout.push(null); - - spawnStub - .withArgs( - tests.s2.execPath, - [ - tests.s2t2.testNameFull, '--reporter', 'xml', '--durations', - 'yes' - ], - tests.s2.execOptions) - .returns(spawnEvent); - return adapter.run([tests.s2t2.id]).then(() => { - assert.deepEqual(testStatesEvents, [ + assert.deepStrictEqual(testStatesEvents, [ {type: 'started', tests: [tests.s2t2.id]}, {type: 'suite', state: 'running', suite: tests.s2}, {type: 'test', state: 'running', test: tests.s2t2}, @@ -378,7 +345,7 @@ describe('C2TestAdapter', function() { state: 'passed', test: tests.s2t2, decorations: undefined, - message: 'Randomness seeded to: 2\n' + message: 'Duration: 0.001294 second(s)\n' }, {type: 'suite', state: 'completed', suite: tests.s2}, {type: 'finished'}, @@ -388,32 +355,8 @@ describe('C2TestAdapter', function() { // it('run: 1 test (skipped)' it('run: 1 test (fails)', function() { - const stdout = new Stream.Readable(); - const spawnEvent: any = new EventEmitter(); - spawnEvent.stdout = stdout; - stdout.on('end', () => { - spawnEvent.emit('close', 1); - }); - stdout.push( - s2HeaderXml + s2t3Xml + - ` - - - `); - stdout.push(null); - - spawnStub - .withArgs( - tests.s2.execPath, - [ - tests.s2t3.testNameFull, '--reporter', 'xml', '--durations', - 'yes' - ], - tests.s2.execOptions) - .returns(spawnEvent); - return adapter.run([tests.s2t3.id]).then(() => { - assert.deepEqual(testStatesEvents, [ + assert.deepStrictEqual(testStatesEvents, [ {type: 'started', tests: [tests.s2t3.id]}, {type: 'suite', state: 'running', suite: tests.s2}, {type: 'test', state: 'running', test: tests.s2t3}, @@ -423,7 +366,7 @@ describe('C2TestAdapter', function() { test: tests.s2t3, decorations: [{line: 20, message: 'Expanded: false'}], message: - 'Randomness seeded to: 2\nDuration: 0.000199 second(s)\n>>> s2t3(line: 19) REQUIRE (line: 21) \n Original:\n std::false_type::value\n Expanded:\n false\n<<<\n' + '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: tests.s2}, {type: 'finished'}, @@ -432,36 +375,14 @@ describe('C2TestAdapter', function() { }); // it('run: 1 test (fails)' - it('run: 1 test (fails) with chunks', function() { - const stdout = new Stream.Readable(); - const spawnEvent: any = new EventEmitter(); - spawnEvent.stdout = stdout; - stdout.on('end', () => { - spawnEvent.emit('close', 1); - }); - - spawnStub - .withArgs( - tests.s2.execPath, - [ - tests.s2t3.testNameFull, '--reporter', 'xml', '--durations', - 'yes' - ], - tests.s1.execOptions) - .returns(spawnEvent); - - s2HeaderXml.split('\n').forEach((l: string) => {stdout.push(l)}); - s2t3Xml.split('\n').forEach((l: string) => {stdout.push(l)}); - stdout.push( - ' \n'); - stdout.push(' \n'); - stdout.push( - ' \n'); - stdout.push('\n'); - stdout.push(null); + it('run: s2t3 with chunks', function() { + const withArgs = spawnStub.withArgs( + tests.s2.execPath, example1.suite2.t3.outputs[0][0]); + withArgs.onCall(withArgs.callCount) + .returns(new ChildProcessStub(example1.suite2.t3.outputs[0][1])); return adapter.run([tests.s2t3.id]).then(() => { - assert.deepEqual(testStatesEvents, [ + assert.deepStrictEqual(testStatesEvents, [ {type: 'started', tests: [tests.s2t3.id]}, {type: 'suite', state: 'running', suite: tests.s2}, {type: 'test', state: 'running', test: tests.s2t3}, @@ -471,7 +392,7 @@ describe('C2TestAdapter', function() { test: tests.s2t3, decorations: [{line: 20, message: 'Expanded: false'}], message: - 'Randomness seeded to: 2\nDuration: 0.000199 second(s)\n>>> s2t3(line: 19) REQUIRE (line: 21) \n Original:\n std::false_type::value\n Expanded:\n false\n<<<\n' + '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: tests.s2}, {type: 'finished'}, @@ -481,23 +402,8 @@ describe('C2TestAdapter', function() { // it('run: 1 test (fails) with chunks' it('run: suite1 (1 succ 1 fails)', function() { - const stdout = new Stream.Readable(); - const spawnEvent: any = new EventEmitter(); - spawnEvent.stdout = stdout; - stdout.on('end', () => { - spawnEvent.emit('close', 1); - }); - stdout.push(s1Xml); - stdout.push(null); - - spawnStub - .withArgs( - tests.s1.execPath, ['--reporter', 'xml', '--durations', 'yes'], - tests.s1.execOptions) - .returns(spawnEvent); - return adapter.run([tests.s1.id]).then(() => { - assert.deepEqual(testStatesEvents, [ + assert.deepStrictEqual(testStatesEvents, [ {type: 'started', tests: [tests.s1.id]}, {type: 'suite', state: 'running', suite: tests.s1}, {type: 'test', state: 'running', test: tests.s1t1}, @@ -506,7 +412,7 @@ describe('C2TestAdapter', function() { state: 'passed', test: tests.s1t1, decorations: undefined, - message: 'Randomness seeded to: 2\nDuration: 0.000174 second(s)\n' + message: 'Duration: 0.000132 second(s)\n' }, {type: 'test', state: 'running', test: tests.s1t2}, { @@ -515,7 +421,7 @@ describe('C2TestAdapter', function() { test: tests.s1t2, decorations: [{line: 14, message: 'Expanded: false'}], message: - 'Randomness seeded to: 2\nDuration: 0.000255 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: tests.s1}, {type: 'finished'}, @@ -525,63 +431,20 @@ describe('C2TestAdapter', function() { // it('run: suite1 (1 succ 1 fails)' it('run: root (at least 2 slots)', function() { - { - const stdout = new Stream.Readable(); - const spawnEvent: any = new EventEmitter(); - spawnEvent.stdout = stdout; - stdout.on('end', () => { - spawnEvent.emit('close', 1); - }); - stdout.push(s1Xml); - stdout.push(null); - - spawnStub - .withArgs( - tests.s1.execPath, - ['--reporter', 'xml', '--durations', 'yes'], - tests.s1.execOptions) - .returns(spawnEvent); - } - { - const stdout = new Stream.Readable(); - const spawnEvent: any = new EventEmitter(); - spawnEvent.stdout = stdout; - stdout.on('end', () => { - spawnEvent.emit('close', 1); - }); - stdout.push(s2Xml); - stdout.push(null); - - spawnStub - .withArgs( - tests.s2.execPath, - ['--reporter', 'xml', '--durations', 'yes'], - tests.s2.execOptions) - .returns(spawnEvent); - } - return adapter.run([tests.root.id]).then(() => { - assert.deepEqual( + assert.deepStrictEqual( {type: 'started', tests: [tests.root.id]}, testStatesEvents[0]); - assert.deepEqual( + assert.deepStrictEqual( {type: 'finished'}, testStatesEvents[testStatesEvents.length - 1]); - const findIndex = function(o: any) { - const i = testStatesEvents.findIndex((v) => { - return deepEqual(o, v); - }); - assert.notEqual(i, -1, 'findIndex failed to find: ' + inspect(o)); - return i; - }; - const s1running = {type: 'suite', state: 'running', suite: tests.s1}; const s1finished = { type: 'suite', state: 'completed', suite: tests.s1 }; - assert.ok(findIndex(s1running) < findIndex(s1finished)); + assert.ok(testStatesEvI(s1running) < testStatesEvI(s1finished)); const s2running = {type: 'suite', state: 'running', suite: tests.s2}; const s2finished = { @@ -589,31 +452,31 @@ describe('C2TestAdapter', function() { state: 'completed', suite: tests.s2 }; - assert.ok(findIndex(s2running) < findIndex(s2finished)); + assert.ok(testStatesEvI(s2running) < testStatesEvI(s2finished)); const s1t1running = { type: 'test', state: 'running', test: tests.s1t1 }; - assert.ok(findIndex(s1running) < findIndex(s1t1running)); + assert.ok(testStatesEvI(s1running) < testStatesEvI(s1t1running)); const s1t1finished = { type: 'test', state: 'passed', test: tests.s1t1, decorations: undefined, - message: 'Randomness seeded to: 2\nDuration: 0.000174 second(s)\n' + message: 'Duration: 0.000132 second(s)\n' }; - assert.ok(findIndex(s1t1running) < findIndex(s1t1finished)); - assert.ok(findIndex(s1t1finished) < findIndex(s1finished)); + assert.ok(testStatesEvI(s1t1running) < testStatesEvI(s1t1finished)); + assert.ok(testStatesEvI(s1t1finished) < testStatesEvI(s1finished)); const s1t2running = { type: 'test', state: 'running', test: tests.s1t2 }; - assert.ok(findIndex(s1running) < findIndex(s1t2running)); + assert.ok(testStatesEvI(s1running) < testStatesEvI(s1t2running)); const s1t2finished = { type: 'test', @@ -621,49 +484,49 @@ describe('C2TestAdapter', function() { test: tests.s1t2, decorations: [{line: 14, message: 'Expanded: false'}], message: - 'Randomness seeded to: 2\nDuration: 0.000255 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' }; - assert.ok(findIndex(s1t2running) < findIndex(s1t2finished)); - assert.ok(findIndex(s1t2finished) < findIndex(s1finished)); + assert.ok(testStatesEvI(s1t2running) < testStatesEvI(s1t2finished)); + assert.ok(testStatesEvI(s1t2finished) < testStatesEvI(s1finished)); const s2t1running = { type: 'test', state: 'running', test: tests.s2t1 }; - assert.ok(findIndex(s2running) < findIndex(s2t1running)); + assert.ok(testStatesEvI(s2running) < testStatesEvI(s2t1running)); const s2t1finished = { type: 'test', state: 'passed', test: tests.s2t1, decorations: undefined, - message: 'Randomness seeded to: 2\nDuration: 0.000165 second(s)\n' + message: 'Duration: 0.00037 second(s)\n' }; - assert.ok(findIndex(s2t1running) < findIndex(s2t1finished)); - assert.ok(findIndex(s2t1finished) < findIndex(s2finished)); + assert.ok(testStatesEvI(s2t1running) < testStatesEvI(s2t1finished)); + assert.ok(testStatesEvI(s2t1finished) < testStatesEvI(s2finished)); const s2t2running = { type: 'test', state: 'running', test: tests.s2t2 }; - assert.ok(findIndex(s2running) < findIndex(s2t2running)); + assert.ok(testStatesEvI(s2running) < testStatesEvI(s2t2running)); const s2t2finished = { type: 'test', state: 'skipped', test: tests.s2t2 }; - assert.ok(findIndex(s2t2running) < findIndex(s2t2finished)); - assert.ok(findIndex(s2t2finished) < findIndex(s2finished)); + assert.ok(testStatesEvI(s2t2running) < testStatesEvI(s2t2finished)); + assert.ok(testStatesEvI(s2t2finished) < testStatesEvI(s2finished)); const s2t3running = { type: 'test', state: 'running', test: tests.s2t3 }; - assert.ok(findIndex(s2running) < findIndex(s2t3running)); + assert.ok(testStatesEvI(s2running) < testStatesEvI(s2t3running)); const s2t3finished = { type: 'test', @@ -671,77 +534,72 @@ describe('C2TestAdapter', function() { test: tests.s2t3, decorations: [{line: 20, message: 'Expanded: false'}], message: - 'Randomness seeded to: 2\nDuration: 0.000199 second(s)\n>>> s2t3(line: 19) REQUIRE (line: 21) \n Original:\n std::false_type::value\n Expanded:\n false\n<<<\n' + '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(findIndex(s2t3running) < findIndex(s2t3finished)); - assert.ok(findIndex(s2t3finished) < findIndex(s2finished)); + assert.ok(testStatesEvI(s2t3running) < testStatesEvI(s2t3finished)); + assert.ok(testStatesEvI(s2t3finished) < testStatesEvI(s2finished)); assert.equal(testStatesEvents.length, 16, inspect(testStatesEvents)); }); - }); - // it('run: root (at least 2 slots)' - - it('run: wrong xml 1', function() { - const stdout = new Stream.Readable(); - const spawnEvent: any = new EventEmitter(); - spawnEvent.stdout = stdout; - stdout.on('end', () => { - spawnEvent.emit('close', 1); - }); - const testCaseBegin = s1t1Xml.split('\n')[1]; - assert.ok(testCaseBegin.indexOf(' { - assert.deepEqual(testStatesEvents, [ - {type: 'started', tests: [tests.s1t1.id]}, - {type: 'suite', state: 'running', suite: tests.s1}, - {type: 'test', state:'running', test: tests.s1t1}, - - { - type: 'test', - state: 'failed', - test: tests.s1t1, - message: 'Unexpected test error. (Is Catch2 crashed?)\n' - }, - - //{type: 'suite', state: 'completed', suite: tests.s1}, - {type: 'finished'}, - ]); - }); - }); - // it('run: wrong xml 1' + }) + + it('run: wrong xml 1', 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( + tests.s1.execPath, example1.suite1.t1.outputs[0][0]); + withArgs.onCall(withArgs.callCount).returns(new ChildProcessStub(part)); + + await adapter.run([tests.s1t1.id]); + + const expected = [ + {type: 'started', tests: [tests.s1t1.id]}, + {type: 'suite', state: 'running', suite: tests.s1}, + {type: 'test', state: 'running', test: tests.s1t1}, + { + type: 'test', + state: 'failed', + test: tests.s1t1, + message: 'Unexpected test error. (Is Catch2 crashed?)\n' + }, + {type: 'suite', state: 'completed', suite: tests.s1}, + {type: 'finished'}, + ]; + assert.deepStrictEqual(testStatesEvents, expected); + + // this tests the sinon stubs too + await adapter.run([tests.s1t1.id]); + assert.deepStrictEqual(testStatesEvents, [ + ...expected, + {type: 'started', tests: [tests.s1t1.id]}, + {type: 'suite', state: 'running', suite: tests.s1}, + {type: 'test', state: 'running', test: tests.s1t1}, + { + type: 'test', + state: 'passed', + test: tests.s1t1, + decorations: undefined, + message: 'Duration: 0.000112 second(s)\n' + }, + {type: 'suite', state: 'completed', suite: tests.s1}, + {type: 'finished'}, + ]); + }) it('cancel: empty', function() { adapter.cancel(); - }); - // it('cancel: empty' + }) it('cancel', function() { const suite1Kill = sinon.spy(); const suite2Kill = sinon.spy(); { - const stdout = new Stream.Readable(); - const spawnEvent: any = new EventEmitter(); + const spawnEvent = + new ChildProcessStub(example1.suite1.outputs[2][1]); spawnEvent.kill = suite1Kill; - spawnEvent.stdout = stdout; - stdout.on('end', () => { - spawnEvent.emit('close', 1); - }); - stdout.push(s1Xml); - stdout.push(null); - spawnStub .withArgs( tests.s1.execPath, @@ -750,16 +608,9 @@ describe('C2TestAdapter', function() { .returns(spawnEvent); } { - const stdout = new Stream.Readable(); - const spawnEvent: any = new EventEmitter(); + const spawnEvent = + new ChildProcessStub(example1.suite2.outputs[2][1]); spawnEvent.kill = suite2Kill; - spawnEvent.stdout = stdout; - stdout.on('end', () => { - spawnEvent.emit('close', 1); - }); - stdout.push(s2Xml); - stdout.push(null); - spawnStub .withArgs( tests.s2.execPath, @@ -770,29 +621,21 @@ describe('C2TestAdapter', function() { const run = adapter.run([tests.root.id]); adapter.cancel(); run.then(() => { - assert.deepEqual( + assert.deepStrictEqual( testStatesEvents, [{type: 'started', tests: [tests.root.id]}, {type: 'finished'}]); assert.equal(suite1Kill.callCount, 1); assert.equal(suite2Kill.callCount, 1); }); - }); - // it('cancel' + }) it('cancel: after run finished', function() { const suite1Kill = sinon.spy(); const suite2Kill = sinon.spy(); { - const stdout = new Stream.Readable(); - const spawnEvent: any = new EventEmitter(); + const spawnEvent = + new ChildProcessStub(example1.suite1.outputs[2][1]); spawnEvent.kill = suite1Kill; - spawnEvent.stdout = stdout; - stdout.on('end', () => { - spawnEvent.emit('close', 1); - }); - stdout.push(s1Xml); - stdout.push(null); - spawnStub .withArgs( tests.s1.execPath, @@ -801,16 +644,9 @@ describe('C2TestAdapter', function() { .returns(spawnEvent); } { - const stdout = new Stream.Readable(); - const spawnEvent: any = new EventEmitter(); + const spawnEvent = + new ChildProcessStub(example1.suite2.outputs[2][1]); spawnEvent.kill = suite2Kill; - spawnEvent.stdout = stdout; - stdout.on('end', () => { - spawnEvent.emit('close', 1); - }); - stdout.push(s2Xml); - stdout.push(null); - spawnStub .withArgs( tests.s2.execPath, @@ -824,19 +660,813 @@ describe('C2TestAdapter', function() { assert.equal(suite1Kill.callCount, 0); assert.equal(suite2Kill.callCount, 0); }); + }) + }) + }) + + describe('executables:', function() { + this.slow(150); + const cwd = path.join(process.cwd(), 'out', 'test'); + + afterEach(() => { + disposeAdapterAndSubscribers(); + stubsResetToThrow(); + return resetConfig(); + }); + + const updateAndVerify = (value: any, expected: any[]) => { + return getConfig() + .update('executables', value) + .then(() => { + const adapter = createAdapterAndSubscribe(); + + const verifyIsCatch2TestExecutable = + sinonSandbox.stub(adapter, 'verifyIsCatch2TestExecutable'); + verifyIsCatch2TestExecutable.returns(Promise.resolve(true)); + + const loadSuiteMock = sinon.expectation.create('loadSuiteMock'); + loadSuiteMock.exactly(expected.length).returns(Promise.resolve()) + sinonSandbox.replace(adapter, 'loadSuite', loadSuiteMock); + + return adapter.load().then(() => { + return loadSuiteMock; + }); + }) + .then((loadSuiteMock) => { + assert.equal(testsEvents.length, 2); + loadSuiteMock.verify(); + const calls = loadSuiteMock.getCalls(); + const args = calls.map((call: any) => { + const arg = call.args[0]; + const filteredKeys = + Object.keys(arg.env).filter(k => k.startsWith('C2TEST')); + const newEnv: {[prop: string]: string} = {}; + filteredKeys.forEach((k: string) => { + newEnv[k] = arg.env[k]; + }) + arg.env = newEnv; + return arg; + }); + assert.deepEqual(args, expected); + }); + }; + + it('"exe1.exe"', () => { + return updateAndVerify('exe1.exe', [{ + name: 'exe1.exe', + path: path.join(cwd, 'exe1.exe'), + regex: '', + cwd: cwd, + env: {} + }]); + }); + + it('["exe1.exe", "exe2.exe"]', () => { + return updateAndVerify(['exe1.exe', 'exe2.exe'], [ + { + name: 'exe1.exe', + path: path.join(cwd, 'exe1.exe'), + regex: '', + cwd: cwd, + env: {} + }, + { + name: 'exe2.exe', + path: path.join(cwd, 'exe2.exe'), + regex: '', + cwd: cwd, + env: {} + } + ]); + }); + + it('{path: "path1"}', () => { + return updateAndVerify({path: 'path1'}, [{ + name: '${dirname} : ${name}', + path: path.join(cwd, 'path1'), + regex: '', + cwd: cwd, + env: {} + }]); + }); + }) + + describe('load: example1', function() { + before(() => { + for (let suite of example1.outputs) { + for (let scenario of suite[1]) { + spawnStub.withArgs(suite[0], scenario[0]).callsFake(() => { + return new ChildProcessStub(scenario[1]); + }); + } + } + + const exists = (path: string) => { + return example1.outputs.findIndex((v) => { + return v[0] == path; + }) != -1; + }; + + fsExistsStub.withArgs(workspaceFolderMatcher) + .callsFake(function( + path: string, cb: (err: any, exists: boolean) => void) { + cb(undefined, exists(path)); + }); + + fsWatchStub.withArgs(workspaceFolderMatcher).callsFake((path: string) => { + if (exists(path)) { + const ee = new class extends EventEmitter { + close() {} + }; + watchEvents.set(path, ee); + return ee; + } else { + throw Error('File not found?'); + } + }); + + 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); + children.push(path.basename(p[0])); + } + + dirContent.forEach((v: string[], k: string) => { + c2fsReaddirSyncStub.withArgs(k).returns(v); }); - // it('cancel: after run finished' + + c2fsStatSyncStub.callsFake((p: string) => { + if (dirContent.has(p)) + return { + isFile() { + return false; + }, + isDirectory() { + return true; + } + }; + const pa = dirContent.get(path.dirname(p)); + if (pa != undefined && pa.indexOf(path.basename(p)) != -1) + return { + isFile() { + return true; + }, + isDirectory() { + return false; + } + }; + throw Error(inspect(['c2fsStatSyncStub', p])); + }); + }) + + after(() => { + stubsResetToThrow(); + }) + + const uniqueIdC = new Set(); + const watchEvents: Map = new Map(); + let adapter: TestAdapter; + let root: TestSuiteInfo; + + 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(); + + example1.assertWithoutChildren(root, uniqueIdC); + assert.deepStrictEqual(testStatesEvents, []); + }); + + afterEach(() => { + uniqueIdC.clear(); + watchEvents.clear(); + disposeAdapterAndSubscribers(); }); - // describe('load: example1' + + context('executables="execPath1"', function() { + before(() => { + return updateConfig('executables', 'execPath1'); + }); + + after(() => { + return updateConfig('executables', undefined); + }); + + let suite1: TestSuiteInfo; + let s1t1: TestInfo; + let s1t2: TestInfo; + + 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.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]); + }); + + 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); + + 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}, + { + 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([root.id]); + assert.deepStrictEqual(testStatesEvents, [...expected, ...expected]); + }); + + it('cancels without any problem', async function() { + adapter.cancel(); + assert.deepStrictEqual(testsEvents, []); + assert.deepStrictEqual(testStatesEvents, []); + + 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); + + adapter.cancel(); + assert.deepStrictEqual(testsEvents, []); + assert.deepStrictEqual(testStatesEvents, expected); + }); + + context('with config: defaultRngSeed=2', function() { + before(() => { + return updateConfig('defaultRngSeed', 2); + }) + + after(() => { + 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: '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); + + await adapter.run([s1t1.id]); + assert.deepStrictEqual(testStatesEvents, [...expected, ...expected]); + }) + }) + }) + + context('executables=["execPath1", "${workspaceFolder}/execPath2"]', () => { + before(() => { + return updateConfig( + 'executables', ['execPath1', '${workspaceFolder}/execPath2']); + }); + + after(() => { + return updateConfig('executables', undefined); + }); + + let suite1: TestSuiteInfo; + let s1t1: TestInfo; + let s1t2: TestInfo; + let suite2: TestSuiteInfo; + let s2t1: TestInfo; + let s2t2: TestInfo; + let s2t3: TestInfo; + + beforeEach(async function() { + assert.deepStrictEqual( + getConfig().get('executables'), + ['execPath1', '${workspaceFolder}/execPath2']); + assert.equal(root.children.length, 2); + + assert.equal(root.children[0].type, 'suite'); + assert.equal(root.children[1].type, 'suite'); + suite1 = root.children[0]; + suite2 = root.children[1]; + if (suite2.label == 'execPath1') { + suite1 = root.children[1]; + suite2 = 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]; + + example1.suite2.assert( + path.join(workspaceFolderUri.path, 'execPath2'), + ['s2t1', 's2t2 [.]', 's2t3'], suite2, uniqueIdC); + 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 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() { + 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]); + }); + + 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); + + await adapter.run([suite1.id]); + assert.deepStrictEqual(testStatesEvents, [...expected, ...expected]); + }); + + it('should run all', async function() { + 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)); + }) + }) + + context('executables=[{...}] and env={...}', function() { + before(async () => { + 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 () => { + await updateConfig('executables', undefined); + await updateConfig('defaultEnv', undefined); + }); + + let suite1: TestSuiteInfo; + let s1t1: TestInfo; + let s1t2: TestInfo; + let suite2: TestSuiteInfo; + let s2t1: TestInfo; + let s2t2: TestInfo; + let s2t3: TestInfo; + + beforeEach(async function() { + assert.equal(root.children.length, 2); + + assert.equal(root.children[0].type, 'suite'); + assert.equal(root.children[1].type, 'suite'); + suite1 = root.children[0]; + suite2 = root.children[1]; + + example1.suite1.assert( + ': execPath1 (' + workspaceFolderUri.path + ')', ['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]; + + example1.suite2.assert( + ': execPath2 (' + workspaceFolderUri.path + ')', + ['s2t1', 's2t2 [.]', 's2t3'], suite2, uniqueIdC); + 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 all', async function() { + 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)); + }) + + it('should get execution options', async function() { + let called1 = false; + spawnStub + .withArgs( + example1.suite1.execPath, sinon.match.any, sinon.match.any) + .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'); + called1 = true; + return new ChildProcessStub(example1.suite1.outputs[2][1]); + }); + await adapter.run([suite1.id]); + assert.ok(called1); + + let called2 = false; + spawnStub + .withArgs( + example1.suite2.execPath, sinon.match.any, sinon.match.any) + .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'); + called2 = true; + return new ChildProcessStub(example1.suite2.outputs[2][1]); + }); + await adapter.run([suite2.id]); + assert.ok(called2); + }); + }) + }) +}) + +describe.skip('a', function() { + this.timeout(99999); + + before(() => { + debugger; + }); + beforeEach(() => { + debugger; }); - // describe('adapter:' -}); -// describe('C2TestAdapter' + after(() => { + debugger; + }); + afterEach(() => { + debugger; + }); + + it('a-it', () => { + debugger; + }); + + describe('b', () => { + before(() => { + debugger; + }); + beforeEach(() => { + debugger; + }); + + after(() => { + debugger; + }); + afterEach(() => { + debugger; + }); + + it('b-it1', () => { + debugger; + }); + + it('b-it2', () => { + debugger; + }); + }); +}); // fswatcher test aztan atiras vscode workspace watcherre // bonyolultabb teszteset parsoleasa de az mehet kulon fileba c2testinfo // mock getExecutables regex meg sima minden test -// cancel test // ExecutableConfig // execOptions -// writing xml \ No newline at end of file +// writing xml +// re-load soame object +// deepstrictequal \ No newline at end of file diff --git a/src/test/FsWrapper.test.ts b/src/test/FsWrapper.test.ts new file mode 100644 index 00000000..a6ea0b33 --- /dev/null +++ b/src/test/FsWrapper.test.ts @@ -0,0 +1,23 @@ +import * as assert from 'assert'; +import {spawnAsync} from '../FsWrapper'; + +describe('FsWrapper.spawnAsync', function() { + it('echoes', async function() { + const r = await spawnAsync('echo', ['apple']); + assert.equal(r.stdout, 'apple\n'); + assert.equal(r.output.length, 1); + assert.equal(r.output[0], 'apple\n'); + assert.equal(r.status, 0); + }) + + it.skip('sleeps', async function() { + this.timeout(1100); + this.slow(1050); + if (process.platform === 'darwin') { + const r = await spawnAsync('sleep', ['1']); + assert.equal(r.stdout, ''); + assert.equal(r.output.length, 0); + assert.equal(r.status, 0); + } + }) +}); \ No newline at end of file diff --git a/src/test/Helpers.ts b/src/test/Helpers.ts new file mode 100644 index 00000000..5ca63c21 --- /dev/null +++ b/src/test/Helpers.ts @@ -0,0 +1,40 @@ +import {EventEmitter} from 'events'; +import {Stream} from 'stream'; + +export class ChildProcessStub extends EventEmitter { + readonly stdout = new Stream.Readable(); + + constructor(data?: string|Iterable) { + super(); + this.stdout.on('end', () => { + this.emit('close', 1); + }); + if (data != undefined) { + if (typeof data != 'string') { + for (let line of data) { + this.stdout.push(line); + } + this.stdout.push(null); + } else { + this.writeAndClose(data); + } + } + } + + kill() { + this.stdout.emit('end'); + } + + writeAndClose(data: string): void { + this.stdout.push(data); + this.stdout.push(null); + } + + writeLineByLineAndClose(data: string): void { + const lines = data.split('\n'); + lines.forEach((l) => { + this.stdout.push(l); + }); + this.stdout.push(null); + } +}; \ No newline at end of file diff --git a/src/test/example1.ts b/src/test/example1.ts new file mode 100644 index 00000000..50783198 --- /dev/null +++ b/src/test/example1.ts @@ -0,0 +1,484 @@ +import * as assert from 'assert'; +import * as path from 'path'; +import * as vscode from 'vscode'; +import {TestInfo, TestSuiteInfo} from 'vscode-test-adapter-api'; + +assert.notEqual(vscode.workspace.workspaceFolders, undefined); +assert.equal(vscode.workspace.workspaceFolders!.length, 1); + +const workspaceFolderUri = vscode.workspace.workspaceFolders![0].uri; + +export const example1 = new class { + readonly suite1 = new class { + readonly execPath = path.join(workspaceFolderUri.path, 'execPath1'); + + readonly t1 = new class { + readonly fullTestName = 's1t1'; + assert(label: string, test: TestInfo, uniqeIdContainer?: Set) { + assert.equal(test.type, 'test'); + assert.equal(test.label, label); + assert.equal( + test.file, path.join(workspaceFolderUri.path, 'suite1.cpp')); + assert.equal(test.line, 7 - 1); + assert.ok(test.skipped == undefined || test.skipped === false); + if (uniqeIdContainer != undefined) { + assert.ok(!uniqeIdContainer.has(test.id)); + uniqeIdContainer.add(test.id); + } + }; + + readonly outputs: [string[], string][] = [ + [ + ['s1t1', '--reporter', 'xml', '--durations', 'yes'], + ` + + + + + + + + + ` + ], + [ + [ + 's1t1', '--reporter', 'xml', '--durations', 'yes', '--rng-seed', '2' + ], + ` + + + + + + + + + + ` + ], + ]; + }; + + readonly t2 = new class { + readonly fullTestName = 's1t2'; + assert(label: string, test: TestInfo, uniqeIdContainer?: Set) { + assert.equal(test.type, 'test'); + assert.equal(test.label, label); + assert.equal( + test.file, path.join(workspaceFolderUri.path, 'suite1.cpp')); + assert.equal(test.line, 13 - 1); + assert.ok(test.skipped == undefined || test.skipped === false); + if (uniqeIdContainer != undefined) { + assert.ok(!uniqeIdContainer.has(test.id)); + uniqeIdContainer.add(test.id); + } + }; + + readonly outputs: [string[], string][] = [ + [ + ['s1t2', '--reporter', 'xml', '--durations', 'yes'], + ` + + + + + + std::false_type::value + + + false + + + + + + + + ` + ], + [ + [ + 's1t2', '--reporter', 'xml', '--durations', 'yes', '--rng-seed', '2' + ], + ` + + + + + + + std::false_type::value + + + false + + + + + + + + ` + ] + ]; + }; + + readonly outputs: [string[], string][] = [ + [['--help'], 'Catch v2.'], + [ + ['[.],*', '--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' + ], + [ + ['--reporter', 'xml', '--durations', 'yes'], + ` + + + + + + + + + std::false_type::value + + + false + + + + + + + + ` + ], + [ + ['--reporter', 'xml', '--durations', 'yes', '--rng-seed', '2'], + ` + + + + + + + + + + std::false_type::value + + + false + + + + + + + + ` + ], + ...this.t1.outputs, + ...this.t2.outputs, + ]; + + assert( + 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')); + 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); + this.t2.assert( + childLabels[1], suite.children[1], uniqeIdContainer); + if (uniqeIdContainer != undefined) { + assert.ok(!uniqeIdContainer.has(suite.id)); + uniqeIdContainer.add(suite.id); + } + } + }; + + readonly suite2 = new class { + readonly execPath = path.join(workspaceFolderUri.path, 'execPath2'); + + readonly t1 = new class { + readonly fullTestName = 's2t1'; + assert(label: string, test: TestInfo, uniqeIdContainer?: Set) { + assert.equal(test.type, 'test'); + assert.equal(test.label, label); + assert.equal( + test.file, path.join(workspaceFolderUri.path, 'suite2.cpp')); + assert.equal(test.line, 7 - 1); + assert.ok(test.skipped == undefined || test.skipped === false); + if (uniqeIdContainer != undefined) { + assert.ok(!uniqeIdContainer.has(test.id)); + uniqeIdContainer.add(test.id); + } + } + + readonly outputs: [string[], string][] = [ + [ + ['s2t1', '--reporter', 'xml', '--durations', 'yes'], + ` + + + + + + + + + ` + ], + [ + [ + 's2t1', '--reporter', 'xml', '--durations', 'yes', '--rng-seed', '2' + ], + ` + + + + + + + + + + ` + ] + ]; + }; + + readonly t2 = new class { + readonly fullTestName = 's2t2'; + assert(label: string, test: TestInfo, uniqeIdContainer?: Set) { + assert.equal(test.type, 'test'); + assert.equal(test.label, label); + assert.equal( + test.file, path.join(workspaceFolderUri.path, 'suite2.cpp')); + assert.equal(test.line, 13 - 1); + assert.ok(test.skipped === true); + if (uniqeIdContainer != undefined) { + assert.ok(!uniqeIdContainer.has(test.id)); + uniqeIdContainer.add(test.id); + } + } + + readonly outputs: [string[], string][] = [ + [ + ['s2t2', '--reporter', 'xml', '--durations', 'yes'], + ` + + + + + + + + + ` + ], + [ + [ + 's2t2', '--reporter', 'xml', '--durations', 'yes', '--rng-seed', '2' + ], + ` + + + + + + + + + + ` + ] + ]; + }; + + readonly t3 = new class { + readonly fullTestName = 's2t3'; + assert(label: string, test: TestInfo, uniqeIdContainer?: Set) { + assert.equal(test.type, 'test'); + assert.equal(test.label, label); + assert.equal( + test.file, path.join(workspaceFolderUri.path, 'suite2.cpp')); + assert.equal(test.line, 19 - 1); + assert.ok(test.skipped == undefined || test.skipped === false); + if (uniqeIdContainer != undefined) { + assert.ok(!uniqeIdContainer.has(test.id)); + uniqeIdContainer.add(test.id); + } + } + + readonly outputs: [string[], string][] = [ + [ + ['s2t3', '--reporter', 'xml', '--durations', 'yes'], + ` + + + + + + std::false_type::value + + + false + + + + + + + + ` + ], + [ + [ + 's2t3', '--reporter', 'xml', '--durations', 'yes', '--rng-seed', '2' + ], + ` + + + + + + + std::false_type::value + + + false + + + + + + + + ` + ] + ]; + }; + + assert( + 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')); + 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); + this.t2.assert( + childLabels[1], suite.children[1], uniqeIdContainer); + this.t3.assert( + childLabels[2], suite.children[2], uniqeIdContainer); + if (uniqeIdContainer != undefined) { + assert.ok(!uniqeIdContainer.has(suite.id)); + uniqeIdContainer.add(suite.id); + } + } + + readonly outputs: [string[], string][] = [ + [['--help'], 'Catch v2.'], + [ + ['[.],*', '--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' + ], + [ + ['--reporter', 'xml', '--durations', 'yes'], + ` + + + + + + + + + std::false_type::value + + + false + + + + + + + + ` + ], + [ + ['--reporter', 'xml', '--durations', 'yes', '--rng-seed', '2'], + ` + + + + + + + + + + std::false_type::value + + + false + + + + + + + + ` + ], + ...this.t1.outputs, ...this.t2.outputs, ...this.t3.outputs + ]; + }; + + assertWithoutChildren(root: TestSuiteInfo, uniqeIdContainer?: Set) { + assert.equal(root.type, 'suite'); + assert.equal(root.label, 'AllTests'); + assert.equal(root.file, undefined); + assert.equal(root.line, undefined); + if (uniqeIdContainer != undefined) { + assert.ok(!uniqeIdContainer.has(root.id)); + uniqeIdContainer.add(root.id); + } + }; + + readonly outputs: [string, [string[], string][]][] = [ + [this.suite1.execPath, this.suite1.outputs], + [this.suite2.execPath, this.suite2.outputs] + ]; +}; \ No newline at end of file