diff --git a/.circleci/config.yml b/.circleci/config.yml index 2f34c43544f..b8f847fce57 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -7,9 +7,7 @@ docker-base: &docker-base - &mysql image: mysql:5.7 environment: - - MYSQL_ROOT_PASSWORD=rootpass - - MYSQL_PASSWORD=userpass - - MYSQL_USER=user + - MYSQL_ALLOW_EMPTY_PASSWORD=yes - MYSQL_DATABASE=db - &redis image: redis:4.0-alpine @@ -26,16 +24,16 @@ build-node-base: &node-base - checkout - run: name: Versions - command: npm version + command: yarn versions - run: name: Install dependencies - command: npm install + command: yarn install - run: name: Test - command: npm test + command: yarn test - run: name: Benchmark - command: npm run bench + command: yarn bench jobs: lint: docker: @@ -45,13 +43,13 @@ jobs: - checkout - run: name: Versions - command: npm version + command: yarn versions - run: name: Install dependencies - command: npm install + command: yarn install - run: name: Lint - command: npm run lint + command: yarn lint test-memory-leaks: <<: *docker-base working_directory: ~/dd-trace-js @@ -59,13 +57,13 @@ jobs: - checkout - run: name: Versions - command: npm version + command: yarn versions - run: name: Install dependencies - command: npm install + command: yarn install - run: name: Test - command: npm run leak + command: yarn leak build-node-4: <<: *node-base docker: diff --git a/.eslintignore b/.eslintignore index c04c27abab6..79aac9048f7 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,2 +1,3 @@ coverage out +test/plugins/versions diff --git a/.gitignore b/.gitignore index aca900292d0..d697d240b30 100644 --- a/.gitignore +++ b/.gitignore @@ -103,4 +103,5 @@ typings/ package-lock.json yarn.lock out +test/plugins/versions !test/node_modules diff --git a/LICENSE-3rdparty.csv b/LICENSE-3rdparty.csv index 5581c355ea1..c69ce79638f 100644 --- a/LICENSE-3rdparty.csv +++ b/LICENSE-3rdparty.csv @@ -46,6 +46,7 @@ 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,semver,ISC,Copyright Isaac Z. Schlueter and Contributors dev,sinon,BSD-3-Clause,Copyright 2010-2017 Christian Johansen diff --git a/README.md b/README.md index c863fa50ffe..2338906ab6d 100644 --- a/README.md +++ b/README.md @@ -24,10 +24,12 @@ Before contributing to this open source project, read our [CONTRIBUTING.md](http Since this project supports multiple Node versions, using a version manager such as [nvm](https://github.com/creationix/nvm) is recommended. -To get started once you have a Node version installed, run: +We use [yarn](https://yarnpkg.com/) for its workspace functionality, so make sure to install that as well. + +To get started once you have Node and yarn installed, run: ```sh -$ npm install +$ yarn ``` ### Testing @@ -45,13 +47,13 @@ $ docker-compose up -d To run the unit tests, use: ```sh -$ npm test +$ yarn test ``` To run the unit tests continuously in watch mode while developing, use: ```sh -$ npm run tdd +$ yarn tdd ``` #### Memory Leaks @@ -59,7 +61,7 @@ $ npm run tdd To run the memory leak tests, use: ```sh -$ npm run leak +$ yarn leak ``` Please note that memory leak tests only run on Node `>=8`. @@ -72,7 +74,7 @@ conform to our coding standards. To run the linter, use: ```sh -$ npm run lint +$ yarn lint ``` ### Continuous Integration @@ -99,5 +101,5 @@ in the `benchmark/index.js` module so that we can keep track of the most efficient algorithm. To run your benchmark, just: ```sh -$ npm run bench +$ yarn bench ``` diff --git a/docker-compose.yml b/docker-compose.yml index 3c23fdc0c45..d43dd206a35 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -7,9 +7,7 @@ services: mysql: image: mysql:5.7 environment: - - MYSQL_ROOT_PASSWORD=rootpass - - MYSQL_PASSWORD=userpass - - MYSQL_USER=user + - MYSQL_ALLOW_EMPTY_PASSWORD=yes - MYSQL_DATABASE=db ports: - "127.0.0.1:3306:3306" diff --git a/package.json b/package.json index 067bedf1b8e..d16df16ca21 100644 --- a/package.json +++ b/package.json @@ -8,8 +8,8 @@ "jsdoc": "gulp jsdoc", "jsdoc:watch": "gulp jsdoc:watch", "lint": "eslint . && node scripts/check_licenses.js", - "tdd": "mocha --watch", - "test": "nyc --reporter text --reporter lcov mocha 'test/**/*.spec.js'", + "tdd": "node ./scripts/install_plugin_modules && mocha --watch", + "test": "node ./scripts/install_plugin_modules && nyc --reporter text --reporter lcov mocha 'test/**/*.spec.js'", "leak": "node --no-warnings ./node_modules/.bin/tape 'test/leak/**/*.js'" }, "repository": { @@ -82,6 +82,7 @@ "pg": "^6.4.2", "proxyquire": "^1.8.0", "redis": "^2.8.0", + "require-dir": "^1.0.0", "retry": "^0.10.1", "sinon": "^4.2.1", "sinon-chai": "^2.14.0", diff --git a/scripts/install_plugin_modules.js b/scripts/install_plugin_modules.js new file mode 100644 index 00000000000..e0836e931a1 --- /dev/null +++ b/scripts/install_plugin_modules.js @@ -0,0 +1,108 @@ +'use strict' + +const fs = require('fs') +const path = require('path') +const requireDir = require('require-dir') +const crypto = require('crypto') +const semver = require('semver') +const exec = require('./helpers/exec') +const plugins = requireDir('../src/plugins') + +const workspaces = new Set() + +run() + +function run () { + assertVersions() + assertWorkspace() + install() +} + +function assertVersions () { + Object.keys(plugins).filter(key => key !== 'index').forEach(key => { + [].concat(plugins[key]).forEach(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, version) + assertFolder(name, version) + assertPackage(name, version) + assertIndex(name, version) +} + +function assertFolder (name, version) { + if (!fs.existsSync(folder())) { + fs.mkdirSync(folder()) + } + + if (!fs.existsSync(folder(name, version))) { + fs.mkdirSync(folder(name, version)) + } +} + +function assertPackage (name, version) { + fs.writeFileSync(filename(name, version, 'package.json'), JSON.stringify({ + name: [name, sha1(version)].filter(val => val).join('-'), + version: '1.0.0', + license: 'BSD-3-Clause', + private: true, + dependencies: { + [name]: version + } + }, null, 2) + '\n') +} + +function assertIndex (name, version) { + const index = `'use strict' + +module.exports = { + get (id) { return require(id || '${name}') }, + version () { return require('${name}/package.json').version } +} +` + fs.writeFileSync(filename(name, version, 'index.js'), index) +} + +function assertWorkspace () { + fs.writeFileSync(filename(null, null, 'package.json'), JSON.stringify({ + name: 'versions', + version: '1.0.0', + license: 'BSD-3-Clause', + private: true, + workspaces: Array.from(workspaces) + }, null, 2) + '\n') +} + +function install () { + exec('yarn', { cwd: folder() }) +} + +function addFolder (name, version) { + const basename = [name, version].filter(val => val).join('@') + workspaces.add(basename) +} + +function folder (name, version) { + const basename = [name, version].filter(val => val).join('@') + return path.join(__dirname, '..', 'test', 'plugins', 'versions', basename) +} + +function filename (name, version, file) { + return path.join(folder(name, version), file) +} + +function sha1 (str) { + if (!str) return + + const shasum = crypto.createHash('sha1') + shasum.update(str) + return shasum.digest('hex') +} diff --git a/scripts/publish_docs.js b/scripts/publish_docs.js index ce7d85f20eb..24398cdbc7c 100644 --- a/scripts/publish_docs.js +++ b/scripts/publish_docs.js @@ -1,6 +1,5 @@ 'use strict' -const fs = require('fs') const exec = require('./helpers/exec') const title = require('./helpers/title') @@ -12,15 +11,10 @@ if (!msg) { throw new Error('Please provide a reason for the change. Example: node scripts/publish_docs.js "fix typo"') } -if (fs.existsSync('yarn.lock')) { - exec('yarn') -} else { - exec('npm install') -} - +exec('yarn install') exec('rm -rf ./out') exec('git clone -b gh-pages --single-branch git@github.com:DataDog/dd-trace-js.git out') -exec('npm run jsdoc') +exec('yarn jsdoc') exec('git add -A', { cwd: './out' }) exec(`git commit -m "${msg}"`, { cwd: './out' }) exec('git push', { cwd: './out' }) diff --git a/scripts/release.js b/scripts/release.js index 121246165ce..8ba3161aa0c 100644 --- a/scripts/release.js +++ b/scripts/release.js @@ -5,7 +5,6 @@ const title = require('./helpers/title') title(`Publishing package to the npm registry`) -exec('npm whoami') exec('git checkout master') exec('git pull') @@ -13,5 +12,5 @@ const pkg = require('../package.json') exec(`git tag v${pkg.version}`) exec(`git push origin refs/tags/v${pkg.version}`) -exec('npm publish') +exec('yarn publish') exec(`node scripts/publish_docs.js "v${pkg.version}"`) diff --git a/src/plugins/express.js b/src/plugins/express.js index b8f57ad4294..07085a1bac9 100644 --- a/src/plugins/express.js +++ b/src/plugins/express.js @@ -84,6 +84,8 @@ function createWrapProcessParams (tracer, config) { return function processParamsWithTrace (layer, called, req, res, done) { const matchers = layer._datadog_matchers + req = done ? req : called + if (matchers) { // Try to guess which path actually matched for (let i = 0; i < matchers.length; i++) { @@ -108,15 +110,16 @@ function createWrapRouterMethod (tracer) { const matchers = extractMatchers(fn) this.stack.slice(offset).forEach(layer => { - const handleRequest = layer.handle_request - const handleError = layer.handle_error - - layer.handle_request = (req, res, next) => { - return handleRequest.call(layer, req, res, wrapNext(tracer, layer, req, next)) - } + const handle = layer.handle - layer.handle_error = (error, req, res, next) => { - return handleError.call(layer, error, req, res, wrapNext(tracer, layer, req, next)) + if (handle.length === 4) { + layer.handle = (error, req, res, next) => { + return handle.call(layer, error, req, res, wrapNext(tracer, layer, req, next)) + } + } else { + layer.handle = (req, res, next) => { + return handle.call(layer, req, res, wrapNext(tracer, layer, req, next)) + } } layer._datadog_matchers = matchers @@ -138,7 +141,7 @@ function wrapNext (tracer, layer, req, next) { req._datadog.scope = tracer.scopeManager().activate(req._datadog.span) return function (error) { - if (!error && layer.path && !layer.regexp.fast_star) { + if (!error && layer.path && !isFastStar(layer)) { req._datadog.paths.pop() } @@ -161,6 +164,14 @@ function extractMatchers (fn) { })) } +function isFastStar (layer) { + if (layer.regexp.fast_star !== undefined) { + return layer.regexp.fast_star + } + + return layer._datadog_matchers.some(matcher => matcher.path === '*') +} + function flatten (arr) { return arr.reduce((acc, val) => Array.isArray(val) ? acc.concat(flatten(val)) : acc.concat(val), []) } diff --git a/test/.eslintrc.json b/test/.eslintrc.json index 45220b03263..fabba82a1b4 100644 --- a/test/.eslintrc.json +++ b/test/.eslintrc.json @@ -10,7 +10,8 @@ "sinon": true, "proxyquire": true, "nock": true, - "wrapIt": true + "wrapIt": true, + "withVersions": true }, "rules": { "no-unused-expressions": 0 diff --git a/test/leak/plugins/mysql.js b/test/leak/plugins/mysql.js index d8fbfd7b78c..a38b949293f 100644 --- a/test/leak/plugins/mysql.js +++ b/test/leak/plugins/mysql.js @@ -11,8 +11,7 @@ const profile = require('../../profile') test('mysql plugin should not leak', t => { const connection = mysql.createConnection({ host: 'localhost', - user: 'user', - password: 'userpass', + user: 'root', database: 'db' }) diff --git a/test/leak/plugins/mysql2.js b/test/leak/plugins/mysql2.js index 579f0216fe6..7fe5e4bfada 100644 --- a/test/leak/plugins/mysql2.js +++ b/test/leak/plugins/mysql2.js @@ -11,8 +11,7 @@ const profile = require('../../profile') test('mysql2 plugin should not leak', t => { const connection = mysql2.createConnection({ host: 'localhost', - user: 'user', - password: 'userpass', + user: 'root', database: 'db' }) diff --git a/test/plugins/agent.js b/test/plugins/agent.js index 29d9736bad8..026961d0fc4 100644 --- a/test/plugins/agent.js +++ b/test/plugins/agent.js @@ -17,10 +17,6 @@ let skip = [] module.exports = { load (plugin, pluginName, config) { - [].concat(plugin).forEach(instrumentation => { - this.wipe(instrumentation.name) - }) - tracer = require('../..') agent = express() agent.use(bodyParser.raw({ type: 'application/msgpack' })) @@ -119,8 +115,8 @@ module.exports = { }) }, - wipe (moduleName) { - const basedir = path.join(__dirname, '..', '..', 'node_modules', moduleName) + wipe () { + const basedir = path.join(__dirname, 'versions') Object.keys(require.cache) .filter(name => name.indexOf(basedir) !== -1) diff --git a/test/plugins/amqplib.spec.js b/test/plugins/amqplib.spec.js index 4fa9a89258d..14104b6c043 100644 --- a/test/plugins/amqplib.spec.js +++ b/test/plugins/amqplib.spec.js @@ -1,264 +1,266 @@ 'use strict' const agent = require('./agent') +const plugin = require('../../src/plugins/amqplib') wrapIt() describe('Plugin', () => { - let plugin let tracer let connection let channel describe('amqplib', () => { - beforeEach(() => { - plugin = require('../../src/plugins/amqplib') - tracer = require('../..') - }) - - afterEach(() => { - agent.close() - connection.close() - }) - - describe('without configuration', () => { - describe('when using a callback', () => { - beforeEach(done => { - agent.load(plugin, 'amqplib') - .then(() => { - require('amqplib/callback_api') - .connect((err, conn) => { - connection = conn + withVersions(plugin, 'amqplib', version => { + beforeEach(() => { + tracer = require('../..') + }) - if (err != null) { - return done(err) - } + afterEach(() => { + connection.close() + agent.close() + agent.wipe() + }) - conn.createChannel((err, ch) => { - channel = ch - done(err) + describe('without configuration', () => { + describe('when using a callback', () => { + beforeEach(done => { + agent.load(plugin, 'amqplib') + .then(() => { + require(`./versions/amqplib@${version}`).get('amqplib/callback_api') + .connect((err, conn) => { + connection = conn + + if (err != null) { + return done(err) + } + + conn.createChannel((err, ch) => { + channel = ch + done(err) + }) }) - }) - }) - .catch(done) - }) - - describe('when sending commands', () => { - it('should do automatic instrumentation for immediate commands', done => { - agent - .use(traces => { - const span = traces[0][0] - - expect(span).to.have.property('name', 'amqp.command') - expect(span).to.have.property('service', 'test-amqp') - expect(span).to.have.property('resource', 'queue.declare test') - expect(span).to.have.property('type', 'worker') - expect(span.meta).to.have.property('out.host', 'localhost') - expect(span.meta).to.have.property('out.port', '5672') - }, 2) - .then(done) - .catch(done) - - channel.assertQueue('test', {}, () => {}) - }) - - it('should do automatic instrumentation for queued commands', done => { - agent - .use(traces => { - const span = traces[0][0] - - expect(span).to.have.property('name', 'amqp.command') - expect(span).to.have.property('service', 'test-amqp') - expect(span).to.have.property('resource', 'queue.delete test') - expect(span).to.have.property('type', 'worker') - expect(span.meta).to.have.property('out.host', 'localhost') - expect(span.meta).to.have.property('out.port', '5672') - }, 3) - .then(done) + }) .catch(done) - - channel.assertQueue('test', {}, () => {}) - channel.deleteQueue('test', () => {}) }) - it('should handle errors', done => { - let error - - agent - .use(traces => { - const span = traces[0][0] - - expect(span).to.have.property('error', 1) - expect(span.meta).to.have.property('error.type', error.name) - expect(span.meta).to.have.property('error.msg', error.message) - expect(span.meta).to.have.property('error.stack', error.stack) - }, 2) - .then(done) - .catch(done) - - try { - channel.deleteQueue(null, () => {}) - } catch (e) { - error = e - } - }) - }) + describe('when sending commands', () => { + it('should do automatic instrumentation for immediate commands', done => { + agent + .use(traces => { + const span = traces[0][0] + + expect(span).to.have.property('name', 'amqp.command') + expect(span).to.have.property('service', 'test-amqp') + expect(span).to.have.property('resource', 'queue.declare test') + expect(span).to.have.property('type', 'worker') + expect(span.meta).to.have.property('out.host', 'localhost') + expect(span.meta).to.have.property('out.port', '5672') + }, 2) + .then(done) + .catch(done) + + channel.assertQueue('test', {}, () => {}) + }) - describe('when publishing messages', () => { - it('should do automatic instrumentation', done => { - agent - .use(traces => { - const span = traces[0][0] - - expect(span).to.have.property('name', 'amqp.command') - expect(span).to.have.property('service', 'test-amqp') - expect(span).to.have.property('resource', 'basic.publish exchange routingKey') - expect(span).to.have.property('type', 'worker') - expect(span.meta).to.have.property('out.host', 'localhost') - expect(span.meta).to.have.property('out.port', '5672') - expect(span.meta).to.have.property('span.kind', 'producer') - expect(span.meta).to.have.property('amqp.routingKey', 'routingKey') - }, 3) - .then(done) - .catch(done) + it('should do automatic instrumentation for queued commands', done => { + agent + .use(traces => { + const span = traces[0][0] + + expect(span).to.have.property('name', 'amqp.command') + expect(span).to.have.property('service', 'test-amqp') + expect(span).to.have.property('resource', 'queue.delete test') + expect(span).to.have.property('type', 'worker') + expect(span.meta).to.have.property('out.host', 'localhost') + expect(span.meta).to.have.property('out.port', '5672') + }, 3) + .then(done) + .catch(done) + + channel.assertQueue('test', {}, () => {}) + channel.deleteQueue('test', () => {}) + }) - channel.assertExchange('exchange', 'direct', {}, () => {}) - channel.publish('exchange', 'routingKey', Buffer.from('content')) + it('should handle errors', done => { + let error + + agent + .use(traces => { + const span = traces[0][0] + + expect(span).to.have.property('error', 1) + expect(span.meta).to.have.property('error.type', error.name) + expect(span.meta).to.have.property('error.msg', error.message) + expect(span.meta).to.have.property('error.stack', error.stack) + }, 2) + .then(done) + .catch(done) + + try { + channel.deleteQueue(null, () => {}) + } catch (e) { + error = e + } + }) }) - it('should handle errors', done => { - let error - - agent - .use(traces => { - const span = traces[0][0] - - expect(span).to.have.property('error', 1) - expect(span.meta).to.have.property('error.type', error.name) - expect(span.meta).to.have.property('error.msg', error.message) - expect(span.meta).to.have.property('error.stack', error.stack) - }, 2) - .then(done) - .catch(done) + describe('when publishing messages', () => { + it('should do automatic instrumentation', done => { + agent + .use(traces => { + const span = traces[0][0] + + expect(span).to.have.property('name', 'amqp.command') + expect(span).to.have.property('service', 'test-amqp') + expect(span).to.have.property('resource', 'basic.publish exchange routingKey') + expect(span).to.have.property('type', 'worker') + expect(span.meta).to.have.property('out.host', 'localhost') + expect(span.meta).to.have.property('out.port', '5672') + expect(span.meta).to.have.property('span.kind', 'producer') + expect(span.meta).to.have.property('amqp.routingKey', 'routingKey') + }, 3) + .then(done) + .catch(done) + + channel.assertExchange('exchange', 'direct', {}, () => {}) + channel.publish('exchange', 'routingKey', Buffer.from('content')) + }) - try { - channel.sendToQueue('test', 'invalid') - } catch (e) { - error = e - } + it('should handle errors', done => { + let error + + agent + .use(traces => { + const span = traces[0][0] + + expect(span).to.have.property('error', 1) + expect(span.meta).to.have.property('error.type', error.name) + expect(span.meta).to.have.property('error.msg', error.message) + expect(span.meta).to.have.property('error.stack', error.stack) + }, 2) + .then(done) + .catch(done) + + try { + channel.sendToQueue('test', 'invalid') + } catch (e) { + error = e + } + }) }) - }) - describe('when consuming messages', () => { - it('should do automatic instrumentation', done => { - let consumerTag - let queue - - agent - .use(traces => { - const span = traces[0][0] - - expect(span).to.have.property('name', 'amqp.command') - expect(span).to.have.property('service', 'test-amqp') - expect(span).to.have.property('resource', `basic.deliver ${queue}`) - expect(span).to.have.property('type', 'worker') - expect(span.meta).to.have.property('out.host', 'localhost') - expect(span.meta).to.have.property('out.port', '5672') - expect(span.meta).to.have.property('span.kind', 'consumer') - expect(span.meta).to.have.property('amqp.consumerTag', consumerTag) - }, 5) - .then(done) - .catch(done) + describe('when consuming messages', () => { + it('should do automatic instrumentation', done => { + let consumerTag + let queue + + agent + .use(traces => { + const span = traces[0][0] + + expect(span).to.have.property('name', 'amqp.command') + expect(span).to.have.property('service', 'test-amqp') + expect(span).to.have.property('resource', `basic.deliver ${queue}`) + expect(span).to.have.property('type', 'worker') + expect(span.meta).to.have.property('out.host', 'localhost') + expect(span.meta).to.have.property('out.port', '5672') + expect(span.meta).to.have.property('span.kind', 'consumer') + expect(span.meta).to.have.property('amqp.consumerTag', consumerTag) + }, 5) + .then(done) + .catch(done) + + channel.assertQueue('', {}, (err, ok) => { + if (err) return done(err) - channel.assertQueue('', {}, (err, ok) => { - if (err) return done(err) + queue = ok.queue - queue = ok.queue + channel.sendToQueue(ok.queue, Buffer.from('content')) + channel.consume(ok.queue, () => {}, {}, (err, ok) => { + if (err) return done(err) + consumerTag = ok.consumerTag + }) + }) + }) - channel.sendToQueue(ok.queue, Buffer.from('content')) - channel.consume(ok.queue, () => {}, {}, (err, ok) => { + it('should run the command callback in the parent context', done => { + channel.assertQueue('', {}, (err, ok) => { if (err) return done(err) - consumerTag = ok.consumerTag + + channel.consume(ok.queue, () => {}, {}, () => { + expect(tracer.scopeManager().active()).to.be.null + done() + }) }) }) - }) - it('should run the command callback in the parent context', done => { - channel.assertQueue('', {}, (err, ok) => { - if (err) return done(err) + it('should run the delivery callback in the current context', done => { + channel.assertQueue('', {}, (err, ok) => { + if (err) return done(err) - channel.consume(ok.queue, () => {}, {}, () => { - expect(tracer.scopeManager().active()).to.be.null - done() + channel.sendToQueue(ok.queue, Buffer.from('content')) + channel.consume(ok.queue, () => { + expect(tracer.scopeManager().active()).to.not.be.null + done() + }, {}, err => err && done(err)) }) }) }) + }) - it('should run the delivery callback in the current context', done => { - channel.assertQueue('', {}, (err, ok) => { - if (err) return done(err) + describe('when using a promise', () => { + beforeEach(() => { + return agent.load(plugin, 'amqplib') + .then(() => require(`./versions/amqplib@${version}`).get().connect()) + .then(conn => (connection = conn)) + .then(conn => conn.createChannel()) + .then(ch => (channel = ch)) + }) - channel.sendToQueue(ok.queue, Buffer.from('content')) - channel.consume(ok.queue, () => { - expect(tracer.scopeManager().active()).to.not.be.null + it('should run the callback in the parent context', done => { + channel.assertQueue('test', {}) + .then(() => { + expect(tracer.scopeManager().active()).to.be.null done() - }, {}, err => err && done(err)) - }) + }) + .catch(done) }) }) }) - describe('when using a promise', () => { - beforeEach(() => { - return agent.load(plugin, 'amqplib') - .then(() => require('amqplib').connect()) - .then(conn => (connection = conn)) - .then(conn => conn.createChannel()) - .then(ch => (channel = ch)) - }) - - it('should run the callback in the parent context', done => { - channel.assertQueue('test', {}) + describe('with configuration', () => { + beforeEach(done => { + agent.load(plugin, 'amqplib', { service: 'test' }) .then(() => { - expect(tracer.scopeManager().active()).to.be.null - done() + require(`./versions/amqplib@${version}`).get('amqplib/callback_api') + .connect((err, conn) => { + connection = conn + + if (err !== null) { + return done(err) + } + + conn.createChannel((err, ch) => { + channel = ch + done(err) + }) + }) }) .catch(done) }) - }) - }) - - describe('with configuration', () => { - beforeEach(done => { - agent.load(plugin, 'amqplib', { service: 'test' }) - .then(() => { - require('amqplib/callback_api') - .connect((err, conn) => { - connection = conn - - if (err !== null) { - return done(err) - } - - conn.createChannel((err, ch) => { - channel = ch - done(err) - }) - }) - }) - .catch(done) - }) - it('should be configured with the correct values', done => { - agent - .use(traces => { - expect(traces[0][0]).to.have.property('service', 'test') - expect(traces[0][0]).to.have.property('resource', 'queue.declare test') - }, 2) - .then(done) - .catch(done) + it('should be configured with the correct values', done => { + agent + .use(traces => { + expect(traces[0][0]).to.have.property('service', 'test') + expect(traces[0][0]).to.have.property('resource', 'queue.declare test') + }, 2) + .then(done) + .catch(done) - channel.assertQueue('test', {}, () => {}) + channel.assertQueue('test', {}, () => {}) + }) }) }) }) diff --git a/test/plugins/elasticsearch.spec.js b/test/plugins/elasticsearch.spec.js index 93efccf78ef..e438bd12c89 100644 --- a/test/plugins/elasticsearch.spec.js +++ b/test/plugins/elasticsearch.spec.js @@ -1,235 +1,237 @@ 'use strict' const agent = require('./agent') +const plugin = require('../../src/plugins/elasticsearch') wrapIt() describe('Plugin', () => { - let plugin let elasticsearch let tracer - describe('elasticsearch', () => { - beforeEach(() => { - plugin = require('../../src/plugins/elasticsearch') - tracer = require('../..') - }) - - afterEach(() => { - agent.close() - }) - - describe('without configuration', () => { - let client - + withVersions(plugin, 'elasticsearch', version => { + describe('elasticsearch', () => { beforeEach(() => { - return agent.load(plugin, 'elasticsearch') - .then(() => { - elasticsearch = require('elasticsearch') - client = new elasticsearch.Client({ - host: 'localhost:9200' - }) - }) - }) - - it('should sanitize the resource name', done => { - agent - .use(traces => { - expect(traces[0][0]).to.have.property('resource', 'POST /logstash-?.?.?/_search') - }) - .then(done) - .catch(done) - - client.search({ index: 'logstash-2000.01.01' }, () => {}) + tracer = require('../..') }) - it('should set the correct tags', done => { - agent - .use(traces => { - expect(traces[0][0].meta).to.have.property('db.type', 'elasticsearch') - expect(traces[0][0].meta).to.have.property('span.kind', 'client') - expect(traces[0][0].meta).to.have.property('elasticsearch.method', 'POST') - expect(traces[0][0].meta).to.have.property('elasticsearch.url', '/docs/_search') - expect(traces[0][0].meta).to.have.property('elasticsearch.body', '{"query":{"match_all":{}}}') - expect(traces[0][0].meta).to.have.property('elasticsearch.params', '{"sort":"name","size":100}') - }) - .then(done) - .catch(done) - - client.search({ - index: 'docs', - sort: 'name', - size: 100, - body: { - query: { - match_all: {} - } - } - }, () => {}) + afterEach(() => { + agent.close() + agent.wipe() }) - it('should skip tags for unavailable fields', done => { - agent - .use(traces => { - expect(traces[0][0].meta).to.not.have.property('elasticsearch.body') - }) - .then(done) - .catch(done) - - client.ping(err => err && done(err)) - }) + describe('without configuration', () => { + let client - describe('when using a callback', () => { - it('should do automatic instrumentation', done => { - agent - .use(traces => { - expect(traces[0][0]).to.have.property('service', 'test-elasticsearch') - expect(traces[0][0]).to.have.property('resource', 'HEAD /') - expect(traces[0][0]).to.have.property('type', 'elasticsearch') + beforeEach(() => { + return agent.load(plugin, 'elasticsearch') + .then(() => { + elasticsearch = require(`./versions/elasticsearch@${version}`).get() + client = new elasticsearch.Client({ + host: 'localhost:9200' + }) }) - .then(done) - .catch(done) - - client.ping(err => err && done(err)) }) - it('should propagate context', done => { + it('should sanitize the resource name', done => { agent .use(traces => { - expect(traces[0][0]).to.have.property('parent_id') - expect(traces[0][0].parent_id).to.not.be.null + expect(traces[0][0]).to.have.property('resource', 'POST /logstash-?.?.?/_search') }) .then(done) .catch(done) - tracer.trace('test', span => { - client.ping(() => span.finish()) - }) - }) - - it('should run the callback in the parent context', done => { - client.ping(error => { - expect(tracer.scopeManager().active()).to.be.null - done(error) - }) + client.search({ index: 'logstash-2000.01.01' }, () => {}) }) - it('should handle errors', done => { - let error - + it('should set the correct tags', done => { agent .use(traces => { - expect(traces[0][0].meta).to.have.property('error.type', error.name) - expect(traces[0][0].meta).to.have.property('error.msg', error.message) - expect(traces[0][0].meta).to.have.property('error.stack', error.stack) + expect(traces[0][0].meta).to.have.property('db.type', 'elasticsearch') + expect(traces[0][0].meta).to.have.property('span.kind', 'client') + expect(traces[0][0].meta).to.have.property('elasticsearch.method', 'POST') + expect(traces[0][0].meta).to.have.property('elasticsearch.url', '/docs/_search') + expect(traces[0][0].meta).to.have.property('elasticsearch.body', '{"query":{"match_all":{}}}') + expect(traces[0][0].meta).to.have.property('elasticsearch.params', '{"sort":"name","size":100}') }) .then(done) .catch(done) - client.search({ index: 'invalid' }, err => { - error = err - }) - }) - - it('should support aborting the query', () => { - expect(() => { - client.ping(() => {}).abort() - }).not.to.throw() + client.search({ + index: 'docs', + sort: 'name', + size: 100, + body: { + query: { + match_all: {} + } + } + }, () => {}) }) - }) - describe('when using a promise', () => { - it('should do automatic instrumentation', done => { + it('should skip tags for unavailable fields', done => { agent .use(traces => { - expect(traces[0][0]).to.have.property('service', 'test-elasticsearch') - expect(traces[0][0]).to.have.property('resource', 'HEAD /') - expect(traces[0][0]).to.have.property('type', 'elasticsearch') + expect(traces[0][0].meta).to.not.have.property('elasticsearch.body') }) .then(done) .catch(done) - client.ping().catch(done) + client.ping(err => err && done(err)) }) - it('should propagate context', done => { - agent - .use(traces => { - expect(traces[0][0]).to.have.property('parent_id') - expect(traces[0][0].parent_id).to.not.be.null - }) - .then(done) - .catch(done) + describe('when using a callback', () => { + it('should do automatic instrumentation', done => { + agent + .use(traces => { + expect(traces[0][0]).to.have.property('service', 'test-elasticsearch') + expect(traces[0][0]).to.have.property('resource', 'HEAD /') + expect(traces[0][0]).to.have.property('type', 'elasticsearch') + }) + .then(done) + .catch(done) + + client.ping(err => err && done(err)) + }) - tracer.trace('test', span => { - client.ping() - .then(() => span.finish()) + it('should propagate context', done => { + agent + .use(traces => { + expect(traces[0][0]).to.have.property('parent_id') + expect(traces[0][0].parent_id).to.not.be.null + }) + .then(done) .catch(done) + + tracer.trace('test', span => { + client.ping(() => span.finish()) + }) }) - }) - it('should run resolved promises in the parent context', () => { - return client.ping() - .then(() => { + it('should run the callback in the parent context', done => { + client.ping(error => { expect(tracer.scopeManager().active()).to.be.null + done(error) }) - }) + }) - it('should run rejected promises in the parent context', done => { - client.search({ index: 'invalid' }) - .catch(() => { - expect(tracer.scopeManager().active()).to.be.null - done() + it('should handle errors', done => { + let error + + agent + .use(traces => { + expect(traces[0][0].meta).to.have.property('error.type', error.name) + expect(traces[0][0].meta).to.have.property('error.msg', error.message) + expect(traces[0][0].meta).to.have.property('error.stack', error.stack) + }) + .then(done) + .catch(done) + + client.search({ index: 'invalid' }, err => { + error = err }) + }) + + it('should support aborting the query', () => { + expect(() => { + client.ping(() => {}).abort() + }).not.to.throw() + }) }) - it('should handle errors', done => { - let error + describe('when using a promise', () => { + it('should do automatic instrumentation', done => { + agent + .use(traces => { + expect(traces[0][0]).to.have.property('service', 'test-elasticsearch') + expect(traces[0][0]).to.have.property('resource', 'HEAD /') + expect(traces[0][0]).to.have.property('type', 'elasticsearch') + }) + .then(done) + .catch(done) - agent.use(traces => { - expect(traces[0][0].meta).to.have.property('error.type', error.name) - expect(traces[0][0].meta).to.have.property('error.msg', error.message) - expect(traces[0][0].meta).to.have.property('error.stack', error.stack) + client.ping().catch(done) }) - .then(done) - .catch(done) - client.search({ index: 'invalid' }) - .catch(err => { - error = err + it('should propagate context', done => { + agent + .use(traces => { + expect(traces[0][0]).to.have.property('parent_id') + expect(traces[0][0].parent_id).to.not.be.null + }) + .then(done) + .catch(done) + + tracer.trace('test', span => { + client.ping() + .then(() => span.finish()) + .catch(done) }) - }) + }) - it('should support aborting the query', () => { - expect(() => { - client.ping().abort() - }).not.to.throw() - }) - }) - }) + it('should run resolved promises in the parent context', () => { + return client.ping() + .then(() => { + expect(tracer.scopeManager().active()).to.be.null + }) + }) - describe('with configuration', () => { - let client + it('should run rejected promises in the parent context', done => { + client.search({ index: 'invalid' }) + .catch(() => { + expect(tracer.scopeManager().active()).to.be.null + done() + }) + }) - beforeEach(() => { - return agent.load(plugin, 'elasticsearch', { service: 'test' }) - .then(() => { - elasticsearch = require('elasticsearch') - client = new elasticsearch.Client({ - host: 'localhost:9200' + it('should handle errors', done => { + let error + + agent.use(traces => { + expect(traces[0][0].meta).to.have.property('error.type', error.name) + expect(traces[0][0].meta).to.have.property('error.msg', error.message) + expect(traces[0][0].meta).to.have.property('error.stack', error.stack) }) + .then(done) + .catch(done) + + client.search({ index: 'invalid' }) + .catch(err => { + error = err + }) }) - }) - it('should be configured with the correct values', done => { - agent - .use(traces => { - expect(traces[0][0]).to.have.property('service', 'test') + it('should support aborting the query', () => { + expect(() => { + client.ping().abort() + }).not.to.throw() }) - .then(done) - .catch(done) + }) + }) + + describe('with configuration', () => { + let client - client.ping(err => err && done(err)) + beforeEach(() => { + return agent.load(plugin, 'elasticsearch', { service: 'test' }) + .then(() => { + elasticsearch = require(`./versions/elasticsearch@${version}`).get() + client = new elasticsearch.Client({ + host: 'localhost:9200' + }) + }) + }) + + it('should be configured with the correct values', done => { + agent + .use(traces => { + expect(traces[0][0]).to.have.property('service', 'test') + }) + .then(done) + .catch(done) + + client.ping(err => err && done(err)) + }) }) }) }) diff --git a/test/plugins/express.spec.js b/test/plugins/express.spec.js index 52bd169dcf0..f98be025dd9 100644 --- a/test/plugins/express.spec.js +++ b/test/plugins/express.spec.js @@ -3,602 +3,603 @@ const axios = require('axios') const getPort = require('get-port') const agent = require('./agent') +const plugin = require('../../src/plugins/express') wrapIt() describe('Plugin', () => { - let plugin let tracer let express let appListener describe('express', () => { - beforeEach(() => { - plugin = require('../../src/plugins/express') - tracer = require('../..') - }) - - afterEach(() => { - agent.close() - appListener.close() - }) - - describe('without configuration', () => { + withVersions(plugin, 'express', version => { beforeEach(() => { - return agent.load(plugin, 'express') - .then(() => { - express = require('express') - }) + tracer = require('../..') }) - it('should do automatic instrumentation on app routes', done => { - const app = express() + afterEach(() => { + agent.close() + appListener.close() + }) - app.get('/user', (req, res) => { - res.status(200).send() + describe('without configuration', () => { + beforeEach(() => { + return agent.load(plugin, 'express') + .then(() => { + express = require(`./versions/express@${version}`).get() + }) }) - getPort().then(port => { - agent - .use(traces => { - expect(traces[0][0]).to.have.property('service', 'test') - expect(traces[0][0]).to.have.property('type', 'http') - expect(traces[0][0]).to.have.property('resource', 'GET /user') - expect(traces[0][0].meta).to.have.property('span.kind', 'server') - expect(traces[0][0].meta).to.have.property('http.url', `http://localhost:${port}/user`) - expect(traces[0][0].meta).to.have.property('http.method', 'GET') - expect(traces[0][0].meta).to.have.property('http.status_code', '200') - }) - .then(done) - .catch(done) + it('should do automatic instrumentation on app routes', done => { + const app = express() - appListener = app.listen(port, 'localhost', () => { - axios - .get(`http://localhost:${port}/user`) - .catch(done) + app.get('/user', (req, res) => { + res.status(200).send() }) - }) - }) - - it('should do automatic instrumentation on routers', done => { - const app = express() - const router = express.Router() - router.get('/user/:id', (req, res) => { - res.status(200).send() - }) + getPort().then(port => { + agent + .use(traces => { + expect(traces[0][0]).to.have.property('service', 'test') + expect(traces[0][0]).to.have.property('type', 'http') + expect(traces[0][0]).to.have.property('resource', 'GET /user') + expect(traces[0][0].meta).to.have.property('span.kind', 'server') + expect(traces[0][0].meta).to.have.property('http.url', `http://localhost:${port}/user`) + expect(traces[0][0].meta).to.have.property('http.method', 'GET') + expect(traces[0][0].meta).to.have.property('http.status_code', '200') + }) + .then(done) + .catch(done) - app.use('/app', router) - - getPort().then(port => { - agent - .use(traces => { - expect(traces[0][0]).to.have.property('service', 'test') - expect(traces[0][0]).to.have.property('type', 'http') - expect(traces[0][0]).to.have.property('resource', 'GET /app/user/:id') - expect(traces[0][0].meta).to.have.property('span.kind', 'server') - expect(traces[0][0].meta).to.have.property('http.url', `http://localhost:${port}/app/user/1`) - expect(traces[0][0].meta).to.have.property('http.method', 'GET') - expect(traces[0][0].meta).to.have.property('http.status_code', '200') + appListener = app.listen(port, 'localhost', () => { + axios + .get(`http://localhost:${port}/user`) + .catch(done) }) - .then(done) - .catch(done) - - appListener = app.listen(port, 'localhost', () => { - axios - .get(`http://localhost:${port}/app/user/1`) - .catch(done) }) }) - }) - it('should surround matchers based on regular expressions', done => { - const app = express() - const router = express.Router() + it('should do automatic instrumentation on routers', done => { + const app = express() + const router = express.Router() - router.get(/^\/user\/(\d)$/, (req, res) => { - res.status(200).send() - }) + router.get('/user/:id', (req, res) => { + res.status(200).send() + }) - app.use('/app', router) + app.use('/app', router) + + getPort().then(port => { + agent + .use(traces => { + expect(traces[0][0]).to.have.property('service', 'test') + expect(traces[0][0]).to.have.property('type', 'http') + expect(traces[0][0]).to.have.property('resource', 'GET /app/user/:id') + expect(traces[0][0].meta).to.have.property('span.kind', 'server') + expect(traces[0][0].meta).to.have.property('http.url', `http://localhost:${port}/app/user/1`) + expect(traces[0][0].meta).to.have.property('http.method', 'GET') + expect(traces[0][0].meta).to.have.property('http.status_code', '200') + }) + .then(done) + .catch(done) - getPort().then(port => { - agent - .use(traces => { - expect(traces[0][0]).to.have.property('resource', 'GET /app(/^\\/user\\/(\\d)$/)') + appListener = app.listen(port, 'localhost', () => { + axios + .get(`http://localhost:${port}/app/user/1`) + .catch(done) }) - .then(done) - .catch(done) - - appListener = app.listen(port, 'localhost', () => { - axios - .get(`http://localhost:${port}/app/user/1`) - .catch(done) }) }) - }) - - it('should support a nested array of paths on the router', done => { - const app = express() - const router = express.Router() - router.get([['/user/:id'], '/users/:id'], (req, res, next) => { - res.status(200).send() - }) + it('should surround matchers based on regular expressions', done => { + const app = express() + const router = express.Router() - app.use('/app', router) + router.get(/^\/user\/(\d)$/, (req, res) => { + res.status(200).send() + }) - getPort().then(port => { - agent - .use(traces => { - expect(traces[0][0]).to.have.property('resource', 'GET /app/user/:id') - }) - .then(done) - .catch(done) + app.use('/app', router) - appListener = app.listen(port, 'localhost', () => { - axios - .get(`http://localhost:${port}/app/user/1`) + getPort().then(port => { + agent + .use(traces => { + expect(traces[0][0]).to.have.property('resource', 'GET /app(/^\\/user\\/(\\d)$/)') + }) + .then(done) .catch(done) + + appListener = app.listen(port, 'localhost', () => { + axios + .get(`http://localhost:${port}/app/user/1`) + .catch(done) + }) }) }) - }) - - it('should only keep the last matching path of a middleware stack', done => { - const app = express() - const router = express.Router() - router.use('/', (req, res, next) => next()) - router.use('*', (req, res, next) => next()) - router.use('/bar', (req, res, next) => next()) - router.use('/bar', (req, res, next) => { - res.status(200).send() - }) + it('should support a nested array of paths on the router', done => { + const app = express() + const router = express.Router() - app.use('/', (req, res, next) => next()) - app.use('*', (req, res, next) => next()) - app.use('/foo/bar', (req, res, next) => next()) - app.use('/foo', router) + router.get([['/user/:id'], '/users/:id'], (req, res, next) => { + res.status(200).send() + }) - getPort().then(port => { - agent - .use(traces => { - expect(traces[0][0]).to.have.property('resource', 'GET /foo/bar') - }) - .then(done) - .catch(done) + app.use('/app', router) - appListener = app.listen(port, 'localhost', () => { - axios - .get(`http://localhost:${port}/foo/bar`) + getPort().then(port => { + agent + .use(traces => { + expect(traces[0][0]).to.have.property('resource', 'GET /app/user/:id') + }) + .then(done) .catch(done) + + appListener = app.listen(port, 'localhost', () => { + axios + .get(`http://localhost:${port}/app/user/1`) + .catch(done) + }) }) }) - }) - it('should support asynchronous routers', done => { - const app = express() - const router = express.Router() - - router.get('/user/:id', (req, res) => { - setTimeout(() => res.status(200).send()) - }) + it('should only keep the last matching path of a middleware stack', done => { + const app = express() + const router = express.Router() - app.use('/app', router) + router.use('/', (req, res, next) => next()) + router.use('*', (req, res, next) => next()) + router.use('/bar', (req, res, next) => next()) + router.use('/bar', (req, res, next) => { + res.status(200).send() + }) - getPort().then(port => { - agent - .use(traces => { - expect(traces[0][0]).to.have.property('resource', 'GET /app/user/:id') - }) - .then(done) - .catch(done) + app.use('/', (req, res, next) => next()) + app.use('*', (req, res, next) => next()) + app.use('/foo/bar', (req, res, next) => next()) + app.use('/foo', router) - appListener = app.listen(port, 'localhost', () => { - axios - .get(`http://localhost:${port}/app/user/1`) + getPort().then(port => { + agent + .use(traces => { + expect(traces[0][0]).to.have.property('resource', 'GET /foo/bar') + }) + .then(done) .catch(done) + + appListener = app.listen(port, 'localhost', () => { + axios + .get(`http://localhost:${port}/foo/bar`) + .catch(done) + }) }) }) - }) - it('should support asynchronous middlewares', done => { - const app = express() - const router = express.Router() + it('should support asynchronous routers', done => { + const app = express() + const router = express.Router() - router.use((req, res, next) => setTimeout(() => next())) - router.get('/user/:id', (req, res) => { - res.status(200).send() - }) + router.get('/user/:id', (req, res) => { + setTimeout(() => res.status(200).send()) + }) - app.use('/app', router) + app.use('/app', router) - getPort().then(port => { - agent - .use(traces => { - expect(traces[0][0]).to.have.property('resource', 'GET /app/user/:id') - }) - .then(done) - .catch(done) - - appListener = app.listen(port, 'localhost', () => { - axios - .get(`http://localhost:${port}/app/user/1`) + getPort().then(port => { + agent + .use(traces => { + expect(traces[0][0]).to.have.property('resource', 'GET /app/user/:id') + }) + .then(done) .catch(done) + + appListener = app.listen(port, 'localhost', () => { + axios + .get(`http://localhost:${port}/app/user/1`) + .catch(done) + }) }) }) - }) - it('should support nested applications', done => { - const app = express() - const childApp = express() + it('should support asynchronous middlewares', done => { + const app = express() + const router = express.Router() - childApp.use('/child', (req, res) => { - res.status(200).send() - }) + router.use((req, res, next) => setTimeout(() => next())) + router.get('/user/:id', (req, res) => { + res.status(200).send() + }) - app.use('/parent', childApp) + app.use('/app', router) - getPort().then(port => { - agent - .use(traces => { - expect(traces[0]).to.have.length(1) - expect(traces[0][0]).to.have.property('resource', 'GET /parent/child') - }) - .then(done) - .catch(done) - - appListener = app.listen(port, 'localhost', () => { - axios - .get(`http://localhost:${port}/parent/child`) + getPort().then(port => { + agent + .use(traces => { + expect(traces[0][0]).to.have.property('resource', 'GET /app/user/:id') + }) + .then(done) .catch(done) + + appListener = app.listen(port, 'localhost', () => { + axios + .get(`http://localhost:${port}/app/user/1`) + .catch(done) + }) }) }) - }) - it('should not lose the current path when changing scope', done => { - const app = express() - const router = express.Router() + it('should support nested applications', done => { + const app = express() + const childApp = express() - router.use((req, res, next) => { - const scope = tracer.scopeManager().active() - const child = tracer.startSpan('child', { - childOf: scope.span() + childApp.use('/child', (req, res) => { + res.status(200).send() }) - tracer.scopeManager().activate(child) + app.use('/parent', childApp) - child.finish() - - next() - }) + getPort().then(port => { + agent + .use(traces => { + expect(traces[0]).to.have.length(1) + expect(traces[0][0]).to.have.property('resource', 'GET /parent/child') + }) + .then(done) + .catch(done) - router.get('/user/:id', (req, res) => { - res.status(200).send() + appListener = app.listen(port, 'localhost', () => { + axios + .get(`http://localhost:${port}/parent/child`) + .catch(done) + }) + }) }) - app.use('/app', router) + it('should not lose the current path when changing scope', done => { + const app = express() + const router = express.Router() - getPort().then(port => { - agent - .use(traces => { - expect(traces[0][1]).to.have.property('resource', 'GET /app/user/:id') + router.use((req, res, next) => { + const scope = tracer.scopeManager().active() + const child = tracer.startSpan('child', { + childOf: scope.span() }) - .then(done) - .catch(done) - appListener = app.listen(port, 'localhost', () => { - axios - .get(`http://localhost:${port}/app/user/123`) - .catch(done) + tracer.scopeManager().activate(child) + + child.finish() + + next() }) - }) - }) - it('should not lose the current path without a scope', done => { - const app = express() - const router = express.Router() + router.get('/user/:id', (req, res) => { + res.status(200).send() + }) - router.use((req, res, next) => { - const scope = tracer.scopeManager().active() + app.use('/app', router) - scope.close() + getPort().then(port => { + agent + .use(traces => { + expect(traces[0][1]).to.have.property('resource', 'GET /app/user/:id') + }) + .then(done) + .catch(done) - next() + appListener = app.listen(port, 'localhost', () => { + axios + .get(`http://localhost:${port}/app/user/123`) + .catch(done) + }) + }) }) - router.get('/user/:id', (req, res) => { - res.status(200).send() - }) + it('should not lose the current path without a scope', done => { + const app = express() + const router = express.Router() - app.use('/app', router) + router.use((req, res, next) => { + const scope = tracer.scopeManager().active() - getPort().then(port => { - agent - .use(traces => { - expect(traces[0][0]).to.have.property('resource', 'GET /app/user/:id') - }) - .then(done) - .catch(done) + scope.close() - appListener = app.listen(port, 'localhost', () => { - axios - .get(`http://localhost:${port}/app/user/123`) - .catch(done) + next() }) - }) - }) - it('should not lose the current path on error', done => { - const app = express() + router.get('/user/:id', (req, res) => { + res.status(200).send() + }) - app.get('/app', (req, res, next) => { - next(new Error()) - }) + app.use('/app', router) - app.use((error, req, res, next) => { - res.status(200).send(error.message) - }) + getPort().then(port => { + agent + .use(traces => { + expect(traces[0][0]).to.have.property('resource', 'GET /app/user/:id') + }) + .then(done) + .catch(done) - getPort().then(port => { - agent - .use(traces => { - expect(traces[0][0]).to.have.property('resource', 'GET /app') + appListener = app.listen(port, 'localhost', () => { + axios + .get(`http://localhost:${port}/app/user/123`) + .catch(done) }) - .then(done) - .catch(done) - - appListener = app.listen(port, 'localhost', () => { - axios - .get(`http://localhost:${port}/app`) - .catch(done) }) }) - }) - - it('should not leak the current scope to other requests when using a task queue', done => { - const app = express() - let handler + it('should not lose the current path on error', done => { + const app = express() - const interval = setInterval(() => { - if (handler) { - handler() + app.get('/app', (req, res, next) => { + next(new Error()) + }) - clearInterval(interval) + app.use((error, req, res, next) => { + res.status(200).send(error.message) + }) - expect(tracer.scopeManager().active()).to.be.null + getPort().then(port => { + agent + .use(traces => { + expect(traces[0][0]).to.have.property('resource', 'GET /app') + }) + .then(done) + .catch(done) - done() - } + appListener = app.listen(port, 'localhost', () => { + axios + .get(`http://localhost:${port}/app`) + .catch(done) + }) + }) }) - app.use((req, res, next) => { - handler = next - }) + it('should not leak the current scope to other requests when using a task queue', done => { + const app = express() - app.get('/app', (req, res) => { - res.status(200).send() - }) + let handler - getPort().then(port => { - appListener = app.listen(port, 'localhost', () => { - axios - .get(`http://localhost:${port}/app`) - .catch(done) + const interval = setInterval(() => { + if (handler) { + handler() + + clearInterval(interval) + + expect(tracer.scopeManager().active()).to.be.null + + done() + } }) - }) - }) - it('should fallback to the the verb if a path pattern could not be found', done => { - const app = express() + app.use((req, res, next) => { + handler = next + }) - app.use((req, res, next) => res.status(200).send()) + app.get('/app', (req, res) => { + res.status(200).send() + }) - getPort().then(port => { - agent - .use(traces => { - expect(traces[0][0]).to.have.property('resource', 'GET') + getPort().then(port => { + appListener = app.listen(port, 'localhost', () => { + axios + .get(`http://localhost:${port}/app`) + .catch(done) }) - .then(done) - .catch(done) - - appListener = app.listen(port, 'localhost', () => { - axios - .get(`http://localhost:${port}/app`) - .catch(done) }) }) - }) - it('should reactivate the span when the active scope is closed', done => { - const app = express() + it('should fallback to the the verb if a path pattern could not be found', done => { + const app = express() - let span - let scope + app.use((req, res, next) => res.status(200).send()) - app.use((req, res, next) => { - scope = tracer.scopeManager().active() - span = scope.span() - scope.close() - next() - }) + getPort().then(port => { + agent + .use(traces => { + expect(traces[0][0]).to.have.property('resource', 'GET') + }) + .then(done) + .catch(done) - app.get('/user', (req, res) => { - const scope = tracer.scopeManager().active() + appListener = app.listen(port, 'localhost', () => { + axios + .get(`http://localhost:${port}/app`) + .catch(done) + }) + }) + }) - res.status(200).send() + it('should reactivate the span when the active scope is closed', done => { + const app = express() - try { - expect(scope).to.not.be.null - expect(scope.span()).to.equal(span) - done() - } catch (e) { - done(e) - } - }) + let span + let scope - getPort().then(port => { - appListener = app.listen(port, 'localhost', () => { - axios.get(`http://localhost:${port}/user`) - .catch(done) + app.use((req, res, next) => { + scope = tracer.scopeManager().active() + span = scope.span() + scope.close() + next() }) - }) - }) - it('should only include paths for routes that matched', done => { - const app = express() - const router = express.Router() + app.get('/user', (req, res) => { + const scope = tracer.scopeManager().active() - router.use('/baz', (req, res, next) => next()) - router.get('/user/:id', (req, res) => { - res.status(200).send() - }) - router.use('/qux', (req, res, next) => next()) + res.status(200).send() - app.use('/foo', (req, res, next) => next()) - app.use('/app', router) - app.use('/bar', (req, res, next) => next()) + try { + expect(scope).to.not.be.null + expect(scope.span()).to.equal(span) + done() + } catch (e) { + done(e) + } + }) - getPort().then(port => { - agent - .use(traces => { - expect(traces[0][0]).to.have.property('resource', 'GET /app/user/:id') + getPort().then(port => { + appListener = app.listen(port, 'localhost', () => { + axios.get(`http://localhost:${port}/user`) + .catch(done) }) - .then(done) - .catch(done) - - appListener = app.listen(port, 'localhost', () => { - axios.get(`http://localhost:${port}/app/user/123`) - .then(res => { - expect(res.status).to.equal(200) - expect(res.data).to.be.empty - done() - }) - .catch(done) }) }) - }) - it('should extract its parent span from the headers', done => { - const app = express() + it('should only include paths for routes that matched', done => { + const app = express() + const router = express.Router() - app.get('/user', (req, res) => { - res.status(200).send() - }) + router.use('/baz', (req, res, next) => next()) + router.get('/user/:id', (req, res) => { + res.status(200).send() + }) + router.use('/qux', (req, res, next) => next()) + + app.use('/foo', (req, res, next) => next()) + app.use('/app', router) + app.use('/bar', (req, res, next) => next()) - getPort().then(port => { - agent.use(traces => { - expect(traces[0][0].trace_id.toString()).to.equal('1234') - expect(traces[0][0].parent_id.toString()).to.equal('5678') - }) - .then(done) - .catch(done) - - appListener = app.listen(port, 'localhost', () => { - axios - .get(`http://localhost:${port}/user`, { - headers: { - 'x-datadog-trace-id': '1234', - 'x-datadog-parent-id': '5678', - 'ot-baggage-foo': 'bar' - } + getPort().then(port => { + agent + .use(traces => { + expect(traces[0][0]).to.have.property('resource', 'GET /app/user/:id') }) + .then(done) .catch(done) + + appListener = app.listen(port, 'localhost', () => { + axios.get(`http://localhost:${port}/app/user/123`) + .then(res => { + expect(res.status).to.equal(200) + expect(res.data).to.be.empty + done() + }) + .catch(done) + }) }) }) - }) - it('should handle errors', done => { - const app = express() + it('should extract its parent span from the headers', done => { + const app = express() - app.use((req, res, next) => { - next() - }) + app.get('/user', (req, res) => { + res.status(200).send() + }) + + getPort().then(port => { + agent.use(traces => { + expect(traces[0][0].trace_id.toString()).to.equal('1234') + expect(traces[0][0].parent_id.toString()).to.equal('5678') + }) + .then(done) + .catch(done) - app.get('/user', (req, res) => { - res.status(500).send() + appListener = app.listen(port, 'localhost', () => { + axios + .get(`http://localhost:${port}/user`, { + headers: { + 'x-datadog-trace-id': '1234', + 'x-datadog-parent-id': '5678', + 'ot-baggage-foo': 'bar' + } + }) + .catch(done) + }) + }) }) - getPort().then(port => { - agent.use(traces => { - expect(traces[0][0]).to.have.property('error', 1) - expect(traces[0][0]).to.have.property('resource', 'GET /user') - expect(traces[0][0].meta).to.have.property('http.status_code', '500') + it('should handle errors', done => { + const app = express() - done() + app.use((req, res, next) => { + next() }) - appListener = app.listen(port, 'localhost', () => { - axios - .get(`http://localhost:${port}/user`, { - validateStatus: status => status === 500 - }) - .catch(done) + app.get('/user', (req, res) => { + res.status(500).send() }) - }) - }) - }) - describe('with configuration', () => { - let config + getPort().then(port => { + agent.use(traces => { + expect(traces[0][0]).to.have.property('error', 1) + expect(traces[0][0]).to.have.property('resource', 'GET /user') + expect(traces[0][0].meta).to.have.property('http.status_code', '500') - beforeEach(() => { - config = { - service: 'custom', - validateStatus: code => code < 400 - } + done() + }) - return agent.load(plugin, 'express', config) - .then(() => { - express = require('express') + appListener = app.listen(port, 'localhost', () => { + axios + .get(`http://localhost:${port}/user`, { + validateStatus: status => status === 500 + }) + .catch(done) + }) }) + }) }) - it('should be configured with the correct service name', done => { - const app = express() + describe('with configuration', () => { + let config - app.get('/user', (req, res) => { - res.status(200).send() - }) + beforeEach(() => { + config = { + service: 'custom', + validateStatus: code => code < 400 + } - getPort().then(port => { - agent - .use(traces => { - expect(traces[0][0]).to.have.property('service', 'custom') + return agent.load(plugin, 'express', config) + .then(() => { + express = require(`./versions/express@${version}`).get() }) - .then(done) - .catch(done) + }) - appListener = app.listen(port, 'localhost', () => { - axios - .get(`http://localhost:${port}/user`) - .catch(done) + it('should be configured with the correct service name', done => { + const app = express() + + app.get('/user', (req, res) => { + res.status(200).send() }) - }) - }) - it('should be configured with the correct status code validator', done => { - const app = express() + getPort().then(port => { + agent + .use(traces => { + expect(traces[0][0]).to.have.property('service', 'custom') + }) + .then(done) + .catch(done) - app.get('/user', (req, res) => { - res.status(400).send() + appListener = app.listen(port, 'localhost', () => { + axios + .get(`http://localhost:${port}/user`) + .catch(done) + }) + }) }) - getPort().then(port => { - agent - .use(traces => { - expect(traces[0][0]).to.have.property('error', 1) - }) - .then(done) - .catch(done) + it('should be configured with the correct status code validator', done => { + const app = express() + + app.get('/user', (req, res) => { + res.status(400).send() + }) - appListener = app.listen(port, 'localhost', () => { - axios - .get(`http://localhost:${port}/user`, { - validateStatus: status => status === 400 + getPort().then(port => { + agent + .use(traces => { + expect(traces[0][0]).to.have.property('error', 1) }) + .then(done) .catch(done) + + appListener = app.listen(port, 'localhost', () => { + axios + .get(`http://localhost:${port}/user`, { + validateStatus: status => status === 400 + }) + .catch(done) + }) }) }) }) diff --git a/test/plugins/graphql.spec.js b/test/plugins/graphql.spec.js index f6a77411cce..6876048dda9 100644 --- a/test/plugins/graphql.spec.js +++ b/test/plugins/graphql.spec.js @@ -1,11 +1,11 @@ 'use strict' const agent = require('./agent') +const plugin = require('../../src/plugins/graphql') wrapIt() describe('Plugin', () => { - let plugin let tracer let graphql let schema @@ -135,561 +135,565 @@ describe('Plugin', () => { } describe('graphql', () => { - beforeEach(() => { - plugin = require('../../src/plugins/graphql') - tracer = require('../..') - - sort = spans => spans.sort((a, b) => a.start.toString() > b.start.toString() ? 1 : -1) - }) + withVersions(plugin, 'graphql', version => { + beforeEach(() => { + tracer = require('../..') - afterEach(() => { - agent.close() - }) + sort = spans => spans.sort((a, b) => a.start.toString() > b.start.toString() ? 1 : -1) + }) - describe('without configuration', () => { - beforeEach(() => { - return agent.load(plugin, 'graphql') - .then(() => { - graphql = require('graphql') - buildSchema() - }) + afterEach(() => { + agent.close() + agent.wipe() }) - it('should instrument operations', done => { - const source = `query MyQuery { hello(name: "world") }` + describe('without configuration', () => { + beforeEach(() => { + return agent.load(plugin, 'graphql') + .then(() => { + graphql = require(`./versions/graphql@${version}`).get() + buildSchema() + }) + }) - agent - .use(traces => { - const spans = sort(traces[0]) + it('should instrument operations', done => { + const source = `query MyQuery { hello(name: "world") }` - expect(spans).to.have.length(3) - expect(spans[0]).to.have.property('service', 'test-graphql') - expect(spans[0]).to.have.property('name', 'graphql.query') - expect(spans[0]).to.have.property('resource', 'query MyQuery') - expect(spans[0].meta).to.have.property('graphql.document', source) - }) - .then(done) - .catch(done) + agent + .use(traces => { + const spans = sort(traces[0]) - graphql.graphql(schema, source).catch(done) - }) + expect(spans).to.have.length(3) + expect(spans[0]).to.have.property('service', 'test-graphql') + expect(spans[0]).to.have.property('name', 'graphql.query') + expect(spans[0]).to.have.property('resource', 'query MyQuery') + expect(spans[0].meta).to.have.property('graphql.document', source) + }) + .then(done) + .catch(done) - it('should instrument fields', done => { - const source = `{ hello(name: "world") }` + graphql.graphql(schema, source).catch(done) + }) - agent - .use(traces => { - const spans = sort(traces[0]) + it('should instrument fields', done => { + const source = `{ hello(name: "world") }` - expect(spans).to.have.length(3) - expect(spans[1]).to.have.property('service', 'test-graphql') - expect(spans[1]).to.have.property('name', 'graphql.field') - expect(spans[1]).to.have.property('resource', 'hello') - }) - .then(done) - .catch(done) + agent + .use(traces => { + const spans = sort(traces[0]) - graphql.graphql(schema, source).catch(done) - }) + expect(spans).to.have.length(3) + expect(spans[1]).to.have.property('service', 'test-graphql') + expect(spans[1]).to.have.property('name', 'graphql.field') + expect(spans[1]).to.have.property('resource', 'hello') + }) + .then(done) + .catch(done) - it('should instrument schema resolvers', done => { - const source = `{ hello(name: "world") }` + graphql.graphql(schema, source).catch(done) + }) - agent - .use(traces => { - const spans = sort(traces[0]) + it('should instrument schema resolvers', done => { + const source = `{ hello(name: "world") }` - expect(spans).to.have.length(3) - expect(spans[2]).to.have.property('service', 'test-graphql') - expect(spans[2]).to.have.property('name', 'graphql.resolve') - expect(spans[2]).to.have.property('resource', 'hello') - }) - .then(done) - .catch(done) + agent + .use(traces => { + const spans = sort(traces[0]) - graphql.graphql(schema, source).catch(done) - }) + expect(spans).to.have.length(3) + expect(spans[2]).to.have.property('service', 'test-graphql') + expect(spans[2]).to.have.property('name', 'graphql.resolve') + expect(spans[2]).to.have.property('resource', 'hello') + }) + .then(done) + .catch(done) - it('should instrument nested field resolvers', done => { - const source = ` - { - human { - name - address { - civicNumber - street + graphql.graphql(schema, source).catch(done) + }) + + it('should instrument nested field resolvers', done => { + const source = ` + { + human { + name + address { + civicNumber + street + } } } - } - ` - - agent - .use(traces => { - const spans = sort(traces[0]) - - expect(spans).to.have.length(11) - - const query = spans[0] - const humanField = spans[1] - const humanResolve = spans[2] - const humanNameField = spans[3] - const humanNameResolve = spans[4] - const addressField = spans[5] - const addressResolve = spans[6] - const addressCivicNumberField = spans[7] - const addressCivicNumberResolve = spans[8] - const addressStreetField = spans[9] - const addressStreetResolve = spans[10] - - expect(query).to.have.property('name', 'graphql.query') - expect(query).to.have.property('resource', 'query') - - expect(humanField).to.have.property('name', 'graphql.field') - expect(humanField).to.have.property('resource', 'human') - expect(humanField.parent_id.toString()).to.equal(query.span_id.toString()) - expect(humanField.duration.toNumber()).to.be.lte(query.duration.toNumber()) - - expect(humanResolve).to.have.property('name', 'graphql.resolve') - expect(humanResolve).to.have.property('resource', 'human') - expect(humanResolve.parent_id.toString()).to.equal(humanField.span_id.toString()) - expect(humanResolve.duration.toNumber()).to.be.lte(humanField.duration.toNumber()) - - expect(humanNameField).to.have.property('name', 'graphql.field') - expect(humanNameField).to.have.property('resource', 'human.name') - expect(humanNameField.parent_id.toString()).to.equal(humanField.span_id.toString()) - - expect(humanNameResolve).to.have.property('name', 'graphql.resolve') - expect(humanNameResolve).to.have.property('resource', 'human.name') - expect(humanNameResolve.parent_id.toString()).to.equal(humanNameField.span_id.toString()) - - expect(addressField).to.have.property('name', 'graphql.field') - expect(addressField).to.have.property('resource', 'human.address') - expect(addressField.parent_id.toString()).to.equal(humanField.span_id.toString()) - expect(addressField.duration.toNumber()).to.be.lte(humanField.duration.toNumber()) - - expect(addressResolve).to.have.property('name', 'graphql.resolve') - expect(addressResolve).to.have.property('resource', 'human.address') - expect(addressResolve.parent_id.toString()).to.equal(addressField.span_id.toString()) - expect(addressResolve.duration.toNumber()).to.be.lte(addressField.duration.toNumber()) - - expect(addressCivicNumberField).to.have.property('name', 'graphql.field') - expect(addressCivicNumberField).to.have.property('resource', 'human.address.civicNumber') - expect(addressCivicNumberField.parent_id.toString()).to.equal(addressField.span_id.toString()) - expect(addressCivicNumberField.duration.toNumber()).to.be.lte(addressField.duration.toNumber()) - - expect(addressCivicNumberResolve).to.have.property('name', 'graphql.resolve') - expect(addressCivicNumberResolve).to.have.property('resource', 'human.address.civicNumber') - expect(addressCivicNumberResolve.parent_id.toString()).to.equal(addressCivicNumberField.span_id.toString()) - expect(addressCivicNumberResolve.duration.toNumber()).to.be.lte(addressCivicNumberField.duration.toNumber()) - - expect(addressStreetField).to.have.property('name', 'graphql.field') - expect(addressStreetField).to.have.property('resource', 'human.address.street') - expect(addressStreetField.parent_id.toString()).to.equal(addressField.span_id.toString()) - expect(addressStreetField.duration.toNumber()).to.be.lte(addressField.duration.toNumber()) - - expect(addressStreetResolve).to.have.property('name', 'graphql.resolve') - expect(addressStreetResolve).to.have.property('resource', 'human.address.street') - expect(addressStreetResolve.parent_id.toString()).to.equal(addressStreetField.span_id.toString()) - expect(addressStreetResolve.duration.toNumber()).to.be.lte(addressStreetField.duration.toNumber()) - }) - .then(done) - .catch(done) - - graphql.graphql(schema, source).catch(done) - }) + ` - it('should instrument list field resolvers', done => { - const source = `{ friends { name } }` - - agent - .use(traces => { - const spans = sort(traces[0]) + agent + .use(traces => { + const spans = sort(traces[0]) - expect(spans).to.have.length(7) + expect(spans).to.have.length(11) + + const query = spans[0] + const humanField = spans[1] + const humanResolve = spans[2] + const humanNameField = spans[3] + const humanNameResolve = spans[4] + const addressField = spans[5] + const addressResolve = spans[6] + const addressCivicNumberField = spans[7] + const addressCivicNumberResolve = spans[8] + const addressStreetField = spans[9] + const addressStreetResolve = spans[10] + + expect(query).to.have.property('name', 'graphql.query') + expect(query).to.have.property('resource', 'query') + + expect(humanField).to.have.property('name', 'graphql.field') + expect(humanField).to.have.property('resource', 'human') + expect(humanField.parent_id.toString()).to.equal(query.span_id.toString()) + expect(humanField.duration.toNumber()).to.be.lte(query.duration.toNumber()) + + expect(humanResolve).to.have.property('name', 'graphql.resolve') + expect(humanResolve).to.have.property('resource', 'human') + expect(humanResolve.parent_id.toString()).to.equal(humanField.span_id.toString()) + expect(humanResolve.duration.toNumber()).to.be.lte(humanField.duration.toNumber()) + + expect(humanNameField).to.have.property('name', 'graphql.field') + expect(humanNameField).to.have.property('resource', 'human.name') + expect(humanNameField.parent_id.toString()).to.equal(humanField.span_id.toString()) + + expect(humanNameResolve).to.have.property('name', 'graphql.resolve') + expect(humanNameResolve).to.have.property('resource', 'human.name') + expect(humanNameResolve.parent_id.toString()).to.equal(humanNameField.span_id.toString()) + + expect(addressField).to.have.property('name', 'graphql.field') + expect(addressField).to.have.property('resource', 'human.address') + expect(addressField.parent_id.toString()).to.equal(humanField.span_id.toString()) + expect(addressField.duration.toNumber()).to.be.lte(humanField.duration.toNumber()) + + expect(addressResolve).to.have.property('name', 'graphql.resolve') + expect(addressResolve).to.have.property('resource', 'human.address') + expect(addressResolve.parent_id.toString()).to.equal(addressField.span_id.toString()) + expect(addressResolve.duration.toNumber()).to.be.lte(addressField.duration.toNumber()) + + expect(addressCivicNumberField).to.have.property('name', 'graphql.field') + expect(addressCivicNumberField).to.have.property('resource', 'human.address.civicNumber') + expect(addressCivicNumberField.parent_id.toString()).to.equal(addressField.span_id.toString()) + expect(addressCivicNumberField.duration.toNumber()).to.be.lte(addressField.duration.toNumber()) + + expect(addressCivicNumberResolve).to.have.property('name', 'graphql.resolve') + expect(addressCivicNumberResolve).to.have.property('resource', 'human.address.civicNumber') + expect(addressCivicNumberResolve.parent_id.toString()) + .to.equal(addressCivicNumberField.span_id.toString()) + expect(addressCivicNumberResolve.duration.toNumber()) + .to.be.lte(addressCivicNumberField.duration.toNumber()) + + expect(addressStreetField).to.have.property('name', 'graphql.field') + expect(addressStreetField).to.have.property('resource', 'human.address.street') + expect(addressStreetField.parent_id.toString()).to.equal(addressField.span_id.toString()) + expect(addressStreetField.duration.toNumber()).to.be.lte(addressField.duration.toNumber()) + + expect(addressStreetResolve).to.have.property('name', 'graphql.resolve') + expect(addressStreetResolve).to.have.property('resource', 'human.address.street') + expect(addressStreetResolve.parent_id.toString()).to.equal(addressStreetField.span_id.toString()) + expect(addressStreetResolve.duration.toNumber()).to.be.lte(addressStreetField.duration.toNumber()) + }) + .then(done) + .catch(done) - const query = spans[0] - const friendsField = spans[1] - const friendsResolve = spans[2] - const friend0NameField = spans[3] - const friend0NameResolve = spans[4] - const friend1NameField = spans[5] - const friend1NameResolve = spans[6] + graphql.graphql(schema, source).catch(done) + }) - expect(query).to.have.property('name', 'graphql.query') - expect(query).to.have.property('resource', 'query') + it('should instrument list field resolvers', done => { + const source = `{ friends { name } }` - expect(friendsField).to.have.property('name', 'graphql.field') - expect(friendsField).to.have.property('resource', 'friends') - expect(friendsField.parent_id.toString()).to.equal(query.span_id.toString()) + agent + .use(traces => { + const spans = sort(traces[0]) - expect(friendsResolve).to.have.property('name', 'graphql.resolve') - expect(friendsResolve).to.have.property('resource', 'friends') - expect(friendsResolve.parent_id.toString()).to.equal(friendsField.span_id.toString()) + expect(spans).to.have.length(7) - expect(friend0NameField).to.have.property('name', 'graphql.field') - expect(friend0NameField).to.have.property('resource', 'friends.0.name') - expect(friend0NameField.parent_id.toString()).to.equal(friendsField.span_id.toString()) + const query = spans[0] + const friendsField = spans[1] + const friendsResolve = spans[2] + const friend0NameField = spans[3] + const friend0NameResolve = spans[4] + const friend1NameField = spans[5] + const friend1NameResolve = spans[6] - expect(friend0NameResolve).to.have.property('name', 'graphql.resolve') - expect(friend0NameResolve).to.have.property('resource', 'friends.0.name') - expect(friend0NameResolve.parent_id.toString()).to.equal(friend0NameField.span_id.toString()) + expect(query).to.have.property('name', 'graphql.query') + expect(query).to.have.property('resource', 'query') - expect(friend1NameField).to.have.property('name', 'graphql.field') - expect(friend1NameField).to.have.property('resource', 'friends.1.name') - expect(friend1NameField.parent_id.toString()).to.equal(friendsField.span_id.toString()) + expect(friendsField).to.have.property('name', 'graphql.field') + expect(friendsField).to.have.property('resource', 'friends') + expect(friendsField.parent_id.toString()).to.equal(query.span_id.toString()) - expect(friend1NameResolve).to.have.property('name', 'graphql.resolve') - expect(friend1NameResolve).to.have.property('resource', 'friends.1.name') - expect(friend1NameResolve.parent_id.toString()).to.equal(friend1NameField.span_id.toString()) - }) - .then(done) - .catch(done) + expect(friendsResolve).to.have.property('name', 'graphql.resolve') + expect(friendsResolve).to.have.property('resource', 'friends') + expect(friendsResolve.parent_id.toString()).to.equal(friendsField.span_id.toString()) - graphql.graphql(schema, source).catch(done) - }) + expect(friend0NameField).to.have.property('name', 'graphql.field') + expect(friend0NameField).to.have.property('resource', 'friends.0.name') + expect(friend0NameField.parent_id.toString()).to.equal(friendsField.span_id.toString()) - it('should instrument mutations', done => { - const source = `mutation { human { name } }` + expect(friend0NameResolve).to.have.property('name', 'graphql.resolve') + expect(friend0NameResolve).to.have.property('resource', 'friends.0.name') + expect(friend0NameResolve.parent_id.toString()).to.equal(friend0NameField.span_id.toString()) - agent - .use(traces => { - const spans = sort(traces[0]) + expect(friend1NameField).to.have.property('name', 'graphql.field') + expect(friend1NameField).to.have.property('resource', 'friends.1.name') + expect(friend1NameField.parent_id.toString()).to.equal(friendsField.span_id.toString()) - expect(spans).to.have.length(5) - expect(spans[0]).to.have.property('name', 'graphql.mutation') - }) - .then(done) - .catch(done) + expect(friend1NameResolve).to.have.property('name', 'graphql.resolve') + expect(friend1NameResolve).to.have.property('resource', 'friends.1.name') + expect(friend1NameResolve.parent_id.toString()).to.equal(friend1NameField.span_id.toString()) + }) + .then(done) + .catch(done) - graphql.graphql(schema, source).catch(done) - }) + graphql.graphql(schema, source).catch(done) + }) - it('should handle a circular schema', done => { - const source = `{ human { pets { owner { name } } } }` + it('should instrument mutations', done => { + const source = `mutation { human { name } }` - graphql.graphql(schema, source) - .then((result) => { - expect(result.data.human.pets[0].owner.name).to.equal('test') + agent + .use(traces => { + const spans = sort(traces[0]) - done() - }) - .catch(done) - }) + expect(spans).to.have.length(5) + expect(spans[0]).to.have.property('name', 'graphql.mutation') + }) + .then(done) + .catch(done) - it('should ignore the default field resolver', done => { - const schema = graphql.buildSchema(` - type Query { - hello: String - } - `) + graphql.graphql(schema, source).catch(done) + }) - const source = `{ hello }` + it('should handle a circular schema', done => { + const source = `{ human { pets { owner { name } } } }` - agent - .use(traces => { - const spans = sort(traces[0]) + graphql.graphql(schema, source) + .then((result) => { + expect(result.data.human.pets[0].owner.name).to.equal('test') - expect(spans).to.have.length(1) - expect(spans[0]).to.have.property('resource', 'query') - }) - .then(done) - .catch(done) + done() + }) + .catch(done) + }) - graphql.graphql(schema, source, { hello: 'world' }).catch(done) - }) + it('should ignore the default field resolver', done => { + const schema = graphql.buildSchema(` + type Query { + hello: String + } + `) - it('should ignore the execution field resolver without a rootValue resolver', done => { - const schema = graphql.buildSchema(` - type Query { - hello: String - } - `) + const source = `{ hello }` - const source = `{ hello }` + agent + .use(traces => { + const spans = sort(traces[0]) - const rootValue = { hello: 'world' } + expect(spans).to.have.length(1) + expect(spans[0]).to.have.property('resource', 'query') + }) + .then(done) + .catch(done) - const fieldResolver = (source, args, contextValue, info) => { - return source[info.fieldName] - } + graphql.graphql(schema, source, { hello: 'world' }).catch(done) + }) - agent - .use(traces => { - const spans = sort(traces[0]) + it('should ignore the execution field resolver without a rootValue resolver', done => { + const schema = graphql.buildSchema(` + type Query { + hello: String + } + `) - expect(spans).to.have.length(1) - expect(spans[0]).to.have.property('resource', 'query') - }) - .then(done) - .catch(done) + const source = `{ hello }` - graphql.graphql({ schema, source, rootValue, fieldResolver }).catch(done) - }) + const rootValue = { hello: 'world' } - it('should not instrument schema resolvers multiple times', done => { - const source = `{ hello(name: "world") }` + const fieldResolver = (source, args, contextValue, info) => { + return source[info.fieldName] + } - agent.use(() => { // skip first call agent .use(traces => { const spans = sort(traces[0]) - expect(spans).to.have.length(3) + expect(spans).to.have.length(1) + expect(spans[0]).to.have.property('resource', 'query') }) .then(done) .catch(done) - graphql.graphql(schema, source).catch(done) + graphql.graphql({ schema, source, rootValue, fieldResolver }).catch(done) }) - graphql.graphql(schema, source).catch(done) - }) + it('should not instrument schema resolvers multiple times', done => { + const source = `{ hello(name: "world") }` - it('should run rootValue resolvers in the current context', done => { - const schema = graphql.buildSchema(` - type Query { - hello: String - } - `) + agent.use(() => { // skip first call + agent + .use(traces => { + const spans = sort(traces[0]) - const source = `{ hello }` + expect(spans).to.have.length(3) + }) + .then(done) + .catch(done) - const rootValue = { - hello () { - expect(tracer.scopeManager().active()).to.not.be.null - done() - } - } + graphql.graphql(schema, source).catch(done) + }) - graphql.graphql({ schema, source, rootValue }).catch(done) - }) + graphql.graphql(schema, source).catch(done) + }) - it('should run returned promise in the parent context', () => { - const schema = graphql.buildSchema(` - type Query { - hello: String - } - `) + it('should run rootValue resolvers in the current context', done => { + const schema = graphql.buildSchema(` + type Query { + hello: String + } + `) - const source = `{ hello }` + const source = `{ hello }` - const rootValue = { - hello () { - return Promise.resolve('test') + const rootValue = { + hello () { + expect(tracer.scopeManager().active()).to.not.be.null + done() + } } - } - - const span = tracer.startSpan('test') - const scope = tracer.scopeManager().activate(span) - return graphql.graphql({ schema, source, rootValue }) - .then(value => { - expect(value).to.have.nested.property('data.hello', 'test') - expect(tracer.scopeManager().active()).to.equal(scope) - }) - }) + graphql.graphql({ schema, source, rootValue }).catch(done) + }) - it('should handle unsupported operations', () => { - const query = `query MyQuery { hello(name: "world") }` - const subscription = `subscription { human { name } }` + it('should run returned promise in the parent context', () => { + const schema = graphql.buildSchema(` + type Query { + hello: String + } + `) - return graphql.graphql(schema, query) - .then(() => graphql.graphql(schema, subscription)) - .then(result => { - expect(result).to.not.have.property('errors') - }) - }) + const source = `{ hello }` - it('should handle calling low level APIs directly', done => { - const source = `query MyQuery { hello(name: "world") }` - const document = graphql.parse(source) + const rootValue = { + hello () { + return Promise.resolve('test') + } + } - agent - .use(traces => { - const spans = sort(traces[0]) + const span = tracer.startSpan('test') + const scope = tracer.scopeManager().activate(span) - expect(spans).to.have.length(3) - expect(spans[0]).to.have.property('service', 'test-graphql') - expect(spans[0]).to.have.property('name', 'graphql.query') - expect(spans[0]).to.have.property('resource', 'query MyQuery') - expect(spans[0].meta).to.have.property('graphql.document', source) - }) - .then(done) - .catch(done) + return graphql.graphql({ schema, source, rootValue }) + .then(value => { + expect(value).to.have.nested.property('data.hello', 'test') + expect(tracer.scopeManager().active()).to.equal(scope) + }) + }) - graphql.execute({ schema, document }) - }) + it('should handle unsupported operations', () => { + const query = `query MyQuery { hello(name: "world") }` + const subscription = `subscription { human { name } }` - it('should handle Source objects', done => { - const source = `query MyQuery { hello(name: "world") }` - const document = graphql.parse(new graphql.Source(source)) + return graphql.graphql(schema, query) + .then(() => graphql.graphql(schema, subscription)) + .then(result => { + expect(result).to.not.have.property('errors') + }) + }) - agent - .use(traces => { - const spans = sort(traces[0]) + it('should handle calling low level APIs directly', done => { + const source = `query MyQuery { hello(name: "world") }` + const document = graphql.parse(source) - expect(spans).to.have.length(3) - expect(spans[0]).to.have.property('service', 'test-graphql') - expect(spans[0]).to.have.property('name', 'graphql.query') - expect(spans[0]).to.have.property('resource', 'query MyQuery') - expect(spans[0].meta).to.have.property('graphql.document', source) - }) - .then(done) - .catch(done) + agent + .use(traces => { + const spans = sort(traces[0]) - graphql.execute(schema, document) - }) + expect(spans).to.have.length(3) + expect(spans[0]).to.have.property('service', 'test-graphql') + expect(spans[0]).to.have.property('name', 'graphql.query') + expect(spans[0]).to.have.property('resource', 'query MyQuery') + expect(spans[0].meta).to.have.property('graphql.document', source) + }) + .then(done) + .catch(done) - it('should handle executor exceptions', done => { - schema = new graphql.GraphQLSchema({ - query: new graphql.GraphQLObjectType({ - name: 'RootQueryType', - fields: { - hello: {} - } - }) + graphql.execute({ schema, document }) }) - const source = `{ hello }` - const document = graphql.parse(source) + it('should handle Source objects', done => { + const source = `query MyQuery { hello(name: "world") }` + const document = graphql.parse(new graphql.Source(source)) - let error + agent + .use(traces => { + const spans = sort(traces[0]) - agent - .use(traces => { - const spans = sort(traces[0]) + expect(spans).to.have.length(3) + expect(spans[0]).to.have.property('service', 'test-graphql') + expect(spans[0]).to.have.property('name', 'graphql.query') + expect(spans[0]).to.have.property('resource', 'query MyQuery') + expect(spans[0].meta).to.have.property('graphql.document', source) + }) + .then(done) + .catch(done) - expect(spans).to.have.length(1) - expect(spans[0]).to.have.property('service', 'test-graphql') - expect(spans[0]).to.have.property('name', 'graphql.query') - expect(spans[0]).to.have.property('resource', 'query') - expect(spans[0].meta).to.have.property('graphql.document', source) - expect(spans[0]).to.have.property('error', 1) - expect(spans[0].meta).to.have.property('error.type', error.name) - expect(spans[0].meta).to.have.property('error.msg', error.message) - expect(spans[0].meta).to.have.property('error.stack', error.stack) + graphql.execute(schema, document) + }) + + it('should handle executor exceptions', done => { + schema = new graphql.GraphQLSchema({ + query: new graphql.GraphQLObjectType({ + name: 'RootQueryType', + fields: { + hello: {} + } + }) }) - .then(done) - .catch(done) - try { - graphql.execute(schema, document) - } catch (e) { - error = e - } - }) + const source = `{ hello }` + const document = graphql.parse(source) - it('should handle resolver exceptions', done => { - const error = new Error('test') + let error - const schema = graphql.buildSchema(` - type Query { - hello: String + agent + .use(traces => { + const spans = sort(traces[0]) + + expect(spans).to.have.length(1) + expect(spans[0]).to.have.property('service', 'test-graphql') + expect(spans[0]).to.have.property('name', 'graphql.query') + expect(spans[0]).to.have.property('resource', 'query') + expect(spans[0].meta).to.have.property('graphql.document', source) + expect(spans[0]).to.have.property('error', 1) + expect(spans[0].meta).to.have.property('error.type', error.name) + expect(spans[0].meta).to.have.property('error.msg', error.message) + expect(spans[0].meta).to.have.property('error.stack', error.stack) + }) + .then(done) + .catch(done) + + try { + graphql.execute(schema, document) + } catch (e) { + error = e } - `) + }) - const source = `{ hello }` + it('should handle resolver exceptions', done => { + const error = new Error('test') + + const schema = graphql.buildSchema(` + type Query { + hello: String + } + `) - const rootValue = { - hello: () => { - throw error + const source = `{ hello }` + + const rootValue = { + hello: () => { + throw error + } } - } - agent - .use(traces => { - const spans = sort(traces[0]) + agent + .use(traces => { + const spans = sort(traces[0]) - expect(spans).to.have.length(3) - expect(spans[2]).to.have.property('error', 1) - expect(spans[2].meta).to.have.property('error.type', error.name) - expect(spans[2].meta).to.have.property('error.msg', error.message) - expect(spans[2].meta).to.have.property('error.stack', error.stack) - }) - .then(done) - .catch(done) + expect(spans).to.have.length(3) + expect(spans[2]).to.have.property('error', 1) + expect(spans[2].meta).to.have.property('error.type', error.name) + expect(spans[2].meta).to.have.property('error.msg', error.message) + expect(spans[2].meta).to.have.property('error.stack', error.stack) + }) + .then(done) + .catch(done) - graphql.graphql({ schema, source, rootValue }).catch(done) - }) + graphql.graphql({ schema, source, rootValue }).catch(done) + }) - it('should handle rejected promises', done => { - const error = new Error('test') + it('should handle rejected promises', done => { + const error = new Error('test') - const schema = graphql.buildSchema(` - type Query { - hello: String - } - `) + const schema = graphql.buildSchema(` + type Query { + hello: String + } + `) - const source = `{ hello }` + const source = `{ hello }` - const rootValue = { - hello: () => { - return Promise.reject(error) + const rootValue = { + hello: () => { + return Promise.reject(error) + } } - } - agent - .use(traces => { - const spans = sort(traces[0]) + agent + .use(traces => { + const spans = sort(traces[0]) - expect(spans).to.have.length(3) - expect(spans[2]).to.have.property('error', 1) - expect(spans[2].meta).to.have.property('error.type', error.name) - expect(spans[2].meta).to.have.property('error.msg', error.message) - expect(spans[2].meta).to.have.property('error.stack', error.stack) - }) - .then(done) - .catch(done) + expect(spans).to.have.length(3) + expect(spans[2]).to.have.property('error', 1) + expect(spans[2].meta).to.have.property('error.type', error.name) + expect(spans[2].meta).to.have.property('error.msg', error.message) + expect(spans[2].meta).to.have.property('error.stack', error.stack) + }) + .then(done) + .catch(done) - graphql.graphql({ schema, source, rootValue }).catch(done) - }) + graphql.graphql({ schema, source, rootValue }).catch(done) + }) - it('should support multiple executions with the same contextValue', done => { - const schema = graphql.buildSchema(` - type Query { - hello: String - } - `) + it('should support multiple executions with the same contextValue', done => { + const schema = graphql.buildSchema(` + type Query { + hello: String + } + `) - const source = `{ hello }` + const source = `{ hello }` - const rootValue = { - hello: () => 'world' - } + const rootValue = { + hello: () => 'world' + } - const contextValue = {} + const contextValue = {} - graphql.graphql({ schema, source, rootValue, contextValue }) - .then(() => graphql.graphql({ schema, source, rootValue, contextValue })) - .then(() => done()) - .catch(done) + graphql.graphql({ schema, source, rootValue, contextValue }) + .then(() => graphql.graphql({ schema, source, rootValue, contextValue })) + .then(() => done()) + .catch(done) + }) }) - }) - describe('with configuration', () => { - beforeEach(() => { - return agent.load(plugin, 'graphql', { service: 'test' }) - .then(() => { - graphql = require('graphql') - buildSchema() - }) - }) + describe('with configuration', () => { + beforeEach(() => { + return agent.load(plugin, 'graphql', { service: 'test' }) + .then(() => { + graphql = require(`./versions/graphql@${version}`).get() + buildSchema() + }) + }) - it('should be configured with the correct values', done => { - const source = `{ hello(name: "world") }` + it('should be configured with the correct values', done => { + const source = `{ hello(name: "world") }` - agent - .use(traces => { - const spans = sort(traces[0]) + agent + .use(traces => { + const spans = sort(traces[0]) - expect(spans).to.have.length(3) - expect(spans[2]).to.have.property('service', 'test') - }) - .then(done) - .catch(done) + expect(spans).to.have.length(3) + expect(spans[2]).to.have.property('service', 'test') + }) + .then(done) + .catch(done) - graphql.graphql(schema, source).catch(done) + graphql.graphql(schema, source).catch(done) + }) }) }) }) diff --git a/test/plugins/mongodb-core.spec.js b/test/plugins/mongodb-core.spec.js index 6bd64a139cd..8b4cd61dad1 100644 --- a/test/plugins/mongodb-core.spec.js +++ b/test/plugins/mongodb-core.spec.js @@ -2,11 +2,11 @@ const agent = require('./agent') const Buffer = require('safe-buffer').Buffer +const plugin = require('../../src/plugins/mongodb-core') wrapIt() describe('Plugin', () => { - let plugin let mongo let server let platform @@ -14,307 +14,308 @@ describe('Plugin', () => { let collection describe('mongodb-core', () => { - beforeEach(() => { - plugin = require('../../src/plugins/mongodb-core') - platform = require('../../src/platform') - tracer = require('../..') + withVersions(plugin, 'mongodb-core', version => { + beforeEach(() => { + platform = require('../../src/platform') + tracer = require('../..') - collection = platform.id().toString() - }) - - afterEach(() => { - agent.close() - server.destroy() - }) - - describe('without configuration', () => { - beforeEach(done => { - agent.load(plugin, 'mongodb-core') - .then(() => { - mongo = require('mongodb-core') - - server = new mongo.Server({ - host: 'localhost', - port: 27017, - reconnect: false - }) - - server.on('connect', () => done()) - server.on('error', done) + collection = platform.id().toString() + }) - server.connect() - }) - .catch(done) + afterEach(() => { + agent.close() + server.destroy() }) - describe('server', () => { - it('should do automatic instrumentation', done => { - agent - .use(traces => { - const span = traces[0][0] - const resource = `insert test.${collection}` - - expect(span).to.have.property('name', 'mongodb.query') - expect(span).to.have.property('service', 'test-mongodb') - expect(span).to.have.property('resource', resource) - expect(span).to.have.property('type', 'mongodb') - expect(span.meta).to.have.property('db.name', `test.${collection}`) - expect(span.meta).to.have.property('out.host', 'localhost') - }) - .then(done) - .catch(done) + describe('without configuration', () => { + beforeEach(done => { + agent.load(plugin, 'mongodb-core') + .then(() => { + mongo = require(`./versions/mongodb-core@${version}`).get() - server.insert(`test.${collection}`, [{ a: 1 }], {}, () => {}) - }) + server = new mongo.Server({ + host: 'localhost', + port: 27017, + reconnect: false + }) - it('should use the correct resource name for arbitrary commands', done => { - agent - .use(traces => { - const span = traces[0][0] - const resource = `planCacheListPlans test.${collection} {}` + server.on('connect', () => done()) + server.on('error', done) - expect(span).to.have.property('resource', resource) + server.connect() }) - .then(done) .catch(done) - - server.command(`test.${collection}`, { - planCacheListPlans: `test.${collection}`, - query: {} - }, () => {}) }) - it('should use a fallback for unknown commands', done => { - agent - .use(traces => { - const span = traces[0][0] - const resource = `unknownCommand test.${collection}` + describe('server', () => { + it('should do automatic instrumentation', done => { + agent + .use(traces => { + const span = traces[0][0] + const resource = `insert test.${collection}` + + expect(span).to.have.property('name', 'mongodb.query') + expect(span).to.have.property('service', 'test-mongodb') + expect(span).to.have.property('resource', resource) + expect(span).to.have.property('type', 'mongodb') + expect(span.meta).to.have.property('db.name', `test.${collection}`) + expect(span.meta).to.have.property('out.host', 'localhost') + }) + .then(done) + .catch(done) + + server.insert(`test.${collection}`, [{ a: 1 }], {}, () => {}) + }) - expect(span).to.have.property('resource', resource) - }) - .then(done) - .catch(done) + it('should use the correct resource name for arbitrary commands', done => { + agent + .use(traces => { + const span = traces[0][0] + const resource = `planCacheListPlans test.${collection} {}` - server.command(`test.${collection}`, { - invalidCommand: `test.${collection}` - }, () => {}) - }) + expect(span).to.have.property('resource', resource) + }) + .then(done) + .catch(done) - it('should sanitize the query as the resource', done => { - agent - .use(traces => { - const span = traces[0][0] - const resource = `find test.${collection} {"foo":"?","bar":{"baz":"?"}}` + server.command(`test.${collection}`, { + planCacheListPlans: `test.${collection}`, + query: {} + }, () => {}) + }) - expect(span).to.have.property('resource', resource) - }) - .then(done) - .catch(done) + it('should use a fallback for unknown commands', done => { + agent + .use(traces => { + const span = traces[0][0] + const resource = `unknownCommand test.${collection}` - server.command(`test.${collection}`, { - find: `test.${collection}`, - query: { - foo: 1, - bar: { - baz: [1, 2, 3] - } - } - }, () => {}) - }) + expect(span).to.have.property('resource', resource) + }) + .then(done) + .catch(done) - it('should sanitize buffers as values and not as objects', done => { - agent - .use(traces => { - const span = traces[0][0] - const resource = `find test.${collection} {"_id":"?"}` + server.command(`test.${collection}`, { + invalidCommand: `test.${collection}` + }, () => {}) + }) - expect(span).to.have.property('resource', resource) - }) - .then(done) - .catch(done) + it('should sanitize the query as the resource', done => { + agent + .use(traces => { + const span = traces[0][0] + const resource = `find test.${collection} {"foo":"?","bar":{"baz":"?"}}` - server.command(`test.${collection}`, { - find: `test.${collection}`, - query: { - _id: Buffer.from('1234') - } - }, () => {}) - }) + expect(span).to.have.property('resource', resource) + }) + .then(done) + .catch(done) - it('should run the callback in the parent context', done => { - server.insert(`test.${collection}`, [{ a: 1 }], {}, () => { - expect(tracer.scopeManager().active()).to.be.null - done() + server.command(`test.${collection}`, { + find: `test.${collection}`, + query: { + foo: 1, + bar: { + baz: [1, 2, 3] + } + } + }, () => {}) }) - }) - it('should handle errors', done => { - let error + it('should sanitize buffers as values and not as objects', done => { + agent + .use(traces => { + const span = traces[0][0] + const resource = `find test.${collection} {"_id":"?"}` - agent - .use(traces => { - expect(traces[0][0].meta).to.have.property('error.type', error.name) - expect(traces[0][0].meta).to.have.property('error.msg', error.message) - expect(traces[0][0].meta).to.have.property('error.stack', error.stack) - }) - .then(done) - .catch(done) + expect(span).to.have.property('resource', resource) + }) + .then(done) + .catch(done) - server.insert('', [{ a: 1 }], (err) => { - error = err - server.destroy() + server.command(`test.${collection}`, { + find: `test.${collection}`, + query: { + _id: Buffer.from('1234') + } + }, () => {}) }) - }) - }) - describe('cursor', () => { - it('should do automatic instrumentation', done => { - agent - .use(traces => { - const span = traces[0][0] - - expect(span).to.have.property('name', 'mongodb.query') - expect(span).to.have.property('service', 'test-mongodb') - expect(span).to.have.property('type', 'mongodb') - expect(span.meta).to.have.property('db.name', `test.${collection}`) - expect(span.meta).to.have.property('out.host', 'localhost') - expect(span.meta).to.have.property('out.port', '27017') + it('should run the callback in the parent context', done => { + server.insert(`test.${collection}`, [{ a: 1 }], {}, () => { + expect(tracer.scopeManager().active()).to.be.null + done() }) - .then(done) - .catch(done) - - const cursor = server.cursor(`test.${collection}`, { - insert: `test.${collection}`, - documents: [{ a: 1 }] - }, {}) + }) - cursor.next() + it('should handle errors', done => { + let error + + agent + .use(traces => { + expect(traces[0][0].meta).to.have.property('error.type', error.name) + expect(traces[0][0].meta).to.have.property('error.msg', error.message) + expect(traces[0][0].meta).to.have.property('error.stack', error.stack) + }) + .then(done) + .catch(done) + + server.insert('', [{ a: 1 }], (err) => { + error = err + server.destroy() + }) + }) }) - it('should have the correct index', done => { - let cursor - - agent.use(() => { - cursor = server.cursor(`test.${collection}`, { - find: `test.${collection}`, - query: {} - }, { batchSize: 1 }) + describe('cursor', () => { + it('should do automatic instrumentation', done => { + agent + .use(traces => { + const span = traces[0][0] + + expect(span).to.have.property('name', 'mongodb.query') + expect(span).to.have.property('service', 'test-mongodb') + expect(span).to.have.property('type', 'mongodb') + expect(span.meta).to.have.property('db.name', `test.${collection}`) + expect(span.meta).to.have.property('out.host', 'localhost') + expect(span.meta).to.have.property('out.port', '27017') + }) + .then(done) + .catch(done) + + const cursor = server.cursor(`test.${collection}`, { + insert: `test.${collection}`, + documents: [{ a: 1 }] + }, {}) cursor.next() }) - agent - .use(traces => { - expect(traces[0][0].meta).to.have.property('mongodb.cursor.index', '0') - }) - .then(() => cursor.next()) - .catch(done) + it('should have the correct index', done => { + let cursor - agent - .use(traces => { - expect(traces[0][0].meta).to.have.property('mongodb.cursor.index', '1') + agent.use(() => { + cursor = server.cursor(`test.${collection}`, { + find: `test.${collection}`, + query: {} + }, { batchSize: 1 }) + + cursor.next() }) - .then(done) - .catch(done) - server.insert(`test.${collection}`, [{ a: 1 }, { a: 2 }], {}) - }) + agent + .use(traces => { + expect(traces[0][0].meta).to.have.property('mongodb.cursor.index', '0') + }) + .then(() => cursor.next()) + .catch(done) + + agent + .use(traces => { + expect(traces[0][0].meta).to.have.property('mongodb.cursor.index', '1') + }) + .then(done) + .catch(done) + + server.insert(`test.${collection}`, [{ a: 1 }, { a: 2 }], {}) + }) - it('should sanitize the query as the resource', done => { - agent - .use(traces => { - const span = traces[0][0] - const resource = `find test.${collection} {"foo":"?","bar":{"baz":"?"}}` + it('should sanitize the query as the resource', done => { + agent + .use(traces => { + const span = traces[0][0] + const resource = `find test.${collection} {"foo":"?","bar":{"baz":"?"}}` - expect(span).to.have.property('resource', resource) - }) - .then(done) - .catch(done) + expect(span).to.have.property('resource', resource) + }) + .then(done) + .catch(done) - const cursor = server.cursor(`test.${collection}`, { - find: `test.${collection}`, - query: { - foo: 1, - bar: { - baz: [1, 2, 3] + const cursor = server.cursor(`test.${collection}`, { + find: `test.${collection}`, + query: { + foo: 1, + bar: { + baz: [1, 2, 3] + } } - } + }) + + cursor.next() }) - cursor.next() - }) + it('should run the callback in the parent context', done => { + const cursor = server.cursor(`test.${collection}`, { + find: `test.${collection}`, + query: { a: 1 } + }) - it('should run the callback in the parent context', done => { - const cursor = server.cursor(`test.${collection}`, { - find: `test.${collection}`, - query: { a: 1 } + cursor.next(() => { + expect(tracer.scopeManager().active()).to.be.null + done() + }) }) - cursor.next(() => { - expect(tracer.scopeManager().active()).to.be.null - done() - }) - }) + it('should handle errors', done => { + let error - it('should handle errors', done => { - let error + agent + .use(traces => { + expect(traces[0][0].meta).to.have.property('error.type', error.name) + expect(traces[0][0].meta).to.have.property('error.msg', error.message) + expect(traces[0][0].meta).to.have.property('error.stack', error.stack) + }) + .then(done) + .catch(done) - agent - .use(traces => { - expect(traces[0][0].meta).to.have.property('error.type', error.name) - expect(traces[0][0].meta).to.have.property('error.msg', error.message) - expect(traces[0][0].meta).to.have.property('error.stack', error.stack) + const cursor = server.cursor(`test.${collection}`, { + find: `test.${collection}`, + query: 'invalid' }) - .then(done) - .catch(done) - const cursor = server.cursor(`test.${collection}`, { - find: `test.${collection}`, - query: 'invalid' - }) - - cursor.next(err => { - error = err + cursor.next(err => { + error = err + }) }) }) }) - }) - describe('with configuration', () => { - let config + describe('with configuration', () => { + let config - beforeEach(done => { - config = { - service: 'custom' - } + beforeEach(done => { + config = { + service: 'custom' + } - agent.load(plugin, 'mongodb-core', config) - .then(() => { - mongo = require('mongodb-core') + agent.load(plugin, 'mongodb-core', config) + .then(() => { + mongo = require(`./versions/mongodb-core@${version}`).get() - server = new mongo.Server({ - host: 'localhost', - port: 27017, - reconnect: false - }) + server = new mongo.Server({ + host: 'localhost', + port: 27017, + reconnect: false + }) - server.on('connect', () => done()) - server.on('error', done) + server.on('connect', () => done()) + server.on('error', done) - server.connect() - }) - .catch(done) - }) + server.connect() + }) + .catch(done) + }) - it('should be configured with the correct values', done => { - agent - .use(traces => { - expect(traces[0][0]).to.have.property('service', 'custom') - }) - .then(done) - .catch(done) + it('should be configured with the correct values', done => { + agent + .use(traces => { + expect(traces[0][0]).to.have.property('service', 'custom') + }) + .then(done) + .catch(done) - server.insert(`test.${collection}`, [{ a: 1 }], () => {}) + server.insert(`test.${collection}`, [{ a: 1 }], () => {}) + }) }) }) }) diff --git a/test/plugins/mysql.spec.js b/test/plugins/mysql.spec.js index cfdcae579bd..dc07f166e73 100644 --- a/test/plugins/mysql.spec.js +++ b/test/plugins/mysql.spec.js @@ -1,198 +1,198 @@ 'use strict' const agent = require('./agent') +const plugin = require('../../src/plugins/mysql') wrapIt() describe('Plugin', () => { - let plugin let mysql let tracer describe('mysql', () => { - beforeEach(() => { - plugin = require('../../src/plugins/mysql') - tracer = require('../..') - }) - - afterEach(() => { - agent.close() - }) - - describe('without configuration', () => { - let connection - + withVersions(plugin, 'mysql', version => { beforeEach(() => { - return agent.load(plugin, 'mysql') - .then(() => { - mysql = require('mysql') - - connection = mysql.createConnection({ - host: 'localhost', - user: 'user', - password: 'userpass', - database: 'db' - }) - - connection.connect() - }) + tracer = require('../..') }) - afterEach(done => { - connection.end(done) + afterEach(() => { + agent.close() + agent.wipe() }) - it('should propagate context to callbacks', done => { - const span = tracer.startSpan('test') - const scope = tracer.scopeManager().activate(span) + describe('without configuration', () => { + let connection + + beforeEach(() => { + return agent.load(plugin, 'mysql') + .then(() => { + mysql = require(`./versions/mysql@${version}`).get() - connection.query('SELECT 1 + 1 AS solution', () => { - const active = tracer.scopeManager().active() - expect(active.span()).to.equal(scope.span()) - done() + connection = mysql.createConnection({ + host: 'localhost', + user: 'root', + database: 'db' + }) + + connection.connect() + }) }) - }) - it('should run the callback in the parent context', done => { - connection.query('SELECT 1 + 1 AS solution', () => { - const active = tracer.scopeManager().active() - expect(active).to.be.null - done() + afterEach(done => { + connection.end(done) }) - }) - it('should run event listeners in the parent context', done => { - const query = connection.query('SELECT 1 + 1 AS solution') + it('should propagate context to callbacks', done => { + const span = tracer.startSpan('test') + const scope = tracer.scopeManager().activate(span) - query.on('result', () => { - const active = tracer.scopeManager().active() - expect(active).to.be.null - done() + connection.query('SELECT 1 + 1 AS solution', () => { + const active = tracer.scopeManager().active() + expect(active.span()).to.equal(scope.span()) + done() + }) }) - }) - it('should do automatic instrumentation', done => { - agent.use(traces => { - expect(traces[0][0]).to.have.property('service', 'test-mysql') - expect(traces[0][0]).to.have.property('resource', 'SELECT 1 + 1 AS solution') - expect(traces[0][0]).to.have.property('type', 'sql') - expect(traces[0][0].meta).to.have.property('db.name', 'db') - expect(traces[0][0].meta).to.have.property('db.user', 'user') - expect(traces[0][0].meta).to.have.property('db.type', 'mysql') - expect(traces[0][0].meta).to.have.property('span.kind', 'client') - - done() + it('should run the callback in the parent context', done => { + connection.query('SELECT 1 + 1 AS solution', () => { + const active = tracer.scopeManager().active() + expect(active).to.be.null + done() + }) }) - connection.query('SELECT 1 + 1 AS solution', (error, results, fields) => { - if (error) throw error + it('should run event listeners in the parent context', done => { + const query = connection.query('SELECT 1 + 1 AS solution') + + query.on('result', () => { + const active = tracer.scopeManager().active() + expect(active).to.be.null + done() + }) }) - }) - it('should handle errors', done => { - let error + it('should do automatic instrumentation', done => { + agent.use(traces => { + expect(traces[0][0]).to.have.property('service', 'test-mysql') + expect(traces[0][0]).to.have.property('resource', 'SELECT 1 + 1 AS solution') + expect(traces[0][0]).to.have.property('type', 'sql') + expect(traces[0][0].meta).to.have.property('db.name', 'db') + expect(traces[0][0].meta).to.have.property('db.user', 'root') + expect(traces[0][0].meta).to.have.property('db.type', 'mysql') + expect(traces[0][0].meta).to.have.property('span.kind', 'client') + + done() + }) - agent.use(traces => { - expect(traces[0][0].meta).to.have.property('error.type', error.name) - expect(traces[0][0].meta).to.have.property('error.msg', error.message) - expect(traces[0][0].meta).to.have.property('error.stack', error.stack) + connection.query('SELECT 1 + 1 AS solution', (error, results, fields) => { + if (error) throw error + }) + }) + + it('should handle errors', done => { + let error + + agent.use(traces => { + expect(traces[0][0].meta).to.have.property('error.type', error.name) + expect(traces[0][0].meta).to.have.property('error.msg', error.message) + expect(traces[0][0].meta).to.have.property('error.stack', error.stack) - done() + done() + }) + + connection.query('INVALID', (err, results, fields) => { + error = err + }) }) - connection.query('INVALID', (err, results, fields) => { - error = err + it('should work without a callback', done => { + agent.use(traces => { + done() + }) + + connection.query('SELECT 1 + 1 AS solution') }) }) - it('should work without a callback', done => { - agent.use(traces => { - done() - }) + describe('with configuration', () => { + let connection + let config - connection.query('SELECT 1 + 1 AS solution') - }) - }) + beforeEach(() => { + config = { + service: 'custom' + } - describe('with configuration', () => { - let connection - let config + return agent.load(plugin, 'mysql', config) + .then(() => { + mysql = require(`./versions/mysql@${version}`).get() - beforeEach(() => { - config = { - service: 'custom' - } - - return agent.load(plugin, 'mysql', config) - .then(() => { - mysql = require('mysql') - - connection = mysql.createConnection({ - host: 'localhost', - user: 'user', - password: 'userpass', - database: 'db' + connection = mysql.createConnection({ + host: 'localhost', + user: 'root', + database: 'db' + }) + + connection.connect() }) + }) - connection.connect() - }) - }) + afterEach(done => { + connection.end(done) + }) - afterEach(done => { - connection.end(done) - }) + it('should be configured with the correct values', done => { + agent.use(traces => { + expect(traces[0][0]).to.have.property('service', 'custom') + done() + }) - it('should be configured with the correct values', done => { - agent.use(traces => { - expect(traces[0][0]).to.have.property('service', 'custom') - done() + connection.query('SELECT 1 + 1 AS solution', () => {}) }) - - connection.query('SELECT 1 + 1 AS solution') }) - }) - describe('with a connection pool', () => { - let pool + describe('with a connection pool', () => { + let pool - beforeEach(() => { - return agent.load(plugin, 'mysql') - .then(() => { - mysql = require('mysql') - - pool = mysql.createPool({ - connectionLimit: 10, - host: 'localhost', - user: 'user', - password: 'userpass' + beforeEach(() => { + return agent.load(plugin, 'mysql') + .then(() => { + mysql = require(`./versions/mysql@${version}`).get() + + pool = mysql.createPool({ + connectionLimit: 10, + host: 'localhost', + user: 'root', + database: 'db' + }) }) - }) - }) + }) - afterEach(done => { - pool.end(done) - }) + afterEach(done => { + pool.end(done) + }) - it('should do automatic instrumentation', done => { - agent.use(traces => { - expect(traces[0][0]).to.have.property('service', 'test-mysql') - expect(traces[0][0]).to.have.property('resource', 'SELECT 1 + 1 AS solution') - expect(traces[0][0]).to.have.property('type', 'sql') - expect(traces[0][0].meta).to.have.property('db.user', 'user') - expect(traces[0][0].meta).to.have.property('db.type', 'mysql') - expect(traces[0][0].meta).to.have.property('span.kind', 'client') + it('should do automatic instrumentation', done => { + agent.use(traces => { + expect(traces[0][0]).to.have.property('service', 'test-mysql') + expect(traces[0][0]).to.have.property('resource', 'SELECT 1 + 1 AS solution') + expect(traces[0][0]).to.have.property('type', 'sql') + expect(traces[0][0].meta).to.have.property('db.user', 'root') + expect(traces[0][0].meta).to.have.property('db.type', 'mysql') + expect(traces[0][0].meta).to.have.property('span.kind', 'client') - done() - }) + done() + }) - pool.query('SELECT 1 + 1 AS solution') - }) + pool.query('SELECT 1 + 1 AS solution', () => {}) + }) - it('should run the callback in the parent context', done => { - pool.query('SELECT 1 + 1 AS solution', () => { - const active = tracer.scopeManager().active() - expect(active).to.be.null - done() + it('should run the callback in the parent context', done => { + pool.query('SELECT 1 + 1 AS solution', () => { + const active = tracer.scopeManager().active() + expect(active).to.be.null + done() + }) }) }) }) diff --git a/test/plugins/mysql2.spec.js b/test/plugins/mysql2.spec.js index 292b85fd20b..1d4b708625d 100644 --- a/test/plugins/mysql2.spec.js +++ b/test/plugins/mysql2.spec.js @@ -1,204 +1,203 @@ 'use strict' const agent = require('./agent') +const plugin = require('../../src/plugins/mysql2') wrapIt() describe('Plugin', () => { - let plugin let mysql2 let tracer describe('mysql2', () => { - beforeEach(() => { - plugin = require('../../src/plugins/mysql2') - tracer = require('../..') - }) - - afterEach(() => { - agent.close() - }) - - describe('without configuration', () => { - let connection - + withVersions(plugin, 'mysql2', version => { beforeEach(() => { - return agent.load(plugin, 'mysql2') - .then(() => { - mysql2 = require('mysql2') - - connection = mysql2.createConnection({ - host: 'localhost', - user: 'user', - password: 'userpass', - database: 'db' - }) - - connection.connect() - }) + tracer = require('../..') }) - afterEach(done => { - connection.end(done) + afterEach(() => { + agent.close() + agent.wipe() }) - it('should propagate context to callbacks', done => { - const span = tracer.startSpan('test') - const scope = tracer.scopeManager().activate(span) + describe('without configuration', () => { + let connection + + beforeEach(() => { + return agent.load(plugin, 'mysql2') + .then(() => { + mysql2 = require(`./versions/mysql2@${version}`).get() - connection.query('SELECT 1 + 1 AS solution', () => { - const active = tracer.scopeManager().active() - expect(active.span()).to.equal(scope.span()) - done() + connection = mysql2.createConnection({ + host: 'localhost', + user: 'root', + database: 'db' + }) + + connection.connect() + }) }) - }) - it('should run the callback in the parent context', done => { - connection.query('SELECT 1 + 1 AS solution', () => { - const active = tracer.scopeManager().active() - expect(active).to.be.null - done() + afterEach(done => { + connection.end(done) }) - }) - it('should run event listeners in the parent context', done => { - const query = connection.query('SELECT 1 + 1 AS solution') + it('should propagate context to callbacks', done => { + const span = tracer.startSpan('test') + const scope = tracer.scopeManager().activate(span) - query.on('result', () => { - const active = tracer.scopeManager().active() - expect(active).to.be.null - done() + connection.query('SELECT 1 + 1 AS solution', () => { + const active = tracer.scopeManager().active() + expect(active.span()).to.equal(scope.span()) + done() + }) }) - }) - it('should do automatic instrumentation', done => { - agent - .use(traces => { - expect(traces[0][0]).to.have.property('service', 'test-mysql') - expect(traces[0][0]).to.have.property('resource', 'SELECT 1 + 1 AS solution') - expect(traces[0][0]).to.have.property('type', 'sql') - expect(traces[0][0].meta).to.have.property('db.name', 'db') - expect(traces[0][0].meta).to.have.property('db.user', 'user') - expect(traces[0][0].meta).to.have.property('db.type', 'mysql') - expect(traces[0][0].meta).to.have.property('span.kind', 'client') + it('should run the callback in the parent context', done => { + connection.query('SELECT 1 + 1 AS solution', () => { + const active = tracer.scopeManager().active() + expect(active).to.be.null + done() }) - .then(done) - .catch(done) - - connection.query('SELECT 1 + 1 AS solution', (error, results, fields) => { - if (error) throw error }) - }) - it('should handle errors', done => { - let error + it('should run event listeners in the parent context', done => { + const query = connection.query('SELECT 1 + 1 AS solution') - agent - .use(traces => { - expect(traces[0][0].meta).to.have.property('error.type', error.name) - expect(traces[0][0].meta).to.have.property('error.msg', error.message) - expect(traces[0][0].meta).to.have.property('error.stack', error.stack) + query.on('result', () => { + const active = tracer.scopeManager().active() + expect(active).to.be.null + done() }) - .then(done) - .catch(done) - - connection.query('INVALID', (err, results, fields) => { - error = err }) - }) - it('should work without a callback', done => { - agent - .use(() => {}) - .then(done) - .catch(done) + it('should do automatic instrumentation', done => { + agent + .use(traces => { + expect(traces[0][0]).to.have.property('service', 'test-mysql') + expect(traces[0][0]).to.have.property('resource', 'SELECT 1 + 1 AS solution') + expect(traces[0][0]).to.have.property('type', 'sql') + expect(traces[0][0].meta).to.have.property('db.name', 'db') + expect(traces[0][0].meta).to.have.property('db.user', 'root') + expect(traces[0][0].meta).to.have.property('db.type', 'mysql') + expect(traces[0][0].meta).to.have.property('span.kind', 'client') + }) + .then(done) + .catch(done) - connection.query('SELECT 1 + 1 AS solution') - }) - }) + connection.query('SELECT 1 + 1 AS solution', (error, results, fields) => { + if (error) throw error + }) + }) - describe('with configuration', () => { - let connection - let config + it('should handle errors', done => { + let error - beforeEach(() => { - config = { - service: 'custom' - } - - return agent.load(plugin, 'mysql2', config) - .then(() => { - mysql2 = require('mysql2') - - connection = mysql2.createConnection({ - host: 'localhost', - user: 'user', - password: 'userpass', - database: 'db' + agent + .use(traces => { + expect(traces[0][0].meta).to.have.property('error.type', error.name) + expect(traces[0][0].meta).to.have.property('error.msg', error.message) + expect(traces[0][0].meta).to.have.property('error.stack', error.stack) }) + .then(done) + .catch(done) - connection.connect() + connection.query('INVALID', (err, results, fields) => { + error = err }) - }) + }) - afterEach(done => { - connection.end(done) + it('should work without a callback', done => { + agent + .use(() => {}) + .then(done) + .catch(done) + + connection.query('SELECT 1 + 1 AS solution') + }) }) - it('should be configured with the correct values', done => { - agent - .use(traces => { - expect(traces[0][0]).to.have.property('service', 'custom') - }) - .then(done) - .catch(done) + describe('with configuration', () => { + let connection + let config - connection.query('SELECT 1 + 1 AS solution') - }) - }) + beforeEach(() => { + config = { + service: 'custom' + } - describe('with a connection pool', () => { - let pool + return agent.load(plugin, 'mysql2', config) + .then(() => { + mysql2 = require(`./versions/mysql2@${version}`).get() - beforeEach(() => { - return agent.load(plugin, 'mysql2') - .then(() => { - mysql2 = require('mysql2') - - pool = mysql2.createPool({ - connectionLimit: 10, - host: 'localhost', - user: 'user', - password: 'userpass' + connection = mysql2.createConnection({ + host: 'localhost', + user: 'root', + database: 'db' + }) + + connection.connect() }) - }) - }) + }) - afterEach(done => { - pool.end(done) - }) + afterEach(done => { + connection.end(done) + }) - it('should do automatic instrumentation', done => { - agent - .use(traces => { - expect(traces[0][0]).to.have.property('service', 'test-mysql') - expect(traces[0][0]).to.have.property('resource', 'SELECT 1 + 1 AS solution') - expect(traces[0][0]).to.have.property('type', 'sql') - expect(traces[0][0].meta).to.have.property('db.user', 'user') - expect(traces[0][0].meta).to.have.property('db.type', 'mysql') - expect(traces[0][0].meta).to.have.property('span.kind', 'client') - }) - .then(done) - .catch(done) + it('should be configured with the correct values', done => { + agent + .use(traces => { + expect(traces[0][0]).to.have.property('service', 'custom') + }) + .then(done) + .catch(done) - pool.query('SELECT 1 + 1 AS solution') + connection.query('SELECT 1 + 1 AS solution') + }) }) - it('should run the callback in the parent context', done => { - pool.query('SELECT 1 + 1 AS solution', () => { - const active = tracer.scopeManager().active() - expect(active).to.be.null - done() + describe('with a connection pool', () => { + let pool + + beforeEach(() => { + return agent.load(plugin, 'mysql2') + .then(() => { + mysql2 = require(`./versions/mysql2@${version}`).get() + + pool = mysql2.createPool({ + connectionLimit: 10, + host: 'localhost', + user: 'root' + }) + }) + }) + + afterEach(done => { + pool.end(done) + }) + + it('should do automatic instrumentation', done => { + agent + .use(traces => { + expect(traces[0][0]).to.have.property('service', 'test-mysql') + expect(traces[0][0]).to.have.property('resource', 'SELECT 1 + 1 AS solution') + expect(traces[0][0]).to.have.property('type', 'sql') + expect(traces[0][0].meta).to.have.property('db.user', 'root') + expect(traces[0][0].meta).to.have.property('db.type', 'mysql') + expect(traces[0][0].meta).to.have.property('span.kind', 'client') + }) + .then(done) + .catch(done) + + pool.query('SELECT 1 + 1 AS solution') + }) + + it('should run the callback in the parent context', done => { + pool.query('SELECT 1 + 1 AS solution', () => { + const active = tracer.scopeManager().active() + expect(active).to.be.null + done() + }) }) }) }) diff --git a/test/plugins/pg.spec.js b/test/plugins/pg.spec.js index 69621f1ea16..d0c19600e82 100644 --- a/test/plugins/pg.spec.js +++ b/test/plugins/pg.spec.js @@ -1,188 +1,189 @@ 'use strict' const agent = require('./agent') +const plugin = require('../../src/plugins/pg') wrapIt() describe('Plugin', () => { - let plugin let pg let client let tracer describe('pg', () => { - beforeEach(() => { - plugin = require('../../src/plugins/pg') - tracer = require('../..') - }) + withVersions(plugin, 'pg', version => { + beforeEach(() => { + tracer = require('../..') + }) - afterEach(() => { - agent.close() - }) + afterEach(() => { + agent.close() + }) - describe('when using a client', () => { - beforeEach(done => { - agent.load(plugin, 'pg') - .then(() => { - pg = require('pg') - - client = new pg.Client({ - user: 'postgres', - password: 'postgres', - database: 'postgres', - application_name: 'test' - }) + describe('when using a client', () => { + beforeEach(done => { + agent.load(plugin, 'pg') + .then(() => { + pg = require(`./versions/pg@${version}`).get() - client.connect(err => done(err)) - }) - .catch(done) - }) + client = new pg.Client({ + user: 'postgres', + password: 'postgres', + database: 'postgres', + application_name: 'test' + }) - it('should do automatic instrumentation when using callbacks', done => { - agent.use(traces => { - expect(traces[0][0]).to.have.property('service', 'test-postgres') - expect(traces[0][0]).to.have.property('resource', 'SELECT $1::text as message') - expect(traces[0][0]).to.have.property('type', 'sql') - expect(traces[0][0].meta).to.have.property('db.name', 'postgres') - expect(traces[0][0].meta).to.have.property('db.user', 'postgres') - expect(traces[0][0].meta).to.have.property('db.type', 'postgres') - expect(traces[0][0].meta).to.have.property('span.kind', 'client') - - done() + client.connect(err => done(err)) + }) + .catch(done) }) - client.query('SELECT $1::text as message', ['Hello world!'], (err, result) => { - if (err) throw err + it('should do automatic instrumentation when using callbacks', done => { + agent.use(traces => { + expect(traces[0][0]).to.have.property('service', 'test-postgres') + expect(traces[0][0]).to.have.property('resource', 'SELECT $1::text as message') + expect(traces[0][0]).to.have.property('type', 'sql') + expect(traces[0][0].meta).to.have.property('db.name', 'postgres') + expect(traces[0][0].meta).to.have.property('db.user', 'postgres') + expect(traces[0][0].meta).to.have.property('db.type', 'postgres') + expect(traces[0][0].meta).to.have.property('span.kind', 'client') + + done() + }) - client.end((err) => { + client.query('SELECT $1::text as message', ['Hello world!'], (err, result) => { if (err) throw err + + client.end((err) => { + if (err) throw err + }) }) }) - }) - it('should handle errors', done => { - agent.use(traces => { - expect(traces[0][0].meta).to.have.property('error.type', 'error') - expect(traces[0][0].meta).to.have.property('error.msg', 'syntax error at or near "INVALID"') - expect(traces[0][0].meta).to.have.property('error.stack') + it('should handle errors', done => { + agent.use(traces => { + expect(traces[0][0].meta).to.have.property('error.type', 'error') + expect(traces[0][0].meta).to.have.property('error.msg', 'syntax error at or near "INVALID"') + expect(traces[0][0].meta).to.have.property('error.stack') - done() + done() + }) + + client.query('INVALID', (err, result) => { + expect(err).to.be.an('error') + + client.end((err) => { + if (err) throw err + }) + }) }) - client.query('INVALID', (err, result) => { - expect(err).to.be.an('error') + it('should run the callback in the parent context', done => { + const span = {} + const scope = tracer.scopeManager().activate(span) + + client.query('SELECT $1::text as message', ['Hello World!'], () => { + const active = tracer.scopeManager().active() + expect(active.span()).to.equal(scope.span()) + done() + }) client.end((err) => { if (err) throw err }) }) - }) - - it('should run the callback in the parent context', done => { - const span = {} - const scope = tracer.scopeManager().activate(span) - client.query('SELECT $1::text as message', ['Hello World!'], () => { - const active = tracer.scopeManager().active() - expect(active.span()).to.equal(scope.span()) - done() - }) + it('should work without a callback', done => { + agent.use(traces => { + done() + }) - client.end((err) => { - if (err) throw err + client.query('SELECT $1::text as message', ['Hello World!']) + client.end((err) => { + if (err) throw err + }) }) }) - it('should work without a callback', done => { - agent.use(traces => { - done() + describe('when using a pool', () => { + let pool + + beforeEach(done => { + agent.load(plugin, 'pg') + .then(() => { + pg = require('pg') + + pool = new pg.Pool({ + user: 'postgres', + password: 'postgres', + database: 'postgres', + application_name: 'test' + }) + + pool.connect((err, c) => { + client = c + done(err) + }) + }) + .catch(done) }) - client.query('SELECT $1::text as message', ['Hello World!']) - client.end((err) => { - if (err) throw err + afterEach(() => { + client && client.release() }) - }) - }) - - describe('when using a pool', () => { - let pool - beforeEach(done => { - agent.load(plugin, 'pg') - .then(() => { - pg = require('pg') + it('should run the callback in the parent context', done => { + const span = {} + const scope = tracer.scopeManager().activate(span) - pool = new pg.Pool({ - user: 'postgres', - password: 'postgres', - database: 'postgres', - application_name: 'test' - }) - - pool.connect((err, c) => { - client = c - done(err) - }) + pool.query('SELECT $1::text as message', ['Hello World!'], () => { + const active = tracer.scopeManager().active() + expect(active.span()).to.equal(scope.span()) + done() }) - .catch(done) - }) - - afterEach(() => { - client && client.release() - }) - - it('should run the callback in the parent context', done => { - const span = {} - const scope = tracer.scopeManager().activate(span) - - pool.query('SELECT $1::text as message', ['Hello World!'], () => { - const active = tracer.scopeManager().active() - expect(active.span()).to.equal(scope.span()) - done() - }) - pool.end((err) => { - if (err) throw err + pool.end((err) => { + if (err) throw err + }) }) }) - }) - - describe('with configuration', () => { - let config - beforeEach(done => { - config = { - service: 'custom' - } + describe('with configuration', () => { + let config - agent.load(plugin, 'pg', config) - .then(() => { - pg = require('pg') + beforeEach(done => { + config = { + service: 'custom' + } - client = new pg.Client({ - user: 'postgres', - password: 'postgres', - database: 'postgres' - }) - - client.connect(err => done(err)) - }) - .catch(done) - }) + agent.load(plugin, 'pg', config) + .then(() => { + pg = require('pg') - it('should be configured with the correct values', done => { - agent.use(traces => { - expect(traces[0][0]).to.have.property('service', 'custom') + client = new pg.Client({ + user: 'postgres', + password: 'postgres', + database: 'postgres' + }) - done() + client.connect(err => done(err)) + }) + .catch(done) }) - client.query('SELECT $1::text as message', ['Hello world!'], (err, result) => { - if (err) throw err + it('should be configured with the correct values', done => { + agent.use(traces => { + expect(traces[0][0]).to.have.property('service', 'custom') - client.end((err) => { + done() + }) + + client.query('SELECT $1::text as message', ['Hello world!'], (err, result) => { if (err) throw err + + client.end((err) => { + if (err) throw err + }) }) }) }) diff --git a/test/plugins/redis.spec.js b/test/plugins/redis.spec.js index fe5dc4a11d8..34b3828d37f 100644 --- a/test/plugins/redis.spec.js +++ b/test/plugins/redis.spec.js @@ -1,130 +1,131 @@ 'use strict' const agent = require('./agent') +const plugin = require('../../src/plugins/redis') wrapIt() describe('Plugin', () => { - let plugin let redis let tracer let client describe('redis', () => { - beforeEach(() => { - plugin = require('../../src/plugins/redis') - tracer = require('../..') - }) - - afterEach(() => { - client.quit() - agent.close() - }) - - describe('without configuration', () => { + withVersions(plugin, 'redis', version => { beforeEach(() => { - return agent.load(plugin, 'redis') - .then(() => { - redis = require('redis') - client = redis.createClient() - }) + tracer = require('../..') }) - it('should do automatic instrumentation when using callbacks', done => { - client.on('error', done) - - agent.use(() => client.get('foo')) // wait for initial info command - agent - .use(traces => { - expect(traces[0][0]).to.have.property('name', 'redis.command') - expect(traces[0][0]).to.have.property('service', 'test-redis') - expect(traces[0][0]).to.have.property('resource', 'get') - expect(traces[0][0]).to.have.property('type', 'redis') - expect(traces[0][0].meta).to.have.property('db.name', '0') - expect(traces[0][0].meta).to.have.property('db.type', 'redis') - expect(traces[0][0].meta).to.have.property('span.kind', 'client') - expect(traces[0][0].meta).to.have.property('out.host', '127.0.0.1') - expect(traces[0][0].meta).to.have.property('out.port', '6379') - }) - .then(done) - .catch(done) + afterEach(() => { + client.quit() + agent.close() }) - it('should run the callback in the parent context', done => { - client.on('error', done) - - client.get('foo', () => { - expect(tracer.scopeManager().active()).to.be.null - done() + describe('without configuration', () => { + beforeEach(() => { + return agent.load(plugin, 'redis') + .then(() => { + redis = require(`./versions/redis@${version}`).get() + client = redis.createClient() + }) }) - }) - - it('should run client emitter listeners in the parent context', done => { - client.on('error', done) - client.on('ready', () => { - expect(tracer.scopeManager().active()).to.be.null - done() + it('should do automatic instrumentation when using callbacks', done => { + client.on('error', done) + + agent.use(() => client.get('foo')) // wait for initial info command + agent + .use(traces => { + expect(traces[0][0]).to.have.property('name', 'redis.command') + expect(traces[0][0]).to.have.property('service', 'test-redis') + expect(traces[0][0]).to.have.property('resource', 'get') + expect(traces[0][0]).to.have.property('type', 'redis') + expect(traces[0][0].meta).to.have.property('db.name', '0') + expect(traces[0][0].meta).to.have.property('db.type', 'redis') + expect(traces[0][0].meta).to.have.property('span.kind', 'client') + expect(traces[0][0].meta).to.have.property('out.host', '127.0.0.1') + expect(traces[0][0].meta).to.have.property('out.port', '6379') + }) + .then(done) + .catch(done) }) - }) - it('should run stream emitter listeners in the parent context', done => { - client.on('error', done) + it('should run the callback in the parent context', done => { + client.on('error', done) - client.stream.on('close', () => { - expect(tracer.scopeManager().active()).to.be.null - done() + client.get('foo', () => { + expect(tracer.scopeManager().active()).to.be.null + done() + }) }) - client.stream.destroy() - }) + it('should run client emitter listeners in the parent context', done => { + client.on('error', done) - it('should handle errors', done => { - let error + client.on('ready', () => { + expect(tracer.scopeManager().active()).to.be.null + done() + }) + }) - client.on('error', done) + it('should run stream emitter listeners in the parent context', done => { + client.on('error', done) - agent.use(() => { // wait for initial info command - client.set('foo', 123, 'bar', (err, res) => { - error = err + client.stream.on('close', () => { + expect(tracer.scopeManager().active()).to.be.null + done() }) + + client.stream.destroy() }) - agent - .use(traces => { - expect(traces[0][0].meta).to.have.property('error.type', error.name) - expect(traces[0][0].meta).to.have.property('error.msg', error.message) - expect(traces[0][0].meta).to.have.property('error.stack', error.stack) - }) - .then(done) - .catch(done) - }) - }) + it('should handle errors', done => { + let error - describe('with configuration', () => { - let config + client.on('error', done) - beforeEach(() => { - config = { - service: 'custom' - } - - return agent.load(plugin, 'redis', config) - .then(() => { - redis = require('redis') - client = redis.createClient() + agent.use(() => { // wait for initial info command + client.set('foo', 123, 'bar', (err, res) => { + error = err + }) }) + + agent + .use(traces => { + expect(traces[0][0].meta).to.have.property('error.type', error.name) + expect(traces[0][0].meta).to.have.property('error.msg', error.message) + expect(traces[0][0].meta).to.have.property('error.stack', error.stack) + }) + .then(done) + .catch(done) + }) }) - it('should be configured with the correct values', done => { - agent - .use(traces => { - expect(traces[0][0]).to.have.property('service', 'custom') - }) - .then(done) - .catch(done) + describe('with configuration', () => { + let config + + beforeEach(() => { + config = { + service: 'custom' + } + + return agent.load(plugin, 'redis', config) + .then(() => { + redis = require(`./versions/redis@${version}`).get() + client = redis.createClient() + }) + }) + + it('should be configured with the correct values', done => { + agent + .use(traces => { + expect(traces[0][0]).to.have.property('service', 'custom') + }) + .then(done) + .catch(done) - client.on('error', done) + client.on('error', done) + }) }) }) }) diff --git a/test/setup.js b/test/setup.js index 1e321a9bf50..c6439773897 100644 --- a/test/setup.js +++ b/test/setup.js @@ -5,6 +5,7 @@ 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 pg = require('pg') const mysql = require('mysql') @@ -33,6 +34,7 @@ global.expect = chai.expect global.proxyquire = proxyquire global.nock = nock global.wrapIt = wrapIt +global.withVersions = withVersions platform.use(node) @@ -96,8 +98,7 @@ function waitForMysql () { operation.attempt(currentAttempt => { const connection = mysql.createConnection({ host: 'localhost', - user: 'user', - password: 'userpass', + user: 'root', database: 'db' }) @@ -233,3 +234,34 @@ function wrapIt () { } } } + +function withVersions (plugin, moduleName, range, cb) { + const instrumentations = [].concat(plugin) + const testVersions = new Map() + + if (!cb) { + cb = range + range = null + } + + instrumentations + .filter(instrumentation => instrumentation.name === moduleName) + .forEach(instrumentation => { + instrumentation.versions + .filter(version => !range || semver.satisfies(version, range)) + .forEach(version => { + const min = semver.coerce(version).version + const max = require(`./plugins/versions/${moduleName}@${version}`).version() + + testVersions.set(min, { range: version, test: min }) + testVersions.set(max, { range: version, test: version }) + }) + }) + + Array.from(testVersions) + .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)) + }) +}