diff --git a/package.json b/package.json index 08057553b..2311919ff 100644 --- a/package.json +++ b/package.json @@ -54,6 +54,7 @@ "@types/extend": "^3.0.0", "@types/glob": "^7.0.0", "@types/is": "0.0.21", + "@types/knex": "^0.15.1", "@types/methods": "^1.1.0", "@types/mocha": "^5.2.5", "@types/ncp": "^2.0.1", diff --git a/src/plugins/plugin-knex.ts b/src/plugins/plugin-knex.ts index 746fbe612..f00f336b7 100644 --- a/src/plugins/plugin-knex.ts +++ b/src/plugins/plugin-knex.ts @@ -55,8 +55,8 @@ module.exports = [ unpatch: unpatchClient }, { - // knex 0.10.x and 0.11.x do not need patching - versions: '>=0.10 <=0.11' + // these knex versions do not need patching + versions: '>=0.10 <=0.11 || >=0.14 <=0.16' } ]; diff --git a/test/fixtures/plugin-fixtures.json b/test/fixtures/plugin-fixtures.json index 3047495c3..87f077da4 100644 --- a/test/fixtures/plugin-fixtures.json +++ b/test/fixtures/plugin-fixtures.json @@ -95,6 +95,20 @@ }, "_mainModule": "knex" }, + "knex0.14": { + "dependencies": { + "knex": "^0.14.0", + "mysql": "^2.13.0" + }, + "_mainModule": "knex" + }, + "knex0.16": { + "dependencies": { + "knex": "^0.16.0", + "mysql": "^2.13.0" + }, + "_mainModule": "knex" + }, "koa1": { "dependencies": { "koa": "^1.1.2" diff --git a/test/plugins/test-trace-knex.ts b/test/plugins/test-trace-knex.ts index e78deef5c..8b3a62e76 100644 --- a/test/plugins/test-trace-knex.ts +++ b/test/plugins/test-trace-knex.ts @@ -13,263 +13,239 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -'use strict'; -import { TraceLabels } from '../../src/trace-labels'; +import * as assert from 'assert'; +import * as knexTypes from 'knex'; -var assert = require('assert'); +import {Tracer} from '../../src/plugin-types'; +import {TraceLabels} from '../../src/trace-labels'; +import * as traceTestModule from '../trace'; +import {describeInterop, hasContext, wait} from '../utils'; -var common = require('./common'/*.js*/); +const TABLE_NAME = 't'; -var RESULT_SIZE = 5; -var TABLE_NAME = 't'; - -var obj = { +const obj = { k: 1, v: 'obj' }; -var versions = { - knex10: './fixtures/knex0.10', - knex11: './fixtures/knex0.11', - knex12: './fixtures/knex0.12', - knex13: './fixtures/knex0.13' -}; +describeInterop('knex', (fixture) => { + const {version, parsedVersion} = fixture; -describe('test-trace-knex', function() { - var agent; - before(function() { - agent = require('../../..').start({ - projectId: '0', - logLevel: 2, - flushDelaySeconds: 1, - enhancedDatabaseReporting: true, - databaseResultReportingSize: RESULT_SIZE - }); - }); + let knex: knexTypes; + let tracer: Tracer; - Object.keys(versions).forEach(function(version) { - describe('with mysql and ' + version, function() { - var knex; - before(function() { - knex = require(versions[version])({ - client: 'mysql', - connection: require('../mysql-config') - }); - }); + before(() => { + traceTestModule.setCLSForTest(); + traceTestModule.setPluginLoaderForTest(); + tracer = traceTestModule.start({enhancedDatabaseReporting: true}); + knex = fixture.require()( + {client: 'mysql', connection: require('../mysql-config')}); + // For local test runs -- drop the table if it exists + return knex.schema.dropTable(TABLE_NAME).catch(() => {}); + }); - after(function() { - knex.destroy(); - }); + after(() => { + knex.destroy(); + traceTestModule.setCLSForTest(traceTestModule.TestCLS); + traceTestModule.setPluginLoaderForTest(traceTestModule.TestPluginLoader); + }); - beforeEach(function(done) { - knex.schema.createTable(TABLE_NAME, function(table) { - table.integer('k', 10); - table.string('v', 10); - }).then(function(result) { + beforeEach(() => { + return knex.schema + .createTable( + TABLE_NAME, + (table) => { + table.integer('k'); + table.string('v', 10); + }) + .then((result) => { assert.ok(result); - knex.insert(obj).into(TABLE_NAME).then(function(result) { + return knex.insert(obj).into(TABLE_NAME).then((result) => { assert.ok(result); - common.cleanTraces(); - done(); + traceTestModule.clearTraceData(); }); }); - }); + }); - afterEach(function(done) { - knex.schema.dropTable(TABLE_NAME).then(function(result) { - assert.ok(result); - common.cleanTraces(); - done(); - }); - }); + afterEach(() => { + return knex.schema.dropTable(TABLE_NAME).then((result) => { + assert.ok(result); + traceTestModule.clearTraceData(); + }); + }); - it('should perform basic operations using ' + version, function(done) { - common.runInTransaction(function(endRootSpan) { - knex(TABLE_NAME).select().then(function(res) { - endRootSpan(); - assert(res); - assert.strictEqual(res.length, 1); - assert.strictEqual(res[0].k, 1); - assert.strictEqual(res[0].v, 'obj'); - var spans = common.getMatchingSpans(function (span) { - return span.name === 'mysql-query'; - }); - if (version === 'knex11') { - assert.strictEqual(spans.length, 2); - assert.strictEqual(spans[0].labels.sql, 'SELECT 1'); - assert.strictEqual(spans[1].labels.sql, 'select * from `t`'); - } - else { - assert.strictEqual(spans.length, 1); - assert.strictEqual(spans[0].labels.sql, 'select * from `t`'); - } - done(); - }); + it('should perform basic operations using ' + version, () => { + return tracer.runInRootSpan({name: 'outer'}, (rootSpan) => { + return knex(TABLE_NAME).select().then((res) => { + rootSpan.endSpan(); + assert(res); + assert.strictEqual(res.length, 1); + assert.strictEqual(res[0].k, 1); + assert.strictEqual(res[0].v, 'obj'); + const spans = traceTestModule.getSpans((span) => { + return span.name === 'mysql-query'; }); + if (parsedVersion.minor === 11) { + assert.strictEqual(spans.length, 2); + assert.strictEqual(spans[0].labels.sql, 'SELECT 1'); + assert.strictEqual(spans[1].labels.sql, 'select * from `t`'); + } else { + assert.strictEqual(spans.length, 1); + assert.strictEqual(spans[0].labels.sql, 'select * from `t`'); + } }); + }); + }); - it('should propagate context using ' + version, function(done) { - common.runInTransaction(function(endRootSpan) { - knex.select().from(TABLE_NAME).then(function(res) { - assert.ok(common.hasContext()); - endRootSpan(); - done(); - }).catch(function(e) { - assert.ifError(e); - }); - }); + it('should propagate context using ' + version, () => { + return tracer.runInRootSpan({name: 'outer'}, (rootSpan) => { + return knex.select().from(TABLE_NAME).then((res) => { + assert.ok(hasContext()); + rootSpan.endSpan(); }); + }); + }); - it('should remove trace frames from stack using ' + version, function(done) { - common.runInTransaction(function(endRootSpan) { - knex.select().from(TABLE_NAME).then(function(res) { - endRootSpan(); - var spans = common.getMatchingSpans(function (span) { - return span.name === 'mysql-query'; - }); - var labels = spans[0].labels; - var stackTrace = JSON.parse(labels[TraceLabels.STACK_TRACE_DETAILS_KEY]); - // Ensure that our patch is on top of the stack - assert( - stackTrace.stack_frame[0].method_name.indexOf('createQuery_trace') !== -1); - done(); - }).catch(function(e) { - assert.ifError(e); - }); + it('should remove trace frames from stack using ' + version, () => { + return tracer.runInRootSpan({name: 'outer'}, (rootSpan) => { + return knex.select().from(TABLE_NAME).then((res) => { + rootSpan.endSpan(); + const spans = traceTestModule.getSpans((span) => { + return span.name === 'mysql-query'; }); + const labels = spans[0].labels; + const stackTrace = + JSON.parse(labels[TraceLabels.STACK_TRACE_DETAILS_KEY]); + // Ensure that our patch is on top of the stack + assert( + stackTrace.stack_frame[0].method_name.indexOf( + 'createQuery_trace') !== -1); }); + }); + }); - it('should work with events using ' + version, function(done) { - common.runInTransaction(function(endRootSpan) { - knex.select().from(TABLE_NAME).on('query-response', function(response, obj, builder) { - var row = response[0]; - assert.ok(row); - assert.strictEqual(row.k, 1); - assert.strictEqual(row.v, 'obj'); - }).on('query-error', function(err, obj) { - assert.ifError(err); - }).then(function(res) { - endRootSpan(); - var spans = common.getMatchingSpans(function (span) { + it('should work with events using ' + version, () => { + return tracer.runInRootSpan({name: 'outer'}, (rootSpan) => { + return knex.select() + .from(TABLE_NAME) + .on('query-response', + (response, obj, builder) => { + const row = response[0]; + assert.ok(row); + assert.strictEqual(row.k, 1); + assert.strictEqual(row.v, 'obj'); + }) + .on('query-error', + (err, obj) => { + assert.ifError(err); + }) + .then((res) => { + rootSpan.endSpan(); + const spans = traceTestModule.getSpans((span) => { return span.name === 'mysql-query'; }); - if (version === 'knex11') { + if (parsedVersion.minor === 11) { assert.strictEqual(spans.length, 2); assert.strictEqual(spans[0].labels.sql, 'SELECT 1'); assert.strictEqual(spans[1].labels.sql, 'select * from `t`'); - } - else { + } else { assert.strictEqual(spans.length, 1); assert.strictEqual(spans[0].labels.sql, 'select * from `t`'); } - done(); - }).catch(function(e) { - assert.ifError(e); }); - }); - }); + }); + }); - it('should work without events or callback using ' + version, function(done) { - common.runInTransaction(function(endRootSpan) { - knex.select().from(TABLE_NAME).then(function(result) { - setTimeout(function() { - endRootSpan(); - var spans = common.getMatchingSpans(function (span) { - return span.name === 'mysql-query'; - }); - if (version === 'knex11') { - assert.strictEqual(spans.length, 2); - assert.strictEqual(spans[0].labels.sql, 'SELECT 1'); - assert.strictEqual(spans[1].labels.sql, 'select * from `t`'); - } - else { - assert.strictEqual(spans.length, 1); - assert.strictEqual(spans[0].labels.sql, 'select * from `t`'); - } - done(); - }, 50); - }); + it('should work without events or callback using ' + version, () => { + return tracer.runInRootSpan({name: 'outer'}, (rootSpan) => { + return knex.select().from(TABLE_NAME).then(async (result) => { + await wait(50); + rootSpan.endSpan(); + const spans = traceTestModule.getSpans((span) => { + return span.name === 'mysql-query'; }); + if (parsedVersion.minor === 11) { + assert.strictEqual(spans.length, 2); + assert.strictEqual(spans[0].labels.sql, 'SELECT 1'); + assert.strictEqual(spans[1].labels.sql, 'select * from `t`'); + } else { + assert.strictEqual(spans.length, 1); + assert.strictEqual(spans[0].labels.sql, 'select * from `t`'); + } }); + }); + }); - it('should perform basic transaction using ' + version, function(done) { - var obj2 = { - k: 2, - v: 'obj2' - }; - common.runInTransaction(function(endRootSpan) { - knex.transaction(function(trx) { + it('should perform basic transaction using ' + version, () => { + const obj2 = {k: 2, v: 'obj2'}; + return tracer.runInRootSpan({name: 'outer'}, (rootSpan) => { + return knex + .transaction((trx) => { knex.insert(obj2) .into(TABLE_NAME) .transacting(trx) - .then(function(res) { + .then((res) => { return trx.select() - .from(TABLE_NAME) - .then(function(res) { - assert.strictEqual(res.length, 2); - assert.strictEqual(res[0].k, 1); - assert.strictEqual(res[0].v, 'obj'); - assert.strictEqual(res[1].k, 2); - assert.strictEqual(res[1].v, 'obj2'); - }).catch(function(err) { - assert.ifError(err); - }); + .from(TABLE_NAME) + .then((res) => { + assert.strictEqual(res.length, 2); + assert.strictEqual(res[0].k, 1); + assert.strictEqual(res[0].v, 'obj'); + assert.strictEqual(res[1].k, 2); + assert.strictEqual(res[1].v, 'obj2'); + }) + .catch((err) => { + assert.ifError(err); + }); }) - .then(function() { + .then(() => { trx.rollback(new Error('Rolling back')); - }).catch(function(err) { + }) + .catch((err) => { assert.ifError(err); }); - }).catch(function(err) { + }) + .catch((err) => { assert.ok(err); assert.strictEqual(err.message, 'Rolling back'); - knex.select() - .from(TABLE_NAME) - .then(function(res) { - endRootSpan(); - assert.strictEqual(res.length, 1); - assert.strictEqual(res[0].k, 1); - assert.strictEqual(res[0].v, 'obj'); - var spans = common.getMatchingSpans(function (span) { - return span.name === 'mysql-query'; - }); - var expectedCmds; - if (version === 'knex10') { - expectedCmds = ['BEGIN;', - 'insert into `t` (`k`, `v`) values (?, ?)', - 'select * from `t`', - 'ROLLBACK;', - 'select * from `t`']; - } - else if (version === 'knex11') { - expectedCmds = ['SELECT 1', - 'BEGIN;', - 'insert into `t` (`k`, `v`) values (?, ?)', - 'select * from `t`', - 'ROLLBACK;', - 'SELECT 1', - 'select * from `t`']; - } - else { - expectedCmds = ['insert into `t` (`k`, `v`) values (?, ?)', - 'select * from `t`', - 'ROLLBACK;', - 'select * from `t`']; - } - assert.strictEqual(expectedCmds.length, spans.length); - for (var i = 0; i < spans.length; i++) { + return knex.select().from(TABLE_NAME).then((res) => { + rootSpan.endSpan(); + assert.strictEqual(res.length, 1); + assert.strictEqual(res[0].k, 1); + assert.strictEqual(res[0].v, 'obj'); + const spans = traceTestModule.getSpans((span) => { + return span.name === 'mysql-query'; + }); + let expectedCmds; + if (parsedVersion.minor === 10 || parsedVersion.minor >= 14) { + expectedCmds = [ + /^BEGIN/, 'insert into `t` (`k`, `v`) values (?, ?)', + 'select * from `t`', /^ROLLBACK/, 'select * from `t`' + ]; + } else if (parsedVersion.minor === 11) { + expectedCmds = [ + 'SELECT 1', 'BEGIN;', + 'insert into `t` (`k`, `v`) values (?, ?)', + 'select * from `t`', 'ROLLBACK;', 'SELECT 1', + 'select * from `t`' + ]; + } else if (parsedVersion.minor < 14) { + expectedCmds = [ + 'insert into `t` (`k`, `v`) values (?, ?)', + 'select * from `t`', 'ROLLBACK;', 'select * from `t`' + ]; + } + assert.strictEqual(expectedCmds.length, spans.length); + for (let i = 0; i < spans.length; i++) { + if (expectedCmds[i] instanceof RegExp) { + assert.ok(!!spans[i].labels.sql.match(expectedCmds[i])); + } else { assert.strictEqual(spans[i].labels.sql, expectedCmds[i]); } - done(); - }).catch(function(err) { - assert.ifError(err); - }); + } + }); }); - }); - }); }); }); }); - -export default {}; diff --git a/test/utils.ts b/test/utils.ts index 56efa21fe..762974ba3 100644 --- a/test/utils.ts +++ b/test/utils.ts @@ -124,6 +124,8 @@ type PluginFixtures = { type FixtureHelper = { /** The module version encapsulated by the fixture. */ version: string; + /** The parsed module version. */ + parsedVersion: {major: number, minor: number, patch: number}; /** When called, loads the fixture. */ require: () => T; /** @@ -164,14 +166,15 @@ function getFixturesForModule(moduleName: string): Array> { .map(key => { const version = require(`./plugins/fixtures/${key}/node_modules/${ moduleName}/package.json`) - .version; + .version as string; + const parsedVersion = semver.parse(version)!; const getModule: () => T = () => require(`./plugins/fixtures/${key}`); // Convenience function -- returns if.skip if the selected module's // version is in the version range given. const skip = (it: Mocha.TestFunction, versionRange: string) => { return semver.satisfies(version, versionRange) ? it.skip : it; }; - return {version, require: getModule, skip}; + return {version, parsedVersion, require: getModule, skip}; }); } diff --git a/tsconfig.json b/tsconfig.json index 3d38ac45b..045b9922b 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -22,6 +22,7 @@ "test/plugins/test-trace-google-gax.ts", "test/plugins/test-trace-http.ts", "test/plugins/test-trace-http2.ts", + "test/plugins/test-trace-knex.ts", "test/logger.ts", "test/nocks.ts", "test/test-cls.ts",