Skip to content

Commit c3ded24

Browse files
committed
test(crashtracker): replace express with core http
Remove express and body-parser dependencies in favor of Node.js core http module. This simplifies the test setup and eliminates third-party dependencies. The yarn install step is moved from the test runner script into the test itself.
1 parent 7c98394 commit c3ded24

File tree

4 files changed

+77
-541
lines changed

4 files changed

+77
-541
lines changed

scripts/test.sh

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,6 @@
22
set -e
33

44
run_test() {
5-
local dir
6-
dir=$(dirname "$1")
7-
if [ -f "${dir}/package.json" ]; then
8-
echo "Installing dependencies for $1"
9-
yarn --cwd "$dir" install
10-
fi
115
echo "Running $1"
126
node "$1"
137
}

test/crashtracker/index.js

Lines changed: 76 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -2,67 +2,84 @@
22

33
const assert = require('node:assert')
44
const { existsSync, rmSync } = require('node:fs')
5+
const http = require('node:http')
56
const path = require('node:path')
67
const { execSync, exec } = require('node:child_process')
78

8-
const bodyParser = require('body-parser')
9-
const express = require('express')
10-
11-
const cwd = __dirname
12-
const stdio = ['inherit', 'inherit', 'inherit']
13-
const uid = process.getuid()
14-
const gid = process.getgid()
15-
const opts = { cwd, stdio, uid, gid }
9+
let currentTest
10+
let PORT
1611

17-
const app = express()
12+
execSync('yarn install', getExecOptions())
1813

19-
rmSync(path.join(cwd, 'stdout.log'), { force: true })
20-
rmSync(path.join(cwd, 'stderr.log'), { force: true })
14+
rmSync(path.join(__dirname, 'stdout.log'), { force: true })
15+
rmSync(path.join(__dirname, 'stderr.log'), { force: true })
2116

2217
let timeout = setTimeout(() => {
23-
execSync('cat stdout.log', opts)
24-
execSync('cat stderr.log', opts)
18+
execSync('cat stdout.log', getExecOptions())
19+
execSync('cat stderr.log', getExecOptions())
2520

2621
throw new Error('No crash report received before timing out.')
2722
}, 10_000)
2823

29-
let currentTest
24+
const server = http.createServer((req, res) => {
25+
if (req.method !== 'POST' || req.url !== '/telemetry/proxy/api/v2/apmtelemetry') {
26+
res.writeHead(404).end()
27+
return
28+
}
3029

31-
app.use(bodyParser.json())
30+
const chunks = []
31+
req.on('data', (chunk) => chunks.push(chunk))
32+
req.on('end', () => {
33+
res.writeHead(200).end()
3234

33-
app.post('/telemetry/proxy/api/v2/apmtelemetry', (req, res) => {
34-
res.status(200).send()
35+
if (!currentTest) {
36+
throw new Error('Received unexpected crash report with no active test.')
37+
}
3538

36-
const logPayload = req.body.payload.logs[0]
37-
const tags = logPayload.tags ? logPayload.tags.split(',') : []
39+
const body = JSON.parse(Buffer.concat(chunks).toString())
40+
const logPayload = body.payload.logs[0]
3841

39-
// Only process crash reports (not pings)
40-
if (!logPayload.is_crash) {
41-
return
42-
}
42+
// Only process crash reports (not pings)
43+
if (!logPayload.is_crash) {
44+
return
45+
}
4346

44-
if (!currentTest) {
45-
throw new Error('Received unexpected crash report with no active test.')
46-
}
47+
const tags = logPayload.tags ? logPayload.tags.split(',') : []
4748

48-
currentTest(logPayload, tags)
49+
currentTest(logPayload, tags)
50+
})
4951
})
5052

51-
let PORT
52-
53-
function runApp (script) {
54-
return new Promise((resolve) => {
55-
exec(`node ${script}`, {
56-
...opts,
57-
env: { ...process.env, PORT },
58-
})
53+
server.listen(async () => {
54+
PORT = server.address().port
5955

60-
currentTest = (logPayload, tags) => {
61-
currentTest = undefined
62-
resolve({ logPayload, tags })
63-
}
56+
await testSegfault()
57+
await testUnhandledError('uncaught-exception', 'app-uncaught-exception', {
58+
expectedType: 'TypeError',
59+
expectedMessage: 'something went wrong',
60+
expectedFrame: 'myFaultyFunction',
6461
})
65-
}
62+
await testUnhandledNonError('uncaught-exception-non-error', 'app-uncaught-exception-non-error', {
63+
expectedFallbackType: 'uncaughtException',
64+
expectedValue: 'a plain string error',
65+
})
66+
await testUnhandledError('unhandled-rejection', 'app-unhandled-rejection', {
67+
expectedType: 'Error',
68+
expectedMessage: 'async went wrong',
69+
expectedFrame: 'myAsyncFaultyFunction',
70+
})
71+
// Node wraps non-Error rejections in an Error with name 'UnhandledPromiseRejection'
72+
// before passing to uncaughtExceptionMonitor, so this hits the Error path.
73+
// However, this test case rejects with a plain string, so the wrapped Error object has useless
74+
// stack trace
75+
await testUnhandledError('unhandled-rejection-non-error', 'app-unhandled-rejection-non-error', {
76+
expectedType: 'UnhandledPromiseRejection',
77+
expectedMessage: 'a plain string rejection',
78+
})
79+
80+
clearTimeout(timeout)
81+
server.close()
82+
})
6683

6784
async function testSegfault () {
6885
const { logPayload, tags } = await runApp('app-seg-fault')
@@ -99,33 +116,25 @@ async function testUnhandledNonError (label, script, { expectedFallbackType, exp
99116
assert.strictEqual(crashReport.error.stack.frames.length, 0, `[${label}] Expected empty stack trace but got ${crashReport.error.stack.frames.length} frames.`)
100117
}
101118

102-
const server = app.listen(async () => {
103-
PORT = server.address().port
119+
function runApp (script) {
120+
return new Promise((resolve) => {
121+
exec(`node ${script}`, getExecOptions({
122+
env: { ...process.env, PORT },
123+
}))
104124

105-
await testSegfault()
106-
await testUnhandledError('uncaught-exception', 'app-uncaught-exception', {
107-
expectedType: 'TypeError',
108-
expectedMessage: 'something went wrong',
109-
expectedFrame: 'myFaultyFunction',
110-
})
111-
await testUnhandledNonError('uncaught-exception-non-error', 'app-uncaught-exception-non-error', {
112-
expectedFallbackType: 'uncaughtException',
113-
expectedValue: 'a plain string error',
114-
})
115-
await testUnhandledError('unhandled-rejection', 'app-unhandled-rejection', {
116-
expectedType: 'Error',
117-
expectedMessage: 'async went wrong',
118-
expectedFrame: 'myAsyncFaultyFunction',
119-
})
120-
// Node wraps non-Error rejections in an Error with name 'UnhandledPromiseRejection'
121-
// before passing to uncaughtExceptionMonitor, so this hits the Error path.
122-
// However, this test case rejects with a plain string, so the wrapped Error object has useless
123-
// stack trace
124-
await testUnhandledError('unhandled-rejection-non-error', 'app-unhandled-rejection-non-error', {
125-
expectedType: 'UnhandledPromiseRejection',
126-
expectedMessage: 'a plain string rejection',
125+
currentTest = (logPayload, tags) => {
126+
currentTest = undefined
127+
resolve({ logPayload, tags })
128+
}
127129
})
130+
}
128131

129-
clearTimeout(timeout)
130-
server.close()
131-
})
132+
function getExecOptions (opts) {
133+
return {
134+
cwd: __dirname,
135+
stdio: 'inherit',
136+
uid: process.getuid(),
137+
gid: process.getgid(),
138+
...opts,
139+
}
140+
}

test/crashtracker/package.json

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@
22
"private": true,
33
"main": "index.js",
44
"dependencies": {
5-
"@datadog/segfaultify": "^0.1.1",
6-
"body-parser": "^1.20.3",
7-
"express": "^4.19.2"
5+
"@datadog/segfaultify": "^0.1.1"
86
}
97
}

0 commit comments

Comments
 (0)