Skip to content

Commit b222371

Browse files
authored
Merge branch 'main' into trentm-lint-examples-express
2 parents 45f3622 + b6d293a commit b222371

File tree

13 files changed

+314
-110
lines changed

13 files changed

+314
-110
lines changed

.github/workflows/test-all-versions.yml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -163,8 +163,6 @@ jobs:
163163
- uses: actions/setup-node@v4
164164
with:
165165
node-version: ${{ matrix.node }}
166-
- name: Set MySQL variables
167-
run: mysql --user=root --password=${MYSQL_ROOT_PASSWORD} --host=${MYSQL_HOST} --port=${MYSQL_PORT} -e "SET GLOBAL log_output='TABLE'; SET GLOBAL general_log = 1;" mysql
168166
- name: Install
169167
run: npm ci
170168
- name: Download Build Artifacts

.github/workflows/unit-test.yml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -176,8 +176,6 @@ jobs:
176176
- uses: actions/setup-node@v4
177177
with:
178178
node-version: ${{ matrix.node }}
179-
- name: Set MySQL variables
180-
run: mysql --user=root --password=${MYSQL_ROOT_PASSWORD} --host=${MYSQL_HOST} --port=${MYSQL_PORT} -e "SET GLOBAL log_output='TABLE'; SET GLOBAL general_log = 1;" mysql
181179
- name: Install
182180
run: npm ci
183181
- name: Download Build Artifacts

CONTRIBUTING.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,34 @@ The conventional commit type (in PR title) is very important to automatically bu
135135

136136
There is no need to update the CHANGELOG in a PR because it will be updated as part of the release process (see [RELEASING.md](RELEASING.md) for more details).
137137

138+
### Testing
139+
140+
Most unit tests case be run via:
141+
142+
```sh
143+
npm test
144+
```
145+
146+
However, some instrumentations require test-services to be running (e.g. the `instrumentation-mongodb` package requires a MongoDB server). Use the `test-services`-related npm scripts to start all required services in Docker and then run the tests with the appropriate configuration to use those services:
147+
148+
```sh
149+
npm run test-services:start # starts services in Docker
150+
npm run test:with-services-config # runs 'npm test' with envvars from test/test-services.env
151+
npm run test-services:stop # stops services in Docker
152+
```
153+
154+
If you only want to test a single package (e.g. the `instrumentation-mongodb`) you can `cd` into it and run the tests after you started the services.
155+
156+
```sh
157+
npm run test-services:start # starts services in Docker
158+
cd plugins/node/opentelemetry-instrumentation-mongodb # get into the instrumenation folder
159+
RUN_MONGODB_TESTS=1 npm test # run the test with the proper config (check each package)
160+
cd ../../.. # go back to root folder
161+
npm run test-services:stop # stops services in Docker
162+
```
163+
164+
NOTE: scripts for each package will be added to avoid extra consumption of resources and improve the development experience.
165+
138166
### Benchmarks
139167

140168
When two or more approaches must be compared, please write a benchmark in the benchmark/index.js module so that we can keep track of the most efficient algorithm.

package-lock.json

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@
1919
"test:browser": "nx run-many -t test:browser",
2020
"test:ci:changed": "nx affected -t test --base=origin/main --head=HEAD",
2121
"test-all-versions": "nx run-many -t test-all-versions",
22+
"test-services:start": "docker compose -f ./test/docker-compose.yaml up -d --wait",
23+
"test-services:stop": "docker compose -f ./test/docker-compose.yaml down",
24+
"test:with-services-env": "cross-env NODE_OPTIONS='-r dotenv/config' DOTENV_CONFIG_PATH=./test/test-services.env npm test",
2225
"changelog": "lerna-changelog",
2326
"lint": "nx run-many -t lint && npm run lint:deps && npm run lint:readme && npm run lint:markdown && npm run lint:semconv-deps",
2427
"lint:fix": "nx run-many -t lint:fix && npm run lint:markdown:fix",
@@ -43,6 +46,7 @@
4346
"devDependencies": {
4447
"@typescript-eslint/eslint-plugin": "5.62.0",
4548
"@typescript-eslint/parser": "5.62.0",
49+
"cross-env": "^7.0.3",
4650
"eslint": "8.7.0",
4751
"eslint-config-airbnb-base": "15.0.0",
4852
"eslint-config-prettier": "8.8.0",

plugins/node/instrumentation-dataloader/src/instrumentation.ts

Lines changed: 58 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -92,64 +92,75 @@ export class DataloaderInstrumentation extends InstrumentationBase<DataloaderIns
9292
return `${MODULE_NAME}.${operation} ${dataloaderName}`;
9393
}
9494

95+
private _wrapBatchLoadFn(
96+
batchLoadFn: Dataloader.BatchLoadFn<unknown, unknown>
97+
): Dataloader.BatchLoadFn<unknown, unknown> {
98+
const instrumentation = this;
99+
100+
return function patchedBatchLoadFn(
101+
this: DataloaderInternal,
102+
...args: Parameters<Dataloader.BatchLoadFn<unknown, unknown>>
103+
) {
104+
if (
105+
!instrumentation.isEnabled() ||
106+
!instrumentation.shouldCreateSpans()
107+
) {
108+
return batchLoadFn.call(this, ...args);
109+
}
110+
111+
const parent = context.active();
112+
const span = instrumentation.tracer.startSpan(
113+
instrumentation.getSpanName(this, 'batch'),
114+
{ links: this._batch?.spanLinks as Link[] | undefined },
115+
parent
116+
);
117+
118+
return context.with(trace.setSpan(parent, span), () => {
119+
return (batchLoadFn.apply(this, args) as Promise<unknown[]>)
120+
.then(value => {
121+
span.end();
122+
return value;
123+
})
124+
.catch(err => {
125+
span.recordException(err);
126+
span.setStatus({
127+
code: SpanStatusCode.ERROR,
128+
message: err.message,
129+
});
130+
span.end();
131+
throw err;
132+
});
133+
});
134+
};
135+
}
136+
95137
private _getPatchedConstructor(
96138
constructor: typeof Dataloader
97139
): typeof Dataloader {
98-
const prototype = constructor.prototype;
99140
const instrumentation = this;
141+
const prototype = constructor.prototype;
142+
143+
if (!instrumentation.isEnabled()) {
144+
return constructor;
145+
}
100146

101147
function PatchedDataloader(
148+
this: DataloaderInternal,
102149
...args: ConstructorParameters<typeof constructor>
103150
) {
104-
const inst = new constructor(...args) as DataloaderInternal;
105-
106-
if (!instrumentation.isEnabled()) {
107-
return inst;
108-
}
151+
// BatchLoadFn is the first constructor argument
152+
// https://github.com/graphql/dataloader/blob/77c2cd7ca97e8795242018ebc212ce2487e729d2/src/index.js#L47
153+
if (typeof args[0] === 'function') {
154+
if (isWrapped(args[0])) {
155+
instrumentation._unwrap(args, 0);
156+
}
109157

110-
if (isWrapped(inst._batchLoadFn)) {
111-
instrumentation._unwrap(inst, '_batchLoadFn');
158+
args[0] = instrumentation._wrapBatchLoadFn(
159+
args[0]
160+
) as Dataloader.BatchLoadFn<unknown, unknown>;
112161
}
113162

114-
instrumentation._wrap(inst, '_batchLoadFn', original => {
115-
return function patchedBatchLoadFn(
116-
this: DataloaderInternal,
117-
...args: Parameters<Dataloader.BatchLoadFn<unknown, unknown>>
118-
) {
119-
if (
120-
!instrumentation.isEnabled() ||
121-
!instrumentation.shouldCreateSpans()
122-
) {
123-
return original.call(this, ...args);
124-
}
125-
126-
const parent = context.active();
127-
const span = instrumentation.tracer.startSpan(
128-
instrumentation.getSpanName(inst, 'batch'),
129-
{ links: this._batch?.spanLinks as Link[] | undefined },
130-
parent
131-
);
132-
133-
return context.with(trace.setSpan(parent, span), () => {
134-
return (original.apply(this, args) as Promise<unknown[]>)
135-
.then(value => {
136-
span.end();
137-
return value;
138-
})
139-
.catch(err => {
140-
span.recordException(err);
141-
span.setStatus({
142-
code: SpanStatusCode.ERROR,
143-
message: err.message,
144-
});
145-
span.end();
146-
throw err;
147-
});
148-
});
149-
};
150-
});
151-
152-
return inst;
163+
return constructor.apply(this, args);
153164
}
154165

155166
PatchedDataloader.prototype = prototype;

plugins/node/instrumentation-dataloader/test/dataloader.test.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -619,4 +619,25 @@ describe('DataloaderInstrumentation', () => {
619619
assert.deepStrictEqual(await alternativeDataloader.loadMany(['test']), [1]);
620620
assert.strictEqual(memoryExporter.getFinishedSpans().length, 5);
621621
});
622+
623+
it('should not prune custom methods', async () => {
624+
class CustomDataLoader extends Dataloader<string, string> {
625+
constructor() {
626+
super(async keys => keys.map((_, idx) => getMd5HashFromIdx(idx)));
627+
}
628+
629+
public async customLoad() {
630+
return this.load('test');
631+
}
632+
}
633+
634+
const customDataloader = new CustomDataLoader();
635+
await customDataloader.customLoad();
636+
637+
assert.strictEqual(memoryExporter.getFinishedSpans().length, 2);
638+
const [batchSpan, loadSpan] = memoryExporter.getFinishedSpans();
639+
640+
assert.strictEqual(loadSpan.name, 'dataloader.load');
641+
assert.strictEqual(batchSpan.name, 'dataloader.batch');
642+
});
622643
});

plugins/node/opentelemetry-instrumentation-mysql/test/index.metrics.test.ts

Lines changed: 4 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ import {
2424
} from '@opentelemetry/sdk-metrics';
2525
import * as assert from 'assert';
2626
import { MySQLInstrumentation } from '../src';
27-
import * as testUtils from '@opentelemetry/contrib-test-utils';
2827
import { registerInstrumentationTesting } from '@opentelemetry/contrib-test-utils';
2928

3029
const instrumentation = registerInstrumentationTesting(
@@ -62,9 +61,9 @@ import * as mysqlTypes from 'mysql';
6261
describe('[email protected]', () => {
6362
let otelTestingMeterProvider;
6463
let inMemoryMetricsExporter: InMemoryMetricExporter;
65-
const testMysql = process.env.RUN_MYSQL_TESTS; // For CI: assumes local mysql db is already available
66-
const testMysqlLocally = process.env.RUN_MYSQL_TESTS_LOCAL; // For local: spins up local mysql db via docker
67-
const shouldTest = testMysql || testMysqlLocally; // Skips these tests if false (default)
64+
// assumes local mysql db is already available in CI or
65+
// using `npm run test-services:start` script at the root folder
66+
const shouldTest = process.env.RUN_MYSQL_TESTS;
6867

6968
function initMeterProvider() {
7069
inMemoryMetricsExporter = new InMemoryMetricExporter(
@@ -90,22 +89,7 @@ describe('[email protected]', () => {
9089
console.log('Skipping test-mysql for metrics.');
9190
this.skip();
9291
}
93-
94-
if (testMysqlLocally) {
95-
testUtils.startDocker('mysql');
96-
// wait 15 seconds for docker container to start
97-
this.timeout(20000);
98-
setTimeout(done, 15000);
99-
} else {
100-
done();
101-
}
102-
});
103-
104-
after(function () {
105-
if (testMysqlLocally) {
106-
this.timeout(5000);
107-
testUtils.cleanUpDocker('mysql');
108-
}
92+
done();
10993
});
11094

11195
describe('#Pool - metrics', () => {

plugins/node/opentelemetry-instrumentation-mysql/test/index.test.ts

Lines changed: 3 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ import {
2525
SEMATTRS_NET_PEER_NAME,
2626
SEMATTRS_NET_PEER_PORT,
2727
} from '@opentelemetry/semantic-conventions';
28-
import * as testUtils from '@opentelemetry/contrib-test-utils';
2928
import {
3029
BasicTracerProvider,
3130
InMemorySpanExporter,
@@ -54,9 +53,9 @@ describe('[email protected]', () => {
5453
let connection: mysqlTypes.Connection;
5554
let pool: mysqlTypes.Pool;
5655
let poolCluster: mysqlTypes.PoolCluster;
57-
const testMysql = process.env.RUN_MYSQL_TESTS; // For CI: assumes local mysql db is already available
58-
const testMysqlLocally = process.env.RUN_MYSQL_TESTS_LOCAL; // For local: spins up local mysql db via docker
59-
const shouldTest = testMysql || testMysqlLocally; // Skips these tests if false (default)
56+
// assumes local mysql db is already available in CI or
57+
// using `npm run test-services:start` script at the root folder
58+
const shouldTest = process.env.RUN_MYSQL_TESTS;
6059
const memoryExporter = new InMemorySpanExporter();
6160
const provider = new BasicTracerProvider({
6261
spanProcessors: [new SimpleSpanProcessor(memoryExporter)],
@@ -68,25 +67,11 @@ describe('[email protected]', () => {
6867
// https://github.com/mochajs/mocha/issues/2683#issuecomment-375629901
6968
this.test!.parent!.pending = true;
7069
this.skip();
71-
}
72-
73-
if (testMysqlLocally) {
74-
testUtils.startDocker('mysql');
75-
// wait 15 seconds for docker container to start
76-
this.timeout(20000);
77-
setTimeout(done, 15000);
7870
} else {
7971
done();
8072
}
8173
});
8274

83-
after(function () {
84-
if (testMysqlLocally) {
85-
this.timeout(5000);
86-
testUtils.cleanUpDocker('mysql');
87-
}
88-
});
89-
9075
beforeEach(() => {
9176
instrumentation.disable();
9277
contextManager = new AsyncLocalStorageContextManager().enable();

plugins/node/opentelemetry-instrumentation-mysql2/test/mysql.test.ts

Lines changed: 30 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -64,28 +64,38 @@ interface Result extends RowDataPacket {
6464
solution: number;
6565
}
6666

67-
describe('mysql2', () => {
68-
const testMysql = process.env.RUN_MYSQL_TESTS; // For CI: assumes local mysql db is already available
69-
const testMysqlLocally = process.env.RUN_MYSQL_TESTS_LOCAL; // For local: spins up local mysql db via docker
70-
const shouldTest = testMysql || testMysqlLocally; // Skips these tests if false (default)
71-
72-
before(function (done) {
73-
if (testMysqlLocally) {
74-
testUtils.startDocker('mysql');
75-
// wait 15 seconds for docker container to start
76-
this.timeout(20000);
77-
setTimeout(done, 15000);
78-
} else {
79-
done();
80-
}
67+
// Helper function to setup the database
68+
const execPromise = (conn: Connection, command: string) => {
69+
return new Promise<void>((res, rej) => {
70+
conn.execute(command, err => {
71+
if (err) rej(err);
72+
else res();
73+
});
8174
});
75+
};
8276

83-
after(function (done) {
84-
if (testMysqlLocally) {
85-
this.timeout(5000);
86-
testUtils.cleanUpDocker('mysql');
77+
describe('mysql2', () => {
78+
// assumes local mysql db is already available in CI or
79+
// using `npm run test-services:start` script at the root folder
80+
const shouldTest = process.env.RUN_MYSQL_TESTS;
81+
82+
before(async function () {
83+
const connection = createConnection({
84+
port,
85+
user: 'root',
86+
host,
87+
password: rootPassword,
88+
database,
89+
});
90+
try {
91+
await execPromise(connection, "SET GLOBAL log_output='TABLE'");
92+
await execPromise(connection, 'SET GLOBAL general_log = 1');
93+
} catch (execErr) {
94+
console.error('MySQL seup error: ', execErr);
95+
this.skip();
96+
} finally {
97+
connection.end();
8798
}
88-
done();
8999
});
90100

91101
describe('callback API', () => {
@@ -358,7 +368,7 @@ describe('mysql2', () => {
358368
it('should not add comment by default', done => {
359369
const span = provider.getTracer('default').startSpan('test span');
360370
context.with(trace.setSpan(context.active(), span), () => {
361-
connection.query('SELECT 1+1 as solution', () => {
371+
connection.query('SELECT 1+1 as solution', (e, r) => {
362372
const spans = memoryExporter.getFinishedSpans();
363373
assert.strictEqual(spans.length, 1);
364374
getLastQueries(1).then(([query]) => {

0 commit comments

Comments
 (0)