dd-trace 5.16.0 → 5.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE-3rdparty.csv +1 -0
- package/ext/exporters.d.ts +1 -1
- package/index.d.ts +54 -1
- package/init.js +40 -1
- package/initialize.mjs +8 -5
- package/package.json +24 -20
- package/packages/datadog-core/src/storage/index.js +1 -10
- package/packages/datadog-esbuild/index.js +5 -1
- package/packages/datadog-instrumentations/src/aws-sdk.js +2 -1
- package/packages/datadog-instrumentations/src/cucumber.js +76 -34
- package/packages/datadog-instrumentations/src/helpers/hook.js +8 -3
- package/packages/datadog-instrumentations/src/helpers/hooks.js +3 -0
- package/packages/datadog-instrumentations/src/helpers/instrument.js +4 -3
- package/packages/datadog-instrumentations/src/helpers/register.js +56 -5
- package/packages/datadog-instrumentations/src/http/server.js +98 -0
- package/packages/datadog-instrumentations/src/mocha/main.js +12 -1
- package/packages/datadog-instrumentations/src/mocha/utils.js +58 -14
- package/packages/datadog-instrumentations/src/mocha/worker.js +1 -0
- package/packages/datadog-instrumentations/src/playwright.js +1 -1
- package/packages/datadog-instrumentations/src/undici.js +18 -0
- package/packages/datadog-instrumentations/src/vitest.js +303 -0
- package/packages/datadog-plugin-aws-sdk/src/base.js +8 -1
- package/packages/datadog-plugin-aws-sdk/src/services/kinesis.js +9 -3
- package/packages/datadog-plugin-aws-sdk/src/services/sns.js +6 -1
- package/packages/datadog-plugin-aws-sdk/src/services/sqs.js +23 -5
- package/packages/datadog-plugin-child_process/src/index.js +1 -1
- package/packages/datadog-plugin-cucumber/src/index.js +24 -1
- package/packages/datadog-plugin-mocha/src/index.js +25 -4
- package/packages/datadog-plugin-openai/src/index.js +52 -30
- package/packages/datadog-plugin-openai/src/token-estimator.js +20 -0
- package/packages/datadog-plugin-undici/src/index.js +12 -0
- package/packages/datadog-plugin-vitest/src/index.js +156 -0
- package/packages/dd-trace/src/appsec/blocking.js +4 -0
- package/packages/dd-trace/src/appsec/channels.js +1 -0
- package/packages/dd-trace/src/appsec/iast/path-line.js +2 -19
- package/packages/dd-trace/src/appsec/iast/vulnerability-reporter.js +4 -0
- package/packages/dd-trace/src/appsec/index.js +45 -11
- package/packages/dd-trace/src/appsec/rasp.js +32 -5
- package/packages/dd-trace/src/appsec/recommended.json +208 -3
- package/packages/dd-trace/src/appsec/remote_config/capabilities.js +1 -0
- package/packages/dd-trace/src/appsec/remote_config/index.js +2 -0
- package/packages/dd-trace/src/appsec/reporter.js +64 -20
- package/packages/dd-trace/src/appsec/sdk/track_event.js +3 -0
- package/packages/dd-trace/src/appsec/stack_trace.js +90 -0
- package/packages/dd-trace/src/appsec/standalone.js +130 -0
- package/packages/dd-trace/src/appsec/telemetry.js +33 -1
- package/packages/dd-trace/src/appsec/waf/index.js +2 -2
- package/packages/dd-trace/src/appsec/waf/waf_context_wrapper.js +2 -2
- package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +4 -2
- package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +4 -2
- package/packages/dd-trace/src/config.js +110 -40
- package/packages/dd-trace/src/constants.js +3 -1
- package/packages/dd-trace/src/datastreams/processor.js +2 -1
- package/packages/dd-trace/src/exporters/agent/index.js +2 -2
- package/packages/dd-trace/src/format.js +22 -2
- package/packages/dd-trace/src/opentelemetry/span.js +33 -7
- package/packages/dd-trace/src/opentracing/propagation/text_map.js +12 -0
- package/packages/dd-trace/src/opentracing/span.js +42 -1
- package/packages/dd-trace/src/opentracing/tracer.js +2 -2
- package/packages/dd-trace/src/plugins/ci_plugin.js +7 -0
- package/packages/dd-trace/src/plugins/index.js +3 -0
- package/packages/dd-trace/src/plugins/util/test.js +5 -1
- package/packages/dd-trace/src/priority_sampler.js +2 -5
- package/packages/dd-trace/src/profiling/profiler.js +1 -1
- package/packages/dd-trace/src/proxy.js +3 -1
- package/packages/dd-trace/src/rate_limiter.js +2 -2
- package/packages/dd-trace/src/service-naming/schemas/v0/web.js +4 -0
- package/packages/dd-trace/src/service-naming/schemas/v1/web.js +4 -0
- package/packages/dd-trace/src/span_stats.js +4 -3
- package/packages/dd-trace/src/tagger.js +10 -1
- package/packages/dd-trace/src/telemetry/init-telemetry.js +75 -0
- package/packages/dd-trace/src/tracer.js +2 -2
- package/packages/dd-trace/src/util.js +6 -1
- package/packages/datadog-core/src/storage/async_hooks.js +0 -49
|
@@ -7,6 +7,7 @@ const Hook = require('./hook')
|
|
|
7
7
|
const requirePackageJson = require('../../../dd-trace/src/require-package-json')
|
|
8
8
|
const log = require('../../../dd-trace/src/log')
|
|
9
9
|
const checkRequireCache = require('../check_require_cache')
|
|
10
|
+
const telemetry = require('../../../dd-trace/src/telemetry/init-telemetry')
|
|
10
11
|
|
|
11
12
|
const {
|
|
12
13
|
DD_TRACE_DISABLED_INSTRUMENTATIONS = '',
|
|
@@ -35,22 +36,38 @@ if (DD_TRACE_DEBUG && DD_TRACE_DEBUG.toLowerCase() !== 'false') {
|
|
|
35
36
|
setImmediate(checkRequireCache.checkForPotentialConflicts)
|
|
36
37
|
}
|
|
37
38
|
|
|
39
|
+
const seenCombo = new Set()
|
|
40
|
+
|
|
38
41
|
// TODO: make this more efficient
|
|
39
42
|
for (const packageName of names) {
|
|
40
43
|
if (disabledInstrumentations.has(packageName)) continue
|
|
41
44
|
|
|
42
|
-
|
|
45
|
+
const hookOptions = {}
|
|
46
|
+
|
|
47
|
+
let hook = hooks[packageName]
|
|
48
|
+
|
|
49
|
+
if (typeof hook === 'object') {
|
|
50
|
+
hookOptions.internals = hook.esmFirst
|
|
51
|
+
hook = hook.fn
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
Hook([packageName], hookOptions, (moduleExports, moduleName, moduleBaseDir, moduleVersion) => {
|
|
43
55
|
moduleName = moduleName.replace(pathSepExpr, '/')
|
|
44
56
|
|
|
45
57
|
// This executes the integration file thus adding its entries to `instrumentations`
|
|
46
|
-
|
|
58
|
+
hook()
|
|
47
59
|
|
|
48
60
|
if (!instrumentations[packageName]) {
|
|
49
61
|
return moduleExports
|
|
50
62
|
}
|
|
51
63
|
|
|
52
|
-
|
|
64
|
+
const namesAndSuccesses = {}
|
|
65
|
+
for (const { name, file, versions, hook, filePattern } of instrumentations[packageName]) {
|
|
66
|
+
let fullFilePattern = filePattern
|
|
53
67
|
const fullFilename = filename(name, file)
|
|
68
|
+
if (fullFilePattern) {
|
|
69
|
+
fullFilePattern = filename(name, fullFilePattern)
|
|
70
|
+
}
|
|
54
71
|
|
|
55
72
|
// Create a WeakMap associated with the hook function so that patches on the same moduleExport only happens once
|
|
56
73
|
// for example by instrumenting both dns and node:dns double the spans would be created
|
|
@@ -58,13 +75,29 @@ for (const packageName of names) {
|
|
|
58
75
|
if (!hook[HOOK_SYMBOL]) {
|
|
59
76
|
hook[HOOK_SYMBOL] = new WeakMap()
|
|
60
77
|
}
|
|
78
|
+
let matchesFile = false
|
|
79
|
+
|
|
80
|
+
matchesFile = moduleName === fullFilename
|
|
61
81
|
|
|
62
|
-
if (
|
|
82
|
+
if (fullFilePattern) {
|
|
83
|
+
// Some libraries include a hash in their filenames when installed,
|
|
84
|
+
// so our instrumentation has to include a '.*' to match them for more than a single version.
|
|
85
|
+
matchesFile = matchesFile || new RegExp(fullFilePattern).test(moduleName)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (matchesFile) {
|
|
63
89
|
const version = moduleVersion || getVersion(moduleBaseDir)
|
|
90
|
+
if (!Object.hasOwnProperty(namesAndSuccesses, name)) {
|
|
91
|
+
namesAndSuccesses[name] = {
|
|
92
|
+
success: false,
|
|
93
|
+
version
|
|
94
|
+
}
|
|
95
|
+
}
|
|
64
96
|
|
|
65
97
|
if (matchVersion(version, versions)) {
|
|
66
98
|
// Check if the hook already has a set moduleExport
|
|
67
99
|
if (hook[HOOK_SYMBOL].has(moduleExports)) {
|
|
100
|
+
namesAndSuccesses[name].success = true
|
|
68
101
|
return moduleExports
|
|
69
102
|
}
|
|
70
103
|
|
|
@@ -76,11 +109,29 @@ for (const packageName of names) {
|
|
|
76
109
|
// Set the moduleExports in the hooks weakmap
|
|
77
110
|
hook[HOOK_SYMBOL].set(moduleExports, name)
|
|
78
111
|
} catch (e) {
|
|
79
|
-
log.
|
|
112
|
+
log.info('Error during ddtrace instrumentation of application, aborting.')
|
|
113
|
+
log.info(e)
|
|
114
|
+
telemetry('error', [
|
|
115
|
+
`error_type:${e.constructor.name}`,
|
|
116
|
+
`integration:${name}`,
|
|
117
|
+
`integration_version:${version}`
|
|
118
|
+
])
|
|
80
119
|
}
|
|
120
|
+
namesAndSuccesses[name].success = true
|
|
81
121
|
}
|
|
82
122
|
}
|
|
83
123
|
}
|
|
124
|
+
for (const name of Object.keys(namesAndSuccesses)) {
|
|
125
|
+
const { success, version } = namesAndSuccesses[name]
|
|
126
|
+
if (!success && !seenCombo.has(`${name}@${version}`)) {
|
|
127
|
+
telemetry('abort.integration', [
|
|
128
|
+
`integration:${name}`,
|
|
129
|
+
`integration_version:${version}`
|
|
130
|
+
])
|
|
131
|
+
log.info(`Found incompatible integration version: ${name}@${version}`)
|
|
132
|
+
seenCombo.add(`${name}@${version}`)
|
|
133
|
+
}
|
|
134
|
+
}
|
|
84
135
|
|
|
85
136
|
return moduleExports
|
|
86
137
|
})
|
|
@@ -10,6 +10,7 @@ const startServerCh = channel('apm:http:server:request:start')
|
|
|
10
10
|
const exitServerCh = channel('apm:http:server:request:exit')
|
|
11
11
|
const errorServerCh = channel('apm:http:server:request:error')
|
|
12
12
|
const finishServerCh = channel('apm:http:server:request:finish')
|
|
13
|
+
const startWriteHeadCh = channel('apm:http:server:response:writeHead:start')
|
|
13
14
|
const finishSetHeaderCh = channel('datadog:http:server:response:set-header:finish')
|
|
14
15
|
|
|
15
16
|
const requestFinishedSet = new WeakSet()
|
|
@@ -20,6 +21,9 @@ const httpsNames = ['https', 'node:https']
|
|
|
20
21
|
addHook({ name: httpNames }, http => {
|
|
21
22
|
shimmer.wrap(http.ServerResponse.prototype, 'emit', wrapResponseEmit)
|
|
22
23
|
shimmer.wrap(http.Server.prototype, 'emit', wrapEmit)
|
|
24
|
+
shimmer.wrap(http.ServerResponse.prototype, 'writeHead', wrapWriteHead)
|
|
25
|
+
shimmer.wrap(http.ServerResponse.prototype, 'write', wrapWrite)
|
|
26
|
+
shimmer.wrap(http.ServerResponse.prototype, 'end', wrapEnd)
|
|
23
27
|
return http
|
|
24
28
|
})
|
|
25
29
|
|
|
@@ -86,3 +90,97 @@ function wrapSetHeader (res) {
|
|
|
86
90
|
}
|
|
87
91
|
})
|
|
88
92
|
}
|
|
93
|
+
|
|
94
|
+
function wrapWriteHead (writeHead) {
|
|
95
|
+
return function wrappedWriteHead (statusCode, reason, obj) {
|
|
96
|
+
if (!startWriteHeadCh.hasSubscribers) {
|
|
97
|
+
return writeHead.apply(this, arguments)
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const abortController = new AbortController()
|
|
101
|
+
|
|
102
|
+
if (typeof reason !== 'string') {
|
|
103
|
+
obj ??= reason
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// support writeHead(200, ['key1', 'val1', 'key2', 'val2'])
|
|
107
|
+
if (Array.isArray(obj)) {
|
|
108
|
+
const headers = {}
|
|
109
|
+
|
|
110
|
+
for (let i = 0; i < obj.length; i += 2) {
|
|
111
|
+
headers[obj[i]] = obj[i + 1]
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
obj = headers
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// this doesn't support explicit duplicate headers, but it's an edge case
|
|
118
|
+
const responseHeaders = Object.assign(this.getHeaders(), obj)
|
|
119
|
+
|
|
120
|
+
startWriteHeadCh.publish({
|
|
121
|
+
req: this.req,
|
|
122
|
+
res: this,
|
|
123
|
+
abortController,
|
|
124
|
+
statusCode,
|
|
125
|
+
responseHeaders
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
if (abortController.signal.aborted) {
|
|
129
|
+
return this
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return writeHead.apply(this, arguments)
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function wrapWrite (write) {
|
|
137
|
+
return function wrappedWrite () {
|
|
138
|
+
if (!startWriteHeadCh.hasSubscribers) {
|
|
139
|
+
return write.apply(this, arguments)
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const abortController = new AbortController()
|
|
143
|
+
|
|
144
|
+
const responseHeaders = this.getHeaders()
|
|
145
|
+
|
|
146
|
+
startWriteHeadCh.publish({
|
|
147
|
+
req: this.req,
|
|
148
|
+
res: this,
|
|
149
|
+
abortController,
|
|
150
|
+
statusCode: this.statusCode,
|
|
151
|
+
responseHeaders
|
|
152
|
+
})
|
|
153
|
+
|
|
154
|
+
if (abortController.signal.aborted) {
|
|
155
|
+
return true
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return write.apply(this, arguments)
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function wrapEnd (end) {
|
|
163
|
+
return function wrappedEnd () {
|
|
164
|
+
if (!startWriteHeadCh.hasSubscribers) {
|
|
165
|
+
return end.apply(this, arguments)
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const abortController = new AbortController()
|
|
169
|
+
|
|
170
|
+
const responseHeaders = this.getHeaders()
|
|
171
|
+
|
|
172
|
+
startWriteHeadCh.publish({
|
|
173
|
+
req: this.req,
|
|
174
|
+
res: this,
|
|
175
|
+
abortController,
|
|
176
|
+
statusCode: this.statusCode,
|
|
177
|
+
responseHeaders
|
|
178
|
+
})
|
|
179
|
+
|
|
180
|
+
if (abortController.signal.aborted) {
|
|
181
|
+
return this
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
return end.apply(this, arguments)
|
|
185
|
+
}
|
|
186
|
+
}
|
|
@@ -21,6 +21,7 @@ const {
|
|
|
21
21
|
runnableWrapper,
|
|
22
22
|
getOnTestHandler,
|
|
23
23
|
getOnTestEndHandler,
|
|
24
|
+
getOnTestRetryHandler,
|
|
24
25
|
getOnHookEndHandler,
|
|
25
26
|
getOnFailHandler,
|
|
26
27
|
getOnPendingHandler,
|
|
@@ -37,10 +38,12 @@ let isSuitesSkipped = false
|
|
|
37
38
|
let skippedSuites = []
|
|
38
39
|
let isEarlyFlakeDetectionEnabled = false
|
|
39
40
|
let isSuitesSkippingEnabled = false
|
|
41
|
+
let isFlakyTestRetriesEnabled = false
|
|
40
42
|
let earlyFlakeDetectionNumRetries = 0
|
|
41
43
|
let knownTests = []
|
|
42
44
|
let itrCorrelationId = ''
|
|
43
45
|
let isForcedToRun = false
|
|
46
|
+
const config = {}
|
|
44
47
|
|
|
45
48
|
// We'll preserve the original coverage here
|
|
46
49
|
const originalCoverageMap = createCoverageMap()
|
|
@@ -227,6 +230,12 @@ addHook({
|
|
|
227
230
|
isEarlyFlakeDetectionEnabled = libraryConfig.isEarlyFlakeDetectionEnabled
|
|
228
231
|
isSuitesSkippingEnabled = libraryConfig.isSuitesSkippingEnabled
|
|
229
232
|
earlyFlakeDetectionNumRetries = libraryConfig.earlyFlakeDetectionNumRetries
|
|
233
|
+
isFlakyTestRetriesEnabled = libraryConfig.isFlakyTestRetriesEnabled
|
|
234
|
+
|
|
235
|
+
config.isEarlyFlakeDetectionEnabled = isEarlyFlakeDetectionEnabled
|
|
236
|
+
config.isSuitesSkippingEnabled = isSuitesSkippingEnabled
|
|
237
|
+
config.earlyFlakeDetectionNumRetries = earlyFlakeDetectionNumRetries
|
|
238
|
+
config.isFlakyTestRetriesEnabled = isFlakyTestRetriesEnabled
|
|
230
239
|
|
|
231
240
|
if (isEarlyFlakeDetectionEnabled) {
|
|
232
241
|
knownTestsCh.publish({
|
|
@@ -317,6 +326,8 @@ addHook({
|
|
|
317
326
|
|
|
318
327
|
this.on('test end', getOnTestEndHandler())
|
|
319
328
|
|
|
329
|
+
this.on('retry', getOnTestRetryHandler())
|
|
330
|
+
|
|
320
331
|
// If the hook passes, 'hook end' will be emitted. Otherwise, 'fail' will be emitted
|
|
321
332
|
this.on('hook end', getOnHookEndHandler())
|
|
322
333
|
|
|
@@ -401,7 +412,7 @@ addHook({
|
|
|
401
412
|
name: 'mocha',
|
|
402
413
|
versions: ['>=5.2.0'],
|
|
403
414
|
file: 'lib/runnable.js'
|
|
404
|
-
}, runnableWrapper)
|
|
415
|
+
}, (runnablePackage) => runnableWrapper(runnablePackage, config))
|
|
405
416
|
|
|
406
417
|
// Only used in parallel mode (--parallel flag is passed)
|
|
407
418
|
// Used to generate suite events and receive test payloads from workers
|
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
const {
|
|
4
4
|
getTestSuitePath,
|
|
5
5
|
removeEfdStringFromTestName,
|
|
6
|
-
addEfdStringToTestName
|
|
6
|
+
addEfdStringToTestName,
|
|
7
|
+
NUM_FAILED_TEST_RETRIES
|
|
7
8
|
} = require('../../../dd-trace/src/plugins/util/test')
|
|
8
9
|
const { channel, AsyncResource } = require('../helpers/instrument')
|
|
9
10
|
const shimmer = require('../../../datadog-shimmer')
|
|
@@ -11,6 +12,8 @@ const shimmer = require('../../../datadog-shimmer')
|
|
|
11
12
|
// test channels
|
|
12
13
|
const testStartCh = channel('ci:mocha:test:start')
|
|
13
14
|
const testFinishCh = channel('ci:mocha:test:finish')
|
|
15
|
+
// after a test has failed, we'll publish to this channel
|
|
16
|
+
const testRetryCh = channel('ci:mocha:test:retry')
|
|
14
17
|
const errorCh = channel('ci:mocha:test:error')
|
|
15
18
|
const skipCh = channel('ci:mocha:test:skip')
|
|
16
19
|
|
|
@@ -70,6 +73,10 @@ function isMochaRetry (test) {
|
|
|
70
73
|
return test._currentRetry !== undefined && test._currentRetry !== 0
|
|
71
74
|
}
|
|
72
75
|
|
|
76
|
+
function isLastRetry (test) {
|
|
77
|
+
return test._currentRetry === test._retries
|
|
78
|
+
}
|
|
79
|
+
|
|
73
80
|
function getTestFullName (test) {
|
|
74
81
|
return `mocha.${getTestSuitePath(test.file, process.cwd())}.${removeEfdStringFromTestName(test.fullTitle())}`
|
|
75
82
|
}
|
|
@@ -84,22 +91,34 @@ function getTestStatus (test) {
|
|
|
84
91
|
return 'pass'
|
|
85
92
|
}
|
|
86
93
|
|
|
87
|
-
function
|
|
94
|
+
function getTestToArKey (test) {
|
|
88
95
|
if (!test.fn) {
|
|
89
|
-
return
|
|
96
|
+
return test
|
|
90
97
|
}
|
|
91
98
|
if (!wrappedFunctions.has(test.fn)) {
|
|
92
|
-
return
|
|
99
|
+
return test.fn
|
|
93
100
|
}
|
|
94
101
|
const originalFn = originalFns.get(test.fn)
|
|
95
|
-
return
|
|
102
|
+
return originalFn
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function getTestAsyncResource (test) {
|
|
106
|
+
const key = getTestToArKey(test)
|
|
107
|
+
return testToAr.get(key)
|
|
96
108
|
}
|
|
97
109
|
|
|
98
|
-
function runnableWrapper (RunnablePackage) {
|
|
110
|
+
function runnableWrapper (RunnablePackage, libraryConfig) {
|
|
99
111
|
shimmer.wrap(RunnablePackage.prototype, 'run', run => function () {
|
|
100
112
|
if (!testStartCh.hasSubscribers) {
|
|
101
113
|
return run.apply(this, arguments)
|
|
102
114
|
}
|
|
115
|
+
// Flaky test retries does not work in parallel mode
|
|
116
|
+
if (libraryConfig?.isFlakyTestRetriesEnabled) {
|
|
117
|
+
this.retries(NUM_FAILED_TEST_RETRIES)
|
|
118
|
+
}
|
|
119
|
+
// The reason why the wrapping logic is here is because we need to cover
|
|
120
|
+
// `afterEach` and `beforeEach` hooks as well.
|
|
121
|
+
// It can't be done in `getOnTestHandler` because it's only called for tests.
|
|
103
122
|
const isBeforeEach = this.parent._beforeEach.includes(this)
|
|
104
123
|
const isAfterEach = this.parent._afterEach.includes(this)
|
|
105
124
|
|
|
@@ -135,11 +154,16 @@ function runnableWrapper (RunnablePackage) {
|
|
|
135
154
|
|
|
136
155
|
function getOnTestHandler (isMain, newTests) {
|
|
137
156
|
return function (test) {
|
|
138
|
-
if (isMochaRetry(test)) {
|
|
139
|
-
return
|
|
140
|
-
}
|
|
141
157
|
const testStartLine = testToStartLine.get(test)
|
|
142
158
|
const asyncResource = new AsyncResource('bound-anonymous-fn')
|
|
159
|
+
|
|
160
|
+
// This may be a retry. If this is the case, `test.fn` is already wrapped,
|
|
161
|
+
// so we need to restore it.
|
|
162
|
+
if (wrappedFunctions.has(test.fn)) {
|
|
163
|
+
const originalFn = originalFns.get(test.fn)
|
|
164
|
+
test.fn = originalFn
|
|
165
|
+
wrappedFunctions.delete(test.fn)
|
|
166
|
+
}
|
|
143
167
|
testToAr.set(test.fn, asyncResource)
|
|
144
168
|
|
|
145
169
|
const {
|
|
@@ -186,7 +210,7 @@ function getOnTestEndHandler () {
|
|
|
186
210
|
// if there are afterEach to be run, we don't finish the test yet
|
|
187
211
|
if (asyncResource && !test.parent._afterEach.length) {
|
|
188
212
|
asyncResource.runInAsyncScope(() => {
|
|
189
|
-
testFinishCh.publish(status)
|
|
213
|
+
testFinishCh.publish({ status, hasBeenRetried: isMochaRetry(test) })
|
|
190
214
|
})
|
|
191
215
|
}
|
|
192
216
|
}
|
|
@@ -197,12 +221,17 @@ function getOnHookEndHandler () {
|
|
|
197
221
|
const test = hook.ctx.currentTest
|
|
198
222
|
if (test && hook.parent._afterEach.includes(hook)) { // only if it's an afterEach
|
|
199
223
|
const isLastAfterEach = hook.parent._afterEach.indexOf(hook) === hook.parent._afterEach.length - 1
|
|
224
|
+
if (test._retries > 0 && !isLastRetry(test)) {
|
|
225
|
+
return
|
|
226
|
+
}
|
|
200
227
|
if (isLastAfterEach) {
|
|
201
228
|
const status = getTestStatus(test)
|
|
202
229
|
const asyncResource = getTestAsyncResource(test)
|
|
203
|
-
asyncResource
|
|
204
|
-
|
|
205
|
-
|
|
230
|
+
if (asyncResource) {
|
|
231
|
+
asyncResource.runInAsyncScope(() => {
|
|
232
|
+
testFinishCh.publish({ status, hasBeenRetried: isMochaRetry(test) })
|
|
233
|
+
})
|
|
234
|
+
}
|
|
206
235
|
}
|
|
207
236
|
}
|
|
208
237
|
}
|
|
@@ -226,7 +255,7 @@ function getOnFailHandler (isMain) {
|
|
|
226
255
|
err.message = `${testOrHook.fullTitle()}: ${err.message}`
|
|
227
256
|
errorCh.publish(err)
|
|
228
257
|
// if it's a hook and it has failed, 'test end' will not be called
|
|
229
|
-
testFinishCh.publish('fail')
|
|
258
|
+
testFinishCh.publish({ status: 'fail', hasBeenRetried: isMochaRetry(test) })
|
|
230
259
|
} else {
|
|
231
260
|
errorCh.publish(err)
|
|
232
261
|
}
|
|
@@ -250,6 +279,20 @@ function getOnFailHandler (isMain) {
|
|
|
250
279
|
}
|
|
251
280
|
}
|
|
252
281
|
|
|
282
|
+
function getOnTestRetryHandler () {
|
|
283
|
+
return function (test) {
|
|
284
|
+
const asyncResource = getTestAsyncResource(test)
|
|
285
|
+
if (asyncResource) {
|
|
286
|
+
const isFirstAttempt = test._currentRetry === 0
|
|
287
|
+
asyncResource.runInAsyncScope(() => {
|
|
288
|
+
testRetryCh.publish(isFirstAttempt)
|
|
289
|
+
})
|
|
290
|
+
}
|
|
291
|
+
const key = getTestToArKey(test)
|
|
292
|
+
testToAr.delete(key)
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
253
296
|
function getOnPendingHandler () {
|
|
254
297
|
return function (test) {
|
|
255
298
|
const testStartLine = testToStartLine.get(test)
|
|
@@ -299,6 +342,7 @@ module.exports = {
|
|
|
299
342
|
testToStartLine,
|
|
300
343
|
getOnTestHandler,
|
|
301
344
|
getOnTestEndHandler,
|
|
345
|
+
getOnTestRetryHandler,
|
|
302
346
|
getOnHookEndHandler,
|
|
303
347
|
getOnFailHandler,
|
|
304
348
|
getOnPendingHandler,
|
|
@@ -249,7 +249,7 @@ function testEndHandler (test, annotations, testStatus, error, isTimeout) {
|
|
|
249
249
|
testAsyncResource.runInAsyncScope(() => {
|
|
250
250
|
testFinishCh.publish({
|
|
251
251
|
testStatus,
|
|
252
|
-
steps: testResult
|
|
252
|
+
steps: testResult?.steps || [],
|
|
253
253
|
error,
|
|
254
254
|
extraTags: annotationTags,
|
|
255
255
|
isNew: test._ddIsNew,
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const {
|
|
4
|
+
addHook
|
|
5
|
+
} = require('./helpers/instrument')
|
|
6
|
+
const shimmer = require('../../datadog-shimmer')
|
|
7
|
+
|
|
8
|
+
const tracingChannel = require('dc-polyfill').tracingChannel
|
|
9
|
+
const ch = tracingChannel('apm:undici:fetch')
|
|
10
|
+
|
|
11
|
+
const { createWrapFetch } = require('./helpers/fetch')
|
|
12
|
+
|
|
13
|
+
addHook({
|
|
14
|
+
name: 'undici',
|
|
15
|
+
versions: ['^4.4.1', '5', '>=6.0.0']
|
|
16
|
+
}, undici => {
|
|
17
|
+
return shimmer.wrap(undici, 'fetch', createWrapFetch(undici.Request, ch))
|
|
18
|
+
})
|