From 8e9c904ed18254263232066065164020e77e17d5 Mon Sep 17 00:00:00 2001 From: "azure-pipelines[bot]" Date: Thu, 15 Nov 2018 13:34:36 -0500 Subject: [PATCH] fix windows support and split plugin tests (#374) --- .circleci/config.yml | 288 +++++++++++++++------- .nycrc | 6 +- LICENSE-3rdparty.csv | 15 +- README.md | 16 +- azure-pipelines.yml | 9 + package.json | 25 +- scripts/install_plugin_modules.js | 37 ++- src/instrumenter.js | 9 + test/leak/plugins/amqp10.js | 22 +- test/leak/plugins/amqplib.js | 4 +- test/leak/plugins/express.js | 2 +- test/leak/plugins/graphql.js | 2 +- test/leak/plugins/memcached.js | 2 +- test/leak/plugins/mongodb-core.js | 2 +- test/leak/plugins/mysql.js | 2 +- test/leak/plugins/mysql2.js | 2 +- test/leak/plugins/pg.js | 2 +- test/leak/plugins/redis.js | 2 +- test/mocha.opts | 2 - test/plugins/externals.json | 36 +-- test/plugins/http.spec.js | 14 +- test/plugins/koa.spec.js | 3 +- test/plugins/pg.spec.js | 4 +- test/plugins/ssl/test.crt | 13 + test/plugins/ssl/test.key | 15 ++ test/proxyquire.js | 3 + test/setup.js | 351 --------------------------- test/setup/all.js | 4 + test/setup/core.js | 131 ++++++++++ test/setup/operation.js | 39 +++ test/setup/services.js | 25 ++ test/setup/services/elasticsearch.js | 22 ++ test/setup/services/memcached.js | 24 ++ test/setup/services/mongo.js | 33 +++ test/setup/services/mysql.js | 27 +++ test/setup/services/postgres.js | 38 +++ test/setup/services/qpid.js | 26 ++ test/setup/services/rabbitmq.js | 22 ++ test/setup/services/redis.js | 26 ++ 39 files changed, 780 insertions(+), 525 deletions(-) create mode 100644 azure-pipelines.yml delete mode 100644 test/mocha.opts create mode 100644 test/plugins/ssl/test.crt create mode 100644 test/plugins/ssl/test.key create mode 100644 test/proxyquire.js delete mode 100644 test/setup.js create mode 100644 test/setup/all.js create mode 100644 test/setup/core.js create mode 100644 test/setup/operation.js create mode 100644 test/setup/services.js create mode 100644 test/setup/services/elasticsearch.js create mode 100644 test/setup/services/memcached.js create mode 100644 test/setup/services/mongo.js create mode 100644 test/setup/services/mysql.js create mode 100644 test/setup/services/postgres.js create mode 100644 test/setup/services/qpid.js create mode 100644 test/setup/services/rabbitmq.js create mode 100644 test/setup/services/redis.js diff --git a/.circleci/config.yml b/.circleci/config.yml index e6aa0f15779..a41ef3a6da0 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,36 +1,10 @@ version: 2 -docker-base: &docker-base + +node-core-base: &node-core-base docker: - image: node:8 - - &postgres - image: postgres:9.5 - - &mysql - image: mysql:5.7 - environment: - - MYSQL_ALLOW_EMPTY_PASSWORD=yes - - MYSQL_DATABASE=db - - &redis - image: redis:4.0-alpine - - &mongo - image: mongo:3.6 - - &elasticsearch - image: docker.elastic.co/elasticsearch/elasticsearch-oss:6.2.4 - environment: - - discovery.type=single-node - - "ES_JAVA_OPTS=-Xms64m -Xmx64m" - - &rabbitmq - image: rabbitmq:3.6-alpine - - &qpid - image: scholzj/qpid-cpp:1.38.0 - command: -p 5673 - environment: - - QPIDD_ADMIN_USERNAME=admin - - QPIDD_ADMIN_PASSWORD=admin - - &memcached - image: memcached:1.5-alpine -build-node-base: &node-base - <<: *docker-base working_directory: ~/dd-trace-js + resource_class: small steps: - checkout - run: @@ -49,16 +23,49 @@ build-node-base: &node-base - ./node_modules - ./yarn.lock - run: - name: Test - command: yarn test + name: Unit tests + command: yarn test:core - run: name: Benchmark command: yarn bench + +node-plugin-base: &node-plugin-base + docker: + - image: node:8 + working_directory: ~/dd-trace-js + resource_class: small + steps: + - checkout + - run: + name: Versions + command: yarn versions + - &restore-yarn-cache + restore_cache: + key: yarn-{{ .Environment.CIRCLE_JOB }}-{{ checksum "package.json" }} + - run: + name: Install dependencies + command: yarn install + - &save-yarn-cache + save_cache: + key: yarn-{{ .Environment.CIRCLE_JOB }}-{{ checksum "package.json" }} + paths: + - ./node_modules + - ./yarn.lock + - run: + name: Unit tests + command: yarn test:plugins + - run: + name: Memory leak tests + command: yarn leak:plugins + jobs: + # Linting + lint: docker: - image: node working_directory: ~/dd-trace-js + resource_class: small steps: - checkout - run: @@ -72,9 +79,34 @@ jobs: - run: name: Lint command: yarn lint - test-memory-leaks: - <<: *docker-base + + # Core tests + + node-core-4: + <<: *node-core-base + docker: + - image: node:4 + + node-core-6: + <<: *node-core-base + docker: + - image: node:6 + + node-core-8: + <<: *node-core-base + docker: + - image: node:8 + + node-core-latest: + <<: *node-core-base + docker: + - image: node + + node-leaks: + docker: + - image: node:8 working_directory: ~/dd-trace-js + resource_class: small steps: - checkout - run: @@ -86,67 +118,138 @@ jobs: command: yarn install - *save-yarn-cache - run: - name: Test - command: yarn leak - build-node-4: - <<: *node-base + name: Memory leak tests + command: yarn leak:core + + # Plugin tests + + node-amqplib: + <<: *node-plugin-base docker: - - image: node:4 - - *postgres - - *mysql - - *redis - - *mongo - - *elasticsearch - - *rabbitmq - - *qpid - - *memcached - build-node-6: - <<: *node-base + - image: node:8 + environment: + - SERVICES=rabbitmq + - PLUGINS=amqplib + - image: rabbitmq:3.6-alpine + + node-amqp10: + <<: *node-plugin-base docker: - - image: node:6 - - *postgres - - *mysql - - *redis - - *mongo - - *elasticsearch - - *rabbitmq - - *qpid - - *memcached - build-node-8: - <<: *node-base + - image: node:8 + environment: + - SERVICES=qpid + - PLUGINS=amqp10 + - image: scholzj/qpid-cpp:1.38.0 + command: -p 5673 + environment: + - QPIDD_ADMIN_USERNAME=admin + - QPIDD_ADMIN_PASSWORD=admin + + node-elasticsearch: + <<: *node-plugin-base docker: - image: node:8 - - *postgres - - *mysql - - *redis - - *mongo - - *elasticsearch - - *rabbitmq - - *qpid - - *memcached - build-node-latest: - <<: *node-base + environment: + - SERVICES=elasticsearch + - PLUGINS=elasticsearch + - image: docker.elastic.co/elasticsearch/elasticsearch-oss:6.2.4 + environment: + - discovery.type=single-node + - "ES_JAVA_OPTS=-Xms64m -Xmx64m" + + node-graphql: + <<: *node-plugin-base docker: - - image: node - - *postgres - - *mysql - - *redis - - *mongo - - *elasticsearch - - *rabbitmq - - *qpid - - *memcached + - image: node:8 + environment: + - SERVICES= + - PLUGINS=graphql + + node-http: + <<: *node-plugin-base + docker: + - image: node:8 + environment: + - SERVICES= + - PLUGINS=http + + node-memcached: + <<: *node-plugin-base + docker: + - image: node:8 + environment: + - SERVICES=memcached + - PLUGINS=memcached + - image: memcached:1.5-alpine + + node-mysql: + <<: *node-plugin-base + docker: + - image: node:8 + environment: + - SERVICES=mysql + - PLUGINS=mysql|mysql2 + - image: mysql:5.7 + environment: + - MYSQL_ALLOW_EMPTY_PASSWORD=yes + - MYSQL_DATABASE=db + + node-mongo: + <<: *node-plugin-base + docker: + - image: node:8 + environment: + - SERVICES=mongo + - PLUGINS=mongodb-core + - image: mongo:3.6 + + node-postgres: + <<: *node-plugin-base + docker: + - image: node:8 + environment: + - SERVICES=postgres + - PLUGINS=pg + - image: postgres:9.5 + + node-redis: + <<: *node-plugin-base + docker: + - image: node:8 + environment: + - SERVICES=redis + - PLUGINS=redis|ioredis + - image: redis:4.0-alpine + + node-web: + <<: *node-plugin-base + docker: + - image: node:8 + environment: + - SERVICES= + - PLUGINS=express|hapi|koa|restify workflows: version: 2 build: jobs: - lint - - test-memory-leaks - - build-node-4 - - build-node-6 - - build-node-8 - - build-node-latest + - node-leaks + - node-core-4 + - node-core-6 + - node-core-8 + - node-core-latest + - node-amqplib + - node-amqp10 + - node-elasticsearch + - node-graphql + - node-http + - node-memcached + - node-mongo + - node-mysql + - node-postgres + - node-redis + - node-web nightly: triggers: - schedule: @@ -156,7 +259,18 @@ workflows: only: - master jobs: - - build-node-4 - - build-node-6 - - build-node-8 - - build-node-latest + - node-core-4 + - node-core-6 + - node-core-8 + - node-core-latest + - node-amqplib + - node-amqp10 + - node-elasticsearch + - node-graphql + - node-http + - node-memcached + - node-mongo + - node-mysql + - node-postgres + - node-redis + - node-web diff --git a/.nycrc b/.nycrc index bcd8df0e91c..92e2957024c 100644 --- a/.nycrc +++ b/.nycrc @@ -1,3 +1,7 @@ { - "include": ["src"] + "include": ["src"], + "reporter": [ + "text", + "lcov" + ] } diff --git a/LICENSE-3rdparty.csv b/LICENSE-3rdparty.csv index f0462f88367..f5116ace8d9 100644 --- a/LICENSE-3rdparty.csv +++ b/LICENSE-3rdparty.csv @@ -18,14 +18,10 @@ require,require-in-the-middle,MIT,Copyright 2016-2018 Thomas Watson Steen require,safe-buffer,MIT,Copyright Feross Aboukhadijeh require,shimmer,BSD-2-Clause,Copyright Forrest L Norvell require,url-parse,MIT,Copyright 2015 Unshift.io Arnout Kazemier the Contributors -dev,amqp10,MIT,Copyright 2014 Michael Lanzetta -dev,amqplib,MIT,Copyright 2013-2014 Michael Bridgen dev,axios,MIT,Copyright 2014-present Matt Zabriskie dev,benchmark,MIT,Copyright 2010-2016 Mathias Bynens Robert Kieffer John-David Dalton -dev,bluebird,MIT,Copyright 2013-2018 Petka Antonov dev,body-parser,MIT,Copyright 2014 Jonathan Ong 2014-2015 Douglas Christopher Wilson dev,chai,MIT,Copyright 2017 Chai.js Assertion Library -dev,elasticsearch,Apache-2.0,Copyright 2013 Elasticsearch BV dev,eslint,MIT,Copyright JS Foundation and other contributors https://js.foundation dev,eslint-config-standard,MIT,Copyright Feross Aboukhadijeh dev,eslint-plugin-import,MIT,Copyright 2015 Ben Mosher @@ -35,22 +31,13 @@ dev,eslint-plugin-standard,MIT,Copyright 2015 Jamund Ferguson dev,eventemitter3,MIT,Copyright 2014 Arnout Kazemier dev,express,MIT,Copyright 2009-2014 TJ Holowaychuk 2013-2014 Roman Shtylman 2014-2015 Douglas Christopher Wilson dev,get-port,MIT,Copyright Sindre Sorhus -dev,graphql,MIT,Copyright 2015-present Facebook Inc. -dev,memcached,MIT,Copyright 2010 Arnout Kazemier and 3rd-Eden dev,mocha,MIT,Copyright 2011-2018 JS Foundation and contributors https://js.foundation -dev,mongodb-core,Apache-2.0,Copyright Christian Kvalheim -dev,mysql,MIT,Copyright 2012 Felix Geisendörfer and contributors -dev,mysql2,MIT,Copyright 2016 Andrey Sidorov and contributors dev,nock,MIT,Copyright 2017 Pedro Teixeira and other contributors -dev,nyc,ISC,Copyright 2015 Contributors -dev,pg,MIT,Copyright 2010-2018 Brian Carlson dev,proxyquire,MIT,Copyright 2013 Thorsten Lorenz -dev,redis,MIT,Copyright 2016 NodeRedis dev,require-dir,MIT,2012-2015 Aseem Kishore dev,retry,MIT,Copyright 2011 Tim Koschützki Felix Geisendörfer -dev,selfsigned,MIT,Copyright 2013 José F. Romaniello dev,semver,ISC,Copyright Isaac Z. Schlueter and Contributors dev,sinon,BSD-3-Clause,Copyright 2010-2017 Christian Johansen dev,sinon-chai,WTFPL and BSD-2-Clause,Copyright 2004 Sam Hocevar 2012–2017 Domenic Denicola dev,tape,MIT,Copyright James Halliday -dev,ws,MIT,Copyright 2011 Einar Otto Stangvik +dev,v8-coverage,ISC,Copyright Eywek diff --git a/README.md b/README.md index 316f9a3ce4e..dad826d5bae 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,9 @@ # dd-trace-js -[![npm](https://img.shields.io/npm/v/dd-trace.svg)](https://www.npmjs.com/package/dd-trace) -[![CircleCI](https://img.shields.io/circleci/project/github/DataDog/dd-trace-js.svg)](https://circleci.com/gh/DataDog/dd-trace-js/tree/master) +[![npm](https://img.shields.io/npm/v/dd-trace.svg?colorB=blue)](https://www.npmjs.com/package/dd-trace) +[![npm (tag)](https://img.shields.io/npm/v/dd-trace/dev.svg)](https://www.npmjs.com/package/dd-trace/v/dev) +[![CircleCI](https://circleci.com/gh/DataDog/dd-trace-js.svg?style=shield)](https://circleci.com/gh/DataDog/dd-trace-js) +[![Build Status](https://dev.azure.com/datadog-apm/dd-trace-js/_apis/build/status/build-node-core-windows)](https://dev.azure.com/datadog-apm/dd-trace-js/_build/latest) **JavaScript APM Tracer** @@ -87,11 +89,11 @@ After installing the `circleci` CLI, simply run one of the following: ```sh $ circleci build --job lint -$ circleci build --job test-memory-leaks -$ circleci build --job build-node-4 -$ circleci build --job build-node-6 -$ circleci build --job build-node-8 -$ circleci build --job build-node-latest +$ circleci build --job test-node-leaks +$ circleci build --job test-node-core-4 +$ circleci build --job test-node-core-6 +$ circleci build --job test-node-core-8 +$ circleci build --job test-node-core-latest ``` ### Benchmarks diff --git a/azure-pipelines.yml b/azure-pipelines.yml new file mode 100644 index 00000000000..31cd1f8f080 --- /dev/null +++ b/azure-pipelines.yml @@ -0,0 +1,9 @@ +pool: + vmImage: win1803 + +steps: +- script: yarn versions && yarn install + displayName: Install dependencies + +- bash: yarn test:core + displayName: Test Core diff --git a/package.json b/package.json index 6b6bd4c9359..a1d98ac2ac1 100644 --- a/package.json +++ b/package.json @@ -8,9 +8,13 @@ "jsdoc": "cd docs && yarn && ./node_modules/.bin/gulp jsdoc", "jsdoc:watch": "cd docs && yarn && ./node_modules/.bin/gulp jsdoc:watch", "lint": "eslint . && node scripts/check_licenses.js", - "tdd": "node ./scripts/install_plugin_modules && mocha --watch", - "test": "node ./scripts/install_plugin_modules && nyc --reporter text --reporter lcov mocha --exit 'test/**/*.spec.js'", - "leak": "(cd test/leak && yarn) && NODE_PATH=./test/leak/node_modules node --no-warnings ./node_modules/.bin/tape 'test/leak/{,!(node_modules)/**/}/*.js'" + "services": "node ./scripts/install_plugin_modules && node test/setup/services", + "tdd": "yarn services && mocha --watch 'test/setup/**/*.js'", + "test": "yarn services && cov8 --include \"src/**/*.js\" -- mocha --exit 'test/setup/all.js' 'test/**/*.spec.js'", + "test:core": "mocha --exit --exclude \"test/plugins/**/*.spec.js\" --file test/setup/core.js \"test/**/*.spec.js\"", + "test:plugins": "yarn services && cov8 --include \"src/**/*.js\" -- mocha --exit --file \"test/setup/all.js\" \"test/plugins/@($(echo $PLUGINS)).spec.js\"", + "leak:core": "node ./scripts/install_plugin_modules && (cd test/leak && yarn) && NODE_PATH=./test/leak/node_modules node --no-warnings ./node_modules/.bin/tape 'test/leak/{,!(node_modules|plugins)/**/}/*.js'", + "leak:plugins": "yarn services && (cd test/leak && yarn) && NODE_PATH=./test/leak/node_modules node --no-warnings ./node_modules/.bin/tape \"test/leak/plugins/@($(echo $PLUGINS)).js\"" }, "repository": { "type": "git", @@ -55,14 +59,10 @@ "url-parse": "^1.4.3" }, "devDependencies": { - "amqp10": "^3.6.0", - "amqplib": "^0.5.2", "axios": "^0.18.0", "benchmark": "^2.1.4", - "bluebird": "^3.5.1", "body-parser": "^1.18.2", "chai": "^4.1.2", - "elasticsearch": "^15.0.0", "eslint": "^4.15.0", "eslint-config-standard": "^11.0.0-beta.0", "eslint-plugin-import": "^2.8.0", @@ -72,23 +72,14 @@ "eventemitter3": "^3.1.0", "express": "^4.16.2", "get-port": "^3.2.0", - "graphql": "^0.13.2", - "memcached": "^2.2.2", "mocha": "^5.2.0", - "mongodb-core": "^3.0.7", - "mysql": "^2.15.0", - "mysql2": "1.5.3", "nock": "^9.6.1", - "nyc": "^11.4.1", - "pg": "^6.4.2", "proxyquire": "^1.8.0", - "redis": "^2.8.0", "require-dir": "^1.0.0", "retry": "^0.10.1", - "selfsigned": "^1.10.3", "sinon": "^4.2.1", "sinon-chai": "^2.14.0", "tape": "^4.9.1", - "ws": "^6.1.0" + "v8-coverage": "^1.0.8" } } diff --git a/scripts/install_plugin_modules.js b/scripts/install_plugin_modules.js index 1f6eec144e1..7d889243e8a 100644 --- a/scripts/install_plugin_modules.js +++ b/scripts/install_plugin_modules.js @@ -14,31 +14,52 @@ const workspaces = new Set() run() function run () { + assertFolder() assertVersions() assertWorkspace() install() } function assertVersions () { - const internals = Object.keys(plugins) + let filter = [] + let names = Object.keys(plugins) .filter(key => key !== 'index') + + if (process.env.hasOwnProperty('PLUGINS')) { + filter = process.env.PLUGINS.split('|') + names = names.filter(name => ~filter.indexOf(name)) + } + + const internals = names .map(key => plugins[key]) .reduce((prev, next) => prev.concat(next), []) - internals.concat(externals).forEach(instrumentation => { - [].concat(instrumentation.versions).forEach(version => { - if (version) { - assertModules(instrumentation.name, version) - assertModules(instrumentation.name, semver.coerce(version).version) - } + internals.forEach(assertInstrumentation) + + Object.keys(externals) + .filter(name => ~filter.indexOf(name)) + .forEach(name => { + [].concat(externals[name]).forEach(assertInstrumentation) }) +} + +function assertInstrumentation (instrumentation) { + [].concat(instrumentation.versions).forEach(version => { + if (version) { + assertModules(instrumentation.name, version) + assertModules(instrumentation.name, semver.coerce(version).version) + } }) } function assertModules (name, version) { + addFolder(name) addFolder(name, version) + assertFolder(name) assertFolder(name, version) + assertPackage(name) assertPackage(name, version) + assertIndex(name) assertIndex(name, version) } @@ -54,7 +75,7 @@ function assertFolder (name, version) { function assertPackage (name, version) { fs.writeFileSync(filename(name, version, 'package.json'), JSON.stringify({ - name: [name, sha1(version)].filter(val => val).join('-'), + name: [name, sha1(name).substr(0, 8), sha1(version)].filter(val => val).join('-'), version: '1.0.0', license: 'BSD-3-Clause', private: true, diff --git a/src/instrumenter.js b/src/instrumenter.js index 2d04986e611..d8c100948c0 100644 --- a/src/instrumenter.js +++ b/src/instrumenter.js @@ -2,10 +2,13 @@ const semver = require('semver') const hook = require('require-in-the-middle') +const path = require('path') const shimmer = require('shimmer') const uniq = require('lodash.uniq') const log = require('./log') +const pathSepExpr = new RegExp(`\\${path.sep}`, 'g') + shimmer({ logger: () => {} }) class Instrumenter { @@ -77,10 +80,16 @@ class Instrumenter { } hookModule (moduleExports, moduleName, moduleBaseDir) { + moduleName = moduleName.replace(pathSepExpr, '/') + if (this._names.indexOf(moduleName) === -1) { return moduleExports } + if (moduleBaseDir) { + moduleBaseDir = moduleBaseDir.replace(pathSepExpr, '/') + } + const moduleVersion = getVersion(moduleBaseDir) Array.from(this._plugins.keys()) diff --git a/test/leak/plugins/amqp10.js b/test/leak/plugins/amqp10.js index 1a5af1e549e..7e8525bd851 100644 --- a/test/leak/plugins/amqp10.js +++ b/test/leak/plugins/amqp10.js @@ -8,7 +8,7 @@ const test = require('tape') const profile = require('../../profile') test('amqp10 plugin should not leak', t => { - const amqp = require('amqp10') + const amqp = require('../../../versions/amqp10').get() const client = new amqp.Client() return client.connect('amqp://admin:admin@localhost:5673') @@ -21,15 +21,31 @@ test('amqp10 plugin should not leak', t => { .then(handlers => { const receiver = handlers[0] const sender = handlers[1] + const deferred = [] - profile(t, operation) + let messageIdx = 0 + let operationIdx = 0 + + for (let i = 0; i < 2000; i++) { + const promise = new Promise((resolve, reject) => { + deferred[i] = { resolve, reject } + }) + + deferred[i].promise = promise + } + + receiver.on('message', () => { + deferred[messageIdx++].resolve() + }) + + profile(t, operation, 200, 10) .then(() => receiver.detach()) .then(() => sender.detach()) .then(() => client.disconnect()) function operation (done) { + deferred[operationIdx++].promise.then(() => done()) sender.send({ key: 'value' }) - receiver.once('message', done) } }) }) diff --git a/test/leak/plugins/amqplib.js b/test/leak/plugins/amqplib.js index 3f942ad414f..4fdb5fbfbc5 100644 --- a/test/leak/plugins/amqplib.js +++ b/test/leak/plugins/amqplib.js @@ -8,7 +8,7 @@ const test = require('tape') const profile = require('../../profile') test('amqplib plugin should not leak when using callbacks', t => { - require('amqplib/callback_api') + require('../../../versions/amqplib').get('amqplib/callback_api') .connect((err, conn) => { if (err) return t.fail(err) @@ -25,7 +25,7 @@ test('amqplib plugin should not leak when using callbacks', t => { }) test('amqplib plugin should not leak when using promises', t => { - require('amqplib').connect() + require('../../../versions/amqplib').get().connect() .then(conn => { return conn.createChannel() .then(ch => { diff --git a/test/leak/plugins/express.js b/test/leak/plugins/express.js index 2f9493705cf..da6df21787a 100644 --- a/test/leak/plugins/express.js +++ b/test/leak/plugins/express.js @@ -5,7 +5,7 @@ require('../../..') .use('express') const test = require('tape') -const express = require('express') +const express = require(`../../../versions/express`).get() const axios = require('axios') const getPort = require('get-port') const profile = require('../../profile') diff --git a/test/leak/plugins/graphql.js b/test/leak/plugins/graphql.js index 8016dc0ff08..9f5dd01c3d0 100644 --- a/test/leak/plugins/graphql.js +++ b/test/leak/plugins/graphql.js @@ -5,7 +5,7 @@ require('../../..') .use('graphql') const test = require('tape') -const graphql = require('graphql') +const graphql = require(`../../../versions/graphql`).get() const profile = require('../../profile') test('graphql plugin should not leak', t => { diff --git a/test/leak/plugins/memcached.js b/test/leak/plugins/memcached.js index 75aabf19d10..d6721be579f 100644 --- a/test/leak/plugins/memcached.js +++ b/test/leak/plugins/memcached.js @@ -5,7 +5,7 @@ require('../../..') .use('memcached') const test = require('tape') -const Memcached = require('memcached') +const Memcached = require('../../../versions/memcached').get() const profile = require('../../profile') test('memcached plugin should not leak', t => { diff --git a/test/leak/plugins/mongodb-core.js b/test/leak/plugins/mongodb-core.js index c58d5d0520c..b5125442ae4 100644 --- a/test/leak/plugins/mongodb-core.js +++ b/test/leak/plugins/mongodb-core.js @@ -5,7 +5,7 @@ require('../../..') .use('mongodb-core') const test = require('tape') -const mongo = require('mongodb-core') +const mongo = require('../../../versions/mongodb-core').get() const profile = require('../../profile') test('mongodb-core plugin should not leak', t => { diff --git a/test/leak/plugins/mysql.js b/test/leak/plugins/mysql.js index a38b949293f..ff77363ab92 100644 --- a/test/leak/plugins/mysql.js +++ b/test/leak/plugins/mysql.js @@ -5,7 +5,7 @@ require('../../..') .use('mysql') const test = require('tape') -const mysql = require('mysql') +const mysql = require('../../../versions/mysql').get() const profile = require('../../profile') test('mysql plugin should not leak', t => { diff --git a/test/leak/plugins/mysql2.js b/test/leak/plugins/mysql2.js index 7fe5e4bfada..568b568fe6a 100644 --- a/test/leak/plugins/mysql2.js +++ b/test/leak/plugins/mysql2.js @@ -5,7 +5,7 @@ require('../../..') .use('mysql2') const test = require('tape') -const mysql2 = require('mysql2') +const mysql2 = require('../../../versions/mysql2').get() const profile = require('../../profile') test('mysql2 plugin should not leak', t => { diff --git a/test/leak/plugins/pg.js b/test/leak/plugins/pg.js index 7ee94988945..06855fb8456 100644 --- a/test/leak/plugins/pg.js +++ b/test/leak/plugins/pg.js @@ -5,7 +5,7 @@ require('../../..') .use('pg') const test = require('tape') -const pg = require('pg') +const pg = require('../../../versions/pg').get() const profile = require('../../profile') test('pg plugin should not leak', t => { diff --git a/test/leak/plugins/redis.js b/test/leak/plugins/redis.js index b05fd6843cb..8f8562a04e2 100644 --- a/test/leak/plugins/redis.js +++ b/test/leak/plugins/redis.js @@ -5,7 +5,7 @@ require('../../..') .use('redis') const test = require('tape') -const redis = require('redis') +const redis = require('../../../versions/redis').get() const profile = require('../../profile') test('redis plugin should not leak', t => { diff --git a/test/mocha.opts b/test/mocha.opts deleted file mode 100644 index d9b3546b2c3..00000000000 --- a/test/mocha.opts +++ /dev/null @@ -1,2 +0,0 @@ ---delay -test/setup.js diff --git a/test/plugins/externals.json b/test/plugins/externals.json index f5bc6cd9f2c..eaf5fed4c25 100644 --- a/test/plugins/externals.json +++ b/test/plugins/externals.json @@ -1,14 +1,22 @@ -[ - { - "name": "apollo-server-core", - "versions": ["1.3.6"] - }, - { - "name": "graphql-tools", - "versions": ["3.1.1"] - }, - { - "name": "koa-websocket", - "versions": ["5.0.1"] - } -] +{ + "graphql": [ + { + "name": "apollo-server-core", + "versions": ["1.3.6"] + }, + { + "name": "graphql-tools", + "versions": ["3.1.1"] + } + ], + "koa": [ + { + "name": "koa-websocket", + "versions": ["5.0.1"] + }, + { + "name": "ws", + "versions": ["6.1.0"] + } + ] +} diff --git a/test/plugins/http.spec.js b/test/plugins/http.spec.js index 33d83a6b0fb..5f2435e36bc 100644 --- a/test/plugins/http.spec.js +++ b/test/plugins/http.spec.js @@ -3,6 +3,10 @@ const getPort = require('get-port') const agent = require('./agent') const semver = require('semver') +const fs = require('fs') +const path = require('path') +const key = fs.readFileSync(path.join(__dirname, './ssl/test.key')) +const cert = fs.readFileSync(path.join(__dirname, './ssl/test.crt')) wrapIt() @@ -12,20 +16,14 @@ describe('Plugin', () => { let http let appListener let tracer - let pems ['http', 'https'].forEach(protocol => { describe(protocol, () => { function server (app, port, listener) { let server if (protocol === 'https') { - if (!pems) { - // Generate self-signed cert - pems = require('selfsigned').generate([{ name: 'commonName', value: 'datadoghq.com' }], { days: 365 }) - // We're fine with self-signed certs for this test - process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0' - } - server = require('https').createServer({ key: pems.private, cert: pems.cert }, app) + process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0' + server = require('https').createServer({ key, cert }, app) } else { server = require('http').createServer(app) } diff --git a/test/plugins/koa.spec.js b/test/plugins/koa.spec.js index a564166bc33..8a4e963ad2b 100644 --- a/test/plugins/koa.spec.js +++ b/test/plugins/koa.spec.js @@ -1,7 +1,6 @@ 'use strict' const axios = require('axios') -const WebSocket = require('ws') const getPort = require('get-port') const agent = require('./agent') const plugin = require('../../src/plugins/koa') @@ -302,10 +301,12 @@ describe('Plugin', () => { }) withVersions(plugin, 'koa-websocket', wsVersion => { + let WebSocket let websockify let ws beforeEach(() => { + WebSocket = require(`../../versions/ws@6.1.0`).get() websockify = require(`../../versions/koa-websocket@${wsVersion}`).get() }) diff --git a/test/plugins/pg.spec.js b/test/plugins/pg.spec.js index 26f4dd64422..e79eab842b1 100644 --- a/test/plugins/pg.spec.js +++ b/test/plugins/pg.spec.js @@ -126,7 +126,7 @@ describe('Plugin', () => { }) beforeEach(done => { - pg = require('pg') + pg = require(`../../versions/pg@${version}`).get() pool = new pg.Pool({ user: 'postgres', @@ -173,7 +173,7 @@ describe('Plugin', () => { }) beforeEach(done => { - pg = require('pg') + pg = require(`../../versions/pg@${version}`).get() client = new pg.Client({ user: 'postgres', diff --git a/test/plugins/ssl/test.crt b/test/plugins/ssl/test.crt new file mode 100644 index 00000000000..1e7cffdb53b --- /dev/null +++ b/test/plugins/ssl/test.crt @@ -0,0 +1,13 @@ +-----BEGIN CERTIFICATE----- +MIIB8zCCAVygAwIBAgIJVbkpkoETXPZRMA0GCSqGSIb3DQEBBQUAMBgxFjAUBgNV +BAMTDWRhdGFkb2docS5jb20wHhcNMTgxMTEzMDIwMjM4WhcNMjEwMjE1MDIwMjM4 +WjAYMRYwFAYDVQQDEw1kYXRhZG9naHEuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GN +ADCBiQKBgQCaDqEOJGuteA4T+cKjwzgWyiDnxrA+I9xj8Tl4q6F0CcaIcxG+dxar +femRa+YkCl8Rm15wwtYTbjcfkP0zeEXKKn1q9ExwAv/jAra4XBaaGWltIYRCEFAb ++tcW/z9nird5JAER2fs2wNxUbgcLgVDk4c4WAtC2El4DhVCfiOLbFwIDAQABo0Uw +QzAMBgNVHRMEBTADAQH/MAsGA1UdDwQEAwIC9DAmBgNVHREEHzAdhhtodHRwOi8v +ZXhhbXBsZS5vcmcvd2ViaWQjbWUwDQYJKoZIhvcNAQEFBQADgYEAR4KtWWONZU6n +AThh5lkK8ia3EIMSmK9R2/5cXGF1d7fcEErpzfIQjmuMdnHX1AFxrIeusk5KKB9H +zrcHvaOPTklWev5yg4LTEQBPqfYZsO/+SRbpECAxrospNaXa/paHOsUA5MC5iL6J +w0dDxXf9VApQ25dTD4lasMWJpn1QPeg= +-----END CERTIFICATE----- diff --git a/test/plugins/ssl/test.key b/test/plugins/ssl/test.key new file mode 100644 index 00000000000..439957f4976 --- /dev/null +++ b/test/plugins/ssl/test.key @@ -0,0 +1,15 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICXgIBAAKBgQCaDqEOJGuteA4T+cKjwzgWyiDnxrA+I9xj8Tl4q6F0CcaIcxG+ +dxarfemRa+YkCl8Rm15wwtYTbjcfkP0zeEXKKn1q9ExwAv/jAra4XBaaGWltIYRC +EFAb+tcW/z9nird5JAER2fs2wNxUbgcLgVDk4c4WAtC2El4DhVCfiOLbFwIDAQAB +AoGAG4MxUA1BDaG7IeYg1I4wniFK7xmUsARHf7G8ycgc9VQpmIS0ToeGDOpXpBjY +6ObRQ5uzqf+hq6arQX/rOB3qmIEvFqlSglxh0H9GDi0XJTmtYZwDvRUEJguyqyS2 +si0WwN336Xdk/mW+ayBVYrQyPDwvTu0Kz0qteXw8SLM6WIECQQDPLb0lR/Gh9FG/ +rHOxeoxIAaQpHuXxlqfWb0O3ilHjU5xJhhCBJMWll7r06nuVP4Kz+m9zHqysCP20 +ryDVPhDJAkEAvlxH9anFnxKij7bbbtLyrXoWG3NVoQ9NpjouHBislNJSVrOJf7ex +cLqxpeVgSsz0rJyjUQN75L2SSfFaqnpc3wJBAMaWHh61Jl1KFm32vHLbd8mxoQw1 +bM20zi5SOoOc1z9iroUfN4TCnZ1Lok4O0OTPmtYvcFLVjUfd2mouD/e25VkCQQCO +v3Ky1Om9/OJsdMoeVA/paOjVEur+iRRId86GATbBrupR2/ZbEOv9xfJbniNUVFeS +bS/HG563jDT8QAImXvXvAkEAs7/LaakRaHB7LBzBjd7379obh088vpvXaAy8NPXH +KtsV94zCuMoszGzuMaA8Wqbi3DVDRuYVNIB9bzMfUNVgyg== +-----END RSA PRIVATE KEY----- diff --git a/test/proxyquire.js b/test/proxyquire.js new file mode 100644 index 00000000000..0379f37963b --- /dev/null +++ b/test/proxyquire.js @@ -0,0 +1,3 @@ +'use strict' + +module.exports = require('proxyquire') diff --git a/test/setup.js b/test/setup.js deleted file mode 100644 index e8752fc4346..00000000000 --- a/test/setup.js +++ /dev/null @@ -1,351 +0,0 @@ -'use strict' - -const sinon = require('sinon') -const chai = require('chai') -const sinonChai = require('sinon-chai') -const proxyquire = require('proxyquire') -const nock = require('nock') -const semver = require('semver') -const retry = require('retry') -const RetryOperation = require('retry/lib/retry_operation') -const pg = require('pg') -const mysql = require('mysql') -const redis = require('redis') -const mongo = require('mongodb-core') -const axios = require('axios') -const amqplib = require('amqplib/callback_api') -const amqp = require('amqp10') -const Memcached = require('memcached') -const platform = require('../src/platform') -const node = require('../src/platform/node') -const ScopeManager = require('../src/scope/scope_manager') -const agent = require('./plugins/agent') -const externals = require('./plugins/externals.json') - -const scopeManager = new ScopeManager() - -const retryOptions = { - retries: 60, - factor: 1, - minTimeout: 5000, - maxTimeout: 5000, - randomize: false -} - -chai.use(sinonChai) - -global.sinon = sinon -global.expect = chai.expect -global.proxyquire = proxyquire -global.nock = nock -global.wrapIt = wrapIt -global.withVersions = withVersions - -platform.use(node) - -after(() => { - scopeManager._disable() -}) - -afterEach(() => { - agent.reset() -}) - -waitForServices() - .then(run) - .catch(err => { - setImmediate(() => { throw err }) - }) - -function waitForServices () { - return Promise.all([ - waitForPostgres(), - waitForMysql(), - waitForRedis(), - waitForMongo(), - waitForElasticsearch(), - waitForRabbitMQ(), - waitForQpid(), - waitForMemcached() - ]) -} - -function waitForPostgres () { - return new Promise((resolve, reject) => { - const operation = createOperation('postgres') - - operation.attempt(currentAttempt => { - const client = new pg.Client({ - user: 'postgres', - password: 'postgres', - database: 'postgres', - application_name: 'test' - }) - - client.connect((err) => { - if (retryOperation(operation, err)) return - if (err) return reject(err) - - client.query('SELECT version()', (err, result) => { - if (retryOperation(operation, err)) return - if (err) return reject(err) - - client.end((err) => { - if (retryOperation(operation, err)) return - if (err) return reject(err) - - resolve() - }) - }) - }) - }) - }) -} - -function waitForMysql () { - return new Promise((resolve, reject) => { - const operation = createOperation('mysql') - - operation.attempt(currentAttempt => { - const connection = mysql.createConnection({ - host: 'localhost', - user: 'root', - database: 'db' - }) - - connection.connect(err => { - if (retryOperation(operation, err)) return - if (err) return reject(err) - - connection.end(() => resolve()) - }) - }) - }) -} - -function waitForRedis () { - return new Promise((resolve, reject) => { - const client = redis.createClient({ - retry_strategy: function (options) { - if (options.attempt > retryOptions.retries) { - return reject(options.error) - } else { - logAttempt('redis', 'failed to connect') - } - - return retryOptions.maxTimeout - } - }) - - client.on('connect', () => { - client.quit() - resolve() - }) - }) -} - -function waitForMongo () { - return new Promise((resolve, reject) => { - const operation = createOperation('mongo') - - operation.attempt(currentAttempt => { - const server = new mongo.Server({ - host: 'localhost', - port: 27017, - reconnect: false - }) - - server.on('connect', server => { - server.destroy() - resolve() - }) - - server.on('error', err => { - if (!retryOperation(operation, err)) { - reject(err) - } - }) - - server.connect() - }) - }) -} - -function waitForElasticsearch () { - return new Promise((resolve, reject) => { - const operation = createOperation('elasticsearch') - - operation.attempt(currentAttempt => { - // Not using ES client because it's buggy for initial connection. - axios.get('http://localhost:9200/_cluster/health?wait_for_status=green&local=true&timeout=100ms') - .then(() => resolve()) - .catch(err => { - if (retryOperation(operation, err)) return - reject(err) - }) - }) - }) -} - -function waitForRabbitMQ () { - return new Promise((resolve, reject) => { - const operation = createOperation('rabbitmq') - - operation.attempt(currentAttempt => { - amqplib - .connect((err, conn) => { - if (retryOperation(operation, err)) return - if (err) return reject(err) - - conn.close(() => resolve()) - }) - }) - }) -} - -function waitForQpid () { - return new Promise((resolve, reject) => { - const operation = retry.operation(retryOptions) - - operation.attempt(currentAttempt => { - const client = new amqp.Client(amqp.Policy.merge({ - reconnect: null - })) - - client.connect('amqp://admin:admin@localhost:5673') - .then(() => client.disconnect()) - .then(() => resolve()) - .catch(err => { - if (operation.retry(err)) return - reject(err) - }) - }) - }) -} - -function waitForMemcached () { - return new Promise((resolve, reject) => { - const operation = createOperation('memcached') - - operation.attempt(currentAttempt => { - const memcached = new Memcached('localhost:11211', { retries: 0 }) - - memcached.version((err, version) => { - if (retryOperation(operation, err)) return - if (err) return reject(err) - - memcached.end() - resolve() - }) - }) - }) -} - -function withoutScope (fn) { - return function () { - let active - - while ((active = scopeManager.active())) { - active.close() - } - - return fn.apply(this, arguments) - } -} - -function wrapIt () { - const it = global.it - - global.it = function (title, fn) { - if (!fn) { - return it.apply(this, arguments) - } - - if (fn.length > 0) { - return it.call(this, title, function (done) { - arguments[0] = withoutScope(agent.wrap(done)) - - return fn.apply(this, arguments) - }) - } else { - return it.call(this, title, function () { - const result = fn.apply(this, arguments) - - if (result && result.then) { - return result - .then(withoutScope(res => res)) - .catch(withoutScope(err => Promise.reject(err))) - .then(() => agent.promise()) - } - - return agent.promise() - .then(() => result) - }) - } - } -} - -function withVersions (plugin, moduleName, range, cb) { - const instrumentations = [].concat(plugin, externals) - const testVersions = new Map() - - if (!cb) { - cb = range - range = null - } - - instrumentations - .filter(instrumentation => instrumentation.name === moduleName) - .forEach(instrumentation => { - instrumentation.versions - .forEach(version => { - try { - const min = semver.coerce(version).version - require(`../versions/${moduleName}@${min}`).get() - testVersions.set(min, { range: version, test: min }) - } catch (e) { - // skip unsupported version - } - - agent.wipe() - - try { - const max = require(`../versions/${moduleName}@${version}`).version() - require(`../versions/${moduleName}@${version}`).get() - testVersions.set(max, { range: version, test: version }) - } catch (e) { - // skip unsupported version - } - - agent.wipe() - }) - }) - - Array.from(testVersions) - .filter(v => !range || semver.satisfies(v[0], range)) - .sort(v => v[0].localeCompare(v[0])) - .map(v => Object.assign({}, v[1], { version: v[0] })) - .forEach(v => { - describe(`with ${moduleName} ${v.range} (${v.version})`, () => cb(v.test)) - }) - - agent.wipe() -} - -function createOperation (service) { - const timeouts = retry.timeouts(retryOptions) - return new RetryOperation(timeouts, Object.assign({ service }, retryOptions)) -} - -function retryOperation (operation, err) { - const shouldRetry = operation.retry(err) - if (shouldRetry) { - logAttempt(operation._options.service, err.message) - } - return shouldRetry -} - -function logAttempt (service, message) { - // eslint-disable-next-line no-console - console.error(`[Retrying connection to ${service}] ${message}`) -} diff --git a/test/setup/all.js b/test/setup/all.js new file mode 100644 index 00000000000..9e2c4b02bab --- /dev/null +++ b/test/setup/all.js @@ -0,0 +1,4 @@ +'use strict' + +require('./core') +require('./services') diff --git a/test/setup/core.js b/test/setup/core.js new file mode 100644 index 00000000000..9813d2856a0 --- /dev/null +++ b/test/setup/core.js @@ -0,0 +1,131 @@ +'use strict' + +const sinon = require('sinon') +const chai = require('chai') +const sinonChai = require('sinon-chai') +const proxyquire = require('../proxyquire') +const nock = require('nock') +const semver = require('semver') +const platform = require('../../src/platform') +const node = require('../../src/platform/node') +const ScopeManager = require('../../src/scope/scope_manager') +const agent = require('../plugins/agent') +const externals = require('../plugins/externals.json') + +const scopeManager = new ScopeManager() + +chai.use(sinonChai) + +global.sinon = sinon +global.expect = chai.expect +global.proxyquire = proxyquire +global.nock = nock +global.wrapIt = wrapIt +global.withVersions = withVersions + +platform.use(node) + +after(() => { + scopeManager._disable() +}) + +afterEach(() => { + agent.reset() +}) + +function withoutScope (fn) { + return function () { + let active + + while ((active = scopeManager.active())) { + active.close() + } + + return fn.apply(this, arguments) + } +} + +function wrapIt () { + const it = global.it + + global.it = function (title, fn) { + if (!fn) { + return it.apply(this, arguments) + } + + if (fn.length > 0) { + return it.call(this, title, function (done) { + arguments[0] = withoutScope(agent.wrap(done)) + + return fn.apply(this, arguments) + }) + } else { + return it.call(this, title, function () { + const result = fn.apply(this, arguments) + + if (result && result.then) { + return result + .then(withoutScope(res => res)) + .catch(withoutScope(err => Promise.reject(err))) + .then(() => agent.promise()) + } + + return agent.promise() + .then(() => result) + }) + } + } +} + +function withVersions (plugin, moduleName, range, cb) { + const instrumentations = [].concat(plugin) + const testVersions = new Map() + + if (externals[moduleName]) { + [].concat(externals[moduleName]).forEach(external => { + instrumentations.push(external) + }) + } + + if (!cb) { + cb = range + range = null + } + + instrumentations + .filter(instrumentation => instrumentation.name === moduleName) + .forEach(instrumentation => { + instrumentation.versions + .forEach(version => { + try { + const min = semver.coerce(version).version + require(`../../versions/${moduleName}@${min}`).get() + testVersions.set(min, { range: version, test: min }) + } catch (e) { + // skip unsupported version + } + + agent.wipe() + + try { + const max = require(`../../versions/${moduleName}@${version}`).version() + require(`../../versions/${moduleName}@${version}`).get() + testVersions.set(max, { range: version, test: version }) + } catch (e) { + // skip unsupported version + } + + agent.wipe() + }) + }) + + Array.from(testVersions) + .filter(v => !range || semver.satisfies(v[0], range)) + .sort(v => v[0].localeCompare(v[0])) + .map(v => Object.assign({}, v[1], { version: v[0] })) + .forEach(v => { + describe(`with ${moduleName} ${v.range} (${v.version})`, () => cb(v.test)) + }) + + agent.wipe() +} diff --git a/test/setup/operation.js b/test/setup/operation.js new file mode 100644 index 00000000000..f951e30931d --- /dev/null +++ b/test/setup/operation.js @@ -0,0 +1,39 @@ +'use strict' + +const retry = require('retry') +const BaseRetryOperation = require('retry/lib/retry_operation') + +const options = { + retries: 300, + factor: 1, + minTimeout: 1000, + maxTimeout: 1000, + randomize: false +} + +class RetryOperation extends BaseRetryOperation { + constructor (service) { + const timeouts = retry.timeouts(options) + + super(timeouts, { service }) + } + + retry (error) { + const shouldRetry = super.retry(error) + + if (shouldRetry) { + logAttempt(this._options.service, error.message) + } + + return shouldRetry + } +} + +Object.assign(RetryOperation, options) + +function logAttempt (service, message) { + // eslint-disable-next-line no-console + console.error(`[Retrying connection to ${service}] ${message}`) +} + +module.exports = RetryOperation diff --git a/test/setup/services.js b/test/setup/services.js new file mode 100644 index 00000000000..2b731084d30 --- /dev/null +++ b/test/setup/services.js @@ -0,0 +1,25 @@ +'use strict' + +const path = require('path') +const fs = require('fs') + +waitForServices() + .catch(err => { + setImmediate(() => { throw err }) + }) + +function waitForServices () { + let names = fs.readdirSync(path.join(__dirname, 'services')) + .map(item => item.replace('.js', '')) + + if (process.env.hasOwnProperty('SERVICES')) { + const filter = process.env.SERVICES.split('|') + names = names.filter(name => ~filter.indexOf(name)) + } + + const promises = names + .map(name => require(`./services/${name}`)) + .map(service => service()) + + return Promise.all(promises) +} diff --git a/test/setup/services/elasticsearch.js b/test/setup/services/elasticsearch.js new file mode 100644 index 00000000000..8df222d39a6 --- /dev/null +++ b/test/setup/services/elasticsearch.js @@ -0,0 +1,22 @@ +'use strict' + +const axios = require('axios') +const RetryOperation = require('../operation') + +function waitForElasticsearch () { + return new Promise((resolve, reject) => { + const operation = new RetryOperation('elasticsearch') + + operation.attempt(currentAttempt => { + // Not using ES client because it's buggy for initial connection. + axios.get('http://localhost:9200/_cluster/health?wait_for_status=green&local=true&timeout=100ms') + .then(() => resolve()) + .catch(err => { + if (operation.retry(err)) return + reject(err) + }) + }) + }) +} + +module.exports = waitForElasticsearch diff --git a/test/setup/services/memcached.js b/test/setup/services/memcached.js new file mode 100644 index 00000000000..9264abf773c --- /dev/null +++ b/test/setup/services/memcached.js @@ -0,0 +1,24 @@ +'use strict' + +const RetryOperation = require('../operation') +const Memcached = require('../../../versions/memcached').get() + +function waitForMemcached () { + return new Promise((resolve, reject) => { + const operation = new RetryOperation('memcached') + + operation.attempt(currentAttempt => { + const memcached = new Memcached('localhost:11211', { retries: 0 }) + + memcached.version((err, version) => { + if (operation.retry(err)) return + if (err) return reject(err) + + memcached.end() + resolve() + }) + }) + }) +} + +module.exports = waitForMemcached diff --git a/test/setup/services/mongo.js b/test/setup/services/mongo.js new file mode 100644 index 00000000000..a85bd048d8d --- /dev/null +++ b/test/setup/services/mongo.js @@ -0,0 +1,33 @@ +'use strict' + +const RetryOperation = require('../operation') +const mongo = require('../../../versions/mongodb-core').get() + +function waitForMongo () { + return new Promise((resolve, reject) => { + const operation = new RetryOperation('mongo') + + operation.attempt(currentAttempt => { + const server = new mongo.Server({ + host: 'localhost', + port: 27017, + reconnect: false + }) + + server.on('connect', server => { + server.destroy() + resolve() + }) + + server.on('error', err => { + if (!operation.retry(err)) { + reject(err) + } + }) + + server.connect() + }) + }) +} + +module.exports = waitForMongo diff --git a/test/setup/services/mysql.js b/test/setup/services/mysql.js new file mode 100644 index 00000000000..e2f341302b5 --- /dev/null +++ b/test/setup/services/mysql.js @@ -0,0 +1,27 @@ +'use strict' + +const RetryOperation = require('../operation') +const mysql = require('../../../versions/mysql').get() + +function waitForMysql () { + return new Promise((resolve, reject) => { + const operation = new RetryOperation('mysql') + + operation.attempt(currentAttempt => { + const connection = mysql.createConnection({ + host: 'localhost', + user: 'root', + database: 'db' + }) + + connection.connect(err => { + if (operation.retry(err)) return + if (err) return reject(err) + + connection.end(() => resolve()) + }) + }) + }) +} + +module.exports = waitForMysql diff --git a/test/setup/services/postgres.js b/test/setup/services/postgres.js new file mode 100644 index 00000000000..8f40d146cc3 --- /dev/null +++ b/test/setup/services/postgres.js @@ -0,0 +1,38 @@ +'use strict' + +const RetryOperation = require('../operation') +const pg = require('../../../versions/pg').get() + +function waitForPostgres () { + return new Promise((resolve, reject) => { + const operation = new RetryOperation('postgres') + + operation.attempt(currentAttempt => { + const client = new pg.Client({ + user: 'postgres', + password: 'postgres', + database: 'postgres', + application_name: 'test' + }) + + client.connect((err) => { + if (operation.retry(err)) return + if (err) return reject(err) + + client.query('SELECT version()', (err, result) => { + if (operation.retry(err)) return + if (err) return reject(err) + + client.end((err) => { + if (operation.retry(err)) return + if (err) return reject(err) + + resolve() + }) + }) + }) + }) + }) +} + +module.exports = waitForPostgres diff --git a/test/setup/services/qpid.js b/test/setup/services/qpid.js new file mode 100644 index 00000000000..2a44c1862b3 --- /dev/null +++ b/test/setup/services/qpid.js @@ -0,0 +1,26 @@ +'use strict' + +const RetryOperation = require('../operation') +const amqp = require('../../../versions/amqp10').get() + +function waitForQpid () { + return new Promise((resolve, reject) => { + const operation = new RetryOperation('qpid') + + operation.attempt(currentAttempt => { + const client = new amqp.Client(amqp.Policy.merge({ + reconnect: null + })) + + client.connect('amqp://admin:admin@localhost:5673') + .then(() => client.disconnect()) + .then(() => resolve()) + .catch(err => { + if (operation.retry(err)) return + reject(err) + }) + }) + }) +} + +module.exports = waitForQpid diff --git a/test/setup/services/rabbitmq.js b/test/setup/services/rabbitmq.js new file mode 100644 index 00000000000..4d88da336c8 --- /dev/null +++ b/test/setup/services/rabbitmq.js @@ -0,0 +1,22 @@ +'use strict' + +const RetryOperation = require('../operation') +const amqplib = require('../../../versions/amqplib').get('amqplib/callback_api') + +function waitForRabbitMQ () { + return new Promise((resolve, reject) => { + const operation = new RetryOperation('rabbitmq') + + operation.attempt(currentAttempt => { + amqplib + .connect((err, conn) => { + if (operation.retry(err)) return + if (err) return reject(err) + + conn.close(() => resolve()) + }) + }) + }) +} + +module.exports = waitForRabbitMQ diff --git a/test/setup/services/redis.js b/test/setup/services/redis.js new file mode 100644 index 00000000000..0947f08b8fe --- /dev/null +++ b/test/setup/services/redis.js @@ -0,0 +1,26 @@ +'use strict' + +const RetryOperation = require('../operation') +const redis = require('../../../versions/redis').get() + +function waitForRedis () { + return new Promise((resolve, reject) => { + const operation = new RetryOperation('redis') + + operation.attempt(currentAttempt => { + const client = redis.createClient({ + retry_strategy: options => { + if (operation.retry(options.error)) return + reject(options.error) + } + }) + + client.on('connect', (a) => { + client.quit() + resolve() + }) + }) + }) +} + +module.exports = waitForRedis