diff --git a/CHANGELOG.md b/CHANGELOG.md index 41c3a42a..a3725422 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] +## [2.3.10] + +Performance and stability impovements. + ## [2.3.9] - 2019-01-03 ### Fixed diff --git a/README.md b/README.md index 03f16146..8a30bf06 100644 --- a/README.md +++ b/README.md @@ -22,9 +22,9 @@ and [Google Test](https://github.com/google/googletest) tests using the | `catch2TestExplorer.debugBreakOnFailure` | Debugger breaks on failure while debugging the test. Catch2: --break; Google Test: --gtest_break_on_failure; | | `catch2TestExplorer.defaultNoThrow` | Skips all assertions that test that an exception is thrown, e.g. REQUIRE_THROWS. This is a Catch2 parameter: --nothrow | | `catch2TestExplorer.defaultRngSeed` | Catch2: `--rng-seed or "time"`; Google Test: `--gtest_random_seed=`; | -| `catch2TestExplorer.defaultWatchTimeoutSec` | Test executables are being watched. In case of one compiles too much this variable can help with it. Unit: second. | -| `catch2TestExplorer.defaultRunningTimeoutSec` | Test executable is running in a process. In case of an inifinite loop, it will run forever, unless this parameter is set. | -| `catch2TestExplorer.workerMaxNumber` | The variable maximize the number of the parallel test execution. | +| `catch2TestExplorer.defaultWatchTimeoutSec` | Test executables are being watched. In case of one compiles too much this variable can help with it. Unit: second. It applies instantly. | +| `catch2TestExplorer.defaultRunningTimeoutSec` | Test executable is running in a process. In case of an inifinite loop, it will run forever, unless this parameter is set. It applies instantly. | +| `catch2TestExplorer.workerMaxNumber` | The variable maximize the number of the parallel test execution. It applies instantly. | | `testExplorer.errorDecoration` | Show error messages from test failures as decorations in the editor. [Details](https://github.com/hbenl/vscode-test-explorer#configuration) | | `testExplorer.gutterDecoration` | Show the state of each test in the editor using Gutter Decorations. [Details](https://github.com/hbenl/vscode-test-explorer#configuration) | | `testExplorer.codeLens` | Show a CodeLens above each test or suite for running or debugging the tests. [Details](https://github.com/hbenl/vscode-test-explorer#configuration) | @@ -83,7 +83,7 @@ Otherwise it only can point to an executable (No _search-pattern_!). "pattern": "{build,Build,BUILD,out,Out,OUT}/**/*{test,Test,TEST}*", "cwd": "${absDirpath}", "env": { - "ExampleENV1": "You can use variables here too, like ${absName}" + "ExampleENV1": "You can use variables here too, like ${absPath}" } } ``` diff --git a/package-lock.json b/package-lock.json index 81d21f15..2bbb3f5f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "vscode-catch2-test-adapter", - "version": "2.3.9", + "version": "2.3.10", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 0c983a63..618bd8b0 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "icon": "resources/icon.png", "author": "Mate Pek", "publisher": "matepek", - "version": "2.3.9", + "version": "2.3.10", "license": "Unlicense", "homepage": "https://github.com/matepek/vscode-catch2-test-adapter", "repository": { diff --git a/src/GoogleTestSuiteInfo.ts b/src/GoogleTestSuiteInfo.ts index d34f41c0..d67c6e41 100644 --- a/src/GoogleTestSuiteInfo.ts +++ b/src/GoogleTestSuiteInfo.ts @@ -266,7 +266,7 @@ export class GoogleTestSuiteInfo extends TestSuiteInfoBase { test: data.currentChild!, state: 'failed', }; - if (runInfo.timeout !== undefined) { + if (runInfo.timeout != undefined) { ev.message = this._getTimeoutMessage(runInfo.timeout); } else { ev.message = 'Fatal error: (Wrong Google Test output.)\nError: ' + inspect(codeOrReason) + '\n'; @@ -277,10 +277,10 @@ export class GoogleTestSuiteInfo extends TestSuiteInfoBase { } } - const isTestRemoved = (runInfo.childrenToRun === 'all' && - this.children.filter(c => !c.skipped).length > - data.processedTestCases.length) || - (runInfo.childrenToRun !== 'all' && data.processedTestCases.length == 0); + const isTestRemoved = (runInfo.timeout == undefined) + && ((runInfo.childrenToRun === 'all' + && this.children.filter(c => !c.skipped).length > data.processedTestCases.length) + || (runInfo.childrenToRun !== 'all' && data.processedTestCases.length == 0)); if (data.unprocessedTestCases.length > 0 || isTestRemoved) { this.allTests diff --git a/src/QueueGraph.ts b/src/QueueGraph.ts deleted file mode 100644 index afd995a3..00000000 --- a/src/QueueGraph.ts +++ /dev/null @@ -1,64 +0,0 @@ -//----------------------------------------------------------------------------- -// vscode-catch2-test-adapter was written by Mate Pek, and is placed in the -// public domain. The author hereby disclaims copyright to this source code. - -export class QueueGraphNode { - constructor( - public readonly name?: string, depends: Iterable = [], - private readonly _handleError?: ((reason: any) => any)) { - this._depends = [...depends]; - // TODO check circular dependency - } - - empty(): boolean { - return this._count == 0; - } - - get size(): number { - return this._count; - } - - get length(): number { - return this._count; - } - - then( - task: (() => TResult1 | PromiseLike), - taskErrorHandler?: ((reason: any) => TResult2 | PromiseLike) | - undefined | null): Promise { - this._count++; - - const previous = this._queue; - const current = Promise.all(this._depends.map(v => v._queue)) - .then(() => { - return previous.then(task); - }); - this._queue = current - .then( - (value: TResult1 | PromiseLike) => { - this._count--; - }, - (reason: any) => { - this._count--; - if (taskErrorHandler) - return taskErrorHandler(reason); - else if (this._handleError) - return this._handleError(reason); - else - throw reason; // fatal: the queue is broken - }); - - return current; - } - - dependsOn(depends: Iterable): void { - for (const dep of depends) { - this._depends.push(dep); - } - // TODO check circular dependency - } - - private _count: number = 0; - private _queue: Promise = Promise.resolve(); - private readonly _depends: Array; -} diff --git a/src/RootTestSuiteInfo.ts b/src/RootTestSuiteInfo.ts index c58e8e5b..61683929 100644 --- a/src/RootTestSuiteInfo.ts +++ b/src/RootTestSuiteInfo.ts @@ -11,7 +11,7 @@ import { TestExecutableInfo } from './TestExecutableInfo' import { TestInfoBase } from './TestInfoBase'; import { TestSuiteInfoBase } from './TestSuiteInfoBase'; import { generateUniqueId } from './IdGenerator'; -import { QueueGraphNode } from './QueueGraph'; +import { TaskQueue } from './TaskQueue'; import { TaskPool } from './TaskPool'; export class RootTestSuiteInfo implements TestSuiteInfo, vscode.Disposable { @@ -21,9 +21,10 @@ export class RootTestSuiteInfo implements TestSuiteInfo, vscode.Disposable { readonly children: TestSuiteInfoBase[] = []; private readonly _executables: TestExecutableInfo[] = []; private _isDisposed = false; + private readonly _taskPool: TaskPool; constructor( - private readonly _allTasks: QueueGraphNode, + private readonly _allTasks: TaskQueue, public readonly log: util.Log, public readonly workspaceFolder: vscode.WorkspaceFolder, private readonly _loadFinishedEmitter: vscode.EventEmitter, @@ -32,20 +33,36 @@ export class RootTestSuiteInfo implements TestSuiteInfo, vscode.Disposable { public readonly testStatesEmitter: vscode.EventEmitter, - public readonly autorunEmitter: vscode.EventEmitter, public readonly variableToValue: [string, string][], public isEnabledSourceDecoration: boolean, public rngSeed: string | number | null, public execWatchTimeout: number, - public execRunningTimeout: null | number, + private _execRunningTimeout: null | number, public isNoThrow: boolean, + workerMaxNumber: number, ) { this.label = workspaceFolder.name + ' (Catch2 and Google Test Explorer)'; this.id = generateUniqueId(); + this._taskPool = new TaskPool(workerMaxNumber); } + set workerMaxNumber(workerMaxNumber: number) { + this._taskPool.maxTaskCount = workerMaxNumber; + } + + get execRunningTimeout() { return this._execRunningTimeout; } + + set execRunningTimeout(value: null | number) { + this._execRunningTimeout = value; + this._execRunningTimeoutChangeEmitter.fire(); + } + + private readonly _execRunningTimeoutChangeEmitter = new vscode.EventEmitter(); + readonly onDidExecRunningTimeoutChanged = this._execRunningTimeoutChangeEmitter.event; + dispose() { this._isDisposed = true; + this._execRunningTimeoutChangeEmitter.dispose(); for (let i = 0; i < this._executables.length; i++) { this._executables[i].dispose(); } @@ -144,11 +161,9 @@ export class RootTestSuiteInfo implements TestSuiteInfo, vscode.Disposable { } } - run(tests: string[], workerMaxNumber: number): Promise { + run(tests: string[]): Promise { this.testStatesEmitter.fire({ type: 'started', tests: tests }); - const taskPool = new TaskPool(workerMaxNumber); - // everybody should remove what they use from it. // and put their children into if they are in it const testSet = new Set(tests); @@ -162,7 +177,7 @@ export class RootTestSuiteInfo implements TestSuiteInfo, vscode.Disposable { const ps: Promise[] = []; for (let i = 0; i < this.children.length; i++) { const child = this.children[i]; - ps.push(child.run(testSet, taskPool)); + ps.push(child.run(testSet, this._taskPool)); } if (testSet.size > 0) { diff --git a/src/TaskPool.ts b/src/TaskPool.ts index 524f9779..1f7a449f 100644 --- a/src/TaskPool.ts +++ b/src/TaskPool.ts @@ -1,19 +1,57 @@ export class TaskPool { /** - * - * @param availableSlot The available slot number. If -1 (negative) means no limit, acquire will always return true. + * @param maxTaskCount Has to be bigger than 0 or `undefined`. */ - constructor(private availableSlot: number) {} + constructor(private _maxTaskCount: number | undefined) { + if (_maxTaskCount != undefined && _maxTaskCount < 1) throw Error('invalid argument'); + } + + get maxTaskCount(): number | undefined { + return this._maxTaskCount; + } + + set maxTaskCount(maxTaskCount: number | undefined) { + if (maxTaskCount != undefined && maxTaskCount < 1) throw Error('invalid argument'); + this._maxTaskCount = maxTaskCount; + + while (this._waitingTasks.length > 0 && this._acquire()) + this._waitingTasks.pop()!(); + } - acquire(): boolean { - if (this.availableSlot < 0) return true; - if (this.availableSlot == 0) return false; - this.availableSlot -= 1; - return true; + scheduleTask(task: () => TResult | PromiseLike): Promise { + return new Promise(resolve => { + if (this._acquire()) + resolve(); + else + this._waitingTasks.unshift(resolve); + }).then(task) + .then( + (v: TResult) => { + this._release(); + return v; + }, + (reason?: any) => { + this._release(); + throw reason; + }); } - release(): void { - if (this.availableSlot < 0) return; - this.availableSlot += 1; + private _runningTaskCount: number = 0; + private readonly _waitingTasks: (() => void)[] = []; + + private _acquire(): boolean { + if (this._maxTaskCount === undefined || this._runningTaskCount < this._maxTaskCount) { + this._runningTaskCount += 1; + return true; + } else { + return false; + } + } + + private _release(): void { + this._runningTaskCount -= 1; + + while (this._waitingTasks.length > 0 && this._acquire()) + this._waitingTasks.pop()!(); } } diff --git a/src/TaskQueue.ts b/src/TaskQueue.ts new file mode 100644 index 00000000..4004fcc4 --- /dev/null +++ b/src/TaskQueue.ts @@ -0,0 +1,52 @@ +//----------------------------------------------------------------------------- +// vscode-catch2-test-adapter was written by Mate Pek, and is placed in the +// public domain. The author hereby disclaims copyright to this source code. + +export class TaskQueue { + constructor(depends: Iterable = [], + public readonly name?: string) { + this._depends = [...depends]; + // TODO check circular dependency + } + + empty(): boolean { + return this._count == 0; + } + + get size(): number { + return this._count; + } + + get length(): number { + return this._count; + } + + then( + task: (() => TResult1 | PromiseLike)): Promise { + this._count++; + + const previous = this._queue; + + const current = Promise.all(this._depends.map(v => v._queue)) + .then(() => { + return previous.then(task); + }); + + const decr = () => { this._count--; }; + + this._queue = current.then(decr, decr); + + return current; + } + + dependsOn(depends: Iterable): void { + for (const dep of depends) { + this._depends.push(dep); + } + // TODO check circular dependency + } + + private _count: number = 0; + private _queue: Promise = Promise.resolve(); + private readonly _depends: Array; +} diff --git a/src/TestAdapter.ts b/src/TestAdapter.ts index cb7c3f24..0c43779b 100644 --- a/src/TestAdapter.ts +++ b/src/TestAdapter.ts @@ -11,7 +11,7 @@ import * as util from 'vscode-test-adapter-util'; import { RootTestSuiteInfo } from './RootTestSuiteInfo'; import { resolveVariables } from './Helpers'; -import { QueueGraphNode } from './QueueGraph'; +import { TaskQueue } from './TaskQueue'; import { TestExecutableInfo } from './TestExecutableInfo'; export class TestAdapter implements api.TestAdapter, vscode.Disposable { @@ -31,7 +31,7 @@ export class TestAdapter implements api.TestAdapter, vscode.Disposable { // because we always want to return with the current allTests suite private readonly _loadFinishedEmitter = new vscode.EventEmitter(); - private _allTasks: QueueGraphNode; + private _allTasks: TaskQueue; private _allTests: RootTestSuiteInfo; private readonly _disposables: vscode.Disposable[] = []; @@ -45,9 +45,7 @@ export class TestAdapter implements api.TestAdapter, vscode.Disposable { process.platform, process.version, process.versions, vscode.version ])); - this._allTasks = new QueueGraphNode('TestAdapter', [], (reason: any) => { - this._log.error('fatal error: QueueGraphNode(TestAdapter): ' + inspect(this)); - }); + this._allTasks = new TaskQueue([], 'TestAdapter'); this._disposables.push(this._testsEmitter); this._disposables.push(this._testStatesEmitter); @@ -106,18 +104,26 @@ export class TestAdapter implements api.TestAdapter, vscode.Disposable { this._allTests.isNoThrow = this._getDefaultNoThrow(this._getConfiguration()); } + if (configChange.affectsConfiguration( + 'catch2TestExplorer.workerMaxNumber', + this.workspaceFolder.uri)) { + this._allTests.workerMaxNumber = + this._getWorkerMaxNumber(this._getConfiguration()); + } })); const config = this._getConfiguration(); this._allTests = new RootTestSuiteInfo( this._allTasks, this._log, this.workspaceFolder, this._loadFinishedEmitter, this._testsEmitter, this._testStatesEmitter, - this._autorunEmitter, this._variableToValue, + this._variableToValue, this._getEnableSourceDecoration(config), this._getDefaultRngSeed(config), this._getDefaultExecWatchTimeout(config), this._getDefaultExecRunningTimeout(config), - this._getDefaultNoThrow(config)); + this._getDefaultNoThrow(config), + this._getWorkerMaxNumber(config) + ); } dispose() { @@ -144,17 +150,19 @@ export class TestAdapter implements api.TestAdapter, vscode.Disposable { const config = this._getConfiguration(); this._allTests.dispose(); - this._allTasks = new QueueGraphNode(); + this._allTasks = new TaskQueue([], 'TestAdapter'); this._allTests = new RootTestSuiteInfo( this._allTasks, this._log, this.workspaceFolder, this._loadFinishedEmitter, this._testsEmitter, this._testStatesEmitter, - this._autorunEmitter, this._variableToValue, + this._variableToValue, this._getEnableSourceDecoration(config), this._getDefaultRngSeed(config), this._getDefaultExecWatchTimeout(config), this._getDefaultExecRunningTimeout(config), - this._getDefaultNoThrow(config)); + this._getDefaultNoThrow(config), + this._getWorkerMaxNumber(config) + ); this._testsEmitter.fire({ type: 'started' }); @@ -186,7 +194,7 @@ export class TestAdapter implements api.TestAdapter, vscode.Disposable { return this._allTasks.then(() => { return this._allTests - .run(tests, this._getWorkerMaxNumber(this._getConfiguration())) + .run(tests) .catch((reason: any) => { this._log.error(inspect(reason)); }); @@ -219,8 +227,7 @@ export class TestAdapter implements api.TestAdapter, vscode.Disposable { const getDebugConfiguration = (): vscode.DebugConfiguration => { const config = this._getConfiguration(); - let template = - this._getDebugConfigurationTemplate(config); + let template = this._getDebugConfigurationTemplate(config); if (template !== null) { //skip @@ -244,16 +251,14 @@ export class TestAdapter implements api.TestAdapter, vscode.Disposable { "cwd": "${cwd}", "env": "${envObj}" }; - } else { - throw 'C2: For debugging \'debugConfigTemplate\' should be set.'; } - if (template !== null && !template.hasOwnProperty('name')) - template = Object.assign({ 'name': "${label} (${suiteLabel})" }, template); - if (template !== null && !template.hasOwnProperty('request')) - template = Object.assign({ 'request': "launch" }, template); + if (!template) { + throw 'C2: For debugging \'debugConfigTemplate\' should be set.'; + } - const args = testInfo.getDebugParams(this._getDebugBreakOnFailure(config)); + template = Object.assign({ 'name': "${label} (${suiteLabel})" }, template); + template = Object.assign({ 'request': "launch" }, template); return resolveVariables(template, [ ...this._variableToValue, @@ -261,7 +266,7 @@ export class TestAdapter implements api.TestAdapter, vscode.Disposable { ["${suiteLabel}", testInfo.parent.label], ["${label}", testInfo.label], ["${exec}", testInfo.parent.execPath], - ["${args}", args], + ["${args}", testInfo.getDebugParams(this._getDebugBreakOnFailure(config))], ["${cwd}", testInfo.parent.execOptions.cwd!], ["${envObj}", testInfo.parent.execOptions.env!], ]); @@ -335,7 +340,7 @@ export class TestAdapter implements api.TestAdapter, vscode.Disposable { } private _getWorkerMaxNumber(config: vscode.WorkspaceConfiguration): number { - return config.get('workerMaxNumber', 4); + return Math.max(1, config.get('workerMaxNumber', 1)); } private _getDefaultExecWatchTimeout(config: vscode.WorkspaceConfiguration): diff --git a/src/TestSuiteInfoBase.ts b/src/TestSuiteInfoBase.ts index 0ceefcf8..d9e2655b 100644 --- a/src/TestSuiteInfoBase.ts +++ b/src/TestSuiteInfoBase.ts @@ -4,7 +4,8 @@ import { ChildProcess, spawn, SpawnOptions } from 'child_process'; import * as path from 'path'; -import { promisify, inspect } from 'util'; +import { inspect } from 'util'; +import * as vscode from 'vscode'; import { TestEvent, TestSuiteInfo } from 'vscode-test-adapter-api'; import { RootTestSuiteInfo } from './RootTestSuiteInfo'; @@ -89,19 +90,13 @@ export abstract class TestSuiteInfoBase implements TestSuiteInfo { if (childrenToRun.length == 0) return Promise.resolve(); } - return this._runInner(childrenToRun, taskPool); + return taskPool.scheduleTask(() => { return this._runInner(childrenToRun); }); } - private _runInner(childrenToRun: TestInfoBase[] | 'all', taskPool: TaskPool): + private _runInner(childrenToRun: TestInfoBase[] | 'all'): Promise { if (this._isKill) return Promise.reject(Error('Test was killed.')); - if (!taskPool.acquire()) { - return new Promise(resolve => setTimeout(resolve, 64)).then(() => { - return this._runInner(childrenToRun, taskPool); - }); - } - this.allTests.testStatesEmitter.fire( { type: 'suite', suite: this, state: 'running' }); @@ -124,24 +119,44 @@ export abstract class TestSuiteInfoBase implements TestSuiteInfo { const runInfo: TestSuiteInfoBaseRunInfo = { process: this._proc, childrenToRun: childrenToRun, - timeout: undefined + timeout: undefined, + timeoutWatcherTrigger: () => { }, }; this.allTests.log.info('proc started'); - - const startTime = Date.now(); - const killIfTimeouts = (): Promise => { - if (runInfo.process === undefined) { return Promise.resolve(); } - else if (this.allTests.execRunningTimeout !== null - && Date.now() - startTime > this.allTests.execRunningTimeout) { - runInfo.process.kill(); - runInfo.timeout = this.allTests.execRunningTimeout; - return Promise.resolve(); - } else { - return promisify(setTimeout)(1000).then(killIfTimeouts); - } - }; - promisify(setTimeout)(1000).then(killIfTimeouts); + { + const startTime = Date.now(); + + const killIfTimeouts = (): Promise => { + return new Promise(resolve => { + const conn = this.allTests.onDidExecRunningTimeoutChanged(() => { + resolve(conn); + }); + + runInfo.timeoutWatcherTrigger = () => { resolve(conn); }; + + if (this.allTests.execRunningTimeout !== null) { + const elapsed = Date.now() - startTime; + const left = this.allTests.execRunningTimeout - elapsed; + if (left <= 0) resolve(conn); + else setTimeout(resolve, left, conn); + } + }).then((conn: vscode.Disposable) => { + conn.dispose(); + if (runInfo.process === undefined) { + return Promise.resolve(); + } else if (this.allTests.execRunningTimeout !== null + && Date.now() - startTime > this.allTests.execRunningTimeout) { + runInfo.process.kill(); + runInfo.timeout = this.allTests.execRunningTimeout; + return Promise.resolve(); + } else { + return killIfTimeouts(); + } + }); + }; + killIfTimeouts(); + } return this._handleProcess(runInfo) .catch((reason: any) => { @@ -151,9 +166,9 @@ export abstract class TestSuiteInfoBase implements TestSuiteInfo { this.allTests.log.info('proc finished: ' + inspect(this.execPath)); this.allTests.testStatesEmitter.fire({ type: 'suite', suite: this, state: 'completed' }); - taskPool.release(); this._proc = undefined; runInfo.process = undefined; + runInfo.timeoutWatcherTrigger(); }); } @@ -234,4 +249,5 @@ export interface TestSuiteInfoBaseRunInfo { process: ChildProcess | undefined; childrenToRun: TestInfoBase[] | 'all'; timeout: number | undefined; + timeoutWatcherTrigger: () => void; } \ No newline at end of file diff --git a/src/test/QueueGraph.test.ts b/src/test/TaskQueue.test.ts similarity index 87% rename from src/test/QueueGraph.test.ts rename to src/test/TaskQueue.test.ts index 9b83b580..28409878 100644 --- a/src/test/QueueGraph.test.ts +++ b/src/test/TaskQueue.test.ts @@ -3,13 +3,13 @@ // public domain. The author hereby disclaims copyright to this source code. import * as assert from 'assert'; -import {promisify} from 'util'; +import { promisify } from 'util'; -import {QueueGraphNode} from '../QueueGraph'; +import { TaskQueue } from '../TaskQueue'; -describe('QueueGraphNode', function() { +describe('TaskQueue', function () { async function waitFor( - test: Mocha.Context, condition: Function, timeout: number = 1000) { + test: Mocha.Context, condition: Function, timeout: number = 1000) { const start = Date.now(); let c = await condition(); while (!c && (Date.now() - start < timeout || !test.enableTimeouts())) { @@ -19,7 +19,7 @@ describe('QueueGraphNode', function() { return c; } - it('promise practice 1', async function() { + it('promise practice 1', async function () { let resolve: Function; let second = false; new Promise(r => { @@ -36,7 +36,7 @@ describe('QueueGraphNode', function() { assert.ok(second); }); - it('promise practice 2', async function() { + it('promise practice 2', async function () { let resolve: Function; let second = false; const p = new Promise(r => { @@ -56,16 +56,16 @@ describe('QueueGraphNode', function() { assert.ok(second); }); - context('example 1', function() { + context('example 1', function () { /** * node1 <___ node * node2 <___/ */ - const node1 = new QueueGraphNode('node1'); - const node2 = new QueueGraphNode('node2'); - const nodeD = new QueueGraphNode('nodeD', [node1, node2]); + const node1 = new TaskQueue([], 'node1'); + const node2 = new TaskQueue([], 'node2'); + const nodeD = new TaskQueue([node1, node2], 'nodeD'); - it('add:depends before', async function() { + it('add:depends before', async function () { this.slow(150); let startD: Function; let hasRunDatOnce = false;