diff --git a/clients/client/README.md b/clients/client/README.md index 8b35b13d7cb..e69a12a727d 100644 --- a/clients/client/README.md +++ b/clients/client/README.md @@ -8,25 +8,19 @@ references from which Client classes are already constructed. ## Calling API End-Points To invoke an API end-point instantiate a taskcluster Client class, these are classes can be created from a JSON reference object, but a number of them are -also built-in to this library. In the following example we instantiate an -instance of the `Queue` Client class and use to to create a task. +also built-in to this library. The following example instantiates an +instance of the `Queue` Client class, showing all available options, and +uses it to to create a task. Note that only the `rootUrl` option is required. ```js var taskcluster = require('taskcluster-client'); // Instantiate the Queue Client class var queue = new taskcluster.Queue({ - // rootUrl for this Taskcluster instance + // rootUrl for this Taskcluster instance (required) rootUrl: 'https://taskcluster.myproject.org', - timeout: 30 * 1000, // timeout for _each_ invidual http request - - // By default we share a global agent if you specify your instance - // will have it's own agent with the given options... - agent: { - // https://nodejs.org/api/http.html#http_new_agent_options - }, - + // Taskcluster credentials (required only for API methods that require scopes) credentials: { clientId: '...', accessToken: '...', @@ -34,6 +28,31 @@ var queue = new taskcluster.Queue({ // this can be either a JSON object or a JSON string. certificate: {...} // Only applicable for temporary credentials } + + // timeout for _each_ invidual http request + timeout: 30 * 1000, + + // maximum number of retries for transient errors (default 5) + retries: 5, + + // Multiplier for computation of retry delay: 2 ^ retry * delayFactor, + // 100 ms is solid for servers, and 500ms - 1s is suitable for background + // processes + delayFactor: 100, + + // Randomization factor added as. + // delay = delay * random([1 - randomizationFactor; 1 + randomizationFactor]) + randomizationFactor: 0.25, + + // Maximum retry delay (defaults to 30 seconds) + maxDelay: 30 * 1000, + + // By default we share a global HTTP agent. If you specify one, your instance + // will have its own agent with the given options... + agent: undefined, + + // Fake methods, for testing + fake: null, }); // Create task using the queue client @@ -65,7 +84,7 @@ This replaces any given options with new values. **NOTE** `PulseListener` is no longer included in `taskcluster-client`; instead, use `PulseConsumer` from -[taskcluster-lib-pulse](https://github.com/taskcluster/taskcluster-lib-pulse). +[taskcluster-lib-pulse](../../libraries/pulse). However, this library helpfully includes bindings for exchanges declared by various Taskcluster services. To use these with `taskcluster-lib-pulse`, @@ -90,7 +109,7 @@ let pc = await pulse.consume({ The set of API entries listed below is generated from the built-in references. Detailed documentation with description, payload and result format details is -available in the [docs reference section](https://docs.taskcluster.net/docs/reference). +available in the reference section of the Taskcluster documentation. On the documentation site, entries have a _signature_. You'll find that it matches the signatures below. Notice that all @@ -632,75 +651,6 @@ queue.defineTask(taskId taskDefinition).then(function(result) { ``` -## Using the Listener -Taskcluster relies on pulse for exchange of messages. You'll need an pulse -credentials for using `taskcluster.PulseListener`. -An outline of how to create an instance and use is given below. Note, you -must call `resume()` before message starts arriving. - -```js -var listener = new taskcluster.PulseListener({ - prefetch: 5, // Number of tasks to process in parallel - credentials: { // If not instance of PulseConnection - username: '...', // Pulse username from pulse guardian - password: '...', // Pulse password from pulse guardian - hostname: '...', // hostname to connect to using username/password - vhost : '...' // virtual host to use on the AMQP host - }, - connection: connection, // If credentials isn't provided - // If no queue name is given, the queue is: - // exclusive, autodeleted and non-durable - // If a queue name is given, the queue is: - // durable, not auto-deleted and non-exclusive - queueName: 'my-queue', // Queue name, undefined if none - maxLength: 0, // Max allowed queue size -}); - -listener.bind({exchange, routingKeyPattern}).then(...); - // bind to an exchange; note that for - // Taskcluster components the argument - // can be created by Client; see above. -listener.connect().then(...); // Setup listener and bind queue -listener.resume().then(...); // Start getting new messages -listener.pause().then(...); // Pause retrieval of new messages -listener.deleteQueue(); // Delete named queue and disconnect -listener.close(); // Disconnect from pulse -``` - -To actually receive messages, subscribe to the listener's `message` event: - -```js -listener.on('message', (message) => async { - message.exchange - message.payload - .. etc. (see "Listening for Events", above) -}); -``` - -**Using `PulseConnection`**, instead of giving a `username` and `password` it -is possible to give the `Listener` the key `connection` which must then be a -`taskcluster.PulseConnection` object. Using a `PulseConnection` object it's -possible to have multiple listeners using the same AMQP TCP connection, which -is the recommended way of using AMQP. Notice, that the `PulseConnection` will -not be closed with the `Listener`s, so you must `close()` it manually. - -```js -var connection = new taskcluster.PulseConnection({ - username: '...', // Pulse username from pulse guardian - password: '...', // Pulse password from pulse guardian - hostname: '...', // hostname to connect to using username/password - vhost : '...' // virtual host to use on the AMQP host -}); - -// Create listener -var listener = new taskcluster.PulseListener({ - connection: connection, // AMQP connection object -}); - - -connection.close(); // Disconnect from AMQP/pulse -``` - ## Relative Date-time Utilities A lot of taskcluster APIs requires ISO 8601 time stamps offset into the future as way of providing expiration, deadlines, etc. These can be easily created diff --git a/clients/client/test/client_test.js b/clients/client/test/client_test.js index 436450e4b7b..d74615d652a 100644 --- a/clients/client/test/client_test.js +++ b/clients/client/test/client_test.js @@ -1,10 +1,11 @@ -suite('client requests/responses', function() { - let taskcluster = require('../'); - let assert = require('assert'); - let path = require('path'); - let nock = require('nock'); - let MonitorManager = require('taskcluster-lib-monitor'); - +const taskcluster = require('../'); +const assert = require('assert'); +const path = require('path'); +const nock = require('nock'); +const MonitorManager = require('taskcluster-lib-monitor'); +const testing = require('taskcluster-lib-testing'); + +suite(testing.suiteName(), function() { // This suite exercises the request and response functionality of // the client against a totally fake service defined by this reference // and implemented via Nock. diff --git a/clients/client/test/credinfo_test.js b/clients/client/test/credinfo_test.js index 305048cbb31..d8e7e34e80f 100644 --- a/clients/client/test/credinfo_test.js +++ b/clients/client/test/credinfo_test.js @@ -1,8 +1,9 @@ -suite('taskcluster.credentialInfo', function() { - let taskcluster = require('../'); - let assert = require('assert'); - let nock = require('nock'); +const taskcluster = require('../'); +const assert = require('assert'); +const nock = require('nock'); +const testing = require('taskcluster-lib-testing'); +suite(testing.suiteName(), function() { teardown(function() { assert(nock.isDone()); nock.cleanAll(); diff --git a/clients/client/test/creds_test.js b/clients/client/test/creds_test.js index 8830f224c6b..3f96003095b 100644 --- a/clients/client/test/creds_test.js +++ b/clients/client/test/creds_test.js @@ -1,9 +1,10 @@ -suite('client credential handling', function() { - let taskcluster = require('../'); - let assert = require('assert'); - let request = require('superagent'); - let _ = require('lodash'); +const taskcluster = require('../'); +const assert = require('assert'); +const request = require('superagent'); +const _ = require('lodash'); +const testing = require('taskcluster-lib-testing'); +suite(testing.suiteName(), function() { // This suite exercises the credential-handling functionality of the client // against a the auth service's testAuthenticate endpoint. diff --git a/clients/client/test/retry_test.js b/clients/client/test/retry_test.js index 687fe660155..f339c4e87ae 100644 --- a/clients/client/test/retry_test.js +++ b/clients/client/test/retry_test.js @@ -1,16 +1,14 @@ -suite('retry-test', function() { - const taskcluster = require('../'); - const assert = require('assert'); - const SchemaSet = require('taskcluster-lib-validate'); - const MonitorManager = require('taskcluster-lib-monitor'); - const APIBuilder = require('taskcluster-lib-api'); - const testing = require('taskcluster-lib-testing'); - const App = require('taskcluster-lib-app'); - const http = require('http'); - const httpProxy = require('http-proxy'); +const taskcluster = require('../'); +const assert = require('assert'); +const SchemaSet = require('taskcluster-lib-validate'); +const MonitorManager = require('taskcluster-lib-monitor'); +const APIBuilder = require('taskcluster-lib-api'); +const testing = require('taskcluster-lib-testing'); +const App = require('taskcluster-lib-app'); - const PROXY_PORT = 60551; - const rootUrl = `http://localhost:${PROXY_PORT}`; +const rootUrl = `http://localhost:60526`; + +suite(testing.suiteName(), function() { let proxier; // Construct API @@ -144,24 +142,6 @@ suite('retry-test', function() { trustProxy: false, apis: [api], }); - - // Finally, we set up a proxy that runs on rootUrl - // and sends requests to either of the services based on path. - - const proxy = httpProxy.createProxyServer({proxyTimeout: 0}); - proxy.on('error', (err, req, res) => { - req.connection.end(); // Nasty hack to make httpProxy pass along the connection close - }); - proxier = http.createServer(function(req, res) { - if (req.url.startsWith('/api/auth/')) { - proxy.web(req, res, {target: 'http://localhost:60552'}); - } else if (req.url.startsWith('/api/retrytest/')) { - proxy.web(req, res, {target: 'http://localhost:60526'}); - } else { - throw new Error(`Unknown service request: ${req.url}`); - } - }); - proxier.listen(PROXY_PORT); }); // Close server diff --git a/clients/client/test/utils_test.js b/clients/client/test/utils_test.js index 232a9c9586d..9c242264ba6 100644 --- a/clients/client/test/utils_test.js +++ b/clients/client/test/utils_test.js @@ -1,8 +1,9 @@ -suite('taskcluster utilities', function() { - let taskcluster = require('../'); - let parseTime = require('../src/parsetime'); - let assert = require('assert'); +const taskcluster = require('../'); +const parseTime = require('../src/parsetime'); +const assert = require('assert'); +const testing = require('taskcluster-lib-testing'); +suite(testing.suiteName(), function() { test('parseTime 1 year', function() { assert.equal(parseTime('1y').years, 1); assert.equal(parseTime('1 yr').years, 1); 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/dev-docs/best-practices/testing.md b/dev-docs/best-practices/testing.md index 67d43d1298a..601a8027f0f 100644 --- a/dev-docs/best-practices/testing.md +++ b/dev-docs/best-practices/testing.md @@ -92,10 +92,13 @@ Import all required modules at the top of a test file, then begin defining suite For all `suite` and `test` calls, use the `function() { .. }` notation instead of an arrow function. Doing so allows use of `this` to access the Mocha object. +Each file should have a top-level suite (or mockSuite) with its title generated by `testing.suiteName()`. + ```javascript const frobs = require('../src/frobs.js'); +const testing = require('taskcluster-lib-testing'); -suite('frobs', function() { +suite(testing.suiteName(), function() { test('frobnicates', async function() { // ... }); diff --git a/infrastructure/builder/README.md b/infrastructure/builder/README.md index b9e4e6c3d2f..a1d79e41c30 100644 --- a/infrastructure/builder/README.md +++ b/infrastructure/builder/README.md @@ -46,7 +46,7 @@ docker run -ti --rm -e PORT=80 -e .. auth/web To run an interactive shell in the image, use ```shell -docker run -ti --rm -e PORT=80 -e .. sh +docker run -ti --rm -e PORT=80 -e .. bash ``` ## Skipping Tasks diff --git a/infrastructure/builder/src/build/monoimage.js b/infrastructure/builder/src/build/monoimage.js index e17dbe92646..470dd38d165 100644 --- a/infrastructure/builder/src/build/monoimage.js +++ b/infrastructure/builder/src/build/monoimage.js @@ -367,7 +367,10 @@ const generateMonoimageTasks = ({tasks, baseDir, cfg, cmdOptions}) => { const dockerfile = [ `FROM ${nodeAlpineImage}`, - 'RUN apk update && apk add nginx && mkdir /run/nginx', + `RUN apk update && \ + apk add nginx && \ + mkdir /run/nginx && \ + apk add bash`, 'COPY app /app', 'ENV HOME=/app', 'WORKDIR /app', diff --git a/infrastructure/builder/src/build/repo.js b/infrastructure/builder/src/build/repo.js deleted file mode 100644 index ec4705963c6..00000000000 --- a/infrastructure/builder/src/build/repo.js +++ /dev/null @@ -1,102 +0,0 @@ -const util = require('util'); -const path = require('path'); -const rimraf = util.promisify(require('rimraf')); -const mkdirp = util.promisify(require('mkdirp')); -const libDocs = require('taskcluster-lib-docs'); -const Stamp = require('./stamp'); -const {gitClone, ensureTask} = require('./utils'); - -const generateRepoTasks = ({tasks, baseDir, spec, cfg, name, cmdOptions}) => { - const repository = spec.build.repositories.find(r => r.name === name); - - ensureTask(tasks, { - title: `Repo ${name} - Clone`, - provides: [ - `repo-${name}-dir`, // full path of the repository - `repo-${name}-exact-source`, // exact source URL for the repository - `repo-${name}-stamp`, - ], - locks: ['git'], - run: async (requirements, utils) => { - // using an external git repository, so clone that - const repoDir = path.join(baseDir, `repo-${name}`); - const source = repository.source; - const {exactRev, changed} = await gitClone({ - dir: repoDir, - url: source, - utils, - }); - - const [repoUrl] = source.split('#'); - const stamp = new Stamp({step: 'repo-clone', version: 1}, - `${repoUrl}#${exactRev}`); - - const provides = { - [`repo-${name}-dir`]: repoDir, - [`repo-${name}-exact-source`]: `${repoUrl}#${exactRev}`, - [`repo-${name}-stamp`]: stamp, - }; - - if (changed) { - return provides; - } else { - return utils.skip({provides}); - } - }, - }); - - if (!repository.docs || typeof repository.docs !== 'object') { - return; - } - - tasks.push({ - title: `Repo ${name} - Generate Docs`, - requires: [ - `repo-${name}-dir`, - `repo-${name}-exact-source`, - `repo-${name}-stamp`, - ], - provides: [ - `docs-${name}-dir`, // full path of the docs dir - `docs-${name}-stamp`, - ], - run: async (requirements, utils) => { - // note that docs directory paths must have this form (${basedir}/docs is - // mounted in docker images) - const docsDir = path.join(baseDir, 'docs', name); - const repoDir = requirements[`repo-${name}-dir`]; - - const stamp = new Stamp({step: 'repo-docs', version: 1}, - {config: repository.docs}, - requirements[`repo-${name}-stamp`]); - const provides = { - [`docs-${name}-dir`]: docsDir, - [`docs-${name}-stamp`]: stamp, - }; - - if (stamp.dirStamped(docsDir)) { - return utils.skip({provides}); - } - - await rimraf(docsDir); - await mkdirp(path.dirname(docsDir)); - - const documentor = await libDocs.documenter({ - projectName: repository.docs.projectName || name, - readme: path.join(repoDir, 'README.md'), - docsFolder: path.join(repoDir, 'docs'), - tier: repository.docs.tier, - menuIndex: repository.docs.menuIndex, - publish: false, - }); - await documentor.write({docsDir}); - - stamp.stampDir(docsDir); - return provides; - }, - }); - - return tasks; -}; - -module.exports = generateRepoTasks; diff --git a/infrastructure/terraform/README.md b/infrastructure/terraform/README.md index 488f07fe386..f6d43e394eb 100644 --- a/infrastructure/terraform/README.md +++ b/infrastructure/terraform/README.md @@ -17,7 +17,7 @@ in the shell you are applying this from. ## Requirements not managed here - A kubernetes cluster -- An (nginx ingress controller)[https://kubernetes.github.io/ingress-nginx/deploy/] in said cluster +- An [nginx ingress controller](https://kubernetes.github.io/ingress-nginx/deploy/) in said cluster - A TLS secret for the rootUrl hostname in the same Kubernetes namespace as the controller - A rabbitmq cluster with the RabbitMQ management plugin enabled - An SES email address set up in AWS. This cannot be created automatically by Terraform. diff --git a/libraries/api/README.md b/libraries/api/README.md index db1e8488276..f6f2e483ed8 100644 --- a/libraries/api/README.md +++ b/libraries/api/README.md @@ -140,7 +140,7 @@ Examples: ### Scopes Scopes should be specified in -[scope expression form](https://github.com/taskcluster/taskcluster-lib-scopes#new-style). +[scope expression form](../scopes#new-style). Parameters are substituted into scopes with `` syntax. For example, the following definition allows the method when *either* the caller's scopes satisfy `queue:create-task..` for the given `provisionerId` @@ -406,7 +406,7 @@ not repeat it! The `APIBuilder` instance will have an async `build` method that takes additional options and returns an API instance which can be passed to -[taskcluster-lib-app](https://github.com/taskcluster/taskcluster-lib-app). The +[taskcluster-lib-app](../app). The options to `builder.build` are: * `rootUrl` - the root URL for this instance of Taskcluster; this is used both to call the @@ -417,9 +417,9 @@ options to `builder.build` are: specified in `context` when the API was declared. The purpose of this parameter is to provide uesful application-specific objects such as Azure table objects or other API clients to the API methods. - * `monitor` (required) - an instance of [taskcluster-lib-monitor](https://github.com/taskcluster/taskcluster-lib-monitor) + * `monitor` (required) - an instance of [taskcluster-lib-monitor](../monitor) * `schemaset` (required) - a schemaset; this is from - [taskcluster-lib-validate](https://github.com/taskcluster/taskcluster-lib-validate). + [taskcluster-lib-validate](../validate). * `signatureValidator` - a validator for Hawk signatures; this is only required for the Auth service, as the default signature validator consults the Auth service. * `nonceManager` - a function to check for replay attacks (seldom used) @@ -437,7 +437,7 @@ reference data structure, and an `express(app)` method that configures the API on the given express app. For most Taskcluster services, the startup process uses -[taskcluster-lib-loader](https://github.com/taskcluster/taskcluster-lib-loader), +[taskcluster-lib-loader](../loader), and the relevant loader components are defined like this: ```js @@ -477,4 +477,4 @@ It should pass, although some tests will be skipped. If you are not modifing functionality tested by the skipped tests you're ready to get started: write some tests for the new functionality, then implement it! If you are modifying something requiring credentials, copy `user-config-example.yml` to `user-config.yml` and fill in the necessary credentials based on the comments in that file. -The taskcluster team has a series of [best practices](/docs/manual/devel/best-practices) which may help guide you in modifying the source code and making a pull request. +The taskcluster team has a series of [best practices](../../dev-docs/best-practices) which may help guide you in modifying the source code and making a pull request. diff --git a/libraries/api/package.json b/libraries/api/package.json index 9fe48558c64..30ef45c8399 100644 --- a/libraries/api/package.json +++ b/libraries/api/package.json @@ -9,5 +9,5 @@ "lint": "eslint src/*.js test/*.js", "test": "mocha test/*_test.js" }, - "main": "./src/builder.js" + "main": "./src/index.js" } diff --git a/libraries/api/src/builder.js b/libraries/api/src/index.js similarity index 100% rename from libraries/api/src/builder.js rename to libraries/api/src/index.js diff --git a/libraries/api/test/auth_test.js b/libraries/api/test/auth_test.js index 4dd57223f21..1a8d70a35f7 100644 --- a/libraries/api/test/auth_test.js +++ b/libraries/api/test/auth_test.js @@ -10,7 +10,7 @@ const testing = require('taskcluster-lib-testing'); const path = require('path'); const debug = require('debug')('auth_test'); -suite('api/auth', function() { +suite(testing.suiteName(), function() { // Reference for test api server let _apiServer = null; diff --git a/libraries/api/test/context_test.js b/libraries/api/test/context_test.js index 999401f417e..09ed963790e 100644 --- a/libraries/api/test/context_test.js +++ b/libraries/api/test/context_test.js @@ -6,8 +6,9 @@ const request = require('superagent'); const slugid = require('slugid'); const path = require('path'); const helper = require('./helper'); +const testing = require('taskcluster-lib-testing'); -suite('API (context)', function() { +suite(testing.suiteName(), function() { const rootUrl = 'http://localhost:4321'; test('Provides context', async () => { // Create test api diff --git a/libraries/api/test/errors_test.js b/libraries/api/test/errors_test.js index 5412cbba3ef..49c9c799530 100644 --- a/libraries/api/test/errors_test.js +++ b/libraries/api/test/errors_test.js @@ -5,8 +5,9 @@ const helper = require('./helper'); const _ = require('lodash'); const libUrls = require('taskcluster-lib-urls'); const expressError = require('../src/middleware/express-error.js'); +const testing = require('taskcluster-lib-testing'); -suite('api/errors', function() { +suite(testing.suiteName(), function() { // Create test api const builder = new APIBuilder({ title: 'Test Api', diff --git a/libraries/api/test/expressions_test.js b/libraries/api/test/expressions_test.js index b782d605ddc..e47ff95ad91 100644 --- a/libraries/api/test/expressions_test.js +++ b/libraries/api/test/expressions_test.js @@ -1,7 +1,8 @@ const assert = require('assert'); const ScopeExpressionTemplate = require('../src/expressions'); +const testing = require('taskcluster-lib-testing'); -suite('expression expansion success', function() { +suite(testing.suiteName(), function() { function scenario(expr, params, result, shouldFail=false) { return () => { diff --git a/libraries/api/test/middleware_test.js b/libraries/api/test/middleware_test.js index 63182a5ec76..e41b1755e06 100644 --- a/libraries/api/test/middleware_test.js +++ b/libraries/api/test/middleware_test.js @@ -1,8 +1,8 @@ const assert = require('assert'); const APIBuilder = require('../'); +const testing = require('taskcluster-lib-testing'); -suite('api/middleware', function() { - +suite(testing.suiteName(), function() { test('middleware is exported', function() { assert(APIBuilder.middleware); }); diff --git a/libraries/api/test/noncemanager_test.js b/libraries/api/test/noncemanager_test.js index 0c752b33f64..3dfd05756f4 100644 --- a/libraries/api/test/noncemanager_test.js +++ b/libraries/api/test/noncemanager_test.js @@ -2,8 +2,9 @@ const API = require('../src/api'); const assert = require('assert'); const Promise = require('promise'); const debug = require('debug')('base:test:nonceManager'); +const testing = require('taskcluster-lib-testing'); -suite('nonceManager test', function() { +suite(testing.suiteName(), function() { // Create a new nonceManager for each test let nonceManager = null; setup(function() { diff --git a/libraries/api/test/publish_test.js b/libraries/api/test/publish_test.js index eef46aa4403..0df8de500c2 100644 --- a/libraries/api/test/publish_test.js +++ b/libraries/api/test/publish_test.js @@ -3,8 +3,9 @@ const config = require('taskcluster-lib-config'); const aws = require('aws-sdk'); const assert = require('assert'); const libUrls = require('taskcluster-lib-urls'); +const testing = require('taskcluster-lib-testing'); -suite('api/publish', function() { +suite(testing.suiteName(), function() { const cfg = config({}); if (!cfg.aws || !cfg.referenceTestBucket) { diff --git a/libraries/api/test/responsetimer_test.js b/libraries/api/test/responsetimer_test.js index 1c135615b50..6d75756bc1c 100644 --- a/libraries/api/test/responsetimer_test.js +++ b/libraries/api/test/responsetimer_test.js @@ -4,8 +4,9 @@ const APIBuilder = require('../'); const MonitorManager = require('taskcluster-lib-monitor'); const helper = require('./helper'); const libUrls = require('taskcluster-lib-urls'); +const testing = require('taskcluster-lib-testing'); -suite('api/responsetimer', function() { +suite(testing.suiteName(), function() { // Create test api const builder = new APIBuilder({ title: 'Test Api', diff --git a/libraries/api/test/route_test.js b/libraries/api/test/route_test.js index 3a755f00c7f..4738895d461 100644 --- a/libraries/api/test/route_test.js +++ b/libraries/api/test/route_test.js @@ -4,8 +4,9 @@ const APIBuilder = require('../'); const slugid = require('slugid'); const helper = require('./helper'); const libUrls = require('taskcluster-lib-urls'); +const testing = require('taskcluster-lib-testing'); -suite('api/route', function() { +suite(testing.suiteName(), function() { const u = path => libUrls.api(helper.rootUrl, 'test', 'v1', path); // Create test api diff --git a/libraries/api/test/scopes_test.js b/libraries/api/test/scopes_test.js index 1ada7696422..dab7caee658 100644 --- a/libraries/api/test/scopes_test.js +++ b/libraries/api/test/scopes_test.js @@ -1,7 +1,8 @@ const assert = require('assert'); const APIBuilder = require('../'); +const testing = require('taskcluster-lib-testing'); -suite('api/route', function() { +suite(testing.suiteName(), function() { // Create test api const builder = new APIBuilder({ title: 'Test Api', diff --git a/libraries/api/test/validate_test.js b/libraries/api/test/validate_test.js index a3136557eb5..9e6f66adc94 100644 --- a/libraries/api/test/validate_test.js +++ b/libraries/api/test/validate_test.js @@ -6,8 +6,9 @@ const libUrls = require('taskcluster-lib-urls'); const path = require('path'); const SchemaSet = require('taskcluster-lib-validate'); const MonitorManager = require('taskcluster-lib-monitor'); +const testing = require('taskcluster-lib-testing'); -suite('api/validate', function() { +suite(testing.suiteName(), function() { const u = path => libUrls.api(helper.rootUrl, 'test', 'v1', path); // Create test api diff --git a/libraries/app/README.md b/libraries/app/README.md index 13ada662550..08e2dd1aded 100644 --- a/libraries/app/README.md +++ b/libraries/app/README.md @@ -4,7 +4,7 @@ This library supports Taskcluster microservices, providing a pre-built Express server based on a common configuration format. The usage is pretty simple. It is generally invoked in a -[taskcluster-lib-loader](https://github.com/taskcluster/taskcluster-lib-loader) +[taskcluster-lib-loader](../loader) stanza named server, like this: ```js @@ -34,7 +34,7 @@ The configuration (here `cfg.server`) has the following options: * `robotsTxt`: include a /robots.txt; *default is true* The values of the `apis` key are from -[taskcluster-lib-api](https://github.com/taskcluster/taskcluster-lib-api); each +[taskcluster-lib-api](../api); each is the result of the `APIBuilder.build` method in that library. In particular, each object should have an `express(app)` method which configures an Express app for the API. diff --git a/libraries/app/package.json b/libraries/app/package.json index 3852586a0d1..2001bb5b28b 100644 --- a/libraries/app/package.json +++ b/libraries/app/package.json @@ -9,5 +9,5 @@ "lint": "eslint src/*.js test/*.js", "test": "mocha test/*_test.js" }, - "main": "./src/app.js" + "main": "./src/index.js" } diff --git a/libraries/app/src/app.js b/libraries/app/src/index.js similarity index 100% rename from libraries/app/src/app.js rename to libraries/app/src/index.js diff --git a/libraries/app/test/app_test.js b/libraries/app/test/app_test.js index a15a003060e..8a962d96dca 100644 --- a/libraries/app/test/app_test.js +++ b/libraries/app/test/app_test.js @@ -3,8 +3,9 @@ let App = require('../'); let request = require('superagent'); let express = require('express'); let isUUID = require('is-uuid'); +const testing = require('taskcluster-lib-testing'); -suite('app', function() { +suite(testing.suiteName(), function() { // Test app creation suite('app({port: 1459})', function() { diff --git a/libraries/azure/test/basic_test.js b/libraries/azure/test/basic_test.js deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/libraries/azure/test/index_test.js b/libraries/azure/test/index_test.js index 12f2a25cfaf..b41171fef5e 100644 --- a/libraries/azure/test/index_test.js +++ b/libraries/azure/test/index_test.js @@ -3,8 +3,9 @@ const {sasCredentials} = require('..'); const nock = require('nock'); const url = require('url'); const libUrls = require('taskcluster-lib-urls'); +const testing = require('taskcluster-lib-testing'); -suite('index_test.js', function() { +suite(testing.suiteName(), function() { let scope; const mockAzureTableSAS = (accessLevel) => { diff --git a/libraries/config/package.json b/libraries/config/package.json index 9286432db67..5441e399dbe 100644 --- a/libraries/config/package.json +++ b/libraries/config/package.json @@ -5,7 +5,7 @@ "author": "Jonas Finnemann Jensen ", "description": "Configuration loader that injects env variables into YAML", "license": "MPL-2.0", - "main": "./src/config", + "main": "./src/index.js", "scripts": { "lint": "eslint src/*.js test/*.js", "test": "mocha test/*_test.js" diff --git a/libraries/config/src/config.js b/libraries/config/src/index.js similarity index 100% rename from libraries/config/src/config.js rename to libraries/config/src/index.js diff --git a/libraries/config/test/config_test.js b/libraries/config/test/config_test.js index 243404cd62c..d7d43c5c80a 100644 --- a/libraries/config/test/config_test.js +++ b/libraries/config/test/config_test.js @@ -1,7 +1,9 @@ -suite('config', function() { - let config = require('../'); - let path = require('path'); - let assume = require('assume'); +const config = require('../'); +const path = require('path'); +const assume = require('assume'); +const testing = require('taskcluster-lib-testing'); + +suite(testing.suiteName(), function() { test('load yaml', () => { let cfg = config({ diff --git a/libraries/docs/README.md b/libraries/docs/README.md index 22d9c8a860d..3b664842f0d 100644 --- a/libraries/docs/README.md +++ b/libraries/docs/README.md @@ -33,21 +33,21 @@ production service starts up. The tarball contains a mixture of documentation from the project's README, auto-generated documentation for pulse exchanges and APIs, and hand-written documentation. -The [taskcluster-docs](https://github.com/taskcluster/taskcluster-docs) project +The [taskcluster-docs](docs/) project then downloads those tarballs and incorporates the results into the documentation page. Documentation Format -------------------- -The format for the tarball that is uploaded to s3 is [documented here](https://github.com/taskcluster/taskcluster-lib-docs/blob/master/docs/format.md). +The format for the tarball that is uploaded to s3 is [documented here](docs/format.md). Usage ----- **[Old Way Only] Do not forget to add the scopes before pushing your service to production! `[auth:aws-s3:read-write:taskcluster-raw-docs//]`** -This library should be included as a component in a [Taskcluster Component Loader](https://github.com/taskcluster/taskcluster-lib-loader) +This library should be included as a component in a [Taskcluster Component Loader](../loader) setup so that it is called upon a service starting in Heroku or as a post-publish step in a library. Options and defaults are listed below. diff --git a/libraries/docs/test/main_test.js b/libraries/docs/test/main_test.js index e90069c9d53..4c4edf2d9d8 100644 --- a/libraries/docs/test/main_test.js +++ b/libraries/docs/test/main_test.js @@ -10,6 +10,7 @@ const APIBuilder = require('taskcluster-lib-api'); const {Exchanges} = require('taskcluster-lib-pulse'); const MockS3UploadStream = require('./mockS3UploadStream'); const tmp = require('tmp'); +const testing = require('taskcluster-lib-testing'); function assertInTarball(shoulds, tarball) { shoulds.push('metadata.json'); @@ -38,7 +39,7 @@ function assertInTarball(shoulds, tarball) { }); } -suite('documenter', () => { +suite(testing.suiteName(), () => { let schemaset = null; let exchanges = null; let references = null; diff --git a/libraries/docs/test/package_test.js b/libraries/docs/test/package_test.js deleted file mode 100644 index 1b565ee6bb4..00000000000 --- a/libraries/docs/test/package_test.js +++ /dev/null @@ -1,15 +0,0 @@ -suite('Package', () => { - let assert = require('assert'); - let pack = require('../package.json'); - let exec = require('child_process'); - - test('git tag must match package version', function() { - let tag = exec.execSync('git tag -l --contains HEAD').toString().trim(); - if (tag === '') { - console.log(' No git tag, no need to check tag!'); - this.skip(); - } - assert.equal('v' + pack.version, tag); - }); - -}); 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/package.json b/libraries/iterate/package.json index bb53a9ae076..8a746945636 100644 --- a/libraries/iterate/package.json +++ b/libraries/iterate/package.json @@ -3,7 +3,7 @@ "private": true, "version": "11.0.0", "description": "A library to share iteration logic", - "main": "src/iterate.js", + "main": "src/index.js", "scripts": { "lint": "eslint src/*.js test/*.js", "test": "mocha" diff --git a/libraries/iterate/src/iterate.js b/libraries/iterate/src/index.js similarity index 66% rename from libraries/iterate/src/iterate.js rename to libraries/iterate/src/index.js index 70e029b9bcf..bda39f3ba5e 100644 --- a/libraries/iterate/src/iterate.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 65e4b837611..579a694e463 100644 --- a/libraries/iterate/test/iterate_test.js +++ b/libraries/iterate/test/iterate_test.js @@ -1,10 +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', @@ -27,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('Iterate', () => { +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; @@ -85,93 +82,113 @@ suite('Iterate', () => { 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(() => { @@ -181,200 +198,111 @@ suite('Iterate', () => { }, }); - 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', @@ -382,230 +310,178 @@ suite('Iterate', () => { '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 { @@ -614,7 +490,7 @@ suite('Iterate', () => { }, }); - let events = new IterateEvents(i, [ + const events = new IterateEvents(i, [ 'started', 'iteration-start', 'iteration-failure', @@ -634,19 +510,17 @@ suite('Iterate', () => { '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 1b3bc69856f..cbf39c258a4 100644 --- a/libraries/iterate/test/watchdog_test.js +++ b/libraries/iterate/test/watchdog_test.js @@ -1,83 +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('watchdog', function() { - let events, start; +suite(testing.suiteName(), function() { + 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/loader/package.json b/libraries/loader/package.json index ceb4d5e3835..47404ff57a8 100644 --- a/libraries/loader/package.json +++ b/libraries/loader/package.json @@ -3,7 +3,7 @@ "private": true, "version": "11.0.0", "description": "a component loader for taskcluster", - "main": "src/loader.js", + "main": "src/index.js", "directories": { "test": "test" }, diff --git a/libraries/loader/src/loader.js b/libraries/loader/src/index.js similarity index 100% rename from libraries/loader/src/loader.js rename to libraries/loader/src/index.js diff --git a/libraries/loader/test/loader_tests.js b/libraries/loader/test/loader_tests.js index 33e47dfa36c..bb71075ea87 100644 --- a/libraries/loader/test/loader_tests.js +++ b/libraries/loader/test/loader_tests.js @@ -1,5 +1,5 @@ let assume = require('assume'); -let subject = require('../src/loader'); +let subject = require('../src'); let assert = require('assert'); suite('component loader', () => { diff --git a/libraries/monitor/README.md b/libraries/monitor/README.md index 3300c2f6423..946f8a6fe72 100644 --- a/libraries/monitor/README.md +++ b/libraries/monitor/README.md @@ -265,16 +265,12 @@ A common pattern in Taskcluster projects is to have handler functions in a worke can be timed (in milliseconds) by wrapping them with `taskcluster-lib-monitor`: ```js -const listener = new taskcluster.PulseListener({ - credentials: {clientId: 'test-client', accessToken: 'test'}, - queueName: 'a-queue-name', -}); - const handler = function(message) { console.log(message); }; -listener.on('message', monitor.timedHandler('logging-listener', handler)); +const consumer = libPulse.consume({..}, + monitor.timedHandler('pulse-listener', handler)); ``` Specifically, `timedHandler` takes a function and wraps it with timing logic, returning a function with the same signature. diff --git a/libraries/monitor/src/index.js b/libraries/monitor/src/index.js index 08a62201005..c03a78d3ff3 100644 --- a/libraries/monitor/src/index.js +++ b/libraries/monitor/src/index.js @@ -211,8 +211,7 @@ class MonitorManager { } _unhandledRejectionHandler(reason, p) { - const err = 'Unhandled Rejection at: Promise ' + p + ' reason: ' + reason; - this.rootMonitor.reportError(err); + this.rootMonitor.reportError(reason); if (!this.bailOnUnhandledRejection) { return; } diff --git a/libraries/monitor/test/base_test.js b/libraries/monitor/test/base_test.js index 341fe358813..90a74f10194 100644 --- a/libraries/monitor/test/base_test.js +++ b/libraries/monitor/test/base_test.js @@ -5,7 +5,7 @@ const testing = require('taskcluster-lib-testing'); const stream = require('stream'); const MonitorManager = require('../src'); -suite('BaseMonitor', function() { +suite(testing.suiteName(), function() { let manager, monitor, messages; setup(function() { @@ -276,7 +276,7 @@ suite('BaseMonitor', function() { '--shouldUnhandle', ], (done, code, output) => { assert.equal(code, 0); - assert(output.includes('UnhandledPromiseRejectionWarning: whaaa')); + assert(output.match(/UnhandledPromiseRejectionWarning:.*whaaa/)); assert.throws(() => JSON.parse(output)); done(); }); @@ -289,7 +289,7 @@ suite('BaseMonitor', function() { '--bailOnUnhandledRejection', ], (done, code, output) => { assert.equal(code, 1); - assert(output.includes('Unhandled Rejection at')); + assert(output.includes('whaaa')); assert(JSON.parse(output)); done(); }); @@ -301,7 +301,7 @@ suite('BaseMonitor', function() { '--patchGlobal', ], (done, code, output) => { assert.equal(code, 0); - assert(output.includes('Unhandled Rejection at')); + assert(output.includes('whaaa')); assert(JSON.parse(output)); done(); }); diff --git a/libraries/monitor/test/exit.js b/libraries/monitor/test/exit.js index ab302e5c88e..f3e86e88b5e 100644 --- a/libraries/monitor/test/exit.js +++ b/libraries/monitor/test/exit.js @@ -20,5 +20,5 @@ if (options.shouldError) { throw new Error('hello there'); } if (options.shouldUnhandle) { - Promise.reject('whaaa'); + Promise.reject(new Error('whaaa')); } diff --git a/libraries/monitor/test/logger_test.js b/libraries/monitor/test/logger_test.js index 4ab680e804a..ec0f80fe562 100644 --- a/libraries/monitor/test/logger_test.js +++ b/libraries/monitor/test/logger_test.js @@ -2,8 +2,9 @@ const assert = require('assert'); const stream = require('stream'); const Ajv = require('ajv'); const MonitorManager = require('../src'); +const testing = require('taskcluster-lib-testing'); -suite('Logging', function() { +suite(testing.suiteName(), function() { let manager, monitor, messages, destination; setup(function() { diff --git a/libraries/monitor/test/registry_test.js b/libraries/monitor/test/registry_test.js index 83455af3ccd..c2a7f628e82 100644 --- a/libraries/monitor/test/registry_test.js +++ b/libraries/monitor/test/registry_test.js @@ -1,7 +1,8 @@ const assert = require('assert'); const MonitorManager = require('../src'); +const testing = require('taskcluster-lib-testing'); -suite('Registry', function() { +suite(testing.suiteName(), function() { test('can add custom message types', function() { const manager = new MonitorManager({ diff --git a/libraries/pulse/README.md b/libraries/pulse/README.md index 74533e121ef..55a4fdac9c9 100644 --- a/libraries/pulse/README.md +++ b/libraries/pulse/README.md @@ -1,7 +1,7 @@ # Pulse Library Library for interacting with Pulse and Taskcluster-Pulse. See [the -docs](https://docs.taskcluster.net/manual/design/apis/pulse) for more +docs](../../ui/docs/manual/design/apis/pulse.md) for more information on Pulse. This library is designed for use in Taskcluster services, both for producing @@ -365,7 +365,7 @@ support for sending messages is quite simple, but this component also handles declaring exchanges, message schemas, and so on, and producing a reference document that can be consumed by client libraries to generate easy-to-use clients. All of this is similar to what -[taskcluster-lib-api](https://github.com/taskcluster/taskcluster-lib-api) does +[taskcluster-lib-api](../api) does for HTTP APIs. ## Declaring Exchanges @@ -389,11 +389,11 @@ const exchanges = new Exchanges({ ``` The `serviceName` should match that passed to -[taskcluster-lib-validate](https://github.com/taskcluster/taskcluster-lib-validate). +[taskcluster-lib-validate](../validate). The `projectName` is used to construct exchange and queue names. It should match the pulse namespace (at least in production deployments) and the name passed to -[taskcluster-lib-monitor](https://github.com/taskcluster/taskcluster-lib-monitor), +[taskcluster-lib-monitor](../monitor), Having created the `exchanges` instance, declare exchanges on it: @@ -472,7 +472,7 @@ By convention, these are prefixed with `route.`. The `exchanges.reference()` method will return a reference document suitable for publication under `/references`. -This is typically passed to [taskcluster-lib-docs](https://github.com/taskcluster/taskcluster-lib-docs) like this: +This is typically passed to [taskcluster-lib-docs](../docs) like this: ```javascript Docs.documenter({ diff --git a/libraries/pulse/test/client_test.js b/libraries/pulse/test/client_test.js index 15ccd8b3300..9f0155ce6eb 100644 --- a/libraries/pulse/test/client_test.js +++ b/libraries/pulse/test/client_test.js @@ -3,8 +3,9 @@ const {Connection} = require('../src/client'); const amqplib = require('amqplib'); const assume = require('assume'); const debugModule = require('debug'); -const libMonitor = require('taskcluster-lib-monitor'); +const MonitorManager = require('taskcluster-lib-monitor'); const slugid = require('slugid'); +const testing = require('taskcluster-lib-testing'); const PULSE_CONNECTION_STRING = process.env.PULSE_CONNECTION_STRING; @@ -22,10 +23,21 @@ const connectionTests = connectionString => { const message = Buffer.from('Hello'); const debug = debugModule('test'); + let monitorManager = null; let monitor; setup(async function() { - monitor = await libMonitor({projectName: 'tests', mock: true}); + monitorManager = new MonitorManager({ + serviceName: 'lib-pulse-test', + }); + monitorManager.setup({ + mock: true, + }); + monitor = monitorManager.monitor('tests'); + }); + + teardown(() => { + monitorManager.terminate(); }); // publish a message to the exchange using just amqplib, declaring the @@ -298,7 +310,7 @@ const connectionTests = connectionString => { }); }; -suite('Client', function() { +suite(testing.suiteName(), function() { suite('with RabbitMQ', function() { suiteSetup(function() { if (!PULSE_CONNECTION_STRING) { diff --git a/libraries/pulse/test/consumer_test.js b/libraries/pulse/test/consumer_test.js index 17fc512b740..2c2048c73e3 100644 --- a/libraries/pulse/test/consumer_test.js +++ b/libraries/pulse/test/consumer_test.js @@ -2,12 +2,30 @@ const {FakeClient, Client, consume, connectionStringCredentials} = require('../s const amqplib = require('amqplib'); const assume = require('assume'); const debugModule = require('debug'); -const libMonitor = require('taskcluster-lib-monitor'); +const MonitorManager = require('taskcluster-lib-monitor'); const assert = require('assert'); +const testing = require('taskcluster-lib-testing'); const PULSE_CONNECTION_STRING = process.env.PULSE_CONNECTION_STRING; -suite('consumer_test.js', function() { +suite(testing.suiteName(), function() { + let monitorManager = null; + let monitor; + + setup(async function() { + monitorManager = new MonitorManager({ + serviceName: 'lib-pulse-test', + }); + monitorManager.setup({ + mock: true, + }); + monitor = monitorManager.monitor('tests'); + }); + + teardown(() => { + monitorManager.terminate(); + }); + suite('PulseConsumer', function() { // use a unique name for each test run, just to ensure nothing interferes const unique = new Date().getTime().toString(); @@ -49,7 +67,6 @@ suite('consumer_test.js', function() { }; test('consume messages', async function() { - const monitor = await libMonitor({projectName: 'tests', mock: true}); const client = new Client({ credentials: connectionStringCredentials(PULSE_CONNECTION_STRING), retirementDelay: 50, @@ -120,7 +137,6 @@ suite('consumer_test.js', function() { }); test('consume messages ephemerally', async function() { - const monitor = await libMonitor({projectName: 'tests', mock: true}); const client = new Client({ credentials: connectionStringCredentials(PULSE_CONNECTION_STRING), retirementDelay: 50, @@ -205,7 +221,6 @@ suite('consumer_test.js', function() { }); test('no queueuName is an error', async function() { - const monitor = await libMonitor({projectName: 'tests', mock: true}); const client = new Client({ credentials: connectionStringCredentials(PULSE_CONNECTION_STRING), retirementDelay: 50, diff --git a/libraries/pulse/test/credentials_test.js b/libraries/pulse/test/credentials_test.js index 33815676357..ddf24893ed5 100644 --- a/libraries/pulse/test/credentials_test.js +++ b/libraries/pulse/test/credentials_test.js @@ -1,8 +1,9 @@ const {pulseCredentials} = require('../src'); const assert = require('assert'); const assume = require('assume'); +const testing = require('taskcluster-lib-testing'); -suite('pulseCredentials', function() { +suite(testing.suiteName(), function() { test('missing arguments are an error', async function() { assume(() => pulseCredentials({password: 'pw', hostname: 'h', vhost: 'v'})) .throws(/username/); diff --git a/libraries/pulse/test/publisher_test.js b/libraries/pulse/test/publisher_test.js index 23dc4df678f..d849712e3e6 100644 --- a/libraries/pulse/test/publisher_test.js +++ b/libraries/pulse/test/publisher_test.js @@ -3,14 +3,14 @@ const path = require('path'); const amqplib = require('amqplib'); const assume = require('assume'); const assert = require('assert'); -const libMonitor = require('taskcluster-lib-monitor'); +const MonitorManager = require('taskcluster-lib-monitor'); const SchemaSet = require('taskcluster-lib-validate'); const libUrls = require('taskcluster-lib-urls'); -const libTesting = require('taskcluster-lib-testing'); +const testing = require('taskcluster-lib-testing'); const PULSE_CONNECTION_STRING = process.env.PULSE_CONNECTION_STRING; -suite('publisher_test.js', function() { +suite(testing.suiteName(), function() { const exchangeOptions = { serviceName: 'lib-pulse', projectName: 'taskcluster-lib-pulse', @@ -55,6 +55,23 @@ suite('publisher_test.js', function() { CCBuilder: msg => [], }; + let monitorManager = null; + let monitor; + + setup(async function() { + monitorManager = new MonitorManager({ + serviceName: 'lib-pulse-test', + }); + monitorManager.setup({ + mock: true, + }); + monitor = monitorManager.monitor('tests'); + }); + + teardown(() => { + monitorManager.terminate(); + }); + suite('Exchanges', function() { test('constructor args required', function() { assume(() => new Exchanges({})).to.throw(/is required/); @@ -186,7 +203,6 @@ suite('publisher_test.js', function() { return; } - const monitor = await libMonitor({projectName: exchangeOptions.projectName, mock: true}); client = new Client({ credentials: connectionStringCredentials(PULSE_CONNECTION_STRING), retirementDelay: 50, @@ -248,7 +264,7 @@ suite('publisher_test.js', function() { test('publish a message', async function() { await publisher.eggHatched({eggId: 'yolks-on-you'}); - await libTesting.poll(async () => { + await testing.poll(async () => { assert.equal(messages.length, 1); const got = messages[0]; assert.equal(got.fields.routingKey, 'yolks-on-you'); @@ -264,7 +280,7 @@ suite('publisher_test.js', function() { const eggIds = [...Array(10000).keys()].map(id => id.toString()); await Promise.all(eggIds.map(eggId => publisher.eggHatched({eggId}))); - await libTesting.poll(async () => { + await testing.poll(async () => { const got = messages.map(msg => msg.fields.routingKey); assert.deepEqual(got.length, eggIds.length, 'got expected number of messages'); assert.deepEqual(got.sort(), eggIds.sort(), 'got exactly the expected messages'); @@ -278,7 +294,7 @@ suite('publisher_test.js', function() { client.connections[0].amqp.close(); // force closure.. await Promise.all(['x', 'y', 'z'].map(eggId => publisher.eggHatched({eggId}))); - await libTesting.poll(async () => { + await testing.poll(async () => { const got = messages.map(msg => msg.fields.routingKey).sort(); assert.deepEqual(got, ['a', 'b', 'c', 'i', 'j', 'k', 'l', 'm', 'x', 'y', 'z']); }); diff --git a/libraries/references/README.md b/libraries/references/README.md index a2ebfc7d621..d70dd76de48 100644 --- a/libraries/references/README.md +++ b/libraries/references/README.md @@ -14,7 +14,7 @@ meta-schemas under `schemas/`. ## Built Services This input format corresponds to a directory structure built by -[taskcluster-builder](https://github.com/taskcluster/taskcluster-builder) +[taskcluster-builder](../../infrastructure/builder) during the cluster build process: ``` @@ -32,7 +32,7 @@ during the cluster build process: ``` This is a subset of the -[taskcluster-lib-docs](https://github.com/taskcluster/taskcluster-lib-docs) +[taskcluster-lib-docs](../docs) documentation tarball format, and `metadata.json` is defined there. The library will in fact load any `.json` files in `references` and interpret them according to their schema. So, the `$schema` property of every file in diff --git a/libraries/references/test/action_schema_test.js b/libraries/references/test/action_schema_test.js index 4001effc77b..816019165e3 100644 --- a/libraries/references/test/action_schema_test.js +++ b/libraries/references/test/action_schema_test.js @@ -1,8 +1,9 @@ const {getCommonSchemas} = require('../src/common-schemas'); const libUrls = require('taskcluster-lib-urls'); const References = require('..'); +const testing = require('taskcluster-lib-testing'); -suite('action_schema_test.js', function() { +suite(testing.suiteName(), function() { const rootUrl = libUrls.testRootUrl(); const references = new References({ diff --git a/libraries/references/test/built-services_test.js b/libraries/references/test/built-services_test.js index eaa23e20807..4a037b54341 100644 --- a/libraries/references/test/built-services_test.js +++ b/libraries/references/test/built-services_test.js @@ -2,8 +2,9 @@ const assert = require('assert'); const {load} = require('../src/built-services'); const mockFs = require('mock-fs'); const References = require('..'); +const testing = require('taskcluster-lib-testing'); -suite('built-services_test.js', function() { +suite(testing.suiteName(), function() { teardown(function() { mockFs.restore(); }); diff --git a/libraries/references/test/common-schemas_test.js b/libraries/references/test/common-schemas_test.js index 25c0b2116b8..89786a53720 100644 --- a/libraries/references/test/common-schemas_test.js +++ b/libraries/references/test/common-schemas_test.js @@ -1,7 +1,8 @@ const assert = require('assert'); const {getCommonSchemas} = require('../src/common-schemas'); +const testing = require('taskcluster-lib-testing'); -suite('common-schemas_test.js', function() { +suite(testing.suiteName(), function() { test('loads common schemas', function() { const schemas = getCommonSchemas(); assert(schemas.some( diff --git a/libraries/references/test/metaschema_test.js b/libraries/references/test/metaschema_test.js index 28e403be3db..8b945de7cf7 100644 --- a/libraries/references/test/metaschema_test.js +++ b/libraries/references/test/metaschema_test.js @@ -1,8 +1,9 @@ const {getCommonSchemas} = require('../src/common-schemas'); const References = require('..'); const libUrls = require('taskcluster-lib-urls'); +const testing = require('taskcluster-lib-testing'); -suite('metaschema_test.js', function() { +suite(testing.suiteName(), function() { let validate; suiteSetup('setup Ajv', function() { diff --git a/libraries/references/test/references_test.js b/libraries/references/test/references_test.js index 71194ae1e42..6c41328ed3c 100644 --- a/libraries/references/test/references_test.js +++ b/libraries/references/test/references_test.js @@ -4,8 +4,9 @@ const {getCommonSchemas} = require('../src/common-schemas'); const {makeSerializable} = require('../src/serializable'); const mockFs = require('mock-fs'); const References = require('..'); +const testing = require('taskcluster-lib-testing'); -suite('references_test.js', function() { +suite(testing.suiteName(), function() { teardown(function() { mockFs.restore(); }); diff --git a/libraries/references/test/serializable_test.js b/libraries/references/test/serializable_test.js index cab5cfa3c07..45f8e05d9ba 100644 --- a/libraries/references/test/serializable_test.js +++ b/libraries/references/test/serializable_test.js @@ -3,8 +3,9 @@ const References = require('..'); const {makeSerializable} = require('../src/serializable'); const {getCommonSchemas} = require('../src/common-schemas'); const libUrls = require('taskcluster-lib-urls'); +const testing = require('taskcluster-lib-testing'); -suite('serializable_test.js', function() { +suite(testing.suiteName(), function() { const rootUrl = libUrls.testRootUrl(); const legacyRootUrl = 'https://taskcluster.net'; diff --git a/libraries/references/test/uri-structured_test.js b/libraries/references/test/uri-structured_test.js index 82aaf223aa8..e881aead710 100644 --- a/libraries/references/test/uri-structured_test.js +++ b/libraries/references/test/uri-structured_test.js @@ -3,8 +3,9 @@ const assert = require('assert'); const References = require('..'); const {readUriStructured, writeUriStructured} = require('../src/uri-structured'); const mockFs = require('mock-fs'); +const testing = require('taskcluster-lib-testing'); -suite('uri-structured_test.js', function() { +suite(testing.suiteName(), function() { teardown(function() { mockFs.restore(); }); diff --git a/libraries/references/test/validate_test.js b/libraries/references/test/validate_test.js index 3cde4061bb9..ab135bd4f6d 100644 --- a/libraries/references/test/validate_test.js +++ b/libraries/references/test/validate_test.js @@ -5,6 +5,7 @@ const merge = require('lodash/merge'); const omit = require('lodash/omit'); const References = require('..'); const {validate} = require('../src/validate'); +const testing = require('taskcluster-lib-testing'); class RefBuilder { constructor() { @@ -76,7 +77,7 @@ class RefBuilder { } } -suite('validate_test.js', function() { +suite(testing.suiteName(), function() { const assertProblems = (references, expected) => { try { validate(references); diff --git a/libraries/scopes/README.md b/libraries/scopes/README.md index 49bd83ead3f..a372d7ee9e6 100644 --- a/libraries/scopes/README.md +++ b/libraries/scopes/README.md @@ -2,7 +2,7 @@ Simple utilities to validate scopes, scope-sets, and scope-expression satisfiability. -For information on scopes, see [the Taskcluster documentation](/docs/manual/integrations/apis/scopes). +For information on scopes, see [the Taskcluster documentation](../../ui/docs/manual/design/apis/hawk/scopes.md). ## Usage diff --git a/libraries/scopes/test/expression_test.js b/libraries/scopes/test/expression_test.js index cdcc9f881a7..d8757026319 100644 --- a/libraries/scopes/test/expression_test.js +++ b/libraries/scopes/test/expression_test.js @@ -1,131 +1,134 @@ const assert = require('assert'); const utils = require('../src/expressions.js'); +const testing = require('taskcluster-lib-testing'); -suite('scope expression validity:', function() { +suite(testing.suiteName(), function() { + suite('scope expression validity:', function() { - function scenario(expr, shouldFail=false) { - return () => { - try { - assert(utils.validExpression(expr)); - } catch (err) { + function scenario(expr, shouldFail=false) { + return () => { + try { + assert(utils.validExpression(expr)); + } catch (err) { + if (shouldFail) { + return; + } + throw err; + } if (shouldFail) { - return; + throw new Error('Should have failed!'); } - throw err; - } - if (shouldFail) { - throw new Error('Should have failed!'); - } - }; - } + }; + } - // All of the following are invalid - test('empty is not OK', scenario({}, 'should-fail')); - test('array is not OK', scenario([], 'should-fail')); - test('int is not OK', scenario(12, 'should-fail')); - test('wrong key is not OK', scenario({Foo: ['abc']}, 'should-fail')); - test('multiple keys is not OK', scenario({AnyOf: ['abc'], AllOf: ['abc']}, 'should-fail')); - test('int value is not OK', scenario({AnyOf: 1}, 'should-fail')); - test('string value is not OK', scenario({AnyOf: 'scope:bar'}, 'should-fail')); - test('object value is not OK', scenario({AnyOf: {}}, 'should-fail')); + // All of the following are invalid + test('empty is not OK', scenario({}, 'should-fail')); + test('array is not OK', scenario([], 'should-fail')); + test('int is not OK', scenario(12, 'should-fail')); + test('wrong key is not OK', scenario({Foo: ['abc']}, 'should-fail')); + test('multiple keys is not OK', scenario({AnyOf: ['abc'], AllOf: ['abc']}, 'should-fail')); + test('int value is not OK', scenario({AnyOf: 1}, 'should-fail')); + test('string value is not OK', scenario({AnyOf: 'scope:bar'}, 'should-fail')); + test('object value is not OK', scenario({AnyOf: {}}, 'should-fail')); - // All of the following should be valid - [ - 'hello-world', - 'scope:foo', - {AnyOf: []}, - {AllOf: []}, - {AnyOf: ['abc']}, - {AllOf: ['abc']}, - {AnyOf: [{AnyOf: ['scope:foo:thing']}]}, - {AllOf: [{AllOf: ['scope:foo:thing', 'scope:bar:thing']}]}, - {AnyOf: [{AllOf: ['scope:foo:thing']}]}, - {AllOf: [{AnyOf: ['scope:foo:thing', 'scope:bar:thing']}]}, - {AllOf: [{AnyOf: [{AllOf: ['foo']}, {AllOf: ['bar']}]}]}, - ].forEach(c => { - test(`${JSON.stringify(c)} is OK`, scenario(c)); + // All of the following should be valid + [ + 'hello-world', + 'scope:foo', + {AnyOf: []}, + {AllOf: []}, + {AnyOf: ['abc']}, + {AllOf: ['abc']}, + {AnyOf: [{AnyOf: ['scope:foo:thing']}]}, + {AllOf: [{AllOf: ['scope:foo:thing', 'scope:bar:thing']}]}, + {AnyOf: [{AllOf: ['scope:foo:thing']}]}, + {AllOf: [{AnyOf: ['scope:foo:thing', 'scope:bar:thing']}]}, + {AllOf: [{AnyOf: [{AllOf: ['foo']}, {AllOf: ['bar']}]}]}, + ].forEach(c => { + test(`${JSON.stringify(c)} is OK`, scenario(c)); + }); }); -}); -suite('scope expression satisfaction:', function() { + suite('scope expression satisfaction:', function() { - function scenario(scopes, expr, shouldFail=false) { - return () => { - try { - assert(utils.satisfiesExpression(scopes, expr)); - } catch (err) { + function scenario(scopes, expr, shouldFail=false) { + return () => { + try { + assert(utils.satisfiesExpression(scopes, expr)); + } catch (err) { + if (shouldFail) { + return; + } + throw err; + } if (shouldFail) { - return; + throw new Error('Should have failed!'); } - throw err; - } - if (shouldFail) { - throw new Error('Should have failed!'); - } - }; - } + }; + } - // The following should _not_ succeed - [ - [[], {AnyOf: []}], - [[], 'missing-scope'], - [['wrong-scope'], 'missing-scope'], - [['ghi'], {AnyOf: ['abc', 'def']}], - [['ghi*'], {AnyOf: ['abc', 'def']}], - [['ghi', 'fff'], {AnyOf: ['abc', 'def']}], - [['ghi*', 'fff*'], {AnyOf: ['abc', 'def']}], - [['abc'], {AnyOf: ['ghi']}], - [['abc*'], {AllOf: ['abc', 'ghi']}], - [[''], {AnyOf: ['abc', 'def']}], - [['abc:def'], {AnyOf: ['abc', 'def']}], - [['xyz', 'abc'], {AllOf: [{AnyOf: [{AllOf: ['foo']}, {AllOf: ['bar']}]}]}], - ].forEach(([s, e]) => { - test(`${JSON.stringify(e)} is _not_ satisfied by ${JSON.stringify(s)}`, scenario(s, e, 'should-fail')); - }); + // The following should _not_ succeed + [ + [[], {AnyOf: []}], + [[], 'missing-scope'], + [['wrong-scope'], 'missing-scope'], + [['ghi'], {AnyOf: ['abc', 'def']}], + [['ghi*'], {AnyOf: ['abc', 'def']}], + [['ghi', 'fff'], {AnyOf: ['abc', 'def']}], + [['ghi*', 'fff*'], {AnyOf: ['abc', 'def']}], + [['abc'], {AnyOf: ['ghi']}], + [['abc*'], {AllOf: ['abc', 'ghi']}], + [[''], {AnyOf: ['abc', 'def']}], + [['abc:def'], {AnyOf: ['abc', 'def']}], + [['xyz', 'abc'], {AllOf: [{AnyOf: [{AllOf: ['foo']}, {AllOf: ['bar']}]}]}], + ].forEach(([s, e]) => { + test(`${JSON.stringify(e)} is _not_ satisfied by ${JSON.stringify(s)}`, scenario(s, e, 'should-fail')); + }); - // The following should succeed - [ - [[], {AllOf: []}], - [['A'], {AllOf: ['A']}], - [['A', 'B'], 'A'], - [['abc'], {AnyOf: ['abc', 'def']}], - [['def'], {AnyOf: ['abc', 'def']}], - [['abc', 'def'], {AnyOf: ['abc', 'def']}], - [['abc*'], {AnyOf: ['abc', 'def']}], - [['abc*'], {AnyOf: ['abc']}], - [['abc*', 'def*'], {AnyOf: ['abc', 'def']}], - [['foo'], {AllOf: [{AnyOf: [{AllOf: ['foo']}, {AllOf: ['bar']}]}]}], - ].forEach(([s, e]) => { - test(`${JSON.stringify(e)} is satisfied by ${JSON.stringify(s)}`, scenario(s, e)); - }); + // The following should succeed + [ + [[], {AllOf: []}], + [['A'], {AllOf: ['A']}], + [['A', 'B'], 'A'], + [['abc'], {AnyOf: ['abc', 'def']}], + [['def'], {AnyOf: ['abc', 'def']}], + [['abc', 'def'], {AnyOf: ['abc', 'def']}], + [['abc*'], {AnyOf: ['abc', 'def']}], + [['abc*'], {AnyOf: ['abc']}], + [['abc*', 'def*'], {AnyOf: ['abc', 'def']}], + [['foo'], {AllOf: [{AnyOf: [{AllOf: ['foo']}, {AllOf: ['bar']}]}]}], + ].forEach(([s, e]) => { + test(`${JSON.stringify(e)} is satisfied by ${JSON.stringify(s)}`, scenario(s, e)); + }); -}); + }); -suite('scope expression failure explanation:', function() { + suite('scope expression failure explanation:', function() { - function scenario(scopes, expr, explanation) { - return () => { - assert.deepEqual(explanation, utils.removeGivenScopes(scopes, expr)); - }; - } + function scenario(scopes, expr, explanation) { + return () => { + assert.deepEqual(explanation, utils.removeGivenScopes(scopes, expr)); + }; + } - [ - [[], {AllOf: []}, null], - [['a'], {AllOf: ['a']}, null], - [['a'], {AllOf: ['a', 'b']}, 'b'], - [[], {AnyOf: []}, {AnyOf: []}], - [['a'], {AnyOf: ['a', 'b']}, null], - [['c'], {AnyOf: ['a', 'b']}, {AnyOf: ['a', 'b']}], - [['ghi'], {AnyOf: ['abc', 'def']}, {AnyOf: ['abc', 'def']}], - [['ghi'], {AllOf: ['abc', 'def', 'ghi']}, {AllOf: ['abc', 'def']}], - [['ghi*', 'fff*'], {AnyOf: ['abc', 'def']}, {AnyOf: ['abc', 'def']}], [ - ['xyz', 'abc'], - {AllOf: [{AnyOf: [{AllOf: ['foo']}, {AllOf: ['bar']}]}]}, - {AnyOf: ['foo', 'bar']}, - ], - ].forEach(([s, e, expl]) => { - test(`Given ${JSON.stringify(s)}, ${JSON.stringify(e)} is explained by ${JSON.stringify(expl)}}`, - scenario(s, e, expl)); + [[], {AllOf: []}, null], + [['a'], {AllOf: ['a']}, null], + [['a'], {AllOf: ['a', 'b']}, 'b'], + [[], {AnyOf: []}, {AnyOf: []}], + [['a'], {AnyOf: ['a', 'b']}, null], + [['c'], {AnyOf: ['a', 'b']}, {AnyOf: ['a', 'b']}], + [['ghi'], {AnyOf: ['abc', 'def']}, {AnyOf: ['abc', 'def']}], + [['ghi'], {AllOf: ['abc', 'def', 'ghi']}, {AllOf: ['abc', 'def']}], + [['ghi*', 'fff*'], {AnyOf: ['abc', 'def']}, {AnyOf: ['abc', 'def']}], + [ + ['xyz', 'abc'], + {AllOf: [{AnyOf: [{AllOf: ['foo']}, {AllOf: ['bar']}]}]}, + {AnyOf: ['foo', 'bar']}, + ], + ].forEach(([s, e, expl]) => { + test(`Given ${JSON.stringify(s)}, ${JSON.stringify(e)} is explained by ${JSON.stringify(expl)}}`, + scenario(s, e, expl)); + }); }); }); diff --git a/libraries/scopes/test/normalize_test.js b/libraries/scopes/test/normalize_test.js index bd60564cd0e..0ed495798cb 100644 --- a/libraries/scopes/test/normalize_test.js +++ b/libraries/scopes/test/normalize_test.js @@ -1,8 +1,9 @@ -suite('normalize', () => { - const utils = require('../src'); - const assert = require('assert'); - const _ = require('lodash'); +const utils = require('../src'); +const assert = require('assert'); +const _ = require('lodash'); +const testing = require('taskcluster-lib-testing'); +suite(testing.suiteName(), () => { suite('scope comparing', function() { const cmp = (a, b) => { if (a < b) { diff --git a/libraries/scopes/test/satsifaction_test.js b/libraries/scopes/test/satsifaction_test.js index daf059e268f..d1fbe45b968 100644 --- a/libraries/scopes/test/satsifaction_test.js +++ b/libraries/scopes/test/satsifaction_test.js @@ -1,7 +1,8 @@ const assert = require('assert'); const utils = require('../src'); +const testing = require('taskcluster-lib-testing'); -suite('satisfaction', function() { +suite(testing.suiteName(), function() { let mktest = function(scopePatterns, scopesets, matches) { return function() { diff --git a/libraries/scopes/test/sets_test.js b/libraries/scopes/test/sets_test.js index 059e9b66d88..c0a438afbb3 100644 --- a/libraries/scopes/test/sets_test.js +++ b/libraries/scopes/test/sets_test.js @@ -1,7 +1,8 @@ const assert = require('assert'); const utils = require('../src'); +const testing = require('taskcluster-lib-testing'); -suite('sets', function() { +suite(testing.suiteName(), function() { suite('scopeUnion', () => { const testScopeUnion = (scope1, scope2, expected, message) => { assert.deepEqual(utils.scopeUnion(scope1, scope2).sort(), expected.sort(), message); diff --git a/libraries/scopes/test/validate_test.js b/libraries/scopes/test/validate_test.js index 553bdd66e99..c8b2586859d 100644 --- a/libraries/scopes/test/validate_test.js +++ b/libraries/scopes/test/validate_test.js @@ -1,7 +1,8 @@ const assert = require('assert'); const utils = require('../src'); +const testing = require('taskcluster-lib-testing'); -suite('validate', function() { +suite(testing.suiteName(), function() { test('Normal-looking scopes are OK', function() { assert(utils.validScope('auth:credentials')); }); diff --git a/libraries/testing/README.md b/libraries/testing/README.md index 9f768b7e93a..2894c7af059 100644 --- a/libraries/testing/README.md +++ b/libraries/testing/README.md @@ -231,47 +231,6 @@ The test output for the first suite will contain something like ``` -PulseTestReceiver ------------------ - -A utility for tests written in mocha, that makes it very easy to wait for a -specific pulse message. This uses real pulse messages, so pulse credentials -will be required. - -**Example:** -```js -suite("MyTests", function() { - let credentials = { - username: '...', // Pulse username - password: '...' // Pulse password - }; - let receiver = new testing.PulseTestReceiver(credentials, mocha) - - test("create task message arrives", async function() { - var taskId = slugid.v4(); - - // Start listening for a message with the above taskId, giving - // it a local name (here, `my-create-task-message`) - await receiver.listenFor( - 'my-create-task-message', - queueEvents.taskCreated({taskId: taskId}) - ); - - // We are now listen for a message with the taskId - // So let's create a task with it - await queue.createTask(taskId, {...}); - - // Now we wait for the message to arrive - let message = await receiver.waitFor('my-create-task-message'); - }); -}); -``` - -The `receiver` object will setup an PulseConnection before all tests and close -the PulseConnection after all tests. This should make tests run faster. All -internal state, ie. the names given to `listenFor` and `waitFor` will be reset -between all tests. - schemas ------- @@ -280,7 +239,7 @@ Test schemas with a positive and negative test cases. The method should be called within a `suite`, as it will call the mocha `test` function to define a test for each schema case. - * `schemasetOptions` - {} // options to pass to the [taskcluster-lib-validate](https://github.com/taskcluster/taskcluster-lib-validate) constructor + * `schemasetOptions` - {} // options to pass to the [taskcluster-lib-validate](../validate) constructor * `cases` - array of test cases * `basePath` - base path for relative pathnames in test cases (default `path.join(__dirname, 'validate')`) @@ -353,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 @@ -364,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 e00446d4061..e89035fd33a 100644 --- a/libraries/testing/package.json +++ b/libraries/testing/package.json @@ -9,5 +9,5 @@ "lint": "eslint src/*.js test/*.js", "test": "mocha test/*_test.js" }, - "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/pulse.js b/libraries/testing/src/pulse.js deleted file mode 100644 index 72fbb390183..00000000000 --- a/libraries/testing/src/pulse.js +++ /dev/null @@ -1,111 +0,0 @@ -let Promise = require('promise'); -let debug = require('debug')('taskcluster-lib-testing:pulse'); -let taskcluster = require('taskcluster-client'); - -/** - * A utility for test written in mocha, that makes very easy to listen for a - * specific message. - * - * credentials: { - * username: '...', // Pulse username - * password: '...' // Pulse password - * } - */ -let PulseTestReceiver = function(credentials, mocha) { - let that = this; - this._connection = new taskcluster.PulseConnection(credentials); - this._listeners = null; - this._promisedMessages = null; - - // **Note**, the functions below are mocha hooks. Ie. they are called by - // mocha, that is also the reason that `PulseTestReceiver` only works in the - // context of a mocha test. Note that we assume mocha is in "tdd" mode. - if (!mocha) { - mocha = require('mocha'); - } - - // Before all tests we ask the pulseConnection to connect, why not it offers - // slightly better performance, and we want tests to run fast - mocha.suiteSetup(function() { - return that._connection.connect(); - }); - - // Before each test we create list of listeners and mapping from "name" to - // promised messages - mocha.setup(function() { - that._listeners = []; - that._promisedMessages = {}; - }); - - // After each test we clean-up all the listeners created - mocha.teardown(function() { - // Because listener is created with a PulseConnection they only have an - // AMQP channel each, and not a full TCP connection, hence, .close() - // should be pretty fast too. Also unnecessary as they get clean-up when - // the PulseConnection closes eventually... But it's nice to keep things - // clean, errors are more likely to surface at the right test this way. - return Promise.all(that._listeners.map(function(listener) { - return listener.close(); - })).then(function() { - that._listeners = null; - that._promisedMessages = null; - }); - }); - - // After all tests we close the PulseConnection, as we haven't named any of - // the queues, they are all auto-delete queues and will be deleted if they - // weren't cleaned up in `teardown()` - mocha.suiteTeardown(function() { - return that._connection.close().then(function() { - that._connection = null; - }); - }); -}; - -PulseTestReceiver.prototype.listenFor = function(name, binding) { - // Check that the `name` haven't be used before in this test. Remember - // that we reset this._promisedMessages before each test (via setup) in mocha. - if (this._promisedMessages[name] !== undefined) { - throw new Error('name: \'' + name + '\' have already been used in this test'); - } - - // Create new listener using the existing PulseConnection, so no new TCP - // connection is opened, it just creates an AMQP channel within the existing - // TCP connection, this is much faster. - let listener = new taskcluster.PulseListener({ - connection: this._connection, - }); - - // Add listener to list so we can cleanup later - this._listeners.push(listener); - - // Create a promise that we got a message - let gotMessage = new Promise(function(accept, reject) { - listener.on('message', accept); - listener.on('error', reject); - }); - - // Insert the promise gotMessage into this._promisedMessages for name, so - // that we can return it when `.waitFor(name)` is called - this._promisedMessages[name] = gotMessage; - - // Start listening - return listener.bind(binding).then(function() { - return listener.resume().then(function() { - debug('Started listening for: %s', name); - }); - }); -}; - -PulseTestReceiver.prototype.waitFor = function(name) { - // Check that the `name` have been used with listenFor in this test - if (this._promisedMessages[name] === undefined) { - throw new Error('listenFor has not been called with name: \'' + name + '\'' + - ' in this test!'); - } - // Otherwise we just return the promise - return this._promisedMessages[name]; -}; - -// Export PulseTestReceiver -module.exports = PulseTestReceiver; 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/suite-name.js b/libraries/testing/src/suite-name.js new file mode 100644 index 00000000000..d8e02a3a64c --- /dev/null +++ b/libraries/testing/src/suite-name.js @@ -0,0 +1,10 @@ +const errorStackParser = require('error-stack-parser'); +const path = require('path'); + +const ROOT_DIR = path.resolve(__dirname, '../../..'); const suiteName = () => { + const o = {}; Error.captureStackTrace(o, suiteName); + const stack = errorStackParser.parse(o); + return path.relative(ROOT_DIR, stack[0].fileName); +}; + +module.exports = suiteName; diff --git a/libraries/testing/src/testing.js b/libraries/testing/src/testing.js deleted file mode 100644 index 97b73bc5fb8..00000000000 --- a/libraries/testing/src/testing.js +++ /dev/null @@ -1,12 +0,0 @@ -exports.PulseTestReceiver = require('./pulse'); -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.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/libraries/testing/test/fakeauth_test.js b/libraries/testing/test/fakeauth_test.js index 4e915f32433..ba8f5a98dce 100644 --- a/libraries/testing/test/fakeauth_test.js +++ b/libraries/testing/test/fakeauth_test.js @@ -10,6 +10,7 @@ const taskcluster = require('taskcluster-client'); const Promise = require('promise'); const path = require('path'); const libUrls = require('taskcluster-lib-urls'); +const testing = require('taskcluster-lib-testing'); const builder = new APIBuilder({ title: 'Test Server', @@ -37,9 +38,9 @@ builder.declare({ } }); -suite('fakeauth', function() { +suite(testing.suiteName(), function() { const rootUrl = 'http://localhost:1208'; - let fakeauth = require('../src/fakeauth'); + let fakeauth = testing.fakeauth; let server; suiteSetup(async function() { diff --git a/libraries/testing/test/schemas_test.js b/libraries/testing/test/schemas_test.js index 1b06306136e..3185233f15c 100644 --- a/libraries/testing/test/schemas_test.js +++ b/libraries/testing/test/schemas_test.js @@ -1,8 +1,8 @@ -const libTesting = require('../'); const path = require('path'); +const testing = require('taskcluster-lib-testing'); -suite('testing.schema', function() { - libTesting.schemas({ +suite(testing.suiteName(), function() { + testing.schemas({ schemasetOptions: { folder: path.join(__dirname, 'schemas'), serviceName: 'test', diff --git a/libraries/testing/test/secrets_test.js b/libraries/testing/test/secrets_test.js index 448313dcc47..929ecd9925c 100644 --- a/libraries/testing/test/secrets_test.js +++ b/libraries/testing/test/secrets_test.js @@ -1,9 +1,9 @@ -const {Secrets, stickyLoader} = require('../'); +const {Secrets, stickyLoader, suiteName} = require('../'); const _ = require('lodash'); const assert = require('assert'); const nock = require('nock'); -suite('Secrets', function() { +suite(suiteName(), function() { let oldTaskId; let savedEnv; diff --git a/libraries/testing/test/stickyloader_test.js b/libraries/testing/test/stickyloader_test.js index 4d1d54e6ca3..c8e725e84e4 100644 --- a/libraries/testing/test/stickyloader_test.js +++ b/libraries/testing/test/stickyloader_test.js @@ -1,8 +1,8 @@ const _ = require('lodash'); const assert = require('assert'); -const {stickyLoader} = require('../'); +const {stickyLoader, suiteName} = require('../'); -suite('stickyLoader', function() { +suite(suiteName(), function() { let loads, sticky; const loader = (component, overwrites) => { diff --git a/libraries/testing/test/utils_test.js b/libraries/testing/test/utils_test.js index 89d28e858af..13dd5c46bbe 100644 --- a/libraries/testing/test/utils_test.js +++ b/libraries/testing/test/utils_test.js @@ -1,10 +1,10 @@ -const libTesting = require('../'); const assert = require('assert'); +const testing = require('taskcluster-lib-testing'); -suite('testing (utilities)', function() { +suite(testing.suiteName(), function() { test('sleep', async function() { const start = new Date().getTime(); - await libTesting.sleep(10); + await testing.sleep(10); const end = new Date().getTime(); // as long as it waited 5ms or more we'll call it good.. assert(end - start > 5, 'did not wait long enough'); @@ -14,7 +14,7 @@ suite('testing (utilities)', function() { const pollFunc = () => { countDown = 5; return async () => { - await libTesting.sleep(1); + await testing.sleep(1); countDown -= 1; if (countDown === 0) { return 'success'; @@ -25,14 +25,14 @@ suite('testing (utilities)', function() { test('poll (success)', async function() { const poll = pollFunc(); - await libTesting.poll(poll, 4, 5); + await testing.poll(poll, 4, 5); assert.equal(countDown, 0); }); test('poll (too-few iterations)', async function() { const poll = pollFunc(); try { - await libTesting.poll(poll, 3, 5); + await testing.poll(poll, 3, 5); } catch (err) { if (!/Something bad/.test(err)) { throw err; diff --git a/libraries/validate/package.json b/libraries/validate/package.json index 9e0f12b7be1..9abf508b04f 100644 --- a/libraries/validate/package.json +++ b/libraries/validate/package.json @@ -9,5 +9,5 @@ "lint": "eslint src/*.js test/*.js", "test": "mocha test/*_test.js" }, - "main": "./src/schemaset.js" + "main": "./src/index.js" } diff --git a/libraries/validate/src/schemaset.js b/libraries/validate/src/index.js similarity index 100% rename from libraries/validate/src/schemaset.js rename to libraries/validate/src/index.js diff --git a/libraries/validate/test/debugging_test.js b/libraries/validate/test/debugging_test.js index f918f5d6aac..5e68d340c9a 100644 --- a/libraries/validate/test/debugging_test.js +++ b/libraries/validate/test/debugging_test.js @@ -1,11 +1,12 @@ -suite('Debugging Tests', () => { - let assert = require('assert'); - let SchemaSet = require('../'); - let rimraf = require('rimraf'); - let fs = require('fs'); - let intercept = require('intercept-stdout'); - let libUrls = require('taskcluster-lib-urls'); +const assert = require('assert'); +const SchemaSet = require('../'); +const rimraf = require('rimraf'); +const fs = require('fs'); +const intercept = require('intercept-stdout'); +const libUrls = require('taskcluster-lib-urls'); +const testing = require('taskcluster-lib-testing'); +suite(testing.suiteName(), () => { test('preview previews', async function() { let stdout = ''; const unhook = intercept(txt => { diff --git a/libraries/validate/test/publish_test.js b/libraries/validate/test/publish_test.js index 87c2ae6590f..dd5d5845a05 100644 --- a/libraries/validate/test/publish_test.js +++ b/libraries/validate/test/publish_test.js @@ -1,12 +1,13 @@ -suite('Publish Tests', () => { - let SchemaSet = require('../'); - let awsMock = require('mock-aws-s3'); - let os = require('os'); - let path = require('path'); - let rimraf = require('rimraf'); - let debug = require('debug')('test'); - let libUrls = require('taskcluster-lib-urls'); +const SchemaSet = require('../'); +const awsMock = require('mock-aws-s3'); +const os = require('os'); +const path = require('path'); +const rimraf = require('rimraf'); +const debug = require('debug')('test'); +const libUrls = require('taskcluster-lib-urls'); +const testing = require('taskcluster-lib-testing'); +suite(testing.suiteName(), () => { let s3 = null; let mockdir = path.join(os.tmpdir(), 'tc-lib-validate', 'buckets'); diff --git a/libraries/validate/test/schemaset_test.js b/libraries/validate/test/schemaset_test.js index 9ef1aa296ca..67a4326a4fc 100644 --- a/libraries/validate/test/schemaset_test.js +++ b/libraries/validate/test/schemaset_test.js @@ -2,8 +2,9 @@ const assert = require('assert'); const SchemaSet = require('../'); const _ = require('lodash'); const libUrls = require('taskcluster-lib-urls'); +const testing = require('taskcluster-lib-testing'); -suite('schemaset_test.js', () => { +suite(testing.suiteName(), () => { const rootUrl = libUrls.testRootUrl(); const makeSchemaSet = () => { diff --git a/libraries/validate/test/util_test.js b/libraries/validate/test/util_test.js index d3aad67a82a..83496f7f001 100644 --- a/libraries/validate/test/util_test.js +++ b/libraries/validate/test/util_test.js @@ -1,7 +1,8 @@ const assert = require('assert'); const {checkRefs} = require('../src/util'); +const testing = require('taskcluster-lib-testing'); -suite('test_util.js', function() { +suite(testing.suiteName(), function() { suite('checkRefs', function() { // include some arrays and objects to test the "deepness" of the check const schemaWith = innards => ({ diff --git a/libraries/validate/test/validate_test.js b/libraries/validate/test/validate_test.js index cf71088c608..bb66647ae40 100644 --- a/libraries/validate/test/validate_test.js +++ b/libraries/validate/test/validate_test.js @@ -1,9 +1,10 @@ -suite('Valid Schema Tests', () => { - const assert = require('assert'); - const SchemaSet = require('../'); - const debug = require('debug')('test'); - const libUrls = require('taskcluster-lib-urls'); +const assert = require('assert'); +const SchemaSet = require('../'); +const debug = require('debug')('test'); +const libUrls = require('taskcluster-lib-urls'); +const testing = require('taskcluster-lib-testing'); +suite(testing.suiteName(), () => { const rootUrl = libUrls.testRootUrl(); let validate = null; diff --git a/package.json b/package.json index bee3bb449e6..f4c620ad5dc 100644 --- a/package.json +++ b/package.json @@ -70,12 +70,12 @@ "mz": "^2.7.0", "netmask": "^1.0.5", "node-fetch": "^2.3.0", - "nodemailer": "^5.0.0", + "nodemailer": "^6.0.0", "passport": "^0.4.0", "passport-github": "^1.1.0", "promise": "^8.0.1", "promisepipe": "^3.0.0", - "quick-lru": "^3.0.0", + "quick-lru": "^4.0.0", "ramda": "^0.26.0", "recursive-readdir-sync": "^1.0.6", "regex-escape": "^3.4.8", @@ -124,11 +124,11 @@ "depcheck": "^0.7.0", "dockerode": "^2.5.8", "dot": "^1.1.2", + "error-stack-parser": "^2.0.2", "eslint": "5.16.0", "fs-extra": "^7.0.1", "glob": "^7.1.3", "got": "^9.5.0", - "http-proxy": "^1.17.0", "intercept-stdout": "^0.1.2", "is-uuid": "^1.0.2", "json-stable-stringify": "^1.0.1", @@ -151,7 +151,7 @@ "tar-fs": "^2.0.0", "taskcluster-lib-testing": "link:libraries/testing", "temporary": "^1.0.0", - "tmp": "^0.0.33", + "tmp": "^0.1.0", "url-join": "^4.0.0", "webpack": "^4.25.1", "webpack-cli": "^3.1.2", diff --git a/services/auth/README.md b/services/auth/README.md index 8da7520e9b5..155795030a9 100644 --- a/services/auth/README.md +++ b/services/auth/README.md @@ -14,4 +14,4 @@ To do so, copy `user-config-example.yml` to `user-config.yml` and fill in the ne Taskcluster team members can provide you with some testing-only credentials -- just ask, and provide a GPG key (use https://keybase.io if you don't have one). You can get your own pulse credentials at https://pulseguardian.mozilla.org. -The taskcluster team has a series of [best practices](/docs/manual/devel/best-practices) which may help guide you in modifying the source code and making a pull request. +The taskcluster team has a series of [best practices](../../dev-docs/best-practices) which may help guide you in modifying the source code and making a pull request. diff --git a/services/auth/src/main.js b/services/auth/src/main.js index 61bf672e2b5..d7409791b04 100755 --- a/services/auth/src/main.js +++ b/services/auth/src/main.js @@ -197,10 +197,16 @@ const load = Loader({ }, }, + // tests use this to inject other APIs. + apis: { + requires: ['api'], + setup: ({api}) => [api], + }, + server: { - requires: ['cfg', 'api', 'docs'], - setup: async ({cfg, api, docs}) => App({ - apis: [api], + requires: ['cfg', 'apis', 'docs'], + setup: async ({cfg, apis, docs}) => App({ + apis, ...cfg.server, }), }, diff --git a/services/auth/test/azure_test.js b/services/auth/test/azure_test.js index c3cd3da7562..4317d906a32 100644 --- a/services/auth/test/azure_test.js +++ b/services/auth/test/azure_test.js @@ -3,8 +3,9 @@ const helper = require('./helper'); const _ = require('lodash'); const azure = require('fast-azure-storage'); const taskcluster = require('taskcluster-client'); +const testing = require('taskcluster-lib-testing'); -helper.secrets.mockSuite(helper.suiteName(__filename), ['app', 'azure'], function(mock, skipping) { +helper.secrets.mockSuite(testing.suiteName(), ['app', 'azure'], function(mock, skipping) { if (mock) { return; // We only test this with real creds } diff --git a/services/auth/test/client_test.js b/services/auth/test/client_test.js index e55a76c961e..ab72fc462ff 100644 --- a/services/auth/test/client_test.js +++ b/services/auth/test/client_test.js @@ -5,7 +5,7 @@ const assume = require('assume'); const testing = require('taskcluster-lib-testing'); const taskcluster = require('taskcluster-client'); -helper.secrets.mockSuite(helper.suiteName(__filename), ['app', 'azure'], function(mock, skipping) { +helper.secrets.mockSuite(testing.suiteName(), ['app', 'azure'], function(mock, skipping) { helper.withPulse(mock, skipping); helper.withEntities(mock, skipping); helper.withRoles(mock, skipping); diff --git a/services/auth/test/containers_test.js b/services/auth/test/containers_test.js index f750a7c500f..975c2d16dc0 100644 --- a/services/auth/test/containers_test.js +++ b/services/auth/test/containers_test.js @@ -3,13 +3,14 @@ const uuid = require('uuid'); const assert = require('assert'); const helper = require('./helper'); const azure = require('fast-azure-storage'); +const testing = require('taskcluster-lib-testing'); const sorted = (arr) => { arr.sort(); return arr; }; -helper.secrets.mockSuite(helper.suiteName(__filename), ['azure'], function(mock, skipping) { +helper.secrets.mockSuite(testing.suiteName(), ['azure'], function(mock, skipping) { if (mock) { return; // This test file only works on real things apparently } diff --git a/services/auth/test/helper.js b/services/auth/test/helper.js index 66dfd1e8f4e..ece7613462d 100644 --- a/services/auth/test/helper.js +++ b/services/auth/test/helper.js @@ -1,8 +1,5 @@ const debug = require('debug')('test-helper'); const assert = require('assert'); -const http = require('http'); -const httpProxy = require('http-proxy'); -const path = require('path'); const _ = require('lodash'); const data = require('../src/data'); const builder = require('../src/api'); @@ -13,12 +10,9 @@ const azure = require('fast-azure-storage'); const uuid = require('uuid'); const Builder = require('taskcluster-lib-api'); const SchemaSet = require('taskcluster-lib-validate'); -const App = require('taskcluster-lib-app'); const {stickyLoader, Secrets, withEntity} = require('taskcluster-lib-testing'); const {FakeClient} = require('taskcluster-lib-pulse'); -exports.suiteName = path.basename; - exports.load = stickyLoader(load); suiteSetup(async function() { @@ -26,8 +20,7 @@ suiteSetup(async function() { exports.load.inject('process', 'test'); }); -const PROXY_PORT = 60551; -exports.rootUrl = `http://localhost:${PROXY_PORT}`; +exports.rootUrl = `http://localhost:60552`; // set up the testing secrets exports.secrets = new Secrets({ @@ -318,8 +311,6 @@ exports.withServers = (mock, skipping) => { }; exports.setupScopes(); - webServer = await exports.load('server'); - // Now set up the test service exports.TestClient = taskcluster.createClient(testServiceBuilder.reference()); exports.testClient = new exports.TestClient({ @@ -339,30 +330,12 @@ exports.withServers = (mock, skipping) => { }), }); - testServer = await App({ - port: 60553, - env: 'development', - forceSSL: false, - trustProxy: false, - rootDocsLink: false, - apis: [testServiceApi], - }); - - // Finally, we set up a proxy that runs on rootUrl - // and sends requests to either of the services based on path. - - const proxy = httpProxy.createProxyServer({}); - proxier = http.createServer(function(req, res) { - if (req.url.startsWith('/api/auth/')) { - proxy.web(req, res, {target: 'http://localhost:60552'}); - } else if (req.url.startsWith(`/api/${testServiceName}/`)) { - proxy.web(req, res, {target: 'http://localhost:60553'}); - } else { - throw new Error(`Unknown service request: ${req.url}`); - } - }); - proxier.listen(PROXY_PORT); - + // include this test API in the APIs served, alongside the normal auth service + exports.load.inject('apis', [ + await exports.load('api'), + testServiceApi, + ]); + webServer = await exports.load('server'); }); setup(() => { diff --git a/services/auth/test/purgeExpiredClients_test.js b/services/auth/test/purgeExpiredClients_test.js index 8df733701ad..9d1adbaa240 100644 --- a/services/auth/test/purgeExpiredClients_test.js +++ b/services/auth/test/purgeExpiredClients_test.js @@ -1,8 +1,9 @@ const helper = require('./helper'); const assume = require('assume'); const taskcluster = require('taskcluster-client'); +const testing = require('taskcluster-lib-testing'); -helper.secrets.mockSuite(helper.suiteName(__filename), ['app', 'azure'], function(mock, skipping) { +helper.secrets.mockSuite(testing.suiteName(), ['app', 'azure'], function(mock, skipping) { helper.withPulse(mock, skipping); helper.withEntities(mock, skipping); helper.withRoles(mock, skipping); diff --git a/services/auth/test/references_test.js b/services/auth/test/references_test.js index 93de192f443..e06e221d42d 100644 --- a/services/auth/test/references_test.js +++ b/services/auth/test/references_test.js @@ -2,8 +2,9 @@ const builder = require('../src/api'); const exchanges = require('../src/exchanges'); const helper = require('./helper'); const References = require('taskcluster-lib-references'); +const testing = require('taskcluster-lib-testing'); -suite('references_test.js', function() { +suite(testing.suiteName(), function() { test('references validate', async function() { const schemaset = await helper.load('schemaset'); const references = References.fromService({schemaset, builder, exchanges}); diff --git a/services/auth/test/remotevalidation_test.js b/services/auth/test/remotevalidation_test.js index 6454cbfc04a..7d7cb03eacc 100644 --- a/services/auth/test/remotevalidation_test.js +++ b/services/auth/test/remotevalidation_test.js @@ -3,8 +3,9 @@ const helper = require('./helper'); const slugid = require('slugid'); const taskcluster = require('taskcluster-client'); const request = require('superagent'); +const testing = require('taskcluster-lib-testing'); -helper.secrets.mockSuite(helper.suiteName(__filename), ['app', 'azure'], function(mock, skipping) { +helper.secrets.mockSuite(testing.suiteName(), ['app', 'azure'], function(mock, skipping) { helper.withPulse(mock, skipping); helper.withEntities(mock, skipping); helper.withRoles(mock, skipping); diff --git a/services/auth/test/role_test.js b/services/auth/test/role_test.js index ca96e83e940..6b85557b273 100644 --- a/services/auth/test/role_test.js +++ b/services/auth/test/role_test.js @@ -6,7 +6,7 @@ const assume = require('assume'); const testing = require('taskcluster-lib-testing'); const taskcluster = require('taskcluster-client'); -helper.secrets.mockSuite(helper.suiteName(__filename), ['app', 'azure'], function(mock, skipping) { +helper.secrets.mockSuite(testing.suiteName(), ['app', 'azure'], function(mock, skipping) { helper.withPulse(mock, skipping); helper.withEntities(mock, skipping, {orderedTests: true}); helper.withRoles(mock, skipping, {orderedTests: true}); diff --git a/services/auth/test/rolelogic_test.js b/services/auth/test/rolelogic_test.js index 849fe28e1fd..84add7c76b1 100644 --- a/services/auth/test/rolelogic_test.js +++ b/services/auth/test/rolelogic_test.js @@ -2,8 +2,9 @@ const helper = require('./helper'); const _ = require('lodash'); const taskcluster = require('taskcluster-client'); const mocha = require('mocha'); +const testing = require('taskcluster-lib-testing'); -helper.secrets.mockSuite(helper.suiteName(__filename), ['app', 'azure'], function(mock, skipping) { +helper.secrets.mockSuite(testing.suiteName(), ['app', 'azure'], function(mock, skipping) { helper.withPulse(mock, skipping); helper.withEntities(mock, skipping); helper.withRoles(mock, skipping); diff --git a/services/auth/test/s3_test.js b/services/auth/test/s3_test.js index 3d8c5aa63a7..7764dbe4020 100644 --- a/services/auth/test/s3_test.js +++ b/services/auth/test/s3_test.js @@ -3,8 +3,9 @@ const slugid = require('slugid'); const aws = require('aws-sdk'); const helper = require('./helper'); const debug = require('debug')('s3_test'); +const testing = require('taskcluster-lib-testing'); -helper.secrets.mockSuite(helper.suiteName(__filename), ['app', 'aws'], function(mock, skipping) { +helper.secrets.mockSuite(testing.suiteName(), ['app', 'aws'], function(mock, skipping) { if (mock) { return; // This is actually testing sts tokens and we are not going to mock those } diff --git a/services/auth/test/scoperesolver_test.js b/services/auth/test/scoperesolver_test.js index 016c2cd68fb..6d15eebeda0 100644 --- a/services/auth/test/scoperesolver_test.js +++ b/services/auth/test/scoperesolver_test.js @@ -6,8 +6,9 @@ const monitorManager = require('../src/monitor'); const assert = require('assert'); const _ = require('lodash'); const assume = require('assume'); +const testing = require('taskcluster-lib-testing'); -suite(helper.suiteName(__filename), () => { +suite(testing.suiteName(), () => { let monitor, scopeResolver; setup(async () => { monitorManager.setup({ diff --git a/services/auth/test/scopesetbuilder_test.js b/services/auth/test/scopesetbuilder_test.js index d5d0d7212f0..833ab28b23b 100644 --- a/services/auth/test/scopesetbuilder_test.js +++ b/services/auth/test/scopesetbuilder_test.js @@ -1,9 +1,10 @@ -suite('ScopeSetBuilder', () => { - const assume = require('assume'); - const _ = require('lodash'); - const {mergeScopeSets, scopeCompare} = require('taskcluster-lib-scopes'); - const ScopeSetBuilder = require('../src/scopesetbuilder'); +const assume = require('assume'); +const _ = require('lodash'); +const {mergeScopeSets, scopeCompare} = require('taskcluster-lib-scopes'); +const ScopeSetBuilder = require('../src/scopesetbuilder'); +const testing = require('taskcluster-lib-testing'); +suite(testing.suiteName(), () => { test('scopes()', () => { assume(new ScopeSetBuilder().scopes()).eql([]); }); diff --git a/services/auth/test/sentry_test.js b/services/auth/test/sentry_test.js index 0018df1a8bd..f853736cc3e 100644 --- a/services/auth/test/sentry_test.js +++ b/services/auth/test/sentry_test.js @@ -1,8 +1,9 @@ const helper = require('./helper'); const taskcluster = require('taskcluster-client'); const assert = require('assert'); +const testing = require('taskcluster-lib-testing'); -helper.secrets.mockSuite(helper.suiteName(__filename), ['app'], function(mock, skipping) { +helper.secrets.mockSuite(testing.suiteName(), ['app'], function(mock, skipping) { if (!mock) { return; // We don't test this with real credentials for now! } diff --git a/services/auth/test/signaturevalidator_test.js b/services/auth/test/signaturevalidator_test.js index 9cf54bd8b4b..bbd49fefee0 100644 --- a/services/auth/test/signaturevalidator_test.js +++ b/services/auth/test/signaturevalidator_test.js @@ -1,4 +1,3 @@ -const helper = require('./helper'); const hawk = require('hawk'); const _ = require('lodash'); const assume = require('assume'); @@ -7,8 +6,9 @@ const crypto = require('crypto'); const taskcluster = require('taskcluster-client'); const sigvalidator = require('../src/signaturevalidator'); const monitorManager = require('../src/monitor'); +const testing = require('taskcluster-lib-testing'); -suite(helper.suiteName(__filename), function() { +suite(testing.suiteName(), function() { let one_hour = taskcluster.fromNow('1 hour'); let two_hours = taskcluster.fromNow('2 hour'); let three_hours = taskcluster.fromNow('3 hour'); diff --git a/services/auth/test/staticclients_test.js b/services/auth/test/staticclients_test.js index 38482c41f95..2bb323ff756 100644 --- a/services/auth/test/staticclients_test.js +++ b/services/auth/test/staticclients_test.js @@ -2,8 +2,9 @@ const assert = require('assert'); const debug = require('debug')('test:static-clients'); const helper = require('./helper'); const assume = require('assume'); +const testing = require('taskcluster-lib-testing'); -helper.secrets.mockSuite(helper.suiteName(__filename), ['app', 'azure'], function(mock, skipping) { +helper.secrets.mockSuite(testing.suiteName(), ['app', 'azure'], function(mock, skipping) { helper.withPulse(mock, skipping); helper.withEntities(mock, skipping); helper.withRoles(mock, skipping); diff --git a/services/auth/test/statsum_test.js b/services/auth/test/statsum_test.js index 6cdff4296b5..d367f0e85f0 100644 --- a/services/auth/test/statsum_test.js +++ b/services/auth/test/statsum_test.js @@ -1,7 +1,8 @@ const helper = require('./helper'); const assert = require('assert'); +const testing = require('taskcluster-lib-testing'); -helper.secrets.mockSuite(helper.suiteName(__filename), ['app', 'azure'], function(mock, skipping) { +helper.secrets.mockSuite(testing.suiteName(), ['app', 'azure'], function(mock, skipping) { helper.withPulse(mock, skipping); helper.withEntities(mock, skipping); helper.withRoles(mock, skipping); diff --git a/services/auth/test/stories_test.js b/services/auth/test/stories_test.js index dca55a6a346..38afb194e22 100644 --- a/services/auth/test/stories_test.js +++ b/services/auth/test/stories_test.js @@ -2,8 +2,9 @@ const assert = require('assert'); const helper = require('./helper'); const assume = require('assume'); const taskcluster = require('taskcluster-client'); +const testing = require('taskcluster-lib-testing'); -helper.secrets.mockSuite(helper.suiteName(__filename), ['app', 'azure'], function(mock, skipping) { +helper.secrets.mockSuite(testing.suiteName(), ['app', 'azure'], function(mock, skipping) { helper.withPulse(mock, skipping); helper.withEntities(mock, skipping, {orderedTests: true}); helper.withRoles(mock, skipping, {orderedTests: true}); diff --git a/services/auth/test/testauth_test.js b/services/auth/test/testauth_test.js index 5647972b0f0..caa936058a5 100644 --- a/services/auth/test/testauth_test.js +++ b/services/auth/test/testauth_test.js @@ -1,5 +1,6 @@ const assert = require('assert'); const helper = require('./helper'); +const testing = require('taskcluster-lib-testing'); const credentials = { clientId: 'tester', @@ -11,127 +12,129 @@ const badcreds = { accessToken: 'wrong', }; -helper.secrets.mockSuite(helper.suiteName(__filename), ['app', 'azure'], function(mock, skipping) { - helper.withPulse(mock, skipping); - helper.withEntities(mock, skipping); - helper.withRoles(mock, skipping); - helper.withServers(mock, skipping); - - let testAuth = (name, {config, requiredScopes, clientScopes, errorCode}) => { - test(name, async () => { - let auth = new helper.AuthClient(config); - await auth.testAuthenticate({requiredScopes, clientScopes}).then(() => { - assert(!errorCode, 'Request was successful, but expected an error ' + - 'with code: ' + errorCode); - }, err => { - assert(errorCode, 'Request failed!'); - assert(err.code === errorCode, 'Expected error with code: ' + - errorCode + ' but got: ' + err.code); +suite(testing.suiteName(), function() { + helper.secrets.mockSuite('testAuth', ['app', 'azure'], function(mock, skipping) { + helper.withPulse(mock, skipping); + helper.withEntities(mock, skipping); + helper.withRoles(mock, skipping); + helper.withServers(mock, skipping); + + let testAuth = (name, {config, requiredScopes, clientScopes, errorCode}) => { + test(name, async () => { + let auth = new helper.AuthClient(config); + await auth.testAuthenticate({requiredScopes, clientScopes}).then(() => { + assert(!errorCode, 'Request was successful, but expected an error ' + + 'with code: ' + errorCode); + }, err => { + assert(errorCode, 'Request failed!'); + assert(err.code === errorCode, 'Expected error with code: ' + + errorCode + ' but got: ' + err.code); + }); }); - }); - }; + }; - testAuth('valid creds', { - config: {rootUrl: helper.rootUrl, credentials}, - requiredScopes: ['test-scope:test'], - clientScopes: ['test-scope:test'], - }); + testAuth('valid creds', { + config: {rootUrl: helper.rootUrl, credentials}, + requiredScopes: ['test-scope:test'], + clientScopes: ['test-scope:test'], + }); - testAuth('valid creds (star scope)', { - config: {rootUrl: helper.rootUrl, credentials}, - requiredScopes: ['test-scope:test'], - clientScopes: ['test-scope:*'], - }); + testAuth('valid creds (star scope)', { + config: {rootUrl: helper.rootUrl, credentials}, + requiredScopes: ['test-scope:test'], + clientScopes: ['test-scope:*'], + }); - testAuth('valid creds (scope subset)', { - config: {rootUrl: helper.rootUrl, credentials}, - requiredScopes: ['test-scope:test2'], - clientScopes: ['test-scope:test1', 'test-scope:test2'], - }); + testAuth('valid creds (scope subset)', { + config: {rootUrl: helper.rootUrl, credentials}, + requiredScopes: ['test-scope:test2'], + clientScopes: ['test-scope:test1', 'test-scope:test2'], + }); - testAuth('invalid creds (scope subset)', { - config: {rootUrl: helper.rootUrl, credentials}, - requiredScopes: ['test-scope:test2'], - clientScopes: ['test-scope:test1', 'test-scope:test2'], - }); + testAuth('invalid creds (scope subset)', { + config: {rootUrl: helper.rootUrl, credentials}, + requiredScopes: ['test-scope:test2'], + clientScopes: ['test-scope:test1', 'test-scope:test2'], + }); - testAuth('invalid creds', { - config: {rootUrl: helper.rootUrl, credentials: badcreds}, - requiredScopes: ['test-scope'], - clientScopes: ['test-scope'], - errorCode: 'AuthenticationFailed', - }); + testAuth('invalid creds', { + config: {rootUrl: helper.rootUrl, credentials: badcreds}, + requiredScopes: ['test-scope'], + clientScopes: ['test-scope'], + errorCode: 'AuthenticationFailed', + }); - testAuth('insufficientScopes', { - config: {rootUrl: helper.rootUrl, credentials}, - requiredScopes: ['test-scope:*'], - clientScopes: ['test-scope'], - errorCode: 'InsufficientScopes', - }); + testAuth('insufficientScopes', { + config: {rootUrl: helper.rootUrl, credentials}, + requiredScopes: ['test-scope:*'], + clientScopes: ['test-scope'], + errorCode: 'InsufficientScopes', + }); - testAuth('authorizedScopes', { - config: {rootUrl: helper.rootUrl, credentials, authorizedScopes: ['test-scope:test']}, - requiredScopes: ['test-scope:test'], - clientScopes: ['test-scope:*'], - }); + testAuth('authorizedScopes', { + config: {rootUrl: helper.rootUrl, credentials, authorizedScopes: ['test-scope:test']}, + requiredScopes: ['test-scope:test'], + clientScopes: ['test-scope:*'], + }); - testAuth('authorizedScopes InsufficientScopes', { - config: {rootUrl: helper.rootUrl, credentials, authorizedScopes: ['test-scope:test1']}, - requiredScopes: ['test-scope:test2'], - clientScopes: ['test-scope:*'], - errorCode: 'InsufficientScopes', - }); + testAuth('authorizedScopes InsufficientScopes', { + config: {rootUrl: helper.rootUrl, credentials, authorizedScopes: ['test-scope:test1']}, + requiredScopes: ['test-scope:test2'], + clientScopes: ['test-scope:*'], + errorCode: 'InsufficientScopes', + }); - testAuth('authorizedScopes over-scoped', { - config: {rootUrl: helper.rootUrl, credentials, authorizedScopes: ['test-scope:*']}, - requiredScopes: ['test-scope:test2'], - clientScopes: ['test-scope:test2'], - errorCode: 'AuthenticationFailed', - }); + testAuth('authorizedScopes over-scoped', { + config: {rootUrl: helper.rootUrl, credentials, authorizedScopes: ['test-scope:*']}, + requiredScopes: ['test-scope:test2'], + clientScopes: ['test-scope:test2'], + errorCode: 'AuthenticationFailed', + }); - testAuth('authorizedScopes badcreds', { - config: {rootUrl: helper.rootUrl, credentials: badcreds, authorizedScopes: ['test-scope:test']}, - requiredScopes: ['test-scope:test'], - clientScopes: ['test-scope:*'], - errorCode: 'AuthenticationFailed', + testAuth('authorizedScopes badcreds', { + config: {rootUrl: helper.rootUrl, credentials: badcreds, authorizedScopes: ['test-scope:test']}, + requiredScopes: ['test-scope:test'], + clientScopes: ['test-scope:*'], + errorCode: 'AuthenticationFailed', + }); }); -}); -helper.secrets.mockSuite(`${helper.suiteName(__filename)} | get`, ['app', 'azure'], function(mock, skipping) { - helper.withPulse(mock, skipping); - helper.withEntities(mock, skipping); - helper.withRoles(mock, skipping); - helper.withServers(mock, skipping); - - let testAuthGet = (name, {config, errorCode}) => { - test(name, async () => { - let auth = new helper.AuthClient(config); - await auth.testAuthenticateGet().then(() => { - assert(!errorCode, 'Request was successful, but expected an error ' + - 'with code: ' + errorCode); - }, err => { - assert(errorCode, 'Request failed!'); - assert(err.code === errorCode, 'Expected error with code: ' + - errorCode + ' but got: ' + err.code); + helper.secrets.mockSuite('testAuthGet', ['app', 'azure'], function(mock, skipping) { + helper.withPulse(mock, skipping); + helper.withEntities(mock, skipping); + helper.withRoles(mock, skipping); + helper.withServers(mock, skipping); + + let testAuthGet = (name, {config, errorCode}) => { + test(name, async () => { + let auth = new helper.AuthClient(config); + await auth.testAuthenticateGet().then(() => { + assert(!errorCode, 'Request was successful, but expected an error ' + + 'with code: ' + errorCode); + }, err => { + assert(errorCode, 'Request failed!'); + assert(err.code === errorCode, 'Expected error with code: ' + + errorCode + ' but got: ' + err.code); + }); }); - }); - }; + }; - testAuthGet('valid creds', { - config: {rootUrl: helper.rootUrl, credentials}, - }); + testAuthGet('valid creds', { + config: {rootUrl: helper.rootUrl, credentials}, + }); - testAuthGet('invalid creds', { - config: {rootUrl: helper.rootUrl, credentials: badcreds}, - errorCode: 'AuthenticationFailed', - }); + testAuthGet('invalid creds', { + config: {rootUrl: helper.rootUrl, credentials: badcreds}, + errorCode: 'AuthenticationFailed', + }); - testAuthGet('authorizedScopes', { - config: { - rootUrl: helper.rootUrl, - credentials, - authorizedScopes: ['test:scopes-abc'], - }, - errorCode: 'InsufficientScopes', + testAuthGet('authorizedScopes', { + config: { + rootUrl: helper.rootUrl, + credentials, + authorizedScopes: ['test:scopes-abc'], + }, + errorCode: 'InsufficientScopes', + }); }); }); diff --git a/services/auth/test/trie_test.js b/services/auth/test/trie_test.js index 9e47d0c6c20..994e005a5a5 100644 --- a/services/auth/test/trie_test.js +++ b/services/auth/test/trie_test.js @@ -1,9 +1,9 @@ -const helper = require('./helper'); const assume = require('assume'); const _ = require('lodash'); const ScopeSetBuilder = require('../src/scopesetbuilder'); const trie = require('../src/trie'); const trietestcases = require('./trietestcases'); +const testing = require('taskcluster-lib-testing'); // This test suite was designed to test every conceivable combination of // inputs to the trie implementation, in an effort to suss out any hidden bugs @@ -13,7 +13,7 @@ const trietestcases = require('./trietestcases'); // total test suite, just set FULL_TESTS=1. const FULL_TESTS = !!process.env.FULL_TESTS; -suite(helper.suiteName(__filename), () => { +suite(testing.suiteName(), () => { /** * Return scope without kleene '*' at the end if scope ends with kleene, otherwise diff --git a/services/auth/test/validate_test.js b/services/auth/test/validate_test.js index 714328a41bf..5cc953a43f8 100644 --- a/services/auth/test/validate_test.js +++ b/services/auth/test/validate_test.js @@ -1,4 +1,3 @@ -const helper = require('./helper'); const path = require('path'); const testing = require('taskcluster-lib-testing'); @@ -68,7 +67,7 @@ const testCases = [ }, ]; -suite(helper.suiteName(__filename), function() { +suite(testing.suiteName(), function() { // Run test cases using schemas testing utility from taskcluster-lib-testing testing.schemas({ schemasetOptions: { diff --git a/services/auth/test/websocktunnel_test.js b/services/auth/test/websocktunnel_test.js index d11755a9539..e0400f44496 100644 --- a/services/auth/test/websocktunnel_test.js +++ b/services/auth/test/websocktunnel_test.js @@ -1,8 +1,9 @@ const helper = require('./helper'); const assert = require('assert'); const jwt = require('jsonwebtoken'); +const testing = require('taskcluster-lib-testing'); -helper.secrets.mockSuite(helper.suiteName(__filename), ['app', 'azure'], function(mock, skipping) { +helper.secrets.mockSuite(testing.suiteName(), ['app', 'azure'], function(mock, skipping) { helper.withPulse(mock, skipping); helper.withEntities(mock, skipping); helper.withRoles(mock, skipping); diff --git a/services/built-in-workers/test/index_test.js b/services/built-in-workers/test/index_test.js index 6bd73e0a37f..ad8dc2203d5 100644 --- a/services/built-in-workers/test/index_test.js +++ b/services/built-in-workers/test/index_test.js @@ -1,7 +1,8 @@ const helper = require('./helper'); const slugid = require('slugid'); +const testing = require('taskcluster-lib-testing'); -suite('TaskQueue_test.js', function() { +suite(testing.suiteName(), function() { helper.withFakeQueue(); test('check succeed worker', async function() { diff --git a/services/github/src/handlers.js b/services/github/src/handlers.js index e48cc74446d..2e12cc3beff 100644 --- a/services/github/src/handlers.js +++ b/services/github/src/handlers.js @@ -133,7 +133,6 @@ class Handlers { const callHandler = (name, handler) => message => { handler.call(this, message).catch(async err => { - debug(`Error (reported to sentry) while calling ${name} handler: ${err}`); await this.monitor.reportError(err); return err; }).then((err=null) => { diff --git a/services/github/test/api_test.js b/services/github/test/api_test.js index 38c8ddca472..da82d01c0f5 100644 --- a/services/github/test/api_test.js +++ b/services/github/test/api_test.js @@ -2,13 +2,14 @@ const helper = require('./helper'); const assert = require('assert'); const _ = require('lodash'); const got = require('got'); +const testing = require('taskcluster-lib-testing'); /** * Tests of endpoints in the api _other than_ * the github webhook endpoint which is tested * in webhook_test.js */ -helper.secrets.mockSuite('api_test.js', ['taskcluster'], function(mock, skipping) { +helper.secrets.mockSuite(testing.suiteName(), ['taskcluster'], function(mock, skipping) { helper.withEntities(mock, skipping); helper.withFakeGithub(mock, skipping); helper.withServer(mock, skipping); diff --git a/services/github/test/fake_github_test.js b/services/github/test/fake_github_test.js index c34a5edbf40..03875f45462 100644 --- a/services/github/test/fake_github_test.js +++ b/services/github/test/fake_github_test.js @@ -2,8 +2,9 @@ const assert = require('assert'); const _ = require('lodash'); const github = require('@octokit/rest'); const fakeGithubAuth = require('./github-auth'); +const testing = require('taskcluster-lib-testing'); -suite('fake github', function() { +suite(testing.suiteName(), function() { function checkKeys(obj, platonic) { let ours = _.filter(Object.keys(obj), k => !k.startsWith('_')); diff --git a/services/github/test/handler_test.js b/services/github/test/handler_test.js index 914548f4a24..2ccfd097b38 100644 --- a/services/github/test/handler_test.js +++ b/services/github/test/handler_test.js @@ -3,12 +3,13 @@ const helper = require('./helper'); const assert = require('assert'); const sinon = require('sinon'); const libUrls = require('taskcluster-lib-urls'); +const testing = require('taskcluster-lib-testing'); /** * This tests the event handlers, faking out all of the services they * interact with. */ -helper.secrets.mockSuite('handlers', ['taskcluster'], function(mock, skipping) { +helper.secrets.mockSuite(testing.suiteName(), ['taskcluster'], function(mock, skipping) { helper.withEntities(mock, skipping); helper.withFakeGithub(mock, skipping); diff --git a/services/github/test/intree_test.js b/services/github/test/intree_test.js index 98fc7f40a48..d05f2044ff6 100644 --- a/services/github/test/intree_test.js +++ b/services/github/test/intree_test.js @@ -4,8 +4,9 @@ const _ = require('lodash'); const helper = require('./helper'); const libUrls = require('taskcluster-lib-urls'); const yaml = require('js-yaml'); +const testing = require('taskcluster-lib-testing'); -suite('intree config', function() { +suite(testing.suiteName(), function() { let intree; suiteSetup(async function() { diff --git a/services/github/test/pulse_test.js b/services/github/test/pulse_test.js index 6b08a02a67d..fb8484eb4ab 100644 --- a/services/github/test/pulse_test.js +++ b/services/github/test/pulse_test.js @@ -1,8 +1,9 @@ const helper = require('./helper'); const assert = require('assert'); const libUrls = require('taskcluster-lib-urls'); +const testing = require('taskcluster-lib-testing'); -helper.secrets.mockSuite('pulse', ['taskcluster'], function(mock, skipping) { +helper.secrets.mockSuite(testing.suiteName(), ['taskcluster'], function(mock, skipping) { helper.withEntities(mock, skipping); helper.withFakePublisher(mock, skipping); helper.withFakeGithub(mock, skipping); diff --git a/services/github/test/references_test.js b/services/github/test/references_test.js index 93de192f443..e06e221d42d 100644 --- a/services/github/test/references_test.js +++ b/services/github/test/references_test.js @@ -2,8 +2,9 @@ const builder = require('../src/api'); const exchanges = require('../src/exchanges'); const helper = require('./helper'); const References = require('taskcluster-lib-references'); +const testing = require('taskcluster-lib-testing'); -suite('references_test.js', function() { +suite(testing.suiteName(), function() { test('references validate', async function() { const schemaset = await helper.load('schemaset'); const references = References.fromService({schemaset, builder, exchanges}); diff --git a/services/github/test/sync_test.js b/services/github/test/sync_test.js index 407328b2530..dd55c9f7038 100644 --- a/services/github/test/sync_test.js +++ b/services/github/test/sync_test.js @@ -1,10 +1,11 @@ const helper = require('./helper'); const assert = require('assert'); +const testing = require('taskcluster-lib-testing'); /** * Tests of installation syncing */ -helper.secrets.mockSuite('syncInstallations', ['taskcluster'], function(mock, skipping) { +helper.secrets.mockSuite(testing.suiteName(), ['taskcluster'], function(mock, skipping) { helper.withEntities(mock, skipping); helper.withFakeGithub(mock, skipping); helper.withServer(mock, skipping); diff --git a/services/github/test/tc-yaml_test.js b/services/github/test/tc-yaml_test.js index ff9c40f0f06..f918fbbc697 100644 --- a/services/github/test/tc-yaml_test.js +++ b/services/github/test/tc-yaml_test.js @@ -1,7 +1,8 @@ const TcYaml = require('../src/tc-yaml'); const assume = require('assume'); +const testing = require('taskcluster-lib-testing'); -suite('tc-yaml_test.js', function() { +suite(testing.suiteName(), function() { suite('VersionOne', function() { const tcyaml = TcYaml.instantiate(1); const cfg = { diff --git a/services/github/test/webhook_test.js b/services/github/test/webhook_test.js index 8374496734a..05d73737a1f 100644 --- a/services/github/test/webhook_test.js +++ b/services/github/test/webhook_test.js @@ -1,7 +1,8 @@ const helper = require('./helper'); const assert = require('assert'); +const testing = require('taskcluster-lib-testing'); -helper.secrets.mockSuite('webhook', ['taskcluster'], function(mock, skipping) { +helper.secrets.mockSuite(testing.suiteName(), ['taskcluster'], function(mock, skipping) { helper.withEntities(mock, skipping); helper.withFakeGithub(mock, skipping); helper.withServer(mock, skipping); diff --git a/services/hooks/src/taskcreator.js b/services/hooks/src/taskcreator.js index ad2ef919e33..66cf83b136a 100644 --- a/services/hooks/src/taskcreator.js +++ b/services/hooks/src/taskcreator.js @@ -26,20 +26,21 @@ class TaskCreator { } taskForHook(hook, context, options) { - let task = jsone(hook.task, _.defaults({}, {taskId: options.taskId}, context)); + const now = options.created; + let task = jsone(hook.task, _.defaults({}, {now, taskId: options.taskId}, context)); if (!task) { return; } - let created = options.created || new Date(); + // only apply created, deadline, and expires if they are not set if (!task.created) { - task.created = created.toJSON(); + task.created = now.toJSON(); } if (!task.deadline) { - task.deadline = taskcluster.fromNowJSON('1 day', created); + task.deadline = taskcluster.fromNowJSON('1 day', now); } if (!task.expires) { - task.expires = taskcluster.fromNowJSON('1 month', created); + task.expires = taskcluster.fromNowJSON('1 month', now); } // If the template did not set a taskGroupId, then set the taskGroupId to diff --git a/services/hooks/test/api_test.js b/services/hooks/test/api_test.js index 6dcc66a0912..f072b707ac5 100644 --- a/services/hooks/test/api_test.js +++ b/services/hooks/test/api_test.js @@ -4,8 +4,9 @@ const assume = require('assume'); const debug = require('debug')('test:api:createhook'); const taskcluster = require('taskcluster-client'); const helper = require('./helper'); +const testing = require('taskcluster-lib-testing'); -helper.secrets.mockSuite('api_test.js', ['taskcluster'], function(mock, skipping) { +helper.secrets.mockSuite(testing.suiteName(), ['taskcluster'], function(mock, skipping) { helper.withEntities(mock, skipping); helper.withTaskCreator(mock, skipping); helper.withPulse(mock, skipping); diff --git a/services/hooks/test/expires_test.js b/services/hooks/test/expires_test.js index 4e262e0dea3..9af521cc7d8 100644 --- a/services/hooks/test/expires_test.js +++ b/services/hooks/test/expires_test.js @@ -1,8 +1,9 @@ const helper = require('./helper'); const taskcluster = require('taskcluster-client'); const assume = require('assume'); +const testing = require('taskcluster-lib-testing'); -suite('expires_test', function() { +suite(testing.suiteName(), function() { helper.secrets.mockSuite('expires_test.js', ['taskcluster'], function(mock, skipping) { helper.withEntities(mock, skipping); diff --git a/services/hooks/test/listeners_test.js b/services/hooks/test/listeners_test.js index 1ce2b053d2a..85cf27f3133 100644 --- a/services/hooks/test/listeners_test.js +++ b/services/hooks/test/listeners_test.js @@ -3,8 +3,9 @@ const assume = require('assume'); const taskcluster = require('taskcluster-client'); const sinon = require('sinon'); const helper = require('./helper'); +const testing = require('taskcluster-lib-testing'); -helper.secrets.mockSuite('listeners_test.js', ['taskcluster'], function(mock, skipping) { +helper.secrets.mockSuite(testing.suiteName(), ['taskcluster'], function(mock, skipping) { helper.withEntities(mock, skipping); helper.withTaskCreator(mock, skipping); helper.withPulse(mock, skipping); diff --git a/services/hooks/test/references_test.js b/services/hooks/test/references_test.js index 93de192f443..e06e221d42d 100644 --- a/services/hooks/test/references_test.js +++ b/services/hooks/test/references_test.js @@ -2,8 +2,9 @@ const builder = require('../src/api'); const exchanges = require('../src/exchanges'); const helper = require('./helper'); const References = require('taskcluster-lib-references'); +const testing = require('taskcluster-lib-testing'); -suite('references_test.js', function() { +suite(testing.suiteName(), function() { test('references validate', async function() { const schemaset = await helper.load('schemaset'); const references = References.fromService({schemaset, builder, exchanges}); diff --git a/services/hooks/test/schedule_hooks_test.js b/services/hooks/test/schedule_hooks_test.js index e09f3f2f972..3c68e7ac6b5 100644 --- a/services/hooks/test/schedule_hooks_test.js +++ b/services/hooks/test/schedule_hooks_test.js @@ -2,8 +2,9 @@ const assert = require('assert'); const Scheduler = require('../src/scheduler'); const helper = require('./helper'); const libUrls = require('taskcluster-lib-urls'); +const testing = require('taskcluster-lib-testing'); -suite('schedule_hooks_test.js', function() { +suite(testing.suiteName(), function() { suiteSetup(async function() { await helper.secrets.setup(); helper.load.save(); diff --git a/services/hooks/test/scheduler_test.js b/services/hooks/test/scheduler_test.js index e89c0f71c80..cfabeb364b0 100644 --- a/services/hooks/test/scheduler_test.js +++ b/services/hooks/test/scheduler_test.js @@ -2,8 +2,9 @@ const _ = require('lodash'); const assume = require('assume'); const helper = require('./helper'); const taskcluster = require('taskcluster-client'); +const testing = require('taskcluster-lib-testing'); -helper.secrets.mockSuite('scheduler_test.js', ['taskcluster'], function(mock, skipping) { +helper.secrets.mockSuite(testing.suiteName(), ['taskcluster'], function(mock, skipping) { helper.withEntities(mock, skipping); helper.withTaskCreator(mock, skipping); @@ -178,6 +179,32 @@ helper.secrets.mockSuite('scheduler_test.js', ['taskcluster'], function(mock, sk assume(updatedHook.nextScheduledDate).is.not.equal(oldScheduledDate); }); + test('on 500 error, no email and nothing changes', async () => { + let oldTaskId = hook.nextTaskId; + let oldScheduledDate = hook.nextScheduledDate; + + helper.creator.shouldFail = { + statusCode: 500, + }; + + let emailSent = false; + scheduler.sendFailureEmail = async (hook, err) => { emailSent = true; }; + + await scheduler.handleHook(hook); + + // no email sent for a 500 + assume(emailSent).is.equal(false); + + let updatedHook = await helper.Hook.load({ + hookGroupId: 'tests', + hookId: 'test', + }, true); + + // nothing got updated.. + assume(updatedHook.nextTaskId).is.equal(oldTaskId); + assume(updatedHook.nextScheduledDate).is.deeply.equal(oldScheduledDate); + }); + test('on error, notify is used with correct options', async () => { helper.creator.shouldFail = true; await scheduler.handleHook(hook); diff --git a/services/hooks/test/taskcreator_test.js b/services/hooks/test/taskcreator_test.js index 9573df63316..4ce21a97853 100644 --- a/services/hooks/test/taskcreator_test.js +++ b/services/hooks/test/taskcreator_test.js @@ -4,11 +4,13 @@ const taskcreator = require('../src/taskcreator'); const debug = require('debug')('test:test_schedule_hooks'); const helper = require('./helper'); const taskcluster = require('taskcluster-client'); +const {sleep} = require('taskcluster-lib-testing'); const _ = require('lodash'); const hookDef = require('./test_definition'); const libUrls = require('taskcluster-lib-urls'); +const testing = require('taskcluster-lib-testing'); -suite('taskcreator_test.js', function() { +suite(testing.suiteName(), function() { helper.secrets.mockSuite('TaskCreator', ['taskcluster'], function(mock, skipping) { helper.withEntities(mock, skipping); @@ -224,6 +226,24 @@ suite('taskcreator_test.js', function() { assume(task.taskGroupId).equals(hook.task.then.taskGroupId); }); + test('firing a task with options.created always generates the same task', async function() { + await helper.Hook.create(defaultHook); + const now = new Date(); + const taskIdA = taskcluster.slugid(); + await creator.fire(defaultHook, {firedBy: 'foo'}, {taskId: taskIdA, created: now}); + const taskA = await fetchFiredTask(taskIdA); + + await sleep(10); // ..enough time passes to have different ms timestamps + + const taskIdB = taskcluster.slugid(); + await creator.fire(defaultHook, {firedBy: 'foo'}, {taskId: taskIdB, created: now}); + const taskB = await fetchFiredTask(taskIdB); + + assume(taskA.created).deeply.equal(taskB.created); + assume(taskA.deadline).deeply.equal(taskB.deadline); + assume(taskA.expires).deeply.equal(taskB.expires); + }); + test('firing a real task includes values from context', async function() { let hook = await createTestHook([], { env: {DUSTIN_LOCATION: '${location}'}, diff --git a/services/hooks/test/validate_test.js b/services/hooks/test/validate_test.js index c431aa9e1dd..7754c7d1cd3 100644 --- a/services/hooks/test/validate_test.js +++ b/services/hooks/test/validate_test.js @@ -1,7 +1,7 @@ const testing = require('taskcluster-lib-testing'); const path = require('path'); -suite('validate', function() { +suite(testing.suiteName(), function() { testing.schemas({ schemasetOptions: { folder: path.join(__dirname, '..', 'schemas'), diff --git a/services/index/test/api_test.js b/services/index/test/api_test.js index 556aec8be61..1387a292e55 100644 --- a/services/index/test/api_test.js +++ b/services/index/test/api_test.js @@ -6,8 +6,9 @@ const taskcluster = require('taskcluster-client'); const request = require('superagent'); const assume = require('assume'); const libUrls = require('taskcluster-lib-urls'); +const testing = require('taskcluster-lib-testing'); -helper.secrets.mockSuite('api_test.js', ['taskcluster'], function(mock, skipping) { +helper.secrets.mockSuite(testing.suiteName(), ['taskcluster'], function(mock, skipping) { helper.withEntities(mock, skipping); helper.withFakeQueue(mock, skipping); helper.withPulse(mock, skipping); diff --git a/services/index/test/index_test.js b/services/index/test/index_test.js index cd2426d5846..1cfb331eff5 100644 --- a/services/index/test/index_test.js +++ b/services/index/test/index_test.js @@ -6,7 +6,7 @@ const _ = require('lodash'); const testing = require('taskcluster-lib-testing'); const taskcluster = require('taskcluster-client'); -helper.secrets.mockSuite('index_test.js', ['taskcluster'], function(mock, skipping) { +helper.secrets.mockSuite(testing.suiteName(), ['taskcluster'], function(mock, skipping) { helper.withEntities(mock, skipping); helper.withFakeQueue(mock, skipping); helper.withPulse(mock, skipping); diff --git a/services/index/test/references_test.js b/services/index/test/references_test.js index 8782e721dc9..54112bd4313 100644 --- a/services/index/test/references_test.js +++ b/services/index/test/references_test.js @@ -1,8 +1,9 @@ const builder = require('../src/api'); const helper = require('./helper'); const References = require('taskcluster-lib-references'); +const testing = require('taskcluster-lib-testing'); -suite('references_test.js', function() { +suite(testing.suiteName(), function() { test('references validate', async function() { const schemaset = await helper.load('schemaset'); const references = References.fromService({schemaset, builder}); diff --git a/services/login/test/api_test.js b/services/login/test/api_test.js index 599d32b40df..d6d23a0a88b 100644 --- a/services/login/test/api_test.js +++ b/services/login/test/api_test.js @@ -1,11 +1,10 @@ const libUrls = require('taskcluster-lib-urls'); -require('mocha'); - -suite('API', function() { - let assume = require('assume'); - let helper = require('./helper'); - let request = require('superagent'); +const assume = require('assume'); +const helper = require('./helper'); +const request = require('superagent'); +const testing = require('taskcluster-lib-testing'); +suite(testing.suiteName(), function() { helper.setup(); suite('credentialsFromAccessToken', function() { diff --git a/services/login/test/handlers_mozilla_auth0_test.js b/services/login/test/handlers_mozilla_auth0_test.js index cc7102abd42..606b81d73ae 100644 --- a/services/login/test/handlers_mozilla_auth0_test.js +++ b/services/login/test/handlers_mozilla_auth0_test.js @@ -1,7 +1,8 @@ const assume = require('assume'); const Handler = require('../src/handlers/mozilla-auth0'); +const testing = require('taskcluster-lib-testing'); -suite('handlers/mozilla-auth0', function() { +suite(testing.suiteName(), function() { let handler = new Handler({ name: 'mozilla-auth0', cfg: { diff --git a/services/login/test/references_test.js b/services/login/test/references_test.js index cb8805b85c4..960d86dd32e 100644 --- a/services/login/test/references_test.js +++ b/services/login/test/references_test.js @@ -1,8 +1,9 @@ const builder = require('../src/api'); const helper = require('./helper'); const References = require('taskcluster-lib-references'); +const testing = require('taskcluster-lib-testing'); -suite('references_test.js', function() { +suite(testing.suiteName(), function() { helper.setup(); test('references validate', async function() { diff --git a/services/login/test/utils_test.js b/services/login/test/utils_test.js index c76ee291396..0a1aba2072d 100644 --- a/services/login/test/utils_test.js +++ b/services/login/test/utils_test.js @@ -1,7 +1,8 @@ const assume = require('assume'); const {encode, decode} = require('../src/utils'); +const testing = require('taskcluster-lib-testing'); -suite('utils', function() { +suite(testing.suiteName(), function() { suite('encoding', () => { test('encode does not encode the pipe symbol', () => { const result = encode('ad|Mozilla-LDAP|haali'); diff --git a/services/notify/test/api_test.js b/services/notify/test/api_test.js index be0d188327a..a65de152291 100644 --- a/services/notify/test/api_test.js +++ b/services/notify/test/api_test.js @@ -1,7 +1,8 @@ const assert = require('assert'); const helper = require('./helper'); +const testing = require('taskcluster-lib-testing'); -helper.secrets.mockSuite(helper.suiteName(__filename), ['taskcluster', 'aws'], function(mock, skipping) { +helper.secrets.mockSuite(testing.suiteName(), ['taskcluster', 'aws'], function(mock, skipping) { helper.withEntities(mock, skipping); helper.withPulse(mock, skipping); helper.withSES(mock, skipping); diff --git a/services/notify/test/handler_test.js b/services/notify/test/handler_test.js index f96bfd9760a..3eee6d75b0d 100644 --- a/services/notify/test/handler_test.js +++ b/services/notify/test/handler_test.js @@ -1,8 +1,9 @@ const _ = require('lodash'); const assert = require('assert'); const helper = require('./helper'); +const testing = require('taskcluster-lib-testing'); -helper.secrets.mockSuite(helper.suiteName(__filename), ['aws'], function(mock, skipping) { +helper.secrets.mockSuite(testing.suiteName(), ['aws'], function(mock, skipping) { helper.withFakeQueue(mock, skipping); helper.withSES(mock, skipping); helper.withPulse(mock, skipping); diff --git a/services/notify/test/ratelimit_test.js b/services/notify/test/ratelimit_test.js index d01a688f945..32caeddd79c 100644 --- a/services/notify/test/ratelimit_test.js +++ b/services/notify/test/ratelimit_test.js @@ -2,9 +2,9 @@ const _ = require('lodash'); const assert = require('assert'); const MockDate = require('mockdate'); const RateLimit = require('../src/ratelimit'); -const helper = require('./helper'); +const testing = require('taskcluster-lib-testing'); -suite(helper.suiteName(__filename), function() { +suite(testing.suiteName(), function() { let rateLimit; setup(async function() { diff --git a/services/notify/test/references_test.js b/services/notify/test/references_test.js index beda95845f1..9b140be2fc0 100644 --- a/services/notify/test/references_test.js +++ b/services/notify/test/references_test.js @@ -3,8 +3,9 @@ const exchanges = require('../src/exchanges'); const monitorManager = require('../src/monitor'); const helper = require('./helper'); const References = require('taskcluster-lib-references'); +const testing = require('taskcluster-lib-testing'); -suite('references_test.js', function() { +suite(testing.suiteName(), function() { test('references validate', async function() { const schemaset = await helper.load('schemaset'); const references = References.fromService({schemaset, builder, exchanges, monitorManager}); diff --git a/services/purge-cache/test/expire_test.js b/services/purge-cache/test/expire_test.js index 8bec938ab2d..38eba48bb7b 100644 --- a/services/purge-cache/test/expire_test.js +++ b/services/purge-cache/test/expire_test.js @@ -1,8 +1,9 @@ const helper = require('./helper'); const taskcluster = require('taskcluster-client'); const assume = require('assume'); +const testing = require('taskcluster-lib-testing'); -helper.secrets.mockSuite(helper.suiteName(__filename), ['taskcluster'], function(mock, skipping) { +helper.secrets.mockSuite(testing.suiteName(), ['taskcluster'], function(mock, skipping) { helper.withEntities(mock, skipping); test('expire nothing', async function() { diff --git a/services/purge-cache/test/helper.js b/services/purge-cache/test/helper.js index 57cfb2e1a88..6401dcd29f8 100644 --- a/services/purge-cache/test/helper.js +++ b/services/purge-cache/test/helper.js @@ -2,6 +2,7 @@ const path = require('path'); const builder = require('../src/api'); const data = require('../src/data'); const taskcluster = require('taskcluster-client'); +const libUrls = require('taskcluster-lib-urls'); const load = require('../src/main'); const {stickyLoader, Secrets, fakeauth, withEntity} = require('taskcluster-lib-testing'); @@ -25,7 +26,7 @@ exports.secrets = new Secrets({ secretName: 'project/taskcluster/testing/taskcluster-purge-cache', secrets: { taskcluster: [ - {env: 'TASKCLUSTER_ROOT_URL', cfg: 'taskcluster.rootUrl', name: 'rootUrl'}, + {env: 'TASKCLUSTER_ROOT_URL', cfg: 'taskcluster.rootUrl', name: 'rootUrl', mock: libUrls.testRootUrl()}, {env: 'TASKCLUSTER_CLIENT_ID', cfg: 'taskcluster.credentials.clientId', name: 'clientId'}, {env: 'TASKCLUSTER_ACCESS_TOKEN', cfg: 'taskcluster.credentials.accessToken', name: 'accessToken'}, ], diff --git a/services/purge-cache/test/purgecache_test.js b/services/purge-cache/test/purgecache_test.js index 7f021c84580..aa6287dd0b2 100644 --- a/services/purge-cache/test/purgecache_test.js +++ b/services/purge-cache/test/purgecache_test.js @@ -1,7 +1,8 @@ const helper = require('./helper'); const assume = require('assume'); +const testing = require('taskcluster-lib-testing'); -helper.secrets.mockSuite(helper.suiteName(__filename), ['taskcluster'], function(mock, skipping) { +helper.secrets.mockSuite(testing.suiteName(), ['taskcluster'], function(mock, skipping) { helper.withEntities(mock, skipping); helper.withServer(mock, skipping); diff --git a/services/purge-cache/test/references_test.js b/services/purge-cache/test/references_test.js index 8782e721dc9..54112bd4313 100644 --- a/services/purge-cache/test/references_test.js +++ b/services/purge-cache/test/references_test.js @@ -1,8 +1,9 @@ const builder = require('../src/api'); const helper = require('./helper'); const References = require('taskcluster-lib-references'); +const testing = require('taskcluster-lib-testing'); -suite('references_test.js', function() { +suite(testing.suiteName(), function() { test('references validate', async function() { const schemaset = await helper.load('schemaset'); const references = References.fromService({schemaset, builder}); 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/artifact_test.js b/services/queue/test/artifact_test.js index 64057ea78a8..902f08e6286 100644 --- a/services/queue/test/artifact_test.js +++ b/services/queue/test/artifact_test.js @@ -17,8 +17,9 @@ const qs = require('querystring'); const urllib = require('url'); const http = require('http'); const https = require('https'); +const testing = require('taskcluster-lib-testing'); -helper.secrets.mockSuite(__filename, ['taskcluster', 'aws', 'azure'], function(mock, skipping) { +helper.secrets.mockSuite(testing.suiteName(), ['taskcluster', 'aws', 'azure'], function(mock, skipping) { helper.withAmazonIPRanges(mock, skipping); helper.withPulse(mock, skipping); helper.withS3(mock, skipping); diff --git a/services/queue/test/bucket_test.js b/services/queue/test/bucket_test.js index 7fdfc842614..ebd7e547235 100644 --- a/services/queue/test/bucket_test.js +++ b/services/queue/test/bucket_test.js @@ -4,8 +4,9 @@ const Bucket = require('../src/bucket'); const debug = require('debug')('test:bucket_test'); const request = require('superagent'); const helper = require('./helper'); +const testing = require('taskcluster-lib-testing'); -helper.secrets.mockSuite(__filename, ['aws'], function(mock, skipping) { +helper.secrets.mockSuite(testing.suiteName(), ['aws'], function(mock, skipping) { helper.withS3(mock, skipping); if (mock) { @@ -16,7 +17,7 @@ helper.secrets.mockSuite(__filename, ['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/canceltask_test.js b/services/queue/test/canceltask_test.js index 03ea70233da..8c215ba9a2a 100644 --- a/services/queue/test/canceltask_test.js +++ b/services/queue/test/canceltask_test.js @@ -4,8 +4,9 @@ const slugid = require('slugid'); const taskcluster = require('taskcluster-client'); const assume = require('assume'); const helper = require('./helper'); +const testing = require('taskcluster-lib-testing'); -helper.secrets.mockSuite(__filename, ['taskcluster', 'aws', 'azure'], function(mock, skipping) { +helper.secrets.mockSuite(testing.suiteName(), ['taskcluster', 'aws', 'azure'], function(mock, skipping) { helper.withAmazonIPRanges(mock, skipping); helper.withPulse(mock, skipping); helper.withS3(mock, skipping); diff --git a/services/queue/test/claimresolver_test.js b/services/queue/test/claimresolver_test.js index 74aded27d7b..f745b81ac83 100644 --- a/services/queue/test/claimresolver_test.js +++ b/services/queue/test/claimresolver_test.js @@ -5,7 +5,7 @@ const taskcluster = require('taskcluster-client'); const helper = require('./helper'); const testing = require('taskcluster-lib-testing'); -helper.secrets.mockSuite(__filename, ['taskcluster', 'aws', 'azure'], function(mock, skipping) { +helper.secrets.mockSuite(testing.suiteName(), ['taskcluster', 'aws', 'azure'], function(mock, skipping) { helper.withAmazonIPRanges(mock, skipping); helper.withPollingServices(mock, skipping); helper.withPulse(mock, skipping); diff --git a/services/queue/test/claimtask_test.js b/services/queue/test/claimtask_test.js index 2c2872cd1ad..343def703fc 100644 --- a/services/queue/test/claimtask_test.js +++ b/services/queue/test/claimtask_test.js @@ -5,7 +5,7 @@ const assume = require('assume'); const helper = require('./helper'); const testing = require('taskcluster-lib-testing'); -helper.secrets.mockSuite(__filename, ['taskcluster', 'aws', 'azure'], function(mock, skipping) { +helper.secrets.mockSuite(testing.suiteName(), ['taskcluster', 'aws', 'azure'], function(mock, skipping) { helper.withAmazonIPRanges(mock, skipping); helper.withPulse(mock, skipping); helper.withS3(mock, skipping); @@ -37,7 +37,7 @@ helper.secrets.mockSuite(__filename, ['taskcluster', 'aws', 'azure'], function(m }, }); - 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(__filename, ['taskcluster', 'aws', 'azure'], function(m 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 a39e0de2d4c..20efef39b62 100644 --- a/services/queue/test/claimwork_test.js +++ b/services/queue/test/claimwork_test.js @@ -4,8 +4,9 @@ const slugid = require('slugid'); const taskcluster = require('taskcluster-client'); const assume = require('assume'); const helper = require('./helper'); +const testing = require('taskcluster-lib-testing'); -helper.secrets.mockSuite(__filename, ['taskcluster', 'aws', 'azure'], function(mock, skipping) { +helper.secrets.mockSuite(testing.suiteName(), ['taskcluster', 'aws', 'azure'], function(mock, skipping) { helper.withAmazonIPRanges(mock, skipping); helper.withPulse(mock, skipping); helper.withS3(mock, skipping); @@ -34,7 +35,7 @@ helper.secrets.mockSuite(__filename, ['taskcluster', 'aws', 'azure'], function(m }; }; - 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', @@ -48,7 +49,7 @@ helper.secrets.mockSuite(__filename, ['taskcluster', 'aws', 'azure'], function(m }); 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/createtask_test.js b/services/queue/test/createtask_test.js index 82be6fb203d..f9429254444 100644 --- a/services/queue/test/createtask_test.js +++ b/services/queue/test/createtask_test.js @@ -5,8 +5,9 @@ const _ = require('lodash'); const taskcluster = require('taskcluster-client'); const assume = require('assume'); const helper = require('./helper'); +const testing = require('taskcluster-lib-testing'); -helper.secrets.mockSuite(__filename, ['taskcluster', 'aws', 'azure'], function(mock, skipping) { +helper.secrets.mockSuite(testing.suiteName(), ['taskcluster', 'aws', 'azure'], function(mock, skipping) { helper.withAmazonIPRanges(mock, skipping); helper.withS3(mock, skipping); helper.withQueueService(mock, skipping); @@ -136,6 +137,30 @@ helper.secrets.mockSuite(__filename, ['taskcluster', 'aws', 'azure'], function(m }); }); + test('createTask is idempotent even when it fails sending pulse messages', async () => { + const taskId = slugid.v4(); + // make the `this.publisher.taskDefined` call in createTask fail.. + const oldTD = helper.publisher.taskDefined; + helper.publisher.taskDefined = async () => { + debug('publisher.taskDefined failing with fake error'); + throw new Error('uhoh'); + }; + try { + try { + await helper.queue + .use({retries: 0}) + .createTask(taskId, taskDef); + } catch (err) { + if (!err.toString().match(/uhoh/)) { + throw err; + } + } + } finally { + helper.publisher.taskDefined = oldTD; + } + await helper.queue.createTask(taskId, taskDef); + }); + test('defineTask', async () => { const taskId = slugid.v4(); diff --git a/services/queue/test/deadline_test.js b/services/queue/test/deadline_test.js index 8ca4940364f..107cf1d1a79 100644 --- a/services/queue/test/deadline_test.js +++ b/services/queue/test/deadline_test.js @@ -6,7 +6,7 @@ const assume = require('assume'); const helper = require('./helper'); const testing = require('taskcluster-lib-testing'); -helper.secrets.mockSuite(__filename, ['taskcluster', 'aws', 'azure'], function(mock, skipping) { +helper.secrets.mockSuite(testing.suiteName(), ['taskcluster', 'aws', 'azure'], function(mock, skipping) { helper.withPollingServices(mock, skipping); helper.withAmazonIPRanges(mock, skipping); helper.withS3(mock, skipping); @@ -36,7 +36,7 @@ helper.secrets.mockSuite(__filename, ['taskcluster', 'aws', 'azure'], function(m 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(__filename, ['taskcluster', 'aws', 'azure'], function(m 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(__filename, ['taskcluster', 'aws', 'azure'], function(m 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(__filename, ['taskcluster', 'aws', 'azure'], function(m 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(__filename, ['taskcluster', 'aws', 'azure'], function(m 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 ec1e2240445..513766d2482 100644 --- a/services/queue/test/dependency_test.js +++ b/services/queue/test/dependency_test.js @@ -7,7 +7,7 @@ const testing = require('taskcluster-lib-testing'); const assume = require('assume'); const helper = require('./helper'); -helper.secrets.mockSuite(__filename, ['taskcluster', 'aws', 'azure'], function(mock, skipping) { +helper.secrets.mockSuite(testing.suiteName(), ['taskcluster', 'aws', 'azure'], function(mock, skipping) { helper.withAmazonIPRanges(mock, skipping); helper.withPollingServices(mock, skipping); helper.withPulse(mock, skipping); @@ -32,7 +32,7 @@ helper.secrets.mockSuite(__filename, ['taskcluster', 'aws', 'azure'], function(m }, }); - 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(__filename, ['taskcluster', 'aws', 'azure'], function(m } 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(__filename, ['taskcluster', 'aws', 'azure'], function(m 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(__filename, ['taskcluster', 'aws', 'azure'], function(m 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(__filename, ['taskcluster', 'aws', 'azure'], function(m 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(__filename, ['taskcluster', 'aws', 'azure'], function(m 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(__filename, ['taskcluster', 'aws', 'azure'], function(m ); }); - 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(__filename, ['taskcluster', 'aws', 'azure'], function(m 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(__filename, ['taskcluster', 'aws', 'azure'], function(m 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(__filename, ['taskcluster', 'aws', 'azure'], function(m 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(__filename, ['taskcluster', 'aws', 'azure'], function(m 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/expirequeues_test.js b/services/queue/test/expirequeues_test.js index ade89267a5b..ae23a557fed 100644 --- a/services/queue/test/expirequeues_test.js +++ b/services/queue/test/expirequeues_test.js @@ -1,6 +1,7 @@ const helper = require('./helper'); +const testing = require('taskcluster-lib-testing'); -helper.secrets.mockSuite(__filename, ['azure'], function(mock, skipping) { +helper.secrets.mockSuite(testing.suiteName(), ['azure'], function(mock, skipping) { helper.withQueueService(mock, skipping); // test functionality elsewhere, here we just test that it can actually run diff --git a/services/queue/test/expiretask_test.js b/services/queue/test/expiretask_test.js index 39c4fc5b80c..1616dfc888f 100644 --- a/services/queue/test/expiretask_test.js +++ b/services/queue/test/expiretask_test.js @@ -3,8 +3,9 @@ const slugid = require('slugid'); const taskcluster = require('taskcluster-client'); const assume = require('assume'); const helper = require('./helper'); +const testing = require('taskcluster-lib-testing'); -helper.secrets.mockSuite(__filename, ['taskcluster', 'aws', 'azure'], function(mock, skipping) { +helper.secrets.mockSuite(testing.suiteName(), ['taskcluster', 'aws', 'azure'], function(mock, skipping) { helper.withAmazonIPRanges(mock, skipping); helper.withPulse(mock, skipping); helper.withS3(mock, skipping); diff --git a/services/queue/test/gettask_test.js b/services/queue/test/gettask_test.js index d394a651153..055e27d7cb5 100644 --- a/services/queue/test/gettask_test.js +++ b/services/queue/test/gettask_test.js @@ -2,8 +2,9 @@ const slugid = require('slugid'); const taskcluster = require('taskcluster-client'); const assume = require('assume'); const helper = require('./helper'); +const testing = require('taskcluster-lib-testing'); -helper.secrets.mockSuite(__filename, ['taskcluster', 'aws', 'azure'], function(mock, skipping) { +helper.secrets.mockSuite(testing.suiteName(), ['taskcluster', 'aws', 'azure'], function(mock, skipping) { helper.withAmazonIPRanges(mock, skipping); helper.withPulse(mock, skipping); helper.withS3(mock, skipping); 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(,