Skip to content

Commit a0e7521

Browse files
BridgeARkota65535
andauthored
fix(bun): do not change module objects and add bun smoke tests (#7498)
Co-authored-by: Tomohiko Ozawa <[email protected]>
1 parent 70ea1a3 commit a0e7521

File tree

14 files changed

+305
-1
lines changed

14 files changed

+305
-1
lines changed

.github/CODEOWNERS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,7 @@
219219
# Language Platform
220220
/* @DataDog/lang-platform-js
221221

222+
/integration-tests/bun/ @DataDog/lang-platform-js
222223
/integration-tests/init.spec.js @DataDog/lang-platform-js
223224
/integration-tests/package-guardrails.spec.js @DataDog/lang-platform-js
224225
/integration-tests/startup.spec.js @DataDog/lang-platform-js

.github/workflows/platform.yml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,19 @@ jobs:
6262
- run: ./node_modules/.bin/bun pm pack --gzip-level 0 --filename bun.tgz && tar -zxf bun.tgz -C bun
6363
- run: diff -r npm bun
6464

65+
bun-runtime:
66+
runs-on: ubuntu-latest
67+
steps:
68+
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
69+
- uses: ./.github/actions/node/active-lts
70+
- uses: ./.github/actions/install
71+
- run: yarn test:integration:bun
72+
- uses: DataDog/junit-upload-github-action@055560f63c405095e9228ba443eee7987e22bb94 # v2.1.1
73+
if: always() && github.actor != 'dependabot[bot]'
74+
with:
75+
api_key: ${{ secrets.DD_API_KEY }}
76+
service: dd-trace-js-tests
77+
6578
core:
6679
runs-on: ubuntu-latest
6780
steps:

integration-tests/bun/bun.spec.js

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
'use strict'
2+
3+
const assert = require('assert')
4+
const path = require('path')
5+
const { spawn } = require('child_process')
6+
7+
const { BUN } = require('../helpers/bun')
8+
const { FakeAgent, useSandbox, sandboxCwd } = require('../helpers')
9+
10+
/**
11+
* Spawn a file under the Bun runtime inside the test sandbox.
12+
*
13+
* @param {string} filename - Path relative to the sandbox root
14+
* @param {Record<string, string>} [env]
15+
* @returns {Promise<{ code: number | null, stdout: string, stderr: string }>}
16+
*/
17+
function runBun (filename, env = {}) {
18+
const cwd = sandboxCwd()
19+
20+
return new Promise((resolve, reject) => {
21+
const proc = spawn(BUN, [path.join(cwd, filename)], {
22+
cwd,
23+
stdio: 'pipe',
24+
env: {
25+
...process.env,
26+
DD_INSTRUMENTATION_TELEMETRY_ENABLED: 'false',
27+
...env,
28+
},
29+
})
30+
31+
let stdout = ''
32+
let stderr = ''
33+
34+
proc.stdout.on('data', (data) => { stdout += data })
35+
proc.stderr.on('data', (data) => { stderr += data })
36+
proc.once('error', reject)
37+
proc.once('exit', (code) => resolve({ code, stdout, stderr }))
38+
})
39+
}
40+
41+
/**
42+
* @param {{ code: number | null, stdout: string, stderr: string }} result
43+
*/
44+
function assertBunSuccess (result) {
45+
const { code, stdout, stderr } = result
46+
47+
assert.strictEqual(code, 0, `Process exited with code ${code}.\nstdout: ${stdout}\nstderr: ${stderr}`)
48+
assert.ok(stdout.includes('ok'), `Expected "ok" in stdout, got: ${stdout}`)
49+
}
50+
51+
describe('Bun runtime smoke tests', function () {
52+
this.timeout(30_000)
53+
54+
useSandbox()
55+
let agent
56+
57+
before(async function () {
58+
agent = await new FakeAgent().start()
59+
})
60+
61+
after(async () => {
62+
await agent?.stop()
63+
})
64+
65+
describe('init order compatibility', () => {
66+
it('should init tracer via CJS require', async function () {
67+
assertBunSuccess(await runBun('bun/init-cjs.js'))
68+
})
69+
70+
it('should init tracer after ESM JSON import (issue #7480)', async function () {
71+
assertBunSuccess(await runBun('bun/init-esm-json-import.mjs'))
72+
})
73+
74+
it('should init tracer when ESM imports dd-trace before package.json', async function () {
75+
assertBunSuccess(await runBun('bun/init-esm-dd-trace-first.mjs'))
76+
})
77+
78+
it('should init tracer when CJS requires package.json before init', async function () {
79+
assertBunSuccess(await runBun('bun/init-cjs-json-before-init.js'))
80+
})
81+
82+
it('should init tracer when CJS requires package.json after init', async function () {
83+
assertBunSuccess(await runBun('bun/init-cjs-json-after-init.js'))
84+
})
85+
})
86+
87+
it('should send trace payload with Bun runtime header', async () => {
88+
const messagePromise = agent.assertMessageReceived(({ headers, payload }) => {
89+
assert.strictEqual(headers['datadog-meta-lang-interpreter'], 'JavaScriptCore')
90+
assert.ok(Array.isArray(payload), 'Expected trace payload array')
91+
assert.ok(payload.length > 0, 'Expected at least one trace')
92+
}, 20_000)
93+
94+
const [bunResult] = await Promise.all([
95+
runBun('bun/export-span.js', {
96+
DD_TRACE_AGENT_URL: `http://127.0.0.1:${agent.port}`,
97+
}),
98+
messagePromise,
99+
])
100+
101+
assertBunSuccess(bunResult)
102+
})
103+
104+
it('should not crash when agent is unavailable', async () => {
105+
assertBunSuccess(await runBun('bun/init-agent-unavailable.js'))
106+
})
107+
108+
it('should emit app-started telemetry under Bun', async () => {
109+
const telemetryPromise = agent.assertTelemetryReceived('app-started', 20_000, 1)
110+
111+
const [bunResult] = await Promise.all([
112+
runBun('bun/init-telemetry.js', {
113+
DD_TRACE_AGENT_URL: `http://127.0.0.1:${agent.port}`,
114+
DD_INSTRUMENTATION_TELEMETRY_ENABLED: 'true',
115+
}),
116+
telemetryPromise,
117+
])
118+
119+
assertBunSuccess(bunResult)
120+
})
121+
122+
it('should auto-instrument basic HTTP traffic', async () => {
123+
const messagePromise = agent.assertMessageReceived(({ payload }) => {
124+
assert.ok(Array.isArray(payload), 'Expected trace payload array')
125+
const spans = payload.flat()
126+
assert.ok(
127+
spans.some(span => span.name === 'web.request' || span.name === 'http.request'),
128+
'Expected web.request or http.request span'
129+
)
130+
}, 20_000)
131+
132+
const [bunResult] = await Promise.all([
133+
runBun('bun/http-instrumentation.js', {
134+
DD_TRACE_AGENT_URL: `http://127.0.0.1:${agent.port}`,
135+
}),
136+
messagePromise,
137+
])
138+
139+
assertBunSuccess(bunResult)
140+
})
141+
})
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
'use strict'
2+
3+
const tracer = require('dd-trace')
4+
5+
tracer.init({
6+
startupLogs: false,
7+
url: process.env.DD_TRACE_AGENT_URL,
8+
flushInterval: 10,
9+
})
10+
11+
const span = tracer.startSpan('bun.smoke')
12+
span.finish()
13+
14+
setTimeout(() => {
15+
// eslint-disable-next-line no-console
16+
console.log('ok')
17+
process.exit()
18+
}, 300)
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
'use strict'
2+
3+
const tracer = require('dd-trace')
4+
5+
tracer.init({
6+
startupLogs: false,
7+
url: process.env.DD_TRACE_AGENT_URL,
8+
flushInterval: 10,
9+
})
10+
11+
const http = require('http')
12+
13+
const server = http.createServer((req, res) => {
14+
res.end('Hello World')
15+
})
16+
17+
server.listen(0, () => {
18+
const address = /** @type {import('net').AddressInfo} */ (server.address())
19+
const url = `http://127.0.0.1:${address.port}`
20+
21+
http.get(url, (res) => {
22+
res.on('data', () => {})
23+
res.on('end', () => {
24+
server.close(() => {
25+
setTimeout(() => {
26+
// eslint-disable-next-line no-console
27+
console.log('ok')
28+
process.exit(0)
29+
}, 300)
30+
})
31+
})
32+
})
33+
})
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
'use strict'
2+
3+
const tracer = require('dd-trace')
4+
5+
tracer.init({
6+
startupLogs: true,
7+
url: process.env.DD_TRACE_AGENT_URL || 'http://127.0.0.1:1',
8+
flushInterval: 10,
9+
})
10+
11+
const span = tracer.startSpan('bun.unavailable-agent')
12+
span.finish()
13+
14+
setTimeout(() => {
15+
// eslint-disable-next-line no-console
16+
console.log('ok')
17+
process.exit(0)
18+
}, 300)
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
'use strict'
2+
3+
const tracer = require('dd-trace')
4+
5+
tracer.init({ startupLogs: false })
6+
7+
const pkg = require('../package.json')
8+
9+
// eslint-disable-next-line no-console
10+
console.log(pkg.name || 'unnamed')
11+
// eslint-disable-next-line no-console
12+
console.log('ok')
13+
process.exit()
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
'use strict'
2+
3+
// package.json must be loaded before dd-trace to verify this order works.
4+
/* eslint-disable import/order */
5+
const pkg = require('../package.json')
6+
const tracer = require('dd-trace')
7+
/* eslint-enable import/order */
8+
9+
tracer.init({ startupLogs: false })
10+
11+
// eslint-disable-next-line no-console
12+
console.log(pkg.name || 'unnamed')
13+
// eslint-disable-next-line no-console
14+
console.log('ok')
15+
process.exit()

integration-tests/bun/init-cjs.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
'use strict'
2+
3+
const tracer = require('dd-trace')
4+
5+
tracer.init({ startupLogs: false })
6+
7+
// eslint-disable-next-line no-console
8+
console.log('ok')
9+
process.exit()
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import tracer from 'dd-trace'
2+
import pkg from '../package.json'
3+
4+
tracer.init({ startupLogs: false })
5+
6+
// eslint-disable-next-line no-console
7+
console.log(pkg.name || 'unnamed')
8+
// eslint-disable-next-line no-console
9+
console.log('ok')
10+
process.exit()

0 commit comments

Comments
 (0)