|
1 | 1 | 'use strict' |
2 | 2 |
|
3 | 3 | const assert = require('node:assert/strict') |
| 4 | +const { execFileSync } = require('node:child_process') |
4 | 5 | const { mkdtempSync, mkdirSync, writeFileSync, rmSync } = require('node:fs') |
5 | 6 | const { tmpdir } = require('node:os') |
6 | 7 | const { join } = require('node:path') |
7 | 8 |
|
8 | | -const { describe, it, after } = require('mocha') |
| 9 | +const { describe, it, before, after } = require('mocha') |
9 | 10 | const sinon = require('sinon') |
10 | 11 | const proxyquire = require('proxyquire') |
11 | 12 |
|
@@ -143,6 +144,57 @@ describe('opentelemetry/api loader', () => { |
143 | 144 | }) |
144 | 145 | }) |
145 | 146 |
|
| 147 | +// The v5 path roots application resolution at the entrypoint. A directory entrypoint |
| 148 | +// (`node .`, `node path/to/app`) sets process.argv[1] to the directory while |
| 149 | +// require.main.filename holds the resolved file; createRequire rooted at the directory |
| 150 | +// resolves from its parent and misses the app's own @opentelemetry/api (issue #6882). |
| 151 | +// require.main can't be faked per-module in-process, so a real child launched with each |
| 152 | +// entrypoint shape is the only place the two globals diverge as they do in production. |
| 153 | +describe('opentelemetry/api loader application entrypoint (v5)', () => { |
| 154 | + const root = mkdtempSync(join(tmpdir(), 'dd-trace-otel-entry-')) |
| 155 | + const appDir = join(root, 'app') |
| 156 | + const appEntry = join(appDir, 'index.js') |
| 157 | + const apiDir = join(appDir, 'node_modules', '@opentelemetry', 'api') |
| 158 | + |
| 159 | + after(() => { |
| 160 | + rmSync(root, { recursive: true, force: true }) |
| 161 | + }) |
| 162 | + |
| 163 | + before(() => { |
| 164 | + mkdirSync(apiDir, { recursive: true }) |
| 165 | + writeFileSync(join(apiDir, 'package.json'), JSON.stringify({ name: '@opentelemetry/api', version: '1.9.0' })) |
| 166 | + writeFileSync(join(apiDir, 'index.js'), 'module.exports = { COPY: \'app\' }\n') |
| 167 | + // Forces the v5 path (DD_MAJOR mocked) so applicationRequire actually runs; the real |
| 168 | + // require.main / process.argv[1] of this child drive the resolution base under test. |
| 169 | + writeFileSync(appEntry, [ |
| 170 | + 'const proxyquire = require(' + JSON.stringify(require.resolve('proxyquire')) + ')', |
| 171 | + 'const { load } = proxyquire(' + JSON.stringify(require.resolve('../../src/opentelemetry/api')) + ', {', |
| 172 | + ' \'../../../../version\': { DD_MAJOR: 5, \'@noCallThru\': true },', |
| 173 | + '})', |
| 174 | + 'try { process.stdout.write(load().COPY ?? \'no-copy\') } catch { process.stdout.write(\'threw\') }', |
| 175 | + ].join('\n') + '\n') |
| 176 | + }) |
| 177 | + |
| 178 | + /** |
| 179 | + * Launches the fixture app with a given entrypoint argument and returns what copy of |
| 180 | + * `@opentelemetry/api` the loader resolved (`'app'` when the application's copy won). |
| 181 | + * |
| 182 | + * @param {string} entrypoint - The path passed to `node` (directory or file). |
| 183 | + * @returns {string} |
| 184 | + */ |
| 185 | + function resolvedCopy (entrypoint) { |
| 186 | + return execFileSync(process.execPath, [entrypoint], { encoding: 'utf8' }) |
| 187 | + } |
| 188 | + |
| 189 | + it('resolves the application copy for a directory entrypoint (node path/to/app)', () => { |
| 190 | + assert.strictEqual(resolvedCopy(appDir), 'app') |
| 191 | + }) |
| 192 | + |
| 193 | + it('resolves the application copy for a file entrypoint (node path/to/app/index.js)', () => { |
| 194 | + assert.strictEqual(resolvedCopy(appEntry), 'app') |
| 195 | + }) |
| 196 | +}) |
| 197 | + |
146 | 198 | // The stubbed suite above fakes node:fs, so it cannot catch a separator mismatch between |
147 | 199 | // path.join (platform-native) and the version walk. This suite writes a real package tree |
148 | 200 | // and lets the production node:fs/node:path run, so the walk is exercised on every platform. |
|
0 commit comments