From 4065368e53f725ce4d1ec0cfa150969d2f58b810 Mon Sep 17 00:00:00 2001 From: Mate Pek Date: Sun, 21 Oct 2018 09:44:57 +0200 Subject: [PATCH] more tests --- CHANGELOG.md | 4 + package-lock.json | 18 +- package.json | 4 +- src/C2TestAdapter.ts | 26 +- src/C2TestSuiteInfo.ts | 128 +-- src/FsWrapper.ts | 47 + src/test/C2TestAdapter.test.ts | 1570 ++++++++++++++++---------------- src/test/FsWrapper.test.ts | 23 + src/test/Helpers.ts | 40 + src/test/example1.ts | 484 ++++++++++ 10 files changed, 1494 insertions(+), 850 deletions(-) create mode 100644 src/FsWrapper.ts create mode 100644 src/test/FsWrapper.test.ts create mode 100644 src/test/Helpers.ts create mode 100644 src/test/example1.ts 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/package-lock.json b/package-lock.json index 9a2fa10b..f21a8291 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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 4d71aaa8..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", diff --git a/src/C2TestAdapter.ts b/src/C2TestAdapter.ts index 9ad00363..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 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; @@ -458,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()) { @@ -507,20 +505,8 @@ export class C2TestAdapter implements TestAdapter, vscode.Disposable { } 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); - } + 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 9ffb9be2..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'; @@ -269,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].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]; - 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 d903f0c1..dd4099d7 100644 --- a/src/test/C2TestAdapter.test.ts +++ b/src/test/C2TestAdapter.test.ts @@ -1,20 +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'; import * as fse from 'fs-extra'; import * as assert from 'assert'; -import {EventEmitter} from 'events'; 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} from 'util'; import {C2AllTestSuiteInfo} from '../C2AllTestSuiteInfo'; import {C2TestAdapter} from '../C2TestAdapter'; -import {Stream} from 'stream'; -import {inspect} from 'util'; +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); @@ -23,6 +29,11 @@ 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'); @@ -30,266 +41,30 @@ const dotVscodePath = path.join(workspaceFolderUri.path, '.vscode'); const sinonSandbox = sinon.createSandbox(); -const example1 = new class { - readonly suite1 = new class { - readonly execPath = path.join(workspaceFolderUri.path, 'execPath1'); - readonly testList = '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'; - - readonly t1 = new class { - readonly fullTestName = 's1t1'; - assert(test: TestInfo, uniqeIdContainer?: Set) { - assert.equal(test.type, 'test'); - assert.equal(test.label, 's1t1'); - 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 xml = ` - - - `; - }; - - readonly t2 = new class { - readonly fullTestName = 's1t2'; - assert(test: TestInfo, uniqeIdContainer?: Set) { - assert.equal(test.type, 'test'); - assert.equal(test.label, 's1t2'); - 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 xml = ` - - - - std::false_type::value - - - false - - - - `; - }; - - assert(suite: TestSuiteInfo, uniqeIdContainer?: Set) { - assert.equal(suite.type, 'suite'); - assert.equal(suite.label, 'execPath1'); - assert.equal( - suite.file, path.join(workspaceFolderUri.path, 'suite1.cpp')); - assert.equal(suite.line, 0); - assert.equal(suite.children.length, 2); - this.t1.assert(suite.children[0], uniqeIdContainer); - this.t2.assert(suite.children[1], uniqeIdContainer); - if (uniqeIdContainer != undefined) { - assert.ok(!uniqeIdContainer.has(suite.id)); - uniqeIdContainer.add(suite.id); - } - } - - readonly xmlHeader = ` - - - `; - - readonly xmlFull = this.xmlHeader + this.t1.xml + this.t2.xml + ` - - - - `; - }; - - readonly suite2 = new class { - readonly execPath = path.join(workspaceFolderUri.path, 'execPath2'); - readonly testList = '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'; - - readonly t1 = new class { - readonly fullTestName = 's2t1'; - assert(test: TestInfo, uniqeIdContainer?: Set) { - assert.equal(test.type, 'test'); - assert.equal(test.label, 's2t1'); - 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 xml = ` - - - `; - }; - - readonly t2 = new class { - readonly fullTestName = 's2t2'; - assert(test: TestInfo, uniqeIdContainer?: Set) { - assert.equal(test.type, 'test'); - assert.equal(test.label, 's2t2 [.]'); - 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 xml = ` - - - `; - }; - - readonly t3 = new class { - readonly fullTestName = 's2t3'; - assert(test: TestInfo, uniqeIdContainer?: Set) { - assert.equal(test.type, 'test'); - assert.equal(test.label, 's2t3'); - 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 xml = ` - - - - std::false_type::value - - - false - - - - `; - }; - - assert(suite: TestSuiteInfo, uniqeIdContainer?: Set) { - assert.equal(suite.type, 'suite'); - assert.equal(suite.label, 'execPath2'); - assert.equal( - suite.file, path.join(workspaceFolderUri.path, 'suite2.cpp')); - assert.equal(suite.line, 0); - assert.equal(suite.children.length, 3); - this.t1.assert(suite.children[0], uniqeIdContainer); - this.t2.assert(suite.children[1], uniqeIdContainer); - this.t3.assert(suite.children[2], uniqeIdContainer); - if (uniqeIdContainer != undefined) { - assert.ok(!uniqeIdContainer.has(suite.id)); - uniqeIdContainer.add(suite.id); - } - } - - readonly xmlHeader = ` - - - `; - - readonly xmlFull = this.xmlHeader + this.t1.xml + - /* this.t2.xml is skipped */ this.t3.xml + ` - - - - `; - }; +/// - 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); - } +describe('C2TestAdapter', function() { + function getConfig() { + return vscode.workspace.getConfiguration( + 'catch2TestExplorer', workspaceFolderUri) }; -}; - -class ChildProcessStub extends EventEmitter { - readonly stdout = new Stream.Readable(); - - constructor(data?: string) { - super(); - this.stdout.on('end', () => { - this.emit('close', 1); - }); - if (data != undefined) this.writeAndClose(data); - } - - 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); + function updateConfig(key: string, value: any) { + return getConfig().update(key, value) } -}; - -/// - -describe('C2TestAdapter', function() { - const config = vscode.workspace.getConfiguration( - 'catch2TestExplorer', workspaceFolderUri); let adapter: C2TestAdapter|undefined; let testsEvents: (TestLoadStartedEvent|TestLoadFinishedEvent)[]; - let testsEventsConnection: vscode.Disposable; + let testsEventsConnection: vscode.Disposable|undefined; let testStatesEvents: (TestRunStartedEvent|TestRunFinishedEvent| TestSuiteEvent|TestEvent)[]; - let testStatesEventsConnection: vscode.Disposable; + let testStatesEventsConnection: vscode.Disposable|undefined; - let spawnStub: any; - let execFileStub: any; - let fsWatchStub: any; - let fsExistsStub: any; + 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( @@ -301,12 +76,30 @@ 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; } + 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); @@ -327,36 +120,52 @@ describe('C2TestAdapter', function() { } function disposeAdapterAndSubscribers() { - testsEventsConnection.dispose(); - testStatesEventsConnection.dispose(); + 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'); - execFileStub = sinonSandbox.stub(child_process, 'execFile'); 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(); }); - function resetStubs() { - spawnStub.reset(); - spawnStub.throws(); - execFileStub.reset(); - execFileStub.throws(); - fsWatchStub.reset(); - fsWatchStub.throws(); - fsExistsStub.reset(); - fsExistsStub.throws(); - } - after(() => { disposeAdapterAndSubscribers(); sinonSandbox.restore(); @@ -390,36 +199,40 @@ describe('C2TestAdapter', function() { 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', () => { const adapter = createAdapterAndSubscribe(); - assert.deepEqual(testsEvents, []); - return config.update('enableSourceDecoration', false).then(() => { + assert.deepStrictEqual(testsEvents, []); + return getConfig().update('enableSourceDecoration', false).then(() => { assert.ok(!adapter.getIsEnabledSourceDecoration()); }); }); it('defaultRngSeed', () => { const adapter = createAdapterAndSubscribe(); - assert.deepEqual(testsEvents, []); - return config.update('defaultRngSeed', 987).then(() => { + assert.deepStrictEqual(testsEvents, []); + return getConfig().update('defaultRngSeed', 987).then(() => { assert.equal(adapter.getRngSeed(), 987); }); }); @@ -435,7 +248,6 @@ describe('C2TestAdapter', function() { afterEach(() => { disposeAdapterAndSubscribers(); - resetStubs(); }); it('fill with empty config', function() { @@ -452,6 +264,20 @@ describe('C2TestAdapter', function() { describe('example1', function() { let tests: any; + 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() .then(() => { @@ -461,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, @@ -485,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( - example1.suite1.xmlHeader + example1.suite1.t1.xml + - ` - - - `); - 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}, @@ -522,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( - example1.suite1.xmlHeader + - ` - - - `); - 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(example1.suite1.xmlHeader + example1.suite2.t2.xml + ` - - - - `); - 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}, @@ -601,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'}, @@ -611,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( - example1.suite1.xmlHeader + example1.suite2.t3.xml + - ` - - - `); - 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}, @@ -646,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'}, @@ -655,38 +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); - - example1.suite1.xmlHeader.split('\n').forEach( - (l: string) => {stdout.push(l)}); - example1.suite2.t3.xml.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}, @@ -696,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'}, @@ -706,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(example1.suite1.xmlFull); - 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}, @@ -731,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}, { @@ -740,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'}, @@ -750,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(example1.suite1.xmlFull); - 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(example1.suite2.xmlFull); - 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 = { @@ -814,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', @@ -846,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', @@ -896,75 +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 = example1.suite1.t1.xml.split('\n')[1]; - assert.ok(testCaseBegin.indexOf(']+>'); + 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)); - return adapter.run([tests.s1t1.id]).then(() => { - 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' + 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(example1.suite1.xmlFull); - stdout.push(null); - spawnStub .withArgs( tests.s1.execPath, @@ -973,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(example1.suite2.xmlFull); - stdout.push(null); - spawnStub .withArgs( tests.s2.execPath, @@ -993,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(example1.suite1.xmlFull); - stdout.push(null); - spawnStub .withArgs( tests.s1.execPath, @@ -1024,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(example1.suite2.xmlFull); - stdout.push(null); - spawnStub .withArgs( tests.s2.execPath, @@ -1047,12 +660,9 @@ describe('C2TestAdapter', function() { assert.equal(suite1Kill.callCount, 0); assert.equal(suite2Kill.callCount, 0); }); - }); - // it('cancel: after run finished' - }); - // describe('example1' - }); - // describe('adapter:' + }) + }) + }) describe('executables:', function() { this.slow(150); @@ -1060,12 +670,13 @@ describe('C2TestAdapter', function() { afterEach(() => { disposeAdapterAndSubscribers(); - resetStubs(); + stubsResetToThrow(); return resetConfig(); }); const updateAndVerify = (value: any, expected: any[]) => { - return config.update('executables', value) + return getConfig() + .update('executables', value) .then(() => { const adapter = createAdapterAndSubscribe(); @@ -1074,7 +685,7 @@ describe('C2TestAdapter', function() { verifyIsCatch2TestExecutable.returns(Promise.resolve(true)); const loadSuiteMock = sinon.expectation.create('loadSuiteMock'); - loadSuiteMock.returns(Promise.resolve()).exactly(expected.length); + loadSuiteMock.exactly(expected.length).returns(Promise.resolve()) sinonSandbox.replace(adapter, 'loadSuite', loadSuiteMock); return adapter.load().then(() => { @@ -1091,7 +702,7 @@ describe('C2TestAdapter', function() { Object.keys(arg.env).filter(k => k.startsWith('C2TEST')); const newEnv: {[prop: string]: string} = {}; filteredKeys.forEach((k: string) => { - newEnv[k] = args.env[k]; + newEnv[k] = arg.env[k]; }) arg.env = newEnv; return arg; @@ -1106,7 +717,7 @@ describe('C2TestAdapter', function() { path: path.join(cwd, 'exe1.exe'), regex: '', cwd: cwd, - env: [] + env: {} }]); }); @@ -1117,14 +728,14 @@ describe('C2TestAdapter', function() { path: path.join(cwd, 'exe1.exe'), regex: '', cwd: cwd, - env: [] + env: {} }, { name: 'exe2.exe', path: path.join(cwd, 'exe2.exe'), regex: '', cwd: cwd, - env: [] + env: {} } ]); }); @@ -1135,109 +746,35 @@ describe('C2TestAdapter', function() { path: path.join(cwd, 'path1'), regex: '', cwd: cwd, - env: [] + env: {} }]); }); - }); - // describe('executables:' - - context('example1', function() { - function fakeExecFileFunc(pathAndContent: Map) { - return function( - path: string, args: string[], - cb: (err: any, stout: string, stderr: string) => void) { - const res = pathAndContent.get(path); - if (res === undefined) { - cb(new Error('fake file not exists.'), '', ''); - } else if (args.length == 1 && args[0] === '--help') { - cb(null, 'Catch v2.', ''); - } else if (deepEqual(args, [ - '[.],*', '--verbosity', 'high', '--list-tests', - '--use-colour', 'no' - ])) { - cb(null, res!, ''); - } else { - assert.ok(false, inspect([path, args])); - }; - }; - }; - - function fakeExistsFunc(pathAndContent: Map) { - return function(path: string, cb: (err: any, exists: boolean) => void) { - cb(undefined, pathAndContent.has(path)); - }; - }; + }) - function fakeSpawn() { - const testTestParams = ['--reporter', 'xml', '--durations', 'yes']; - - spawnStub - .withArgs( - example1.suite1.execPath, - [example1.suite1.t1.fullTestName, ...testTestParams]) - .callsFake(() => { - return new ChildProcessStub( - example1.suite1.xmlHeader + example1.suite1.t1.xml + - ''); - }); - spawnStub - .withArgs( - example1.suite1.execPath, - [example1.suite1.t2.fullTestName, ...testTestParams]) - .callsFake(() => { - return new ChildProcessStub( - example1.suite1.xmlHeader + example1.suite1.t2.xml + - ''); - }); - spawnStub.withArgs(example1.suite1.execPath, testTestParams) - .callsFake(() => { - return new ChildProcessStub(example1.suite1.xmlFull); - }); - - spawnStub - .withArgs( - example1.suite2.execPath, - [example1.suite2.t1.fullTestName, ...testTestParams]) - .callsFake(() => { - return new ChildProcessStub( - example1.suite2.xmlHeader + example1.suite2.t1.xml + - ''); - }); - spawnStub - .withArgs( - example1.suite2.execPath, - [example1.suite2.t2.fullTestName, ...testTestParams]) - .callsFake(() => { - return new ChildProcessStub( - example1.suite2.xmlHeader + example1.suite2.t2.xml + - ''); - }); - spawnStub - .withArgs( - example1.suite2.execPath, - [example1.suite2.t3.fullTestName, ...testTestParams]) - .callsFake(() => { - return new ChildProcessStub( - example1.suite2.xmlHeader + example1.suite2.t3.xml + - ''); - }); - spawnStub.withArgs(example1.suite2.execPath, testTestParams) - .callsFake(() => { - return new ChildProcessStub(example1.suite2.xmlFull); + 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]); }); - } + } + } - function fakeFs(pathAndContent: Iterable<[string, string]>) { - const map = new Map(pathAndContent); - execFileStub.reset(); - execFileStub.callsFake(fakeExecFileFunc(map)); + const exists = (path: string) => { + return example1.outputs.findIndex((v) => { + return v[0] == path; + }) != -1; + }; - fsExistsStub.reset(); - fsExistsStub.callsFake(fakeExistsFunc(map)); + fsExistsStub.withArgs(workspaceFolderMatcher) + .callsFake(function( + path: string, cb: (err: any, exists: boolean) => void) { + cb(undefined, exists(path)); + }); - fsWatchStub.reset(); - fsWatchStub.callsFake((path: string) => { - if (map.has(path)) { + fsWatchStub.withArgs(workspaceFolderMatcher).callsFake((path: string) => { + if (exists(path)) { const ee = new class extends EventEmitter { close() {} }; @@ -1248,71 +785,324 @@ describe('C2TestAdapter', function() { } }); - fakeSpawn(); - }; + 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); + }); + + 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; - before(() => { - fakeFs([ - [example1.suite1.execPath, example1.suite1.testList], - [example1.suite2.execPath, example1.suite2.testList] - ]); - }); + beforeEach(async function() { + adapter = createAdapterAndSubscribe(); + await adapter.load(); - beforeEach(() => { - watchEvents.clear(); - uniqueIdC.clear(); - }) + 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(); - after(() => { - resetStubs(); + example1.assertWithoutChildren(root, uniqueIdC); + assert.deepStrictEqual(testStatesEvents, []); }); - context.only('load with config: executables="execPath1"', function() { - let adapter: TestAdapter; - let root: TestSuiteInfo; + afterEach(() => { + uniqueIdC.clear(); + watchEvents.clear(); + disposeAdapterAndSubscribers(); + }); + context('executables="execPath1"', function() { before(() => { - return config.update('executables', 'execPath1'); + return updateConfig('executables', 'execPath1'); }); - beforeEach(async function() { - adapter = createAdapterAndSubscribe(); - await adapter.load(); + after(() => { + return updateConfig('executables', undefined); + }); - assert.equal(testsEvents.length, 2, inspect(testsEvents)); - assert.equal(testsEvents[1].type, 'finished'); - assert.ok((testsEvents[1]).suite); - root = (testsEvents[1]).suite!; + let suite1: TestSuiteInfo; + let s1t1: TestInfo; + let s1t2: TestInfo; - example1.assertWithoutChildren(root, uniqueIdC); + beforeEach(async function() { + assert.deepStrictEqual( + getConfig().get('executables'), 'execPath1'); assert.equal(root.children.length, 1); - example1.suite1.assert(root.children[0], uniqueIdC); + 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'}, + ]); }); - afterEach(() => { - disposeAdapterAndSubscribers(); + 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 resetConfig(); + 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.deepEqual(testStatesEvents, [ + assert.deepStrictEqual(testStatesEvents, [ {type: 'started', tests: ['not existing id']}, {type: 'finished'}, ]); }); it('should run s1t1 with success', async function() { - const suite1 = root.children[0]; - const s1t1 = suite1.children[0]; - await adapter.run([s1t1.id]); const expected = [ {type: 'started', tests: [s1t1.id]}, @@ -1323,22 +1113,18 @@ describe('C2TestAdapter', function() { state: 'passed', test: 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: suite1}, {type: 'finished'}, ]; - assert.deepEqual(testStatesEvents, expected); + assert.deepStrictEqual(testStatesEvents, expected); await adapter.run([s1t1.id]); - assert.deepEqual(testStatesEvents, [...expected, ...expected]); + assert.deepStrictEqual(testStatesEvents, [...expected, ...expected]); }); it('should run suite1', async function() { - const suite1 = root.children[0]; - const s1t1 = suite1.children[0]; - const s1t2 = suite1.children[1]; - await adapter.run([suite1.id]); const expected = [ {type: 'started', tests: [suite1.id]}, @@ -1349,7 +1135,7 @@ describe('C2TestAdapter', function() { state: 'passed', test: 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: s1t2}, { @@ -1358,20 +1144,278 @@ describe('C2TestAdapter', function() { test: 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: suite1}, {type: 'finished'}, ]; - assert.deepEqual(testStatesEvents, expected); + assert.deepStrictEqual(testStatesEvents, expected); await adapter.run([suite1.id]); - assert.deepEqual(testStatesEvents, [...expected, ...expected]); + assert.deepStrictEqual(testStatesEvents, [...expected, ...expected]); }); - }); - }); // descibe('example1' -}); -// describe('C2TestAdapter' + + 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); 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