diff --git a/.github/workflows/test-all-versions.yml b/.github/workflows/test-all-versions.yml index 3029878065..83a261dbfb 100644 --- a/.github/workflows/test-all-versions.yml +++ b/.github/workflows/test-all-versions.yml @@ -163,8 +163,6 @@ jobs: - uses: actions/setup-node@v4 with: node-version: ${{ matrix.node }} - - name: Set MySQL variables - 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 - name: Install run: npm ci - name: Download Build Artifacts diff --git a/.github/workflows/unit-test.yml b/.github/workflows/unit-test.yml index 68826df198..d2fe1de428 100644 --- a/.github/workflows/unit-test.yml +++ b/.github/workflows/unit-test.yml @@ -176,8 +176,6 @@ jobs: - uses: actions/setup-node@v4 with: node-version: ${{ matrix.node }} - - name: Set MySQL variables - 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 - name: Install run: npm ci - name: Download Build Artifacts diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e1ee8e3479..95e527646e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -135,6 +135,34 @@ The conventional commit type (in PR title) is very important to automatically bu 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). +### Testing + +Most unit tests case be run via: + +```sh +npm test +``` + +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: + +```sh +npm run test-services:start # starts services in Docker +npm run test:with-services-config # runs 'npm test' with envvars from test/test-services.env +npm run test-services:stop # stops services in Docker +``` + +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. + +```sh +npm run test-services:start # starts services in Docker +cd plugins/node/opentelemetry-instrumentation-mongodb # get into the instrumenation folder +RUN_MONGODB_TESTS=1 npm test # run the test with the proper config (check each package) +cd ../../.. # go back to root folder +npm run test-services:stop # stops services in Docker +``` + +NOTE: scripts for each package will be added to avoid extra consumption of resources and improve the development experience. + ### Benchmarks 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. diff --git a/package-lock.json b/package-lock.json index a9d1a85955..5c0c1b102e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,6 +19,7 @@ "devDependencies": { "@typescript-eslint/eslint-plugin": "5.62.0", "@typescript-eslint/parser": "5.62.0", + "cross-env": "^7.0.3", "eslint": "8.7.0", "eslint-config-airbnb-base": "15.0.0", "eslint-config-prettier": "8.8.0", diff --git a/package.json b/package.json index 0c36977163..99b30a22cd 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,9 @@ "test:browser": "nx run-many -t test:browser", "test:ci:changed": "nx affected -t test --base=origin/main --head=HEAD", "test-all-versions": "nx run-many -t test-all-versions", + "test-services:start": "docker compose -f ./test/docker-compose.yaml up -d --wait", + "test-services:stop": "docker compose -f ./test/docker-compose.yaml down", + "test:with-services-env": "cross-env NODE_OPTIONS='-r dotenv/config' DOTENV_CONFIG_PATH=./test/test-services.env npm test", "changelog": "lerna-changelog", "lint": "nx run-many -t lint && npm run lint:deps && npm run lint:readme && npm run lint:markdown && npm run lint:semconv-deps", "lint:fix": "nx run-many -t lint:fix && npm run lint:markdown:fix", @@ -43,6 +46,7 @@ "devDependencies": { "@typescript-eslint/eslint-plugin": "5.62.0", "@typescript-eslint/parser": "5.62.0", + "cross-env": "^7.0.3", "eslint": "8.7.0", "eslint-config-airbnb-base": "15.0.0", "eslint-config-prettier": "8.8.0", diff --git a/plugins/node/opentelemetry-instrumentation-mysql/test/index.metrics.test.ts b/plugins/node/opentelemetry-instrumentation-mysql/test/index.metrics.test.ts index 3661904b5b..89c40c891b 100644 --- a/plugins/node/opentelemetry-instrumentation-mysql/test/index.metrics.test.ts +++ b/plugins/node/opentelemetry-instrumentation-mysql/test/index.metrics.test.ts @@ -24,7 +24,6 @@ import { } from '@opentelemetry/sdk-metrics'; import * as assert from 'assert'; import { MySQLInstrumentation } from '../src'; -import * as testUtils from '@opentelemetry/contrib-test-utils'; import { registerInstrumentationTesting } from '@opentelemetry/contrib-test-utils'; const instrumentation = registerInstrumentationTesting( @@ -62,9 +61,9 @@ import * as mysqlTypes from 'mysql'; describe('mysql@2.x-Metrics', () => { let otelTestingMeterProvider; let inMemoryMetricsExporter: InMemoryMetricExporter; - const testMysql = process.env.RUN_MYSQL_TESTS; // For CI: assumes local mysql db is already available - const testMysqlLocally = process.env.RUN_MYSQL_TESTS_LOCAL; // For local: spins up local mysql db via docker - const shouldTest = testMysql || testMysqlLocally; // Skips these tests if false (default) + // assumes local mysql db is already available in CI or + // using `npm run test-services:start` script at the root folder + const shouldTest = process.env.RUN_MYSQL_TESTS; function initMeterProvider() { inMemoryMetricsExporter = new InMemoryMetricExporter( @@ -90,22 +89,7 @@ describe('mysql@2.x-Metrics', () => { console.log('Skipping test-mysql for metrics.'); this.skip(); } - - if (testMysqlLocally) { - testUtils.startDocker('mysql'); - // wait 15 seconds for docker container to start - this.timeout(20000); - setTimeout(done, 15000); - } else { - done(); - } - }); - - after(function () { - if (testMysqlLocally) { - this.timeout(5000); - testUtils.cleanUpDocker('mysql'); - } + done(); }); describe('#Pool - metrics', () => { diff --git a/plugins/node/opentelemetry-instrumentation-mysql/test/index.test.ts b/plugins/node/opentelemetry-instrumentation-mysql/test/index.test.ts index 122c1cb2ac..cd0ccc6e7f 100644 --- a/plugins/node/opentelemetry-instrumentation-mysql/test/index.test.ts +++ b/plugins/node/opentelemetry-instrumentation-mysql/test/index.test.ts @@ -25,7 +25,6 @@ import { SEMATTRS_NET_PEER_NAME, SEMATTRS_NET_PEER_PORT, } from '@opentelemetry/semantic-conventions'; -import * as testUtils from '@opentelemetry/contrib-test-utils'; import { BasicTracerProvider, InMemorySpanExporter, @@ -54,9 +53,9 @@ describe('mysql@2.x-Tracing', () => { let connection: mysqlTypes.Connection; let pool: mysqlTypes.Pool; let poolCluster: mysqlTypes.PoolCluster; - const testMysql = process.env.RUN_MYSQL_TESTS; // For CI: assumes local mysql db is already available - const testMysqlLocally = process.env.RUN_MYSQL_TESTS_LOCAL; // For local: spins up local mysql db via docker - const shouldTest = testMysql || testMysqlLocally; // Skips these tests if false (default) + // assumes local mysql db is already available in CI or + // using `npm run test-services:start` script at the root folder + const shouldTest = process.env.RUN_MYSQL_TESTS; const memoryExporter = new InMemorySpanExporter(); const provider = new BasicTracerProvider({ spanProcessors: [new SimpleSpanProcessor(memoryExporter)], @@ -68,25 +67,11 @@ describe('mysql@2.x-Tracing', () => { // https://github.com/mochajs/mocha/issues/2683#issuecomment-375629901 this.test!.parent!.pending = true; this.skip(); - } - - if (testMysqlLocally) { - testUtils.startDocker('mysql'); - // wait 15 seconds for docker container to start - this.timeout(20000); - setTimeout(done, 15000); } else { done(); } }); - after(function () { - if (testMysqlLocally) { - this.timeout(5000); - testUtils.cleanUpDocker('mysql'); - } - }); - beforeEach(() => { instrumentation.disable(); contextManager = new AsyncLocalStorageContextManager().enable(); diff --git a/plugins/node/opentelemetry-instrumentation-mysql2/test/mysql.test.ts b/plugins/node/opentelemetry-instrumentation-mysql2/test/mysql.test.ts index 5874b69adc..1169e1e3d3 100644 --- a/plugins/node/opentelemetry-instrumentation-mysql2/test/mysql.test.ts +++ b/plugins/node/opentelemetry-instrumentation-mysql2/test/mysql.test.ts @@ -64,28 +64,38 @@ interface Result extends RowDataPacket { solution: number; } -describe('mysql2', () => { - const testMysql = process.env.RUN_MYSQL_TESTS; // For CI: assumes local mysql db is already available - const testMysqlLocally = process.env.RUN_MYSQL_TESTS_LOCAL; // For local: spins up local mysql db via docker - const shouldTest = testMysql || testMysqlLocally; // Skips these tests if false (default) - - before(function (done) { - if (testMysqlLocally) { - testUtils.startDocker('mysql'); - // wait 15 seconds for docker container to start - this.timeout(20000); - setTimeout(done, 15000); - } else { - done(); - } +// Helper function to setup the database +const execPromise = (conn: Connection, command: string) => { + return new Promise((res, rej) => { + conn.execute(command, err => { + if (err) rej(err); + else res(); + }); }); +}; - after(function (done) { - if (testMysqlLocally) { - this.timeout(5000); - testUtils.cleanUpDocker('mysql'); +describe('mysql2', () => { + // assumes local mysql db is already available in CI or + // using `npm run test-services:start` script at the root folder + const shouldTest = process.env.RUN_MYSQL_TESTS; + + before(async function () { + const connection = createConnection({ + port, + user: 'root', + host, + password: rootPassword, + database, + }); + try { + await execPromise(connection, "SET GLOBAL log_output='TABLE'"); + await execPromise(connection, 'SET GLOBAL general_log = 1'); + } catch (execErr) { + console.error('MySQL seup error: ', execErr); + this.skip(); + } finally { + connection.end(); } - done(); }); describe('callback API', () => { @@ -358,7 +368,7 @@ describe('mysql2', () => { it('should not add comment by default', done => { const span = provider.getTracer('default').startSpan('test span'); context.with(trace.setSpan(context.active(), span), () => { - connection.query('SELECT 1+1 as solution', () => { + connection.query('SELECT 1+1 as solution', (e, r) => { const spans = memoryExporter.getFinishedSpans(); assert.strictEqual(spans.length, 1); getLastQueries(1).then(([query]) => { diff --git a/plugins/node/opentelemetry-instrumentation-pg/test/pg.test.ts b/plugins/node/opentelemetry-instrumentation-pg/test/pg.test.ts index 5b9b6f4f19..267e0b968a 100644 --- a/plugins/node/opentelemetry-instrumentation-pg/test/pg.test.ts +++ b/plugins/node/opentelemetry-instrumentation-pg/test/pg.test.ts @@ -64,7 +64,7 @@ const memoryExporter = new InMemorySpanExporter(); const CONFIG = { user: process.env.POSTGRES_USER || 'postgres', password: process.env.POSTGRES_PASSWORD || 'postgres', - database: process.env.POSTGRES_DB || 'postgres', + database: process.env.POSTGRES_DB || 'otel_pg_database', host: process.env.POSTGRES_HOST || 'localhost', port: process.env.POSTGRES_PORT ? parseInt(process.env.POSTGRES_PORT, 10) diff --git a/test/docker-compose.yaml b/test/docker-compose.yaml new file mode 100644 index 0000000000..973d500839 --- /dev/null +++ b/test/docker-compose.yaml @@ -0,0 +1,121 @@ +# A `docker compose` config file to run tests services for testing +# `@opentelemetry/instrumentation-*` locally. +# +# Note: This isn't used in CI. CI uses GitHub Actions' `services: ...` for +# defining test services. +# +# Usage: +# npm run test-services:start [services...] +# npm run test-services:stop [services...] + +name: opentelemetry-js-contrib-test-services + +services: + cassandra: + image: cassandra:3 + environment: + MAX_HEAP_SIZE: "1G" + HEAP_NEWSIZE: 400m + ports: + - "9042:9042" + healthcheck: + test: ["CMD-SHELL", "[ $$(nodetool statusgossip) = running ]"] + interval: 1s + timeout: 10s + retries: 30 + + memcached: + image: memcached:1.6.37-alpine + ports: + - 11211:11211 + + mongodb: + image: mongo:7 + ports: + - "27017:27017" + healthcheck: + test: ["CMD", "mongosh", "--eval", "db.runCommand('ping').ok", "--quiet"] + interval: 1s + timeout: 10s + retries: 30 + + mssql: + # Tags listed at https://hub.docker.com/r/microsoft/mssql-server + # Docs: https://learn.microsoft.com/en-us/sql/linux/quickstart-install-connect-docker + image: mcr.microsoft.com/mssql/server:2022-CU14-ubuntu-22.04 + platform: linux/amd64 + environment: + - ACCEPT_EULA=Y + - MSSQL_SA_PASSWORD=mssql_passw0rd + ports: + - "1433:1433" + healthcheck: + test: ["CMD", "/opt/mssql-tools18/bin/sqlcmd", "-C", "-S", "mssql", "-U", "sa", "-P", "mssql_passw0rd", "-Q", "select 1"] + interval: 1s + timeout: 10s + retries: 30 + + mysql: + image: mysql:5.7 + # No ARM64 image layer. See https://stackoverflow.com/a/65592942 + platform: linux/x86_64 + environment: + MYSQL_USER: "otel" + MYSQL_PASSWORD: "secret" + MYSQL_DATABASE: "otel_mysql_database" + MYSQL_ROOT_PASSWORD: "rootpw" + ports: + - "33306:3306" + healthcheck: + test: ["CMD", "mysqladmin" ,"ping"] + interval: 1s + timeout: 10s + retries: 30 + + oracledb: + image: gvenzl/oracle-free:slim + environment: + APP_USER: "otel" + APP_USER_PASSWORD: "secret" + ORACLE_PASSWORD: "oracle" + ports: + - 1521:1521 + healthcheck: + test: ["CMD", "sqlplus" ,"system/oracle@//localhost/FREEPDB1"] + interval: 10s + timeout: 5s + retries: 30 + + postgres: + # https://github.com/docker-library/docs/blob/master/postgres/README.md#how-to-extend-this-image + image: postgres:16-alpine + ports: + - "54320:5432" + environment: + POSTGRES_HOST_AUTH_METHOD: "trust" + POSTGRES_USER: "postgres" + POSTGRES_DB: "otel_pg_database" + POSTGRES_PASSWORD: "postgres" + healthcheck: + test: ["CMD", "pg_isready"] + interval: 1s + timeout: 10s + retries: 30 + + rabbitmq: + image: rabbitmq:3 + environment: + RABBITMQ_DEFAULT_USER: "username" + RABBITMQ_DEFAULT_PASS: "password" + ports: + - "22221:5672" + + redis: + image: redis:7 + ports: + - "6379:6379" + healthcheck: + test: ["CMD", "redis-cli", "ping"] + interval: 1s + timeout: 10s + retries: 30 diff --git a/test/test-services.env b/test/test-services.env new file mode 100644 index 0000000000..4f8a62f10b --- /dev/null +++ b/test/test-services.env @@ -0,0 +1,43 @@ +RUN_CASSANDRA_TESTS=1 +CASSANDRA_HOST=localhostç + +RUN_MEMCACHED_TESTS=1 +OPENTELEMETRY_MEMCACHED_HOST=localhost +OPENTELEMETRY_MEMCACHED_PORT=11211 + +RUN_MONGODB_TESTS=1 +MONGODB_DB=opentelemetry-tests +MONGODB_HOST=127.0.0.1 +MONGODB_PORT=27017 + +RUN_MSSQL_TESTS=1 +MSSQL_PASSWORD=mssql_passw0rd + +RUN_MYSQL_TESTS=1 +MYSQL_DATABASE=otel_mysql_database +MYSQL_HOST=127.0.0.1 +MYSQL_PASSWORD=secret +MYSQL_ROOT_PASSWORD=rootpw +MYSQL_PORT=33306 +MYSQL_USER=otel + +RUN_ORACLEDB_TESTS=1 +ORACLE_HOSTNAME=localhost +ORACLE_PORT=1521 +ORACLE_CONNECTSTRING=localhost:1521/freepdb1 +ORACLE_USER=otel +ORACLE_PASSWORD=secret +ORACLE_SERVICENAME=FREEPDB1 + +RUN_POSTGRES_TESTS=1 +POSTGRES_DB=otel_pg_database +POSTGRES_HOST=localhost +POSTGRES_PORT=54320 +POSTGRES_USER=postgres +POSTGRES_PASSWORD=postgres + +RUN_REDIS_TESTS=1 +OPENTELEMETRY_REDIS_HOST=localhost +OPENTELEMETRY_REDIS_PORT=6379 + +RUN_RABBIT_TESTS=1 \ No newline at end of file