diff --git a/dev-docs/best-practices/libraries.md b/dev-docs/best-practices/libraries.md index 3e05a1aa16e..36e77a32507 100644 --- a/dev-docs/best-practices/libraries.md +++ b/dev-docs/best-practices/libraries.md @@ -12,3 +12,4 @@ This file will automatically be linked from the `README.md` in the root of the r Library source code should be in a `src` subdirectory. No transpilation should be used: write JS that can be interpreted directly by the Node version in use in the repository. +The `main` property in `package.json` should point to `src/index.js`, which may then load other parts of the library. diff --git a/libraries/iterate/README.md b/libraries/iterate/README.md index f2f14248321..9c1947eae07 100644 --- a/libraries/iterate/README.md +++ b/libraries/iterate/README.md @@ -17,7 +17,7 @@ i = new Iterate({ maxIterationTime: 10000, watchdogTime: 5000, waitTime: 2000, - handler: async (watchdog, state) => { + handler: async watchdog => { await doSomeWork(); watchdog.touch(); // tell Iterate that we`re doing work still await doMoreWork(); @@ -39,7 +39,7 @@ i.on(`stopped`, () => { The constructor for the `Iterate` class takes an options object, with the following properties. All times are in milliseconds. -* `handler`: the async function to call repeatedly, called as `await handler(watchdog, state)`. +* `handler`: the async function to call repeatedly, called as `await handler(watchdog)`. See details below. * `monitor` (optional): instance of a `taskcluster-lib-monitor` instance with a name appropriate for this iterate instance. This is used to report errors. @@ -51,25 +51,33 @@ All times are in milliseconds. * `waitTime`: the time to wait between finishing an iteration and beginning the next. * `maxIterations` (optional, default infinite): Complete up to this many iterations and then successfully exit. Failed iterations count. -* `maxFailures` (optional, default 7): number of failures to tolerate before considering the iteration loop a failure by emitting an `error` event. - This provides a balance between quick recovery from transient errors and the crashing the process for persistent errors. +* `maxFailures` (optional, default 0): number of consecutive failures to tolerate before considering the iteration loop a failure by emitting an `error` event. + Disabled if set to 0. * `watchdogTime`: this is the time within which `watchdog.touch` must be called or the iteration is considered a failure. If this value is omitted or zero, the watchdog is disabled. The main function of the `Iterate` instance is to call `handler` repeatedly. -This is an async function, receiving two parameters -- `(watchdog, state)`. +This begins after a call to the `Iterate` instance's `start()` method, which returns a Promise that resolves once the first iteration begins (on the next tick). +To stop iteration, call the `stop()` method; this returns a Promise that resolves when any ongoing iterations are complete. -The `watchdog` parameter is basically a ticking timebomb that must be defused frequently by calling its `.touch()` method. +The handler is an async function, receiving one parameter -- `watchdog`. +This is basically a ticking timebomb that must be defused frequently by calling its `.touch()` method (unless it is not enabled). It has methods `.start()`, `.stop()` and `.touch()` and emits `expired` when it expires. -What it allows an implementor is the abilty to say that while the absolute maximum iteration interval (`maxIterationTime`), incremental progress should be made. +What it allows an implementor is the abilty to say that within the absolute maximum iteration interval (`maxIterationTime`), incremental progress should be made. The idea here is that after each chunk of work in the handler, you run `.touch()`. If the `watchdogTime` duration elapses without a touch, then the iteration is considered faild. This way, you can have a handler that can be marked as failing without waiting the full `maxIterationTime`. -The `state` parameter is an object that is passed in to the handler function. -It allows each iteration to accumulate data and use on following iterations. -Because this object is passed in by reference, changes to properties on the object are saved, but reassignment of the state variable will not be saved. -In other words, do `state.data = {count: 1}` and not `state = {count:1}`. +If `maxFailures` is set, then the `Iterate` instance will emit an `error` event when the specified number of iteration failures have occurred with out intervening successful iterations. +This provides an escape from the situation where an application is "wedged" and some external action is required to restart it. +Typically, this entails exiting the process and allowing the hosting environment to automatically restart it. +Since all of the intervening failures were logged, this can be as simple as: + +```js +iterator.on('error', () => { + process.exit(1); +}); +``` ## Events @@ -77,15 +85,11 @@ Iterate is an event emitter. When relevant events occur, the following events are emitted. If the `error` event does not have a listener, the process will exit with a non-zero exit code when it would otherwise be emitted. -* `started`: when overall iteration starts -* `stopped`: when overall iteration is finished -* `completed`: only when we have a max number of iterations, when we - finish the last iteration +* `started`: when Iterate instance starts +* `stopped`: when Iterate instance has stopped * `iteration-start`: when an individual iteration starts -* `iteration-success`: when an individual iteration completes with - success. provides the value that handler resolves with -* `iteration-failure`: provides iteration error -* `iteration-complete`: when an iteration is complete regardless of outcome -* `error`: when the iteration is considered to be concluded and provides - list of iteration errors. If there are no handlers and this event is - emitted, an exception will be thrown in a process.nextTick callback. +* `iteration-success`: when an individual iteration completes successfully +* `iteration-failure`: when an individual iteration fails +* `iteration-complete`: when an iteration completes, regardless of outcome +* `error`: when the Iterate instance has failed (due to reaching maxFailures), + containing the most recent error. diff --git a/libraries/iterate/src/index.js b/libraries/iterate/src/index.js index 70e029b9bcf..bda39f3ba5e 100644 --- a/libraries/iterate/src/index.js +++ b/libraries/iterate/src/index.js @@ -1,6 +1,6 @@ -let WatchDog = require('./watchdog'); -let debug = require('debug')('iterate'); -let events = require('events'); +const WatchDog = require('./watchdog'); +const debug = require('debug')('iterate'); +const events = require('events'); /** * The Iterate Class. See README.md for explanation of constructor @@ -14,8 +14,8 @@ class Iterate extends events.EventEmitter { // Set default values opts = Object.assign({}, { watchdogTime: 0, + maxFailures: 0, maxIterations: 0, - maxFailures: 7, minIterationTime: 0, }, opts); @@ -62,8 +62,11 @@ class Iterate extends events.EventEmitter { // Decide whether iteration should continue this.keepGoing = false; - // We want to be able to share state between iterations - this.sharedState = {}; + // Called when stop is called (used to break out of waitTime sleep) + this.onStopCall = null; + + // Fires when stopped, only set when started + this.stopPromise = null; // Store the iteration timeout so that a `.stop()` call during an iteration // inhibits a handler from running @@ -72,14 +75,15 @@ class Iterate extends events.EventEmitter { async single_iteration() { debug('running handler'); - let start = new Date(); - let watchdog = new WatchDog(this.watchdogTime); + const start = new Date(); + const watchdog = new WatchDog(this.watchdogTime); let maxIterationTimeTimer; // build a promise that will reject when either the watchdog // times out or the maxIterationTimeTimer expires - let timeoutRejector = new Promise((resolve, reject) => { + const timeoutRejector = new Promise((resolve, reject) => { watchdog.on('expired', () => { + debug('watchdog expired'); reject(new Error('watchdog exceeded')); }); @@ -92,7 +96,7 @@ class Iterate extends events.EventEmitter { watchdog.start(); await Promise.race([ timeoutRejector, - Promise.resolve(this.handler(watchdog, this.sharedState)), + Promise.resolve(this.handler(watchdog)), ]); } finally { // stop the timers regardless of success or failure @@ -100,7 +104,7 @@ class Iterate extends events.EventEmitter { watchdog.stop(); } - let duration = new Date() - start; + const duration = new Date() - start; if (this.minIterationTime > 0 && duration < this.minIterationTime) { throw new Error('Handler duration was less than minIterationTime'); } @@ -110,6 +114,9 @@ class Iterate extends events.EventEmitter { async iterate() { let currentIteration = 0; let failures = []; + + this.emit('started'); + while (true) { currentIteration++; let iterError; @@ -149,80 +156,60 @@ class Iterate extends events.EventEmitter { // When we reach the end of a set number of iterations, we'll stop if (this.maxIterations > 0 && currentIteration >= this.maxIterations) { debug(`reached max iterations of ${this.maxIterations}`); - this.stop(); - this.emit('completed'); - // fall through to also send 'stopped' + this.keepGoing = false; } - if (failures.length >= this.maxFailures) { - this.__emitFatalError(failures); - return; - } else if (!this.keepGoing) { - this.stop(); - this.emit('stopped'); - return; + if (this.maxFailures > 0 && failures.length >= this.maxFailures) { + this.emit('error', failures[failures.length - 1]); + } + + if (!this.keepGoing) { + break; } if (this.waitTime > 0) { - debug('waiting for next iteration'); - await new Promise(resolve => { - this.currentTimeout = setTimeout(resolve, this.waitTime); + debug('waiting for next iteration or stop'); + const stopPromise = new Promise(resolve => { + this.onStopCall = resolve; }); - } - } - } + let waitTimeTimeout; + const waitTimePromise = new Promise(resolve => { + waitTimeTimeout = setTimeout(resolve, this.waitTime); + }); + await Promise.race([stopPromise, waitTimePromise]); - /** - * Special function which knows how to emit the final error and then throw an - * unhandled exception where appropriate. Also stop trying to iterate - * further. - */ - __emitFatalError(failures) { - if (this.currentTimeout) { - clearTimeout(this.currentTimeout); - } - this.stop(); - this.emit('stopped'); - if (this.monitor) { - let err = new Error('Fatal iteration error'); - err.failures = failures; - this.monitor.reportError(err); - } - if (this.listeners('error').length > 0) { - this.emit('error', failures); - } else { - debug('fatal error:'); - for (let x of failures) { - debug(` * ${x.stack || x}`); + this.onStopCall = null; + clearTimeout(waitTimeTimeout); + + if (!this.keepGoing) { + break; + } } - debug('trying to crash process'); - process.nextTick(() => { - throw new Error(`Errors:\n=====\n${failures.map(x => x.stack || x).join('=====\n')}`); - }); } + this.emit('stopped'); } start() { debug('starting'); + this.stoppedPromise = new Promise(resolve => { + this.on('stopped', resolve); + }); this.keepGoing = true; - // Two reasons we call it this way: - // 1. first call should have same exec env as following - // 2. start should return immediately - this.currentTimeout = setTimeout(async () => { - debug('starting iteration'); - this.emit('started'); - try { - await this.iterate(); - } catch (err) { - console.error(err.stack || err); - } - }, 0); + return new Promise(resolve => { + this.once('started', resolve); + // start iteration; any failures here are a programming error in this + // library and so should be considered fatal + this.iterate().catch(err => this.emit('error', err)); + }); } stop() { this.keepGoing = false; - debug('stopped'); + if (this.onStopCall) { + this.onStopCall(); + } + return this.stoppedPromise; } } diff --git a/libraries/iterate/src/watchdog.js b/libraries/iterate/src/watchdog.js index 75df657f065..ad3830d6423 100644 --- a/libraries/iterate/src/watchdog.js +++ b/libraries/iterate/src/watchdog.js @@ -1,4 +1,4 @@ -let events = require('events'); +const events = require('events'); /** * This is a watch dog timer. Think of it as a ticking timebomb which will diff --git a/libraries/iterate/test/iterate_test.js b/libraries/iterate/test/iterate_test.js index 08bfbc7c313..579a694e463 100644 --- a/libraries/iterate/test/iterate_test.js +++ b/libraries/iterate/test/iterate_test.js @@ -1,11 +1,10 @@ -let subject = require('../'); -let sandbox = require('sinon').createSandbox(); -let assume = require('assume'); -let debug = require('debug')('iterate-test'); -let MonitorManager = require('taskcluster-lib-monitor'); +const subject = require('../'); +const assume = require('assume'); +const debug = require('debug')('iterate-test'); +const MonitorManager = require('taskcluster-lib-monitor'); const testing = require('taskcluster-lib-testing'); -let possibleEvents = [ +const possibleEvents = [ 'started', 'stopped', 'completed', @@ -28,57 +27,54 @@ class IterateEvents { } } - assert(f) { - let dl = () => { // dl === dump list + assert() { + const dl = () => { // dl === dump list return `\nExpected: ${JSON.stringify(this.expectedOrder, null, 2)}` + `\nActual: ${JSON.stringify(this.orderOfEmission, null, 2)}`; }; if (this.orderOfEmission.length !== this.expectedOrder.length) { - return f(new Error(`order emitted differs in length from expectation ${dl()}`)); + throw(new Error(`order emitted differs in length from expectation ${dl()}`)); } for (let i = 0 ; i < this.orderOfEmission.length ; i++) { if (this.orderOfEmission[i] !== this.expectedOrder[i]) { - return f(new Error(`order emitted differs in content ${dl()}`)); + throw(new Error(`order emitted differs in content ${dl()}`)); } } debug(`Events Emitted: ${JSON.stringify(this.orderOfEmission)}`); - return f(); } - } suite(testing.suiteName(), () => { let manager; let monitor; - setup(async () => { + suiteSetup(async () => { manager = new MonitorManager({serviceName: 'iterate'}); manager.setup({mock: true}); monitor = manager.monitor(); }); - teardown(() => { - sandbox.restore(); + suiteTeardown(() => { manager.terminate(); }); - test('should be able to start and stop', done => { + const runWithFakeTime = fn => { + return testing.runWithFakeTime(fn, { + systemTime: 0, + }); + }; + + test('should be able to start and stop', runWithFakeTime(async function() { let iterations = 0; - let i = new subject({ + const i = new subject({ maxIterationTime: 3000, waitTime: 1000, - handler: async (watchdog, state) => { - // In order to get the looping stuff to work, I had to stop the - // watchdog timer. This will be tested in the tests for the - // Iterate.iterate method - watchdog.on('expired', () => { - done(new Error('incremental watch dog expiration')); - }); - + watchDog: 0, + handler: async watchdog => { debug('iterate!'); iterations++; return 1; @@ -86,93 +82,113 @@ suite(testing.suiteName(), () => { monitor, }); - i.on('error', err => { - done(err); + let err = null; + i.on('error', e => { err = e; }); + i.on('stopped', () => { err = new Error('unexpected stopped event'); }); + + i.start(); + + await testing.sleep(5000); + + assume(err).to.equal(null); + assume(iterations).equals(5); + assume(i.keepGoing).is.ok(); + await i.stop(); + assume(i.keepGoing).is.not.ok(); + assume(manager.messages.length).equals(5); + manager.messages.forEach(message => { + assume(message.Fields.status).equals('success'); }); + })); - i.on('stopped', () => { - done(); + test('should stop after current iteration completes', runWithFakeTime(async function() { + const i = new subject({ + maxIterationTime: 3000, + waitTime: 10, + maxIterations: 5, + watchDog: 0, + handler: async watchdog => { + await testing.sleep(500); + }, }); i.start(); + await i.stop(); - setTimeout(() => { - assume(iterations).equals(5); - assume(i.keepGoing).is.ok(); - i.stop(); - assume(i.keepGoing).is.not.ok(); - assume(manager.messages.length).equals(5); - manager.messages.forEach(message => { - assume(message.Fields.status).equals('success'); - }); - }, 5000); + assume(+new Date()).atleast(500); + })); - }); + test('should stop in the midst of waitTime', runWithFakeTime(async function() { + const i = new subject({ + maxIterationTime: 3000, + waitTime: 1000, + maxIterations: 2, + watchDog: 0, + handler: async watchdog => { + await testing.sleep(500); + }, + }); - test('should stop after correct number of iterations', done => { + i.start(); + await testing.sleep(1000); + await i.stop(); + + assume(+new Date()).between(1000, 1100); + })); + + test('should stop after correct number of iterations', runWithFakeTime(async function() { let iterations = 0; - let i = new subject({ + const i = new subject({ maxIterationTime: 3000, waitTime: 10, maxIterations: 5, - handler: async (watchdog, state) => { - watchdog.on('expired', () => { - done(new Error('incremental watch dog expiration')); - }); - + watchDog: 0, + handler: async watchdog => { debug('iterate!'); iterations++; return 1; }, }); - i.on('error', err => { - done(err); - }); - i.start(); - i.on('completed', () => { - assume(iterations).equals(5); - assume(i.keepGoing).is.not.ok(); - i.stop(); - done(); + await new Promise(resolve => { + i.on('stopped', resolve); }); - }); - test('should emit error when iteration watchdog expires', done => { - let i = new subject({ + assume(iterations).equals(5); + assume(i.keepGoing).is.not.ok(); + })); + + test('should emit error when iteration watchdog expires', runWithFakeTime(async function() { + const i = new subject({ maxIterationTime: 5000, watchdogTime: 1000, waitTime: 1000, maxFailures: 1, - handler: async (watchdog, state) => { - // In order to get the looping stuff to work, I had to stop the - // watchdog timer. This will be tested in the tests for the - // Iterate.iterate method - i.on('error', err => { - debug('correctly getting expired watchdog timer'); - i.stop(); - assume(i.keepGoing).is.not.ok(); - done(); - }); - return new Promise((res, rej) => { - setTimeout(res, 2000); - }); - + handler: async watchdog => { + return testing.sleep(2000); }, }); + let err = null; + i.on('error', e => { err = e; }); + i.start(); - }); + await testing.sleep(1500); + i.stop(); + + assume(i.keepGoing).is.not.ok(); + assume(err).matches(/watchdog exceeded/); + })); - test('should emit error when overall iteration limit is hit', done => { - let i = new subject({ + test('should emit error when overall iteration limit is hit', runWithFakeTime(async function() { + const i = new subject({ maxIterationTime: 1000, waitTime: 1000, maxFailures: 1, - handler: async (watchdog, state) => { + handler: async watchdog => { watchdog.stop(); return new Promise((res, rej) => { setTimeout(() => { @@ -182,200 +198,111 @@ suite(testing.suiteName(), () => { }, }); - i.on('error', err => { - i.stop(); - assume(i.keepGoing).is.not.ok(); - done(); - }); + let err = null; + i.on('error', e => { err = e; }); i.start(); - }); + await testing.sleep(1500); + i.stop(); + + assume(i.keepGoing).is.not.ok(); + assume(err).matches(/Iteration exceeded maximum time allowed/); + })); - test('should emit iteration-failure when async handler fails', done => { - let i = new subject({ + test('should emit iteration-failure when async handler fails', runWithFakeTime(async function() { + const i = new subject({ maxIterationTime: 1000, waitTime: 1000, maxFailures: 100, - handler: async (watchdog, state) => { + handler: async watchdog => { throw new Error('uhoh'); }, }); - i.on('iteration-failure', err => { - i.stop(); - assume(i.keepGoing).is.not.ok(); - done(); - }); + let sawEvent = false; + i.on('iteration-failure', () => { sawEvent = true; }); i.start(); - }); + await testing.sleep(500); + i.stop(); - test('should emit iteration-failure when sync handler fails', done => { - let i = new subject({ + assume(i.keepGoing).is.not.ok(); + assume(sawEvent).is.ok(); + })); + + test('should emit iteration-failure when sync handler fails', runWithFakeTime(async function() { + const i = new subject({ maxIterationTime: 1000, waitTime: 1000, maxFailures: 100, - handler: (watchdog, state) => { + handler: watchdog => { throw new Error('uhoh'); }, }); - i.on('iteration-failure', err => { - i.stop(); - assume(i.keepGoing).is.not.ok(); - done(); - }); + let sawEvent = false; + i.on('iteration-failure', () => { sawEvent = true; }); i.start(); - }); + await testing.sleep(500); + i.stop(); - test('should emit error when iteration is too quick', done => { - let i = new subject({ + assume(i.keepGoing).is.not.ok(); + assume(sawEvent).is.ok(); + })); + + test('should emit iteration-failure when iteration is too quick', runWithFakeTime(async function() { + const i = new subject({ maxIterationTime: 12000, minIterationTime: 10000, waitTime: 1000, - handler: async (watchdog, state) => { - watchdog.stop(); - return 1; + watchDog: 0, + handler: async watchdog => { + await testing.sleep(100); }, }); - i.start(); - - i.on('error', err => { - debug('correctly getting expired watchdog timer'); - i.stop(); - assume(i.keepGoing).is.not.ok(); - done(); - }); - }); - - test('should emit error after too many failures', done => { - let i = new subject({ - maxIterationTime: 12000, - maxFailures: 1, - waitTime: 1000, - handler: async (watchdog, state) => { - return new Promise((res, rej) => { - rej(new Error('hi')); - }); - }, - }); + let iterFailed = false; + i.on('iteration-failure', () => { iterFailed = true; }); i.start(); + await testing.sleep(300); + i.stop(); - i.on('error', err => { - i.stop(); - assume(i.keepGoing).is.not.ok(); - done(); - }); - - }); - - test('should cause uncaughtException when error event is unhandled', done => { - // NOTE: Mocha has it's own uncaught exception listener. If we were to - // leave it in force during this test, we'd end up getting two results from - // the test. One failure from the mocha handler and one pass from our own - // handler. This is obviously not ideal, and it's sort of a risk that we - // mess up the uncaught exception handling for future tests - - let origListeners = process.listeners('uncaughtException'); - process.removeAllListeners('uncaughtException'); - - let uncaughtHandler = function(err) { - process.removeAllListeners('uncaughtException'); - for (let listener of origListeners) { - process.on('uncaughtException', listener); - } - i.stop(); - assume(i.keepGoing).is.not.ok(); - done(); - }; - - process.on('uncaughtException', uncaughtHandler); + assume(iterFailed).is.ok(); + })); - let i = new subject({ + test('should emit error after too many failures', runWithFakeTime(async function() { + const i = new subject({ maxIterationTime: 12000, maxFailures: 1, waitTime: 1000, - handler: async (watchdog, state) => { - return new Promise((res, rej) => { - rej(new Error('hi')); - }); - }, - }); - - i.start(); - - }); - - test('should share state between iterations', done => { - let iterations = 0; - let v = {a: 1}; - - let i = new subject({ - maxIterationTime: 3000, - waitTime: 1000, - maxIterations: 2, - maxFailures: 1, - handler: async (watchdog, state) => { - watchdog.on('expired', () => { - done(new Error('incremental watch dog expiration')); - }); - - if (iterations === 0) { - assume(state).deeply.equals({}); - state.v = v; - } else if (iterations === 1) { - assume(state.v).deeply.equals(v); - assume(state.v).equals(v); - } else { - done(new Error('too many iterations')); - } - - debug('iterate!'); - iterations++; - return 1; + handler: async watchdog => { + throw new Error('uhoh'); }, }); - i.on('error', err => { - done(err); - }); - - i.on('iteration-error', err => { - done(err); - }); + let err = null; + i.on('error', e => { err = e; }); i.start(); + await testing.sleep(1500); + i.stop(); - i.on('completed', () => { - assume(iterations).equals(2); - assume(i.keepGoing).is.not.ok(); - i.stop(); - assume(i.keepGoing).is.not.ok(); - done(); - }); - }); - - test('should emit correct events for single iteration', done => { - let iterations = 0; //eslint-disable-line no-unused-vars + assume(i.keepGoing).is.not.ok(); + assume(err).matches(/uhoh/); + })); - let i = new subject({ + test('should emit correct events for single iteration', runWithFakeTime(async function() { + const i = new subject({ maxIterationTime: 3000, waitTime: 1000, - handler: async (watchdog, state) => { + handler: async watchdog => { debug('iterate!'); - iterations++; - return 1; }, }); - i.on('error', err => { - done(err); - }); - - let events = new IterateEvents(i, [ + const events = new IterateEvents(i, [ 'started', 'iteration-start', 'iteration-success', @@ -383,230 +310,178 @@ suite(testing.suiteName(), () => { 'stopped', ]); - i.on('stopped', () => { - events.assert(done); - }); - - i.on('started', () => { - i.stop(); - }); - i.start(); - }); + await i.stop(); - test('should emit correct events with maxIterations', done => { - let iterations = 0; //eslint-disable-line no-unused-vars + events.assert(); + })); - let i = new subject({ + test('should emit correct events with maxIterations', runWithFakeTime(async function() { + const i = new subject({ maxIterationTime: 3000, maxIterations: 1, waitTime: 1000, - handler: async (watchdog, state) => { + handler: async watchdog => { debug('iterate!'); - iterations++; - return 1; }, }); - i.on('error', err => { - done(err); - }); - - let events = new IterateEvents(i, [ + const events = new IterateEvents(i, [ 'started', 'iteration-start', 'iteration-success', 'iteration-complete', - 'completed', 'stopped', ]); - i.on('error', err => { - done(err); - }); - - i.on('stopped', () => { - events.assert(done); - }); - - i.on('started', () => { - i.stop(); - }); - i.start(); - }); + await testing.sleep(2000); + await i.stop(); + + events.assert(); + })); suite('event emission ordering', () => { - test('should be correct with maxFailures and maxIterations', done => { - let i = new subject({ + test('should be correct with maxFailures and maxIterations', runWithFakeTime(async function() { + const i = new subject({ maxIterationTime: 3000, maxIterations: 1, maxFailures: 1, waitTime: 1000, - handler: async (watchdog, state) => { + handler: async watchdog => { debug('iterate!'); throw new Error('hi'); }, }); - let events = new IterateEvents(i, [ + const events = new IterateEvents(i, [ 'started', 'iteration-start', 'iteration-failure', 'iteration-complete', - 'completed', - 'stopped', 'error', + 'stopped', ]); - i.on('error', () => { - events.assert(done); - }); - - i.on('started', () => { - i.stop(); - }); - i.start(); - }); + await i.stop(); - test('should be correct with maxFailures only', done => { - let i = new subject({ + events.assert(); + })); + + test('should be correct with maxFailures only', runWithFakeTime(async function() { + const i = new subject({ maxIterationTime: 3000, maxFailures: 1, waitTime: 1000, - handler: async (watchdog, state) => { + handler: async watchdog => { debug('iterate!'); throw new Error('hi'); }, }); - let events = new IterateEvents(i, [ + const events = new IterateEvents(i, [ 'started', 'iteration-start', 'iteration-failure', 'iteration-complete', - 'stopped', 'error', + 'stopped', ]); - i.on('error', () => { - events.assert(done); - }); - - i.on('started', () => { - i.stop(); - }); - i.start(); - }); + await i.stop(); + + events.assert(); + })); - test('should be correct when handler takes too little time', done => { - let i = new subject({ + test('should be correct when handler takes too little time', runWithFakeTime(async function() { + const i = new subject({ maxIterationTime: 3000, minIterationTime: 100000, maxFailures: 1, waitTime: 1000, - handler: async (watchdog, state) => { + handler: async watchdog => { return true; }, }); - let events = new IterateEvents(i, [ + const events = new IterateEvents(i, [ 'started', 'iteration-start', 'iteration-failure', 'iteration-complete', - 'stopped', 'error', + 'stopped', ]); - i.on('error', () => { - events.assert(done); - }); - - i.on('started', () => { - i.stop(); - }); - i.start(); - }); + await i.stop(); + + events.assert(); + })); - test('should be correct when handler takes too long (incremental watchdog)', done => { - let i = new subject({ + test('should be correct when handler takes too long (incremental watchdog)', runWithFakeTime(async function() { + const i = new subject({ maxIterationTime: 5000, maxFailures: 1, watchdogTime: 100, waitTime: 1000, - handler: async (watchdog, state) => { - return new Promise((res, rej) => { - setTimeout(res, 3000); - }); + handler: async watchdog => { + await testing.sleep(3000); }, }); - let events = new IterateEvents(i, [ + const events = new IterateEvents(i, [ 'started', 'iteration-start', 'iteration-failure', 'iteration-complete', - 'stopped', 'error', + 'stopped', ]); - i.on('error', () => { - events.assert(done); - }); - - i.on('started', () => { - i.stop(); - }); - i.start(); - }); + await i.stop(); + + events.assert(); + })); - test('should be correct when handler takes too long (overall time)', done => { - let i = new subject({ + test('should be correct when handler takes too long (overall time)', runWithFakeTime(async function() { + const i = new subject({ maxIterationTime: 3000, maxFailures: 1, waitTime: 1000, - handler: async (watchdog, state) => { - return new Promise((res, rej) => { - setTimeout(res, 6000); - }); + handler: async watchdog => { + await testing.sleep(6000); }, }); - let events = new IterateEvents(i, [ + const events = new IterateEvents(i, [ 'started', 'iteration-start', 'iteration-failure', 'iteration-complete', - 'stopped', 'error', + 'stopped', ]); - i.on('error', () => { - events.assert(done); - }); - - i.on('started', () => { - i.stop(); - }); - i.start(); - }); + await i.stop(); - test('should be correct with mixed results', done => { + events.assert(); + })); + + test('should be correct with mixed results', runWithFakeTime(async function() { let iterations = 0; - let i = new subject({ + const i = new subject({ maxIterationTime: 3000, maxIterations: 6, maxFailures: 5, waitTime: 1000, - handler: async (watchdog, state) => { + handler: async watchdog=> { if (iterations++ % 2 === 0) { throw new Error('even, so failing'); } else { @@ -615,7 +490,7 @@ suite(testing.suiteName(), () => { }, }); - let events = new IterateEvents(i, [ + const events = new IterateEvents(i, [ 'started', 'iteration-start', 'iteration-failure', @@ -635,19 +510,17 @@ suite(testing.suiteName(), () => { 'iteration-start', 'iteration-success', 'iteration-complete', - 'completed', 'stopped', ]); - i.on('stopped', () => { - events.assert(done); - }); + i.start(); - i.on('error', err => { - done(err); + // wait for the maxIterations to take effect.. + await new Promise(resolve => { + i.on('stopped', resolve); }); - i.start(); - }); + events.assert(); + })); }); }); diff --git a/libraries/iterate/test/watchdog_test.js b/libraries/iterate/test/watchdog_test.js index 9a9b649c466..cbf39c258a4 100644 --- a/libraries/iterate/test/watchdog_test.js +++ b/libraries/iterate/test/watchdog_test.js @@ -1,84 +1,77 @@ -let subject = require('../src/watchdog'); -let sinon = require('sinon'); -let assume = require('assume'); +const subject = require('../src/watchdog'); +const assume = require('assume'); const testing = require('taskcluster-lib-testing'); suite(testing.suiteName(), function() { - let events, start; + let events; - setup(function() { - this.clock = sinon.useFakeTimers(); - start = new Date(); - }); - - teardown(function() { - this.clock.restore(); - }); + const runWithFakeTime = fn => { + return testing.runWithFakeTime(fn, { + systemTime: 0, + }); + }; const listen = w => { events = []; - w.on('expired', () => events.push(['expired', new Date() - start])); + w.on('expired', () => events.push(['expired', +new Date()])); }; - test('should emit expired event', function() { - let w = new subject(1 * 1000); + test('should emit expired event', runWithFakeTime(async function() { + const w = new subject(1 * 1000); listen(w); w.start(); - this.clock.tick(1000); + await testing.sleep(1000); assume(events).to.deeply.equal([ ['expired', 1000], ]); - }); + })); - test('should not expire early', function() { - let w = new subject(1 * 1000); + test('should not expire early', runWithFakeTime(async function() { + const w = new subject(1 * 1000); listen(w); w.start(); - this.clock.tick(999); + await testing.sleep(999); w.stop(); assume(events).to.deeply.equal([]); - }); + })); - test('should expire on time', function() { - let w = new subject(1 * 1000); + test('should expire on time', runWithFakeTime(async function() { + const w = new subject(1 * 1000); listen(w); w.start(); - this.clock.tick(1000); + await testing.sleep(1000); w.stop(); assume(events).to.deeply.equal([ ['expired', 1000], ]); - }); + })); - test('should not expire twice', function() { - let w = new subject(1 * 1000); + test('should not expire twice', runWithFakeTime(async function() { + const w = new subject(1 * 1000); listen(w); w.start(); - this.clock.tick(1000); - this.clock.tick(1000); - this.clock.tick(1000); + await testing.sleep(3000); w.stop(); assume(events).to.deeply.equal([ ['expired', 1000], ]); - }); + })); - test('touching should reset timer', function() { - let w = new subject(1 * 1000); + test('touching should reset timer', runWithFakeTime(async function() { + const w = new subject(1 * 1000); listen(w); w.start(); // We do this three times to ensure that the // time period stays constant and doesn't grow // or shrink over time - this.clock.tick(999); + await testing.sleep(999); w.touch(); - this.clock.tick(999); + await testing.sleep(999); w.touch(); - this.clock.tick(999); - this.clock.tick(1); + await testing.sleep(1000); w.stop(); assume(events).to.deeply.equal([ ['expired', 2998], ]); - }); + })); }); diff --git a/libraries/testing/README.md b/libraries/testing/README.md index 46580bd2bbe..2894c7af059 100644 --- a/libraries/testing/README.md +++ b/libraries/testing/README.md @@ -312,8 +312,8 @@ This function assumes the following config values: And assumes that the `exports` argument has a `load` function corresponding to a sticky loader. -Utilities ---------- +Time +---- ### Sleep @@ -323,6 +323,30 @@ The `sleep` function returns a promise that resolves after a delay. poorly-isolated tests. Consider writing the tests to use a "fake" clock or to poll for the expected state. +### Fake Time + +When testing functionality that involves timers, it is helpful to be able to simulate the rapid passage of time. +The `testing.runWithFakeTime(, {mock, maxTime, ...})` uses [zurvan](https://github.com/tlewowski/zurvan) to do just that. +It is used to wrap an argument to a Mocha `test` function, avoiding interfering with Mocha's timers: +```js +test('noun should verb', runWithFakeTime(async function() { + ... +}, { + mock, + maxTime: 60000, +})); +``` + +The `maxTime` option is the total amount of simulated time to spend running the test; it defaults to 30 seconds. + +The `mock` option is for use with `mockSuite` and can be omitted otherwise. +Fake time is only used when mocking; in a real situation, we are interacting with real services and must use the same clock they do. + +Any other options are passed directly to zurvan. + +Utilities +--------- + ### Poll The `poll` function will repeatedly call a function that returns a promise diff --git a/libraries/testing/package.json b/libraries/testing/package.json index 87ce73444ca..71cd88594bc 100644 --- a/libraries/testing/package.json +++ b/libraries/testing/package.json @@ -24,5 +24,5 @@ "engines": { "node": ">=8" }, - "main": "./src/testing.js" + "main": "./src/index.js" } diff --git a/libraries/testing/src/index.js b/libraries/testing/src/index.js new file mode 100644 index 00000000000..df03b8fcbd8 --- /dev/null +++ b/libraries/testing/src/index.js @@ -0,0 +1,10 @@ +module.exports = { + schemas: require('./schemas'), + fakeauth: require('./fakeauth'), + stickyLoader: require('./stickyloader'), + Secrets: require('./secrets'), + poll: require('./poll'), + ...require('./time'), + withEntity: require('./with-entity'), + suiteName: require('./suite-name'), +}; diff --git a/libraries/testing/src/poll.js b/libraries/testing/src/poll.js index 94d2002dc2f..eb48913ac06 100644 --- a/libraries/testing/src/poll.js +++ b/libraries/testing/src/poll.js @@ -1,6 +1,6 @@ const Debug = require('debug'); const debug = Debug('taskcluster-lib-testing:poll'); -const sleep = require('./sleep'); +const {sleep} = require('./time'); /** * Poll a function that returns a promise until the promise is resolved without diff --git a/libraries/testing/src/sleep.js b/libraries/testing/src/sleep.js deleted file mode 100644 index cf612ab3f90..00000000000 --- a/libraries/testing/src/sleep.js +++ /dev/null @@ -1,8 +0,0 @@ -/** Return promise that is resolved in `delay` ms */ -const sleep = function(delay) { - return new Promise(function(accept) { - setTimeout(accept, delay); - }); -}; - -module.exports = sleep; diff --git a/libraries/testing/src/testing.js b/libraries/testing/src/testing.js deleted file mode 100644 index b80f42cf23d..00000000000 --- a/libraries/testing/src/testing.js +++ /dev/null @@ -1,12 +0,0 @@ -exports.schemas = require('./schemas'); -exports.fakeauth = require('./fakeauth'); -exports.stickyLoader = require('./stickyloader'); -exports.Secrets = require('./secrets'); -exports.poll = require('./poll'); -exports.sleep = require('./sleep'); -exports.withEntity = require('./with-entity'); -exports.suiteName = require('./suite-name'); - -exports.createMockAuthServer = () => { - throw new Error('No longer available; use fakeauth instead'); -}; diff --git a/libraries/testing/src/time.js b/libraries/testing/src/time.js new file mode 100644 index 00000000000..4d6c29e6891 --- /dev/null +++ b/libraries/testing/src/time.js @@ -0,0 +1,55 @@ +const zurvan = require('zurvan'); +const timers = require('timers'); + +/** Return promise that is resolved in `delay` ms */ +exports.sleep = function(delay) { + return new Promise(function(accept) { + setTimeout(accept, delay); + }); +}; + +exports.runWithFakeTime = (fn, {mock=true, maxTime=30000, ...zurvanOptions}={}) => { + if (!mock) { + // if not mocking, we can't use fake time as it will cause all sorts + // of timeouts to occur immediately + return fn; + } + return async function wrap() { + await zurvan.interceptTimers({ + systemTime: new Date(), + denyImplicitTimer: true, + throwOnInvalidClearTimer: false, // superagent does this.. + rejectOnCallbackFailure: true, + fakeNodeDedicatedTimers: false, // so we can call a real timers.setImmediate + ...zurvanOptions, + }); + + let finished, err; + this.slow(maxTime); + fn.apply(this, []).then( + () => { + finished = true; + }, + e => { + finished = true; + err = e; + }); + + // intermingle setImmediate calls with advanceTime calls, so that things zurvan cannot + // successfully fake (like JS files internal to Node) get a chance to run. + let time = maxTime; + while (time > 0 && !finished) { + await zurvan.advanceTime(100); + time -= 100; + await new Promise(resolve => timers.setImmediate(resolve)); + } + + await zurvan.releaseTimers(); + if (err) { + throw err; + } + if (!finished) { + throw new Error(`test case not finished after faked passage of ${maxTime}ms`); + } + }; +}; diff --git a/services/queue/src/claimresolver.js b/services/queue/src/claimresolver.js index b4ef358464f..0c7bdf2cad3 100644 --- a/services/queue/src/claimresolver.js +++ b/services/queue/src/claimresolver.js @@ -66,22 +66,21 @@ class ClaimResolver { await Promise.all(loops); }, }); + + this.iterator.on('error', () => { + this.monitor.alert('iteration failed repeatedly; terminating process'); + process.exit(1); + }); } /** Start polling */ async start() { - return new Promise((res, rej) => { - this.iterator.once('started', res); - this.iterator.start(); - }); + return this.iterator.start(); } /** Terminate iteration, returns promise that polling is stopped */ terminate() { - return new Promise((res, rej) => { - this.iterator.once('stopped', res); - this.iterator.stop(); - }); + return this.iterator.stop(); } /** Poll for messages and handle them in a loop */ diff --git a/services/queue/src/deadlineresolver.js b/services/queue/src/deadlineresolver.js index e938bb0bc2a..5c96bebef6d 100644 --- a/services/queue/src/deadlineresolver.js +++ b/services/queue/src/deadlineresolver.js @@ -77,22 +77,21 @@ class DeadlineResolver { await Promise.all(loops); }, }); + + this.iterator.on('error', () => { + this.monitor.alert('iteration failed repeatedly; terminating process'); + process.exit(1); + }); } /** Start polling */ async start() { - return new Promise((res, rej) => { - this.iterator.once('started', res); - this.iterator.start(); - }); + return this.iterator.start(); } /** Terminate iteration, returns promise that polling is stopped */ terminate() { - return new Promise((res, rej) => { - this.iterator.once('stopped', res); - this.iterator.stop(); - }); + return this.iterator.stop(); } /** Poll for messages and handle them in a loop */ diff --git a/services/queue/src/dependencyresolver.js b/services/queue/src/dependencyresolver.js index a3b369d557c..b01f3d70978 100644 --- a/services/queue/src/dependencyresolver.js +++ b/services/queue/src/dependencyresolver.js @@ -52,22 +52,21 @@ class DependencyResolver { await Promise.all(loops); }, }); + + this.iterator.on('error', () => { + this.monitor.alert('iteration failed repeatedly; terminating process'); + process.exit(1); + }); } /** Start polling for resolved-task messages */ async start() { - return new Promise((res, rej) => { - this.iterator.once('started', res); - this.iterator.start(); - }); + return this.iterator.start(); } /** Terminate iteration, returns promise that polling is stopped */ - async terminate() { - return new Promise((res, rej) => { - this.iterator.once('stopped', res); - this.iterator.stop(); - }); + terminate() { + return this.iterator.stop(); } /** Poll for messages and handle them in a loop */ diff --git a/services/queue/test/bucket_test.js b/services/queue/test/bucket_test.js index fea0a3bf181..ebd7e547235 100644 --- a/services/queue/test/bucket_test.js +++ b/services/queue/test/bucket_test.js @@ -17,7 +17,7 @@ helper.secrets.mockSuite(testing.suiteName(), ['aws'], function(mock, skipping) } let bucket; - setup(async function() { + setup('load bucket', async function() { if (!skipping()) { bucket = await helper.load('publicArtifactBucket'); } diff --git a/services/queue/test/claimtask_test.js b/services/queue/test/claimtask_test.js index 704696fde66..343def703fc 100644 --- a/services/queue/test/claimtask_test.js +++ b/services/queue/test/claimtask_test.js @@ -37,7 +37,7 @@ helper.secrets.mockSuite(testing.suiteName(), ['taskcluster', 'aws', 'azure'], f }, }); - test('can claimTask', helper.runWithFakeTime(async function() { + test('can claimTask', testing.runWithFakeTime(async function() { const taskId = slugid.v4(); debug('### Creating task'); @@ -98,7 +98,7 @@ helper.secrets.mockSuite(testing.suiteName(), ['taskcluster', 'aws', 'azure'], f assume(takenUntil4.getTime()).is.greaterThan(takenUntil.getTime() - 1); assume(takenUntil4.getTime()).is.greaterThan(takenUntil2.getTime() - 1); assume(takenUntil4.getTime()).is.greaterThan(takenUntil3.getTime() - 1); - }, mock)); + }, {mock})); test('claimTask is idempotent', async () => { const taskId = slugid.v4(); diff --git a/services/queue/test/claimwork_test.js b/services/queue/test/claimwork_test.js index fe3d41f86e6..20efef39b62 100644 --- a/services/queue/test/claimwork_test.js +++ b/services/queue/test/claimwork_test.js @@ -35,7 +35,7 @@ helper.secrets.mockSuite(testing.suiteName(), ['taskcluster', 'aws', 'azure'], f }; }; - test('claimWork from empty queue', helper.runWithFakeTime(async function() { + test('claimWork from empty queue', testing.runWithFakeTime(async function() { helper.scopes( 'queue:claim-work:no-provisioner-extended-extended/' + workerType, 'queue:worker-id:my-worker-group-extended-extended/my-worker-extended-extended', @@ -49,7 +49,7 @@ helper.secrets.mockSuite(testing.suiteName(), ['taskcluster', 'aws', 'azure'], f }); assert(result.tasks.length === 0, 'Did not expect any claims'); assert(new Date() - started >= 20 * 1000, 'Expected 20s sleep'); - }, mock, 25000)); + }, {mock, maxTime: 25000})); test('claimWork requires scopes', async () => { // wrong provisionerId scope diff --git a/services/queue/test/deadline_test.js b/services/queue/test/deadline_test.js index 96ea49fca36..107cf1d1a79 100644 --- a/services/queue/test/deadline_test.js +++ b/services/queue/test/deadline_test.js @@ -36,7 +36,7 @@ helper.secrets.mockSuite(testing.suiteName(), ['taskcluster', 'aws', 'azure'], f return {taskId: slugid.v4(), task}; }; - test('Resolve unscheduled task deadline', helper.runWithFakeTime(async () => { + test('Resolve unscheduled task deadline', testing.runWithFakeTime(async () => { const {taskId, task} = makeTask(); debug('### Define task'); @@ -74,9 +74,9 @@ helper.secrets.mockSuite(testing.suiteName(), ['taskcluster', 'aws', 'azure'], f debug('### Validate task status'); const r2 = await helper.queue.status(taskId); assume(r2.status.state).equals('exception'); - }, mock)); + }, {mock})); - test('Resolve pending task deadline', helper.runWithFakeTime(async () => { + test('Resolve pending task deadline', testing.runWithFakeTime(async () => { const {taskId, task} = makeTask(); debug('### Creating task'); @@ -105,9 +105,9 @@ helper.secrets.mockSuite(testing.suiteName(), ['taskcluster', 'aws', 'azure'], f debug('### Validate task status'); const r2 = await helper.queue.status(taskId); assume(r2.status.state).deep.equals('exception'); - }, mock)); + }, {mock})); - test('Resolve running task deadline', helper.runWithFakeTime(async () => { + test('Resolve running task deadline', testing.runWithFakeTime(async () => { const {taskId, task} = makeTask(); debug('### Creating task'); @@ -143,9 +143,9 @@ helper.secrets.mockSuite(testing.suiteName(), ['taskcluster', 'aws', 'azure'], f debug('### Validate task status'); const r3 = await helper.queue.status(taskId); assume(r3.status.state).deep.equals('exception'); - }, mock)); + }, {mock})); - test('Resolve completed task by deadline (no change)', helper.runWithFakeTime(async () => { + test('Resolve completed task by deadline (no change)', testing.runWithFakeTime(async () => { const {taskId, task} = makeTask(); debug('### Creating task'); @@ -179,5 +179,5 @@ helper.secrets.mockSuite(testing.suiteName(), ['taskcluster', 'aws', 'azure'], f debug('### Validate task status'); const r4 = await helper.queue.status(taskId); assume(r4.status).deep.equals(r3.status); - }, mock)); + }, {mock})); }); diff --git a/services/queue/test/dependency_test.js b/services/queue/test/dependency_test.js index b3c9ede7759..513766d2482 100644 --- a/services/queue/test/dependency_test.js +++ b/services/queue/test/dependency_test.js @@ -32,7 +32,7 @@ helper.secrets.mockSuite(testing.suiteName(), ['taskcluster', 'aws', 'azure'], f }, }); - test('taskA <- taskB', helper.runWithFakeTime(async () => { + test('taskA <- taskB', testing.runWithFakeTime(async () => { let taskIdA = slugid.v4(); let taskIdB = slugid.v4(); @@ -136,9 +136,9 @@ helper.secrets.mockSuite(testing.suiteName(), ['taskcluster', 'aws', 'azure'], f } await helper.stopPollingService(); - }, mock)); + }, {mock})); - test('taskA <- taskB, taskC, taskD, taskE', helper.runWithFakeTime(async () => { + test('taskA <- taskB, taskC, taskD, taskE', testing.runWithFakeTime(async () => { let taskIdA = slugid.v4(); let taskIdB = slugid.v4(); let taskIdC = slugid.v4(); @@ -222,9 +222,9 @@ helper.secrets.mockSuite(testing.suiteName(), ['taskcluster', 'aws', 'azure'], f assume(tids).contains(taskIdE); await helper.stopPollingService(); - }, mock)); + }, {mock})); - test('taskA, taskB <- taskC && taskA <- taskD', helper.runWithFakeTime(async () => { + test('taskA, taskB <- taskC && taskA <- taskD', testing.runWithFakeTime(async () => { let taskIdA = slugid.v4(); let taskIdB = slugid.v4(); let taskIdC = slugid.v4(); @@ -291,9 +291,9 @@ helper.secrets.mockSuite(testing.suiteName(), ['taskcluster', 'aws', 'azure'], f Infinity); await helper.stopPollingService(); - }, mock)); + }, {mock})); - test('taskA <- taskA (self-dependency)', helper.runWithFakeTime(async () => { + test('taskA <- taskA (self-dependency)', testing.runWithFakeTime(async () => { let taskIdA = slugid.v4(); let taskA = _.defaults({ dependencies: [taskIdA], @@ -314,9 +314,9 @@ helper.secrets.mockSuite(testing.suiteName(), ['taskcluster', 'aws', 'azure'], f workerGroup: 'my-worker-group-extended-extended', workerId: 'my-worker-extended-extended', }); - }, mock)); + }, {mock})); - test('taskA, taskB <- taskB (self-dependency)', helper.runWithFakeTime(async () => { + test('taskA, taskB <- taskB (self-dependency)', testing.runWithFakeTime(async () => { let taskIdA = slugid.v4(); let taskIdB = slugid.v4(); @@ -348,7 +348,7 @@ helper.secrets.mockSuite(testing.suiteName(), ['taskcluster', 'aws', 'azure'], f let r3 = await helper.queue.status(taskIdB); assume(r3.status.state).equals('unscheduled'); helper.checkNoNextMessage('task-pending'); // because of the self-dep - }, mock)); + }, {mock})); test('taskX <- taskA (missing dependency)', async () => { let taskIdA = slugid.v4(); @@ -377,7 +377,7 @@ helper.secrets.mockSuite(testing.suiteName(), ['taskcluster', 'aws', 'azure'], f ); }); - test('taskA <- taskB (reportFailed)', helper.runWithFakeTime(async () => { + test('taskA <- taskB (reportFailed)', testing.runWithFakeTime(async () => { let taskIdA = slugid.v4(); let taskIdB = slugid.v4(); @@ -409,9 +409,9 @@ helper.secrets.mockSuite(testing.suiteName(), ['taskcluster', 'aws', 'azure'], f assume(r3.status.state).equals('unscheduled'); await helper.stopPollingService(); - }, mock)); + }, {mock})); - test('taskA <- taskB (cancelTask)', helper.runWithFakeTime(async () => { + test('taskA <- taskB (cancelTask)', testing.runWithFakeTime(async () => { let taskIdA = slugid.v4(); let taskIdB = slugid.v4(); @@ -439,9 +439,9 @@ helper.secrets.mockSuite(testing.suiteName(), ['taskcluster', 'aws', 'azure'], f assume(r3.status.state).equals('unscheduled'); await helper.stopPollingService(); - }, mock)); + }, {mock})); - test('taskA <- taskB (reportFailed w. all-resolved)', helper.runWithFakeTime(async () => { + test('taskA <- taskB (reportFailed w. all-resolved)', testing.runWithFakeTime(async () => { let taskIdA = slugid.v4(); let taskIdB = slugid.v4(); @@ -479,9 +479,9 @@ helper.secrets.mockSuite(testing.suiteName(), ['taskcluster', 'aws', 'azure'], f Infinity); await helper.stopPollingService(); - }, mock)); + }, {mock})); - test('expiration of relationships', helper.runWithFakeTime(async () => { + test('expiration of relationships', testing.runWithFakeTime(async () => { const taskIdA = slugid.v4(); const taskA = _.defaults({ dependencies: [taskIdA], @@ -533,6 +533,5 @@ helper.secrets.mockSuite(testing.suiteName(), ['taskcluster', 'aws', 'azure'], f const r10 = await TaskRequirement.load({taskId: taskIdB, requiredTaskId: taskIdB}, true); assert(r9, 'Expected TaskDependency'); assert(r10, 'Expected TaskRequirement'); - }, mock)); - + }, {mock})); }); diff --git a/services/queue/test/helper.js b/services/queue/test/helper.js index a90cff7ca7c..2e3bd6250ce 100644 --- a/services/queue/test/helper.js +++ b/services/queue/test/helper.js @@ -10,8 +10,6 @@ const mockAwsS3 = require('mock-aws-s3'); const nock = require('nock'); const FakeBlobStore = require('./fake_blob_store'); const {fakeauth, stickyLoader, Secrets, withEntity} = require('taskcluster-lib-testing'); -const zurvan = require('zurvan'); -const timers = require('timers'); const {FakeClient} = require('taskcluster-lib-pulse'); const helper = module.exports; @@ -65,58 +63,6 @@ setup(async function() { helper.monitor.reset(); }); -/** - * helper.runWithFakeTime(,