diff --git a/packages/collector/.gitignore b/packages/collector/.gitignore index 343d4a7b2a..01a1850e6b 100644 --- a/packages/collector/.gitignore +++ b/packages/collector/.gitignore @@ -11,4 +11,7 @@ test/tracing/misc/typescript/ts_esm/dist test/tracing/misc/typescript/ts_cjs/dist test/tracing/misc/esbuild-external/dist/ -test/tracing/misc/esbuild-external/.env \ No newline at end of file +test/tracing/misc/esbuild-external/.env + +test/tracing/opentelemetry/package-lock.json +test/tracing/opentelemetry/package.json diff --git a/packages/collector/test/test_util/ProcessControls.js b/packages/collector/test/test_util/ProcessControls.js index 3d4774a607..222b6854b4 100644 --- a/packages/collector/test/test_util/ProcessControls.js +++ b/packages/collector/test/test_util/ProcessControls.js @@ -56,7 +56,9 @@ class ProcessControls { } if (process.env.RUN_ESM && !opts.execArgv) { - const esmLoader = [`--import=${path.join(__dirname, '..', '..', 'esm-register.mjs')}`]; + const esmLoader = [ + `--import=${opts.esmLoaderPath ? opts.esmLoaderPath : path.join(__dirname, '..', '..', 'esm-register.mjs')}` + ]; try { // Custom appPath is provided, use that. here we check the exact file name for esm app diff --git a/packages/collector/test/tracing/opentelemetry/package.json b/packages/collector/test/tracing/opentelemetry/package.json deleted file mode 100644 index 4dd8d34f08..0000000000 --- a/packages/collector/test/tracing/opentelemetry/package.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "name": "opentelemetry-instrumentations-test", - "version": "1.0.0", - "author": "", - "license": "ISC", - "description": "", - "dependencies": { - "@opentelemetry/api": "1.9.0", - "@opentelemetry/api-v1.3.0": "npm:@opentelemetry/api@1.3.0" - } -} diff --git a/packages/collector/test/tracing/opentelemetry/tedious-app.js b/packages/collector/test/tracing/opentelemetry/tedious-app.js index ad4b733151..01ac63d8df 100644 --- a/packages/collector/test/tracing/opentelemetry/tedious-app.js +++ b/packages/collector/test/tracing/opentelemetry/tedious-app.js @@ -14,15 +14,19 @@ process.on('SIGTERM', () => { require('@instana/collector')(); const express = require('express'); const fs = require('fs'); +const path = require('path'); const { isCI } = require('@instana/core/test/test_util'); const port = require('../../test_util/app-port')(); - -// collector -> typeorm -> mssql v10 -> tedious v16 -// We cant install typeorm on root because we have mssql v11 installed on root. -// If we require tedious here, it will load the dependency from the -// node_modules folder of the collector package. -// TODO: https://jsw.ibm.com/browse/INSTA-7722 -const tedious = require('../../../../../node_modules/tedious'); +const tedious = require('tedious'); + +const tediousPath = require.resolve('tedious'); +const expectedLocalPath = path.resolve(__dirname, 'node_modules', 'tedious'); +if (!tediousPath.includes(expectedLocalPath)) { + throw new Error( + // eslint-disable-next-line max-len + `tedious must be loaded from local node_modules. Expected path containing: ${expectedLocalPath}, but got: ${tediousPath}` + ); +} const Connection = tedious.Connection; const Request = tedious.Request; diff --git a/packages/collector/test/tracing/opentelemetry/tedious-app.mjs b/packages/collector/test/tracing/opentelemetry/tedious-app.mjs new file mode 100644 index 0000000000..2ea1975f44 --- /dev/null +++ b/packages/collector/test/tracing/opentelemetry/tedious-app.mjs @@ -0,0 +1,148 @@ +/* + * (c) Copyright IBM Corp. 2025 + */ + +/* eslint-disable no-console */ + +// NOTE: c8 bug https://github.com/bcoe/c8/issues/166 +process.on('SIGTERM', () => { + process.disconnect(); + process.exit(0); +}); + +import express from 'express'; +import fs from 'fs'; +import bodyParser from 'body-parser'; +import { createRequire } from 'module'; +import { fileURLToPath } from 'url'; +import { dirname, resolve } from 'path'; +import testUtil from '../../../../core/test/test_util/index.js'; +import getAppPort from '../../test_util/app-port.js'; +const port = getAppPort(); +const isCI = testUtil.isCI; +import tedious from 'tedious'; +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); +const require = createRequire(import.meta.url); +const tediousPath = require.resolve('tedious'); +const expectedLocalPath = resolve(__dirname, 'node_modules', 'tedious'); +if (!tediousPath.includes(expectedLocalPath)) { + throw new Error( + `tedious must be loaded from local node_modules. Expected path containing: ${expectedLocalPath}, but got: ${tediousPath}` + ); +} + +const Connection = tedious.Connection; +const Request = tedious.Request; +const app = express(); + +app.use(bodyParser.json()); + +// Locally: +// To obtain the credentials for the Azure SQL Database, you can find them in 1password. Search for +// "Team Node.js: Azure SQL credentials", download the file and copy this to your CMD line: +// export AZURE_SQL_CONFIG=~/Downloads/nodejs-tracer-azure-sql-server.json +if (!isCI() && !process.env.AZURE_SQL_CONFIG) { + throw new Error('Please set the env variable `AZURE_SQL_CONFIG`.'); +} + +const azureConfig = process.env.AZURE_SQL_CONFIG + ? JSON.parse(fs.readFileSync(process.env.AZURE_SQL_CONFIG, 'utf-8')) + : null; + +const config = { + server: azureConfig?.AZURE_SQL_SERVER || process.env.AZURE_SQL_SERVER, + authentication: { + type: 'default', + options: { + userName: azureConfig?.AZURE_SQL_USERNAME || process.env.AZURE_SQL_USERNAME, + password: azureConfig?.AZURE_SQL_PWD || process.env.AZURE_SQL_PWD + } + }, + options: { + database: azureConfig?.AZURE_SQL_DATABASE || process.env.AZURE_SQL_DATABASE, + connectTimeout: 30000 + } +}; + +let connected = false; +let connection; + +const retryDelay = 30000; +const maxRetries = 2; +let currentRetry = 0; + +(function connectWithRetry() { + if (connection) { + connection.close(); + } + connection = new Connection(config); + connection.connect(); + + connection.on('connect', err => { + if (err) { + console.warn('Connection error', err); + if (currentRetry < maxRetries) { + currentRetry++; + console.warn(`Retrying connection after ${retryDelay} ms (Retry ${currentRetry}/${maxRetries})`); + setTimeout(connectWithRetry, retryDelay); + } else { + console.error('Maximum retries reached. Unable to establish a connection.'); + connection.close(); + } + } else { + connected = true; + console.warn('Connected to the database'); + } + }); +})(); + +const executeStatement = (query, isBatch, res) => { + const request = new Request(query, error => { + if (error) { + console.error('Error on executeStatement.', error); + res.status(500).send('Internal Server Error'); + } + }); + + request.on('requestCompleted', () => { + res.send('OK'); + }); + + if (isBatch) { + connection.execSqlBatch(request); + } else { + connection.execSql(request); + } +}; + +app.get('/', (req, res) => { + if (!connected) { + res.sendStatus(500); + } else { + res.sendStatus(200); + } +}); + +app.get('/packages', (req, res) => { + const query = 'SELECT * FROM packages'; + executeStatement(query, false, res); +}); + +app.delete('/packages', (req, res) => { + const id = 11; + const query = `DELETE FROM packages WHERE id = ${id}`; + executeStatement(query, false, res); +}); + +app.post('/packages/batch', (req, res) => { + const batchQuery = ` + INSERT INTO packages (id, name, version) VALUES (11, 'BatchPackage1', 1); + INSERT INTO packages (id, name, version) VALUES (11, 'BatchPackage2', 2); +`; + executeStatement(batchQuery, true, res); +}); +app.listen(port, () => { + // eslint-disable-next-line no-console + console.warn(`Listening on port: ${port}`); +}); diff --git a/packages/collector/test/tracing/opentelemetry/test.js b/packages/collector/test/tracing/opentelemetry/test.js index 609f4bec63..bf9dbcd733 100644 --- a/packages/collector/test/tracing/opentelemetry/test.js +++ b/packages/collector/test/tracing/opentelemetry/test.js @@ -35,6 +35,9 @@ mochaSuiteFn('opentelemetry tests', function () { return; } + execSync('rm -rf package-lock.json', { cwd: __dirname, stdio: 'inherit' }); + execSync('rm -rf package.json', { cwd: __dirname, stdio: 'inherit' }); + execSync('rm -rf node_modules', { cwd: __dirname, stdio: 'inherit' }); execSync('./preinstall.sh', { cwd: __dirname, stdio: 'inherit' }); }); @@ -53,18 +56,35 @@ mochaSuiteFn('opentelemetry tests', function () { if (process.env.INSTANA_TEST_SKIP_INSTALLING_DEPS === 'true') { return; } - execSync('npm install --no-save --no-package-lock --prefix ./ ./core.tgz', { + + execSync('npm install --save --prefix ./ ./core.tgz', { + cwd: __dirname, + stdio: 'inherit' + }); + + execSync('npm install --save --prefix ./ ./collector.tgz', { + cwd: __dirname, + stdio: 'inherit' + }); + + execSync('npm install --save --prefix ./ @opentelemetry/api@1.9.0', { cwd: __dirname, stdio: 'inherit' }); - execSync('npm install --no-save --no-package-lock --prefix ./ ./collector.tgz', { + execSync('npm install --save --prefix ./ "@opentelemetry/api-v1.3.0@npm:@opentelemetry/api@1.3.0"', { cwd: __dirname, stdio: 'inherit' }); }); + // TODO: Restify test is broken in v24. See Issue: https://github.com/restify/node-restify/issues/1984 - const restifyTest = semver.gte(process.versions.node, '24.0.0') ? describe.skip : describe; + let restifyTest = semver.gte(process.versions.node, '24.0.0') ? describe.skip : describe; + + if (process.env.RUN_ESM === 'true') { + restifyTest = describe.skip; + } + restifyTest('restify', function () { describe('opentelemetry is enabled', function () { globalAgent.setUpCleanUpHooks(); @@ -275,7 +295,12 @@ mochaSuiteFn('opentelemetry tests', function () { }); }); - describe('fs', function () { + let runFs = describe; + if (process.env.RUN_ESM === 'true') { + runFs = describe.skip; + } + + runFs('fs', function () { globalAgent.setUpCleanUpHooks(); const agentControls = globalAgent.instance; @@ -396,7 +421,12 @@ mochaSuiteFn('opentelemetry tests', function () { .then(() => retry(() => agentControls.getSpans().then(spans => expect(spans).to.be.empty)))); }); - describe('socket.io', function () { + let runSocketIo = describe; + if (process.env.RUN_ESM === 'true') { + runSocketIo = describe.skip; + } + + runSocketIo('socket.io', function () { globalAgent.setUpCleanUpHooks(); const agentControls = globalAgent.instance; let socketIOServerPort; @@ -567,10 +597,20 @@ mochaSuiteFn('opentelemetry tests', function () { this.timeout(1000 * 60 * 2); before(async () => { + if (process.env.INSTANA_TEST_SKIP_INSTALLING_DEPS !== 'true') { + const rootPackageJson = require('../../../../../package.json'); + const tediousVersion = rootPackageJson.devDependencies.tedious; + execSync(`npm i "tedious@${tediousVersion}" --prefix ./ --save`, { + cwd: __dirname, + stdio: 'inherit' + }); + } + controls = new ProcessControls({ appPath: path.join(__dirname, './tedious-app'), useGlobalAgent: true, cwd: __dirname, + esmLoaderPath: path.join(__dirname, 'node_modules', '@instana', 'collector', 'esm-register.mjs'), enableOtelIntegration: true, env: { OTEL_API_VERSION: version @@ -700,7 +740,12 @@ mochaSuiteFn('opentelemetry tests', function () { }); }); - describe('OracleDB', function () { + let runOracleDb = describe; + if (process.env.RUN_ESM === 'true') { + runOracleDb = describe.skip; + } + + runOracleDb('OracleDB', function () { this.timeout(1000 * 60); describe('opentelemetry is enabled', function () { @@ -836,12 +881,18 @@ mochaSuiteFn('opentelemetry tests', function () { }); }); - mochaSuiteFn('when otel sdk and instana is enabled', function () { + let runOtelSdkAndInstana = mochaSuiteFn; + if (process.env.RUN_ESM === 'true') { + runOtelSdkAndInstana = describe.skip; + } + + runOtelSdkAndInstana('when otel sdk and instana is enabled', function () { this.timeout(config.getTestTimeout() * 4); before(async () => { if (process.env.INSTANA_TEST_SKIP_INSTALLING_DEPS === 'true') { return; } + execSync('rm -rf ./otel-sdk-and-instana/node_modules', { cwd: __dirname, stdio: 'inherit' }); execSync('npm install --no-save --no-package-lock', { cwd: path.join(__dirname, './otel-sdk-and-instana'),