|
2 | 2 |
|
3 | 3 | const assert = require('node:assert') |
4 | 4 | const { existsSync, rmSync } = require('node:fs') |
| 5 | +const http = require('node:http') |
5 | 6 | const path = require('node:path') |
6 | 7 | const { execSync, exec } = require('node:child_process') |
7 | 8 |
|
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 |
16 | 11 |
|
17 | | -const app = express() |
| 12 | +execSync('yarn install', getExecOptions()) |
18 | 13 |
|
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 }) |
21 | 16 |
|
22 | 17 | 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()) |
25 | 20 |
|
26 | 21 | throw new Error('No crash report received before timing out.') |
27 | 22 | }, 10_000) |
28 | 23 |
|
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 | + } |
30 | 29 |
|
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() |
32 | 34 |
|
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 | + } |
35 | 38 |
|
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] |
38 | 41 |
|
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 | + } |
43 | 46 |
|
44 | | - if (!currentTest) { |
45 | | - throw new Error('Received unexpected crash report with no active test.') |
46 | | - } |
| 47 | + const tags = logPayload.tags ? logPayload.tags.split(',') : [] |
47 | 48 |
|
48 | | - currentTest(logPayload, tags) |
| 49 | + currentTest(logPayload, tags) |
| 50 | + }) |
49 | 51 | }) |
50 | 52 |
|
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 |
59 | 55 |
|
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', |
64 | 61 | }) |
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 | +}) |
66 | 83 |
|
67 | 84 | async function testSegfault () { |
68 | 85 | const { logPayload, tags } = await runApp('app-seg-fault') |
@@ -99,33 +116,25 @@ async function testUnhandledNonError (label, script, { expectedFallbackType, exp |
99 | 116 | assert.strictEqual(crashReport.error.stack.frames.length, 0, `[${label}] Expected empty stack trace but got ${crashReport.error.stack.frames.length} frames.`) |
100 | 117 | } |
101 | 118 |
|
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 | + })) |
104 | 124 |
|
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 | + } |
127 | 129 | }) |
| 130 | +} |
128 | 131 |
|
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 | +} |
0 commit comments