dd-trace 5.92.0 → 5.94.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/package.json +15 -11
- package/packages/datadog-instrumentations/src/helpers/bundler-register.js +23 -0
- package/packages/datadog-instrumentations/src/jest.js +118 -32
- package/packages/datadog-instrumentations/src/mocha/main.js +6 -0
- package/packages/datadog-instrumentations/src/mocha/utils.js +89 -5
- package/packages/datadog-instrumentations/src/playwright.js +10 -0
- package/packages/datadog-instrumentations/src/vitest.js +119 -0
- package/packages/datadog-plugin-aws-sdk/src/base.js +5 -0
- package/packages/datadog-plugin-cypress/src/cypress-plugin.js +12 -0
- package/packages/datadog-plugin-jest/src/index.js +6 -0
- package/packages/datadog-plugin-mocha/src/index.js +11 -0
- package/packages/datadog-plugin-playwright/src/index.js +9 -0
- package/packages/datadog-plugin-vitest/src/index.js +9 -0
- package/packages/datadog-webpack/index.js +187 -0
- package/packages/datadog-webpack/src/loader.js +27 -0
- package/packages/datadog-webpack/src/log.js +32 -0
- package/packages/dd-trace/src/ci-visibility/early-flake-detection/get-known-tests.js +103 -32
- package/packages/dd-trace/src/ci-visibility/exporters/test-worker/writer.js +10 -21
- package/packages/dd-trace/src/config/supported-configurations.json +2 -2
- package/packages/dd-trace/src/crashtracking/index.js +7 -1
- package/packages/dd-trace/src/exporters/common/docker.js +1 -0
- package/packages/dd-trace/src/exporters/common/request.js +26 -17
- package/packages/dd-trace/src/opentracing/span.js +5 -0
- package/packages/dd-trace/src/plugin_manager.js +10 -7
- package/packages/dd-trace/src/plugins/util/test.js +76 -0
- package/packages/dd-trace/src/priority_sampler.js +6 -3
- package/packages/dd-trace/src/profiling/profiler.js +78 -47
- package/packages/dd-trace/src/profiling/profilers/wall.js +35 -28
- package/packages/dd-trace/src/proxy.js +4 -3
- package/packages/dd-trace/src/tracer_metadata.js +10 -1
- package/webpack.js +3 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dd-trace",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.94.0",
|
|
4
4
|
"description": "Datadog APM tracing client for JavaScript",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"typings": "index.d.ts",
|
|
@@ -37,6 +37,8 @@
|
|
|
37
37
|
"test:trace:guardrails:ci": "nyc -- npm run test:trace:guardrails",
|
|
38
38
|
"test:esbuild": "mocha packages/datadog-esbuild/test/**/*.spec.js",
|
|
39
39
|
"test:esbuild:ci": "nyc -- npm run test:esbuild",
|
|
40
|
+
"test:webpack": "mocha packages/datadog-webpack/test/**/*.spec.js",
|
|
41
|
+
"test:webpack:ci": "nyc -- npm run test:webpack",
|
|
40
42
|
"test:instrumentations": "mocha \"packages/datadog-instrumentations/test/@(${PLUGINS}).spec.js\"",
|
|
41
43
|
"test:instrumentations:ci": "yarn services && nyc -- npm run test:instrumentations",
|
|
42
44
|
"test:instrumentations:misc": "mocha packages/datadog-instrumentations/test/*/**/*.spec.js",
|
|
@@ -67,6 +69,7 @@
|
|
|
67
69
|
"test:integration:cypress": "mocha --timeout 60000 \"integration-tests/cypress/*.spec.js\"",
|
|
68
70
|
"test:integration:debugger": "mocha --timeout 60000 \"integration-tests/debugger/*.spec.js\"",
|
|
69
71
|
"test:integration:esbuild": "mocha --timeout 60000 \"integration-tests/esbuild/*.spec.js\"",
|
|
72
|
+
"test:integration:webpack": "mocha --timeout 60000 \"integration-tests/webpack/*.spec.js\"",
|
|
70
73
|
"test:integration:openfeature": "mocha --timeout 60000 \"integration-tests/openfeature/*.spec.js\"",
|
|
71
74
|
"test:integration:jest": "mocha --timeout 60000 \"integration-tests/jest/*.spec.js\"",
|
|
72
75
|
"test:integration:mocha": "mocha --timeout 60000 \"integration-tests/mocha/*.spec.js\"",
|
|
@@ -110,6 +113,7 @@
|
|
|
110
113
|
"ci/**/*",
|
|
111
114
|
"cypress/**/*",
|
|
112
115
|
"esbuild.js",
|
|
116
|
+
"webpack.js",
|
|
113
117
|
"ext/**/*",
|
|
114
118
|
"index.d.ts",
|
|
115
119
|
"index.js",
|
|
@@ -138,21 +142,21 @@
|
|
|
138
142
|
"import-in-the-middle": "^3.0.0"
|
|
139
143
|
},
|
|
140
144
|
"optionalDependencies": {
|
|
141
|
-
"@datadog/libdatadog": "0.
|
|
145
|
+
"@datadog/libdatadog": "0.9.2",
|
|
142
146
|
"@datadog/native-appsec": "11.0.1",
|
|
143
147
|
"@datadog/native-iast-taint-tracking": "4.1.0",
|
|
144
148
|
"@datadog/native-metrics": "3.1.1",
|
|
145
|
-
"@datadog/openfeature-node-server": "^1.1.
|
|
146
|
-
"@datadog/pprof": "5.14.
|
|
149
|
+
"@datadog/openfeature-node-server": "^1.1.1",
|
|
150
|
+
"@datadog/pprof": "5.14.1",
|
|
147
151
|
"@datadog/wasm-js-rewriter": "5.0.1",
|
|
148
152
|
"@opentelemetry/api": ">=1.0.0 <1.10.0",
|
|
149
153
|
"@opentelemetry/api-logs": "<1.0.0",
|
|
150
|
-
"oxc-parser": "^0.
|
|
154
|
+
"oxc-parser": "^0.121.0"
|
|
151
155
|
},
|
|
152
156
|
"devDependencies": {
|
|
153
157
|
"@actions/core": "^3.0.0",
|
|
154
158
|
"@actions/github": "^9.0.0",
|
|
155
|
-
"@babel/helpers": "^7.
|
|
159
|
+
"@babel/helpers": "^7.29.2",
|
|
156
160
|
"@eslint/eslintrc": "^3.3.5",
|
|
157
161
|
"@eslint/js": "^9.39.2",
|
|
158
162
|
"@msgpack/msgpack": "^3.1.3",
|
|
@@ -165,12 +169,12 @@
|
|
|
165
169
|
"axios": "^1.13.4",
|
|
166
170
|
"benchmark": "^2.1.4",
|
|
167
171
|
"body-parser": "^2.2.2",
|
|
168
|
-
"bun": "1.3.
|
|
172
|
+
"bun": "1.3.11",
|
|
169
173
|
"codeowners-audit": "^2.9.0",
|
|
170
174
|
"eslint": "^9.39.2",
|
|
171
|
-
"eslint-plugin-cypress": "^6.2.
|
|
175
|
+
"eslint-plugin-cypress": "^6.2.1",
|
|
172
176
|
"eslint-plugin-import": "^2.32.0",
|
|
173
|
-
"eslint-plugin-jsdoc": "^62.8.
|
|
177
|
+
"eslint-plugin-jsdoc": "^62.8.1",
|
|
174
178
|
"eslint-plugin-mocha": "^11.2.0",
|
|
175
179
|
"eslint-plugin-n": "^17.23.2",
|
|
176
180
|
"eslint-plugin-promise": "^7.2.1",
|
|
@@ -194,11 +198,11 @@
|
|
|
194
198
|
"retry": "^0.13.1",
|
|
195
199
|
"semifies": "^1.0.0",
|
|
196
200
|
"semver": "^7.7.2",
|
|
197
|
-
"sinon": "^21.0.
|
|
201
|
+
"sinon": "^21.0.3",
|
|
198
202
|
"tiktoken": "^1.0.21",
|
|
199
203
|
"typescript": "^5.9.2",
|
|
200
204
|
"workerpool": "^10.0.0",
|
|
201
|
-
"yaml": "^2.8.
|
|
205
|
+
"yaml": "^2.8.3",
|
|
202
206
|
"yarn-deduplicate": "^6.0.2"
|
|
203
207
|
}
|
|
204
208
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
+
const Module = require('module')
|
|
3
4
|
const dc = require('dc-polyfill')
|
|
4
5
|
|
|
5
6
|
const log = require('../../../dd-trace/src/log')
|
|
@@ -11,6 +12,28 @@ const {
|
|
|
11
12
|
const hooks = require('./hooks')
|
|
12
13
|
const instrumentations = require('./instrumentations')
|
|
13
14
|
|
|
15
|
+
// register.js has now set up ritm (require-in-the-middle). In bundled
|
|
16
|
+
// environments (webpack, esbuild), Node.js built-in modules required by
|
|
17
|
+
// dd-trace internal modules (e.g. http from request.js) may have been loaded
|
|
18
|
+
// before ritm was active. The bundler's module cache then returns those
|
|
19
|
+
// pre-loaded modules for any subsequent require() calls, bypassing ritm.
|
|
20
|
+
// Re-requiring them via the real Module.prototype.require ensures ritm applies
|
|
21
|
+
// their instrumentation hooks.
|
|
22
|
+
//
|
|
23
|
+
// In regular Node.js, `module` is an instance of Module. In bundlers, the
|
|
24
|
+
// module wrapper object is a plain object (not a Module instance), so we use
|
|
25
|
+
// that to detect a bundled context and avoid unintended side-effects in
|
|
26
|
+
// normal Node.js (e.g. shimmer-wrapping http before ESM modules load).
|
|
27
|
+
if (!(module instanceof Module)) {
|
|
28
|
+
for (const name of ['http', 'https']) {
|
|
29
|
+
try {
|
|
30
|
+
Module.prototype.require.call(module, name)
|
|
31
|
+
} catch {
|
|
32
|
+
// Built-in not available in this environment, skip
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
14
37
|
const CHANNEL = 'dd-trace:bundler:load'
|
|
15
38
|
|
|
16
39
|
if (!dc.subscribe) {
|
|
@@ -15,6 +15,8 @@ const {
|
|
|
15
15
|
JEST_WORKER_LOGS_PAYLOAD_CODE,
|
|
16
16
|
getTestEndLine,
|
|
17
17
|
isModifiedTest,
|
|
18
|
+
DYNAMIC_NAME_RE,
|
|
19
|
+
collectDynamicNamesFromTraces,
|
|
18
20
|
} = require('../../dd-trace/src/plugins/util/test')
|
|
19
21
|
const {
|
|
20
22
|
SEED_SUFFIX_RE,
|
|
@@ -97,7 +99,10 @@ const originalHookFns = new WeakMap()
|
|
|
97
99
|
const retriedTestsToNumAttempts = new Map()
|
|
98
100
|
const newTestsTestStatuses = new Map()
|
|
99
101
|
const attemptToFixRetriedTestsStatuses = new Map()
|
|
100
|
-
const
|
|
102
|
+
const wrappedWorkerChannels = new WeakMap()
|
|
103
|
+
// New tests whose names contain likely dynamic data (timestamps, UUIDs, etc.)
|
|
104
|
+
// Populated in-process for runInBand, and via worker-report:trace for parallel mode.
|
|
105
|
+
const newTestsWithDynamicNames = new Set()
|
|
101
106
|
const testSuiteMockedFiles = new Map()
|
|
102
107
|
const testsToBeRetried = new Set()
|
|
103
108
|
// Per-test: how many EFD retries were determined after the first execution.
|
|
@@ -154,25 +159,60 @@ function getTestStats (testStatuses) {
|
|
|
154
159
|
* @param {string[]} quarantineNames
|
|
155
160
|
* @param {number} totalCount
|
|
156
161
|
*/
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
162
|
+
/**
|
|
163
|
+
* Renders a truncated bullet list from an array of items.
|
|
164
|
+
*
|
|
165
|
+
* @param {Array<{ text: string, suffix?: string }>} items
|
|
166
|
+
* @returns {string}
|
|
167
|
+
*/
|
|
168
|
+
function formatList (items) {
|
|
169
|
+
const shown = items.slice(0, MAX_IGNORED_TEST_NAMES)
|
|
170
|
+
const more = items.length - shown.length
|
|
171
|
+
const moreSuffix = more > 0 ? `\n ... and ${more} more` : ''
|
|
172
|
+
return shown.map(({ text, suffix }) => ` • ${text}${suffix ? ` (${suffix})` : ''}`).join('\n') + moreSuffix
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Logs a single "Datadog Test Optimization" summary at session end,
|
|
177
|
+
* combining all relevant sections (ignored failures, dynamic names).
|
|
178
|
+
*
|
|
179
|
+
* @param {{ efdNames: string[], quarantineNames: string[], totalCount: number } | undefined} ignoredFailures
|
|
180
|
+
*/
|
|
181
|
+
function logSessionSummary (ignoredFailures) {
|
|
182
|
+
const sections = []
|
|
183
|
+
|
|
184
|
+
if (ignoredFailures) {
|
|
185
|
+
const items = []
|
|
186
|
+
for (const n of ignoredFailures.efdNames) {
|
|
187
|
+
items.push({ text: n, suffix: 'Early Flake Detection' })
|
|
188
|
+
}
|
|
189
|
+
for (const n of ignoredFailures.quarantineNames) {
|
|
190
|
+
items.push({ text: n, suffix: 'Quarantine' })
|
|
191
|
+
}
|
|
192
|
+
sections.push(
|
|
193
|
+
`${ignoredFailures.totalCount} test failure(s) were ignored. Exit code set to 0.\n\n` +
|
|
194
|
+
formatList(items)
|
|
195
|
+
)
|
|
161
196
|
}
|
|
162
|
-
|
|
163
|
-
|
|
197
|
+
|
|
198
|
+
if (newTestsWithDynamicNames.size > 0) {
|
|
199
|
+
const items = [...newTestsWithDynamicNames].map(name => ({ text: name }))
|
|
200
|
+
sections.push(
|
|
201
|
+
`${newTestsWithDynamicNames.size} test(s) detected as new but their names contain ` +
|
|
202
|
+
'dynamic data (timestamps, UUIDs, etc.).\n' +
|
|
203
|
+
'Tests with changing names are always treated as new on every run, ' +
|
|
204
|
+
'causing unnecessary Early Flake Detection retries and preventing correct new test detection.\n' +
|
|
205
|
+
'Consider using stable, deterministic test names.\n\n' +
|
|
206
|
+
formatList(items)
|
|
207
|
+
)
|
|
208
|
+
newTestsWithDynamicNames.clear()
|
|
164
209
|
}
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
const list = shown.map(({ name, reason }) => ` • ${name} (${reason})`).join('\n')
|
|
210
|
+
|
|
211
|
+
if (sections.length === 0) return
|
|
212
|
+
|
|
169
213
|
const line = '-'.repeat(50)
|
|
170
|
-
// eslint-disable-next-line no-console -- Intentional user-facing
|
|
171
|
-
console.warn(
|
|
172
|
-
`\n${line}\nDatadog Test Optimization\n${line}\n` +
|
|
173
|
-
`${totalCount} test failure(s) were ignored. Exit code set to 0.\n\n` +
|
|
174
|
-
`${list}${moreSuffix}\n`
|
|
175
|
-
)
|
|
214
|
+
// eslint-disable-next-line no-console -- Intentional user-facing session summary
|
|
215
|
+
console.warn(`\n${line}\nDatadog Test Optimization\n${line}\n${sections.join('\n\n')}\n`)
|
|
176
216
|
}
|
|
177
217
|
|
|
178
218
|
function getWrappedEnvironment (BaseEnvironment, jestVersion) {
|
|
@@ -457,6 +497,7 @@ function getWrappedEnvironment (BaseEnvironment, jestVersion) {
|
|
|
457
497
|
}
|
|
458
498
|
|
|
459
499
|
const isJestRetry = event.test?.invocations > 1
|
|
500
|
+
const hasDynamicName = isNewTest && DYNAMIC_NAME_RE.test(testName)
|
|
460
501
|
const ctx = {
|
|
461
502
|
name: testName,
|
|
462
503
|
suite: this.testSuite,
|
|
@@ -472,6 +513,7 @@ function getWrappedEnvironment (BaseEnvironment, jestVersion) {
|
|
|
472
513
|
isDisabled,
|
|
473
514
|
isQuarantined,
|
|
474
515
|
isModified,
|
|
516
|
+
hasDynamicName,
|
|
475
517
|
testSuiteAbsolutePath: this.testSuiteAbsolutePath,
|
|
476
518
|
}
|
|
477
519
|
testContexts.set(event.test, ctx)
|
|
@@ -564,6 +606,11 @@ function getWrappedEnvironment (BaseEnvironment, jestVersion) {
|
|
|
564
606
|
if (!isAttemptToFix && this.isKnownTestsEnabled) {
|
|
565
607
|
const isNew = !this.knownTestsForThisSuite.includes(testFullName)
|
|
566
608
|
if (isNew && !isSkipped && !retriedTestsToNumAttempts.has(testFullName)) {
|
|
609
|
+
if (DYNAMIC_NAME_RE.test(testFullName)) {
|
|
610
|
+
// Populated directly for runInBand; for parallel workers the main process
|
|
611
|
+
// collects these from the TEST_HAS_DYNAMIC_NAME span tag via worker-report:trace.
|
|
612
|
+
newTestsWithDynamicNames.add(`${this.testSuite} › ${testFullName}`)
|
|
613
|
+
}
|
|
567
614
|
retriedTestsToNumAttempts.set(testFullName, 0)
|
|
568
615
|
if (this.isEarlyFlakeDetectionEnabled) {
|
|
569
616
|
testsToBeRetried.add(testFullName)
|
|
@@ -722,7 +769,6 @@ function getWrappedEnvironment (BaseEnvironment, jestVersion) {
|
|
|
722
769
|
error: formatJestError(originalError),
|
|
723
770
|
shouldSetProbe,
|
|
724
771
|
promises,
|
|
725
|
-
finalStatus,
|
|
726
772
|
})
|
|
727
773
|
}
|
|
728
774
|
|
|
@@ -1333,13 +1379,7 @@ function getCliWrapper (isNewJestVersion) {
|
|
|
1333
1379
|
})
|
|
1334
1380
|
}
|
|
1335
1381
|
|
|
1336
|
-
|
|
1337
|
-
logIgnoredFailuresSummary(
|
|
1338
|
-
ignoredFailuresSummary.efdNames,
|
|
1339
|
-
ignoredFailuresSummary.quarantineNames,
|
|
1340
|
-
ignoredFailuresSummary.totalCount
|
|
1341
|
-
)
|
|
1342
|
-
}
|
|
1382
|
+
logSessionSummary(ignoredFailuresSummary)
|
|
1343
1383
|
|
|
1344
1384
|
numSkippedSuites = 0
|
|
1345
1385
|
|
|
@@ -1793,8 +1833,14 @@ addHook({
|
|
|
1793
1833
|
|
|
1794
1834
|
function onMessageWrapper (onMessage) {
|
|
1795
1835
|
return function () {
|
|
1796
|
-
const
|
|
1836
|
+
const response = arguments[0]
|
|
1837
|
+
if (!Array.isArray(response)) {
|
|
1838
|
+
return onMessage.apply(this, arguments)
|
|
1839
|
+
}
|
|
1840
|
+
|
|
1841
|
+
const [code, data] = response
|
|
1797
1842
|
if (code === JEST_WORKER_TRACE_PAYLOAD_CODE) { // datadog trace payload
|
|
1843
|
+
collectDynamicNamesFromTraces(data, newTestsWithDynamicNames)
|
|
1798
1844
|
workerReportTraceCh.publish(data)
|
|
1799
1845
|
return
|
|
1800
1846
|
}
|
|
@@ -1855,15 +1901,40 @@ function sendWrapper (send) {
|
|
|
1855
1901
|
}
|
|
1856
1902
|
}
|
|
1857
1903
|
|
|
1904
|
+
function wrapWorkerChannel (worker) {
|
|
1905
|
+
const workerChannel = worker._child || worker._worker
|
|
1906
|
+
if (!workerChannel) return
|
|
1907
|
+
|
|
1908
|
+
shimmer.wrap(workerChannel, worker._child ? 'send' : 'postMessage', sendWrapper)
|
|
1909
|
+
}
|
|
1910
|
+
|
|
1911
|
+
function wrapWorker (worker) {
|
|
1912
|
+
// ChildProcessWorker uses _child (child_process), ExperimentalWorker uses _worker (worker_threads)
|
|
1913
|
+
const workerChannel = worker._child || worker._worker
|
|
1914
|
+
if (!workerChannel) return
|
|
1915
|
+
|
|
1916
|
+
wrapWorkerChannel(worker)
|
|
1917
|
+
shimmer.wrap(worker, '_onMessage', onMessageWrapper)
|
|
1918
|
+
workerChannel.removeAllListeners('message')
|
|
1919
|
+
workerChannel.on('message', worker._onMessage.bind(worker))
|
|
1920
|
+
}
|
|
1921
|
+
|
|
1858
1922
|
function enqueueWrapper (enqueue) {
|
|
1859
1923
|
return function () {
|
|
1860
1924
|
shimmer.wrap(arguments[0], 'onStart', onStart => function (worker) {
|
|
1861
|
-
if (worker
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1925
|
+
if (worker) {
|
|
1926
|
+
const currentChannel = worker._child || worker._worker
|
|
1927
|
+
const previousChannel = wrappedWorkerChannels.get(worker)
|
|
1928
|
+
if (currentChannel !== previousChannel) {
|
|
1929
|
+
if (previousChannel) {
|
|
1930
|
+
// Worker restarted — only re-wrap the new child's send/postMessage
|
|
1931
|
+
wrapWorkerChannel(worker)
|
|
1932
|
+
} else {
|
|
1933
|
+
// First time seeing this worker — full setup
|
|
1934
|
+
wrapWorker(worker)
|
|
1935
|
+
}
|
|
1936
|
+
wrappedWorkerChannels.set(worker, currentChannel)
|
|
1937
|
+
}
|
|
1867
1938
|
}
|
|
1868
1939
|
return onStart.apply(this, arguments)
|
|
1869
1940
|
})
|
|
@@ -1892,6 +1963,21 @@ addHook({
|
|
|
1892
1963
|
return childProcessWorker
|
|
1893
1964
|
})
|
|
1894
1965
|
|
|
1966
|
+
addHook({
|
|
1967
|
+
name: 'jest-worker',
|
|
1968
|
+
versions: ['>=24.9.0 <30.0.0'],
|
|
1969
|
+
file: 'build/workers/NodeThreadsWorker.js',
|
|
1970
|
+
}, (nodeThreadsWorker) => {
|
|
1971
|
+
const ExperimentalWorker = nodeThreadsWorker.default
|
|
1972
|
+
shimmer.wrap(ExperimentalWorker.prototype, 'send', sendWrapper)
|
|
1973
|
+
if (ExperimentalWorker.prototype._onMessage) {
|
|
1974
|
+
shimmer.wrap(ExperimentalWorker.prototype, '_onMessage', onMessageWrapper)
|
|
1975
|
+
} else if (ExperimentalWorker.prototype.onMessage) {
|
|
1976
|
+
shimmer.wrap(ExperimentalWorker.prototype, 'onMessage', onMessageWrapper)
|
|
1977
|
+
}
|
|
1978
|
+
return nodeThreadsWorker
|
|
1979
|
+
})
|
|
1980
|
+
|
|
1895
1981
|
addHook({
|
|
1896
1982
|
name: 'jest-worker',
|
|
1897
1983
|
versions: ['>=30.0.0'],
|
|
@@ -14,6 +14,8 @@ const {
|
|
|
14
14
|
mergeCoverage,
|
|
15
15
|
resetCoverage,
|
|
16
16
|
getIsFaultyEarlyFlakeDetection,
|
|
17
|
+
collectDynamicNamesFromTraces,
|
|
18
|
+
logDynamicNamesWarning,
|
|
17
19
|
} = require('../../../dd-trace/src/plugins/util/test')
|
|
18
20
|
|
|
19
21
|
const {
|
|
@@ -34,6 +36,7 @@ const {
|
|
|
34
36
|
getRunTestsWrapper,
|
|
35
37
|
testsAttemptToFix,
|
|
36
38
|
testsStatuses,
|
|
39
|
+
newTestsWithDynamicNames,
|
|
37
40
|
} = require('./utils')
|
|
38
41
|
|
|
39
42
|
require('./common')
|
|
@@ -207,6 +210,8 @@ function getOnEndHandler (isParallel) {
|
|
|
207
210
|
isTestManagementEnabled: config.isTestManagementTestsEnabled,
|
|
208
211
|
isParallel,
|
|
209
212
|
})
|
|
213
|
+
|
|
214
|
+
logDynamicNamesWarning(newTestsWithDynamicNames)
|
|
210
215
|
}
|
|
211
216
|
}
|
|
212
217
|
|
|
@@ -552,6 +557,7 @@ function onMessage (message) {
|
|
|
552
557
|
if (Array.isArray(message)) {
|
|
553
558
|
const [messageCode, payload] = message
|
|
554
559
|
if (messageCode === MOCHA_WORKER_TRACE_PAYLOAD_CODE) {
|
|
560
|
+
collectDynamicNamesFromTraces(payload, newTestsWithDynamicNames)
|
|
555
561
|
workerReportTraceCh.publish(payload)
|
|
556
562
|
}
|
|
557
563
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
const { getTestSuitePath } = require('../../../dd-trace/src/plugins/util/test')
|
|
3
|
+
const { getTestSuitePath, DYNAMIC_NAME_RE } = require('../../../dd-trace/src/plugins/util/test')
|
|
4
4
|
const { channel } = require('../helpers/instrument')
|
|
5
5
|
const shimmer = require('../../../datadog-shimmer')
|
|
6
6
|
|
|
@@ -23,6 +23,7 @@ const testToStartLine = new WeakMap()
|
|
|
23
23
|
const testFileToSuiteCtx = new Map()
|
|
24
24
|
const wrappedFunctions = new WeakSet()
|
|
25
25
|
const newTests = {}
|
|
26
|
+
const newTestsWithDynamicNames = new Set()
|
|
26
27
|
const testsAttemptToFix = new Set()
|
|
27
28
|
const testsQuarantined = new Set()
|
|
28
29
|
const testsStatuses = new Map()
|
|
@@ -221,6 +222,10 @@ function getOnTestHandler (isMain) {
|
|
|
221
222
|
testInfo.isDisabled = isDisabled
|
|
222
223
|
testInfo.isQuarantined = isQuarantined
|
|
223
224
|
testInfo.isModified = isModified
|
|
225
|
+
testInfo.hasDynamicName = isNew && DYNAMIC_NAME_RE.test(test.fullTitle())
|
|
226
|
+
if (testInfo.hasDynamicName) {
|
|
227
|
+
newTestsWithDynamicNames.add(`${getTestSuitePath(test.file, process.cwd())} › ${test.fullTitle()}`)
|
|
228
|
+
}
|
|
224
229
|
// We want to store the result of the new tests
|
|
225
230
|
if (isNew) {
|
|
226
231
|
const testFullName = getTestFullName(test)
|
|
@@ -241,6 +246,44 @@ function getOnTestHandler (isMain) {
|
|
|
241
246
|
}
|
|
242
247
|
}
|
|
243
248
|
|
|
249
|
+
function getFinalStatus ({
|
|
250
|
+
status,
|
|
251
|
+
hasFailedAllRetries,
|
|
252
|
+
isFlakyTestRetriesEnabled,
|
|
253
|
+
isLastAtrAttempt,
|
|
254
|
+
isEfdRetry,
|
|
255
|
+
isLastEfdRetry,
|
|
256
|
+
isAttemptToFix,
|
|
257
|
+
isLastAttemptToFix,
|
|
258
|
+
attemptToFixPassed,
|
|
259
|
+
isQuarantined,
|
|
260
|
+
isDisabled,
|
|
261
|
+
}) {
|
|
262
|
+
// Note that intermediate executions DO NOT report a final status tag
|
|
263
|
+
|
|
264
|
+
// If the test is quarantined or disabled, regardless of its actual execution result or active retry features,
|
|
265
|
+
// the final status of its last execution should be reported as 'skip'.
|
|
266
|
+
if (isQuarantined || isDisabled) {
|
|
267
|
+
return 'skip'
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
const isAtrActive = isFlakyTestRetriesEnabled && !isAttemptToFix && !isEfdRetry
|
|
271
|
+
|
|
272
|
+
// When no retry feature is active, every execution is final
|
|
273
|
+
if (!isAtrActive && !isEfdRetry && !isAttemptToFix) {
|
|
274
|
+
return status
|
|
275
|
+
}
|
|
276
|
+
if (isAtrActive && isLastAtrAttempt) {
|
|
277
|
+
return hasFailedAllRetries ? 'fail' : 'pass'
|
|
278
|
+
}
|
|
279
|
+
if (isEfdRetry && isLastEfdRetry) {
|
|
280
|
+
return hasFailedAllRetries ? 'fail' : 'pass'
|
|
281
|
+
}
|
|
282
|
+
if (isAttemptToFix && isLastAttemptToFix) {
|
|
283
|
+
return attemptToFixPassed ? 'pass' : 'fail'
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
244
287
|
function getOnTestEndHandler (config) {
|
|
245
288
|
return async function (test) {
|
|
246
289
|
const ctx = getTestContext(test)
|
|
@@ -271,6 +314,11 @@ function getOnTestEndHandler (config) {
|
|
|
271
314
|
|
|
272
315
|
const isLastAttempt = testStatuses.length === config.testManagementAttemptToFixRetries + 1
|
|
273
316
|
const isLastEfdRetry = testStatuses.length === config.earlyFlakeDetectionNumRetries + 1
|
|
317
|
+
const isLastAtrAttempt = getIsLastRetry(test) || (config.isFlakyTestRetriesEnabled && status === 'pass')
|
|
318
|
+
|
|
319
|
+
// Needed for the getFinalStatus call. This is because EFD does NOT tag as
|
|
320
|
+
// EFD retry the first run of the test. It only tags as retries the clones
|
|
321
|
+
const isEfdRetry = test._ddIsEfdRetry || (test._ddIsNew && config.isEarlyFlakeDetectionEnabled)
|
|
274
322
|
|
|
275
323
|
if (test._ddIsAttemptToFix && isLastAttempt) {
|
|
276
324
|
if (testStatuses.includes('fail')) {
|
|
@@ -299,8 +347,29 @@ function getOnTestEndHandler (config) {
|
|
|
299
347
|
!test._ddIsAttemptToFix &&
|
|
300
348
|
!test._ddIsEfdRetry
|
|
301
349
|
|
|
302
|
-
|
|
303
|
-
|
|
350
|
+
const { isFlakyTestRetriesEnabled } = config
|
|
351
|
+
const { _ddIsAttemptToFix, _ddIsQuarantined, _ddIsDisabled } = test
|
|
352
|
+
|
|
353
|
+
const finalStatus = getFinalStatus({
|
|
354
|
+
status,
|
|
355
|
+
hasFailedAllRetries,
|
|
356
|
+
isFlakyTestRetriesEnabled,
|
|
357
|
+
isLastAtrAttempt,
|
|
358
|
+
isEfdRetry,
|
|
359
|
+
isLastEfdRetry,
|
|
360
|
+
isAttemptToFix: _ddIsAttemptToFix,
|
|
361
|
+
isLastAttemptToFix: isLastAttempt,
|
|
362
|
+
attemptToFixPassed,
|
|
363
|
+
isQuarantined: _ddIsQuarantined,
|
|
364
|
+
isDisabled: _ddIsDisabled,
|
|
365
|
+
})
|
|
366
|
+
|
|
367
|
+
// If there are afterEach to be run, we don't finish the test yet.
|
|
368
|
+
// Disabled tests (marked pending by us) are finished immediately without waiting for afterEach hooks.
|
|
369
|
+
// In older mocha versions, pending tests don't run afterEach hooks, so we can't rely on
|
|
370
|
+
// getOnHookEndHandler to finish the test. This mirrors Jest's approach where the skip handler
|
|
371
|
+
// directly sets finalStatus without waiting for hooks
|
|
372
|
+
if (ctx && (!getAfterEachHooks(test).length || test._ddIsDisabled)) {
|
|
304
373
|
testFinishCh.publish({
|
|
305
374
|
status,
|
|
306
375
|
hasBeenRetried: isMochaRetry(test),
|
|
@@ -311,7 +380,10 @@ function getOnTestEndHandler (config) {
|
|
|
311
380
|
isAttemptToFixRetry,
|
|
312
381
|
isAtrRetry,
|
|
313
382
|
...ctx.currentStore,
|
|
383
|
+
finalStatus,
|
|
314
384
|
})
|
|
385
|
+
} else if (ctx) { // if there is an afterEach to run, let's store the finalStatus for getOnHookEndHandler
|
|
386
|
+
ctx.finalStatus = finalStatus
|
|
315
387
|
}
|
|
316
388
|
}
|
|
317
389
|
}
|
|
@@ -325,12 +397,15 @@ function getOnHookEndHandler () {
|
|
|
325
397
|
if (isLastAfterEach) {
|
|
326
398
|
const status = getTestStatus(test)
|
|
327
399
|
const ctx = getTestContext(test)
|
|
328
|
-
|
|
400
|
+
// Disabled tests are already finished in getOnTestEndHandler,
|
|
401
|
+
// skip to avoid double-publishing
|
|
402
|
+
if (ctx && !test._ddIsDisabled) {
|
|
329
403
|
testFinishCh.publish({
|
|
330
404
|
status,
|
|
331
405
|
hasBeenRetried: isMochaRetry(test),
|
|
332
406
|
isLastRetry: getIsLastRetry(test),
|
|
333
407
|
...ctx.currentStore,
|
|
408
|
+
finalStatus: ctx.finalStatus,
|
|
334
409
|
})
|
|
335
410
|
}
|
|
336
411
|
}
|
|
@@ -356,7 +431,15 @@ function getOnFailHandler (isMain) {
|
|
|
356
431
|
testContext.err = err
|
|
357
432
|
errorCh.runStores(testContext, () => {})
|
|
358
433
|
// if it's a hook and it has failed, 'test end' will not be called
|
|
359
|
-
|
|
434
|
+
// quarantined and disabled tests always report 'skip'
|
|
435
|
+
// as final status, even when hooks fail
|
|
436
|
+
const isSkippedByManagement = test._ddIsQuarantined || test._ddIsDisabled
|
|
437
|
+
testFinishCh.publish({
|
|
438
|
+
status: 'fail',
|
|
439
|
+
hasBeenRetried: isMochaRetry(test),
|
|
440
|
+
...testContext.currentStore,
|
|
441
|
+
finalStatus: isSkippedByManagement ? 'skip' : 'fail',
|
|
442
|
+
})
|
|
360
443
|
} else {
|
|
361
444
|
testContext.err = err
|
|
362
445
|
errorCh.runStores(testContext, () => {})
|
|
@@ -519,6 +602,7 @@ module.exports = {
|
|
|
519
602
|
testFileToSuiteCtx,
|
|
520
603
|
getRunTestsWrapper,
|
|
521
604
|
newTests,
|
|
605
|
+
newTestsWithDynamicNames,
|
|
522
606
|
testsQuarantined,
|
|
523
607
|
testsAttemptToFix,
|
|
524
608
|
testsStatuses,
|
|
@@ -8,6 +8,8 @@ const {
|
|
|
8
8
|
getTestSuitePath,
|
|
9
9
|
PLAYWRIGHT_WORKER_TRACE_PAYLOAD_CODE,
|
|
10
10
|
getIsFaultyEarlyFlakeDetection,
|
|
11
|
+
DYNAMIC_NAME_RE,
|
|
12
|
+
logDynamicNamesWarning,
|
|
11
13
|
} = require('../../dd-trace/src/plugins/util/test')
|
|
12
14
|
const log = require('../../dd-trace/src/log')
|
|
13
15
|
const {
|
|
@@ -70,6 +72,7 @@ let isImpactedTestsEnabled = false
|
|
|
70
72
|
let modifiedFiles = {}
|
|
71
73
|
const quarantinedOrDisabledTestsAttemptToFix = []
|
|
72
74
|
let quarantinedButNotAttemptToFixFqns = new Set()
|
|
75
|
+
const newTestsWithDynamicNames = new Set()
|
|
73
76
|
let rootDir = ''
|
|
74
77
|
let sessionProjects = []
|
|
75
78
|
|
|
@@ -381,6 +384,9 @@ function testEndHandler ({
|
|
|
381
384
|
|
|
382
385
|
if (testStatuses.length === 0) {
|
|
383
386
|
testsToTestStatuses.set(testFqn, [testStatus])
|
|
387
|
+
if (test._ddIsNew && DYNAMIC_NAME_RE.test(getTestFullname(test))) {
|
|
388
|
+
newTestsWithDynamicNames.add(`${getTestSuitePath(test._requireFile, rootDir)} › ${getTestFullname(test)}`)
|
|
389
|
+
}
|
|
384
390
|
} else {
|
|
385
391
|
testStatuses.push(testStatus)
|
|
386
392
|
}
|
|
@@ -432,6 +438,7 @@ function testEndHandler ({
|
|
|
432
438
|
error,
|
|
433
439
|
extraTags: annotationTags,
|
|
434
440
|
isNew: test._ddIsNew,
|
|
441
|
+
hasDynamicName: test._ddIsNew && DYNAMIC_NAME_RE.test(getTestFullname(test)),
|
|
435
442
|
isAttemptToFix: test._ddIsAttemptToFix,
|
|
436
443
|
isAttemptToFixRetry: test._ddIsAttemptToFixRetry,
|
|
437
444
|
isQuarantined: test._ddIsQuarantined,
|
|
@@ -784,6 +791,8 @@ function runAllTestsWrapper (runAllTests, playwrightVersion) {
|
|
|
784
791
|
}
|
|
785
792
|
}
|
|
786
793
|
|
|
794
|
+
logDynamicNamesWarning(newTestsWithDynamicNames)
|
|
795
|
+
|
|
787
796
|
const flushWait = new Promise(resolve => {
|
|
788
797
|
onDone = resolve
|
|
789
798
|
})
|
|
@@ -1281,6 +1290,7 @@ addHook({
|
|
|
1281
1290
|
error,
|
|
1282
1291
|
extraTags: annotationTags,
|
|
1283
1292
|
isNew: test._ddIsNew,
|
|
1293
|
+
hasDynamicName: test._ddIsNew && DYNAMIC_NAME_RE.test(getTestFullname(test)),
|
|
1284
1294
|
isRetry: retry > 0,
|
|
1285
1295
|
isEfdRetry: test._ddIsEfdRetry,
|
|
1286
1296
|
isAttemptToFix: test._ddIsAttemptToFix,
|