dd-trace 5.99.1 → 5.100.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 +0 -1
- package/package.json +4 -4
- package/packages/datadog-instrumentations/src/cucumber.js +69 -5
- package/packages/datadog-instrumentations/src/express.js +3 -2
- package/packages/datadog-instrumentations/src/helpers/hooks.js +1 -0
- package/packages/datadog-instrumentations/src/hono.js +15 -4
- package/packages/datadog-instrumentations/src/jest.js +84 -58
- package/packages/datadog-instrumentations/src/mocha/main.js +18 -22
- package/packages/datadog-instrumentations/src/mocha/utils.js +114 -96
- package/packages/datadog-instrumentations/src/mocha/worker.js +2 -2
- package/packages/datadog-instrumentations/src/path-to-regexp.js +44 -0
- package/packages/datadog-instrumentations/src/playwright.js +108 -18
- package/packages/datadog-instrumentations/src/router.js +53 -33
- package/packages/datadog-instrumentations/src/vitest.js +76 -30
- package/packages/datadog-plugin-aws-sdk/src/base.js +1 -1
- package/packages/datadog-plugin-aws-sdk/src/services/dynamodb.js +1 -1
- package/packages/datadog-plugin-bullmq/src/consumer.js +3 -2
- package/packages/datadog-plugin-bullmq/src/producer.js +25 -11
- package/packages/datadog-plugin-cypress/src/cypress-plugin.js +32 -9
- package/packages/datadog-plugin-cypress/src/support.js +22 -21
- package/packages/datadog-plugin-grpc/src/client.js +1 -1
- package/packages/datadog-plugin-grpc/src/server.js +1 -1
- package/packages/datadog-plugin-mongodb-core/src/index.js +2 -3
- package/packages/datadog-plugin-playwright/src/index.js +6 -0
- package/packages/datadog-plugin-router/src/index.js +13 -0
- package/packages/dd-trace/index.js +4 -3
- package/packages/dd-trace/src/aiguard/sdk.js +2 -2
- package/packages/dd-trace/src/baggage.js +10 -0
- package/packages/dd-trace/src/config/generated-config-types.d.ts +17 -41
- package/packages/dd-trace/src/config/index.js +6 -5
- package/packages/dd-trace/src/config/normalize-service.js +31 -0
- package/packages/dd-trace/src/config/supported-configurations.json +15 -32
- package/packages/dd-trace/src/debugger/config.js +1 -1
- package/packages/dd-trace/src/encode/0.4.js +1 -1
- package/packages/dd-trace/src/encode/tags-processors.js +3 -3
- package/packages/dd-trace/src/heap_snapshots.js +4 -4
- package/packages/dd-trace/src/openfeature/eval-metrics-hook.js +2 -2
- package/packages/dd-trace/src/opentelemetry/context_manager.js +11 -8
- package/packages/dd-trace/src/opentelemetry/span-helpers.js +170 -0
- package/packages/dd-trace/src/opentelemetry/span.js +14 -42
- package/packages/dd-trace/src/opentelemetry/tracer.js +11 -36
- package/packages/dd-trace/src/opentracing/propagation/text_map.js +31 -10
- package/packages/dd-trace/src/opentracing/propagation/tracestate.js +42 -12
- package/packages/dd-trace/src/opentracing/span.js +3 -2
- package/packages/dd-trace/src/plugins/util/ci.js +119 -32
- package/packages/dd-trace/src/plugins/util/test.js +293 -27
- package/packages/dd-trace/src/profiling/ssi-heuristics.js +2 -2
- package/packages/dd-trace/src/propagation-hash/index.js +1 -1
- package/packages/dd-trace/src/proxy.js +3 -3
- package/packages/dd-trace/src/runtime_metrics/runtime_metrics.js +1 -1
- package/packages/dd-trace/src/span_processor.js +1 -1
- package/packages/dd-trace/src/telemetry/telemetry.js +7 -5
- package/packages/dd-trace/src/tracer_metadata.js +1 -1
- package/vendor/dist/path-to-regexp/LICENSE +0 -21
- package/vendor/dist/path-to-regexp/index.js +0 -1
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
3
|
const METHODS = [...require('http').METHODS.map(v => v.toLowerCase()), 'all']
|
|
4
|
-
const pathToRegExp = require('../../../vendor/dist/path-to-regexp')
|
|
5
4
|
const shimmer = require('../../datadog-shimmer')
|
|
6
5
|
const { addHook, channel } = require('./helpers/instrument')
|
|
6
|
+
const { getCompileToRegexp } = require('./path-to-regexp')
|
|
7
7
|
|
|
8
8
|
const {
|
|
9
9
|
getRouterMountPaths,
|
|
@@ -19,15 +19,22 @@ const {
|
|
|
19
19
|
} = require('./helpers/router-helper')
|
|
20
20
|
|
|
21
21
|
function isFastStar (layer, matchers) {
|
|
22
|
-
return layer.regexp?.fast_star ?? matchers.
|
|
22
|
+
return layer.regexp?.fast_star ?? matchers.hasStarPath
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
function isFastSlash (layer, matchers) {
|
|
26
|
-
return layer.regexp?.fast_slash ?? matchers.
|
|
26
|
+
return layer.regexp?.fast_slash ?? matchers.hasSlashPath
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
// TODO: Move this function to a shared file between Express and Router
|
|
30
|
-
|
|
30
|
+
/**
|
|
31
|
+
* @param {string} name Channel namespace (`apm:<name>:middleware:*`).
|
|
32
|
+
* @param {((pattern: string | RegExp) => RegExp | undefined) | undefined} compile
|
|
33
|
+
* Host-resolved path-to-regexp compile adapter, or undefined when the host
|
|
34
|
+
* instance ships no path-to-regexp. Captured here so each express/router
|
|
35
|
+
* instance keeps the dialect it actually loaded.
|
|
36
|
+
*/
|
|
37
|
+
function createWrapRouterMethod (name, compile) {
|
|
31
38
|
const enterChannel = channel(`apm:${name}:middleware:enter`)
|
|
32
39
|
const exitChannel = channel(`apm:${name}:middleware:exit`)
|
|
33
40
|
const finishChannel = channel(`apm:${name}:middleware:finish`)
|
|
@@ -35,8 +42,6 @@ function createWrapRouterMethod (name) {
|
|
|
35
42
|
const nextChannel = channel(`apm:${name}:middleware:next`)
|
|
36
43
|
const routeAddedChannel = channel(`apm:${name}:route:added`)
|
|
37
44
|
|
|
38
|
-
const regexpCache = Object.create(null)
|
|
39
|
-
|
|
40
45
|
function wrapLayerHandle (layer, original) {
|
|
41
46
|
original._name = original._name || layer.name
|
|
42
47
|
|
|
@@ -55,13 +60,16 @@ function createWrapRouterMethod (name) {
|
|
|
55
60
|
|
|
56
61
|
let route
|
|
57
62
|
|
|
58
|
-
if (matchers) {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
63
|
+
if (matchers?.length && !isFastStar(layer, matchers) && !isFastSlash(layer, matchers)) {
|
|
64
|
+
if (matchers.length === 1) {
|
|
65
|
+
// The host already matched this layer; the lone pattern is the route.
|
|
66
|
+
route = matchers[0].path
|
|
67
|
+
} else {
|
|
68
|
+
for (const matcher of matchers) {
|
|
69
|
+
if (matcher.regex?.test(layer.path)) {
|
|
70
|
+
route = matcher.path
|
|
71
|
+
break
|
|
72
|
+
}
|
|
65
73
|
}
|
|
66
74
|
}
|
|
67
75
|
}
|
|
@@ -124,25 +132,35 @@ function createWrapRouterMethod (name) {
|
|
|
124
132
|
return []
|
|
125
133
|
}
|
|
126
134
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
},
|
|
135
|
-
}))
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
function cachedPathToRegExp (pattern) {
|
|
139
|
-
const maybeCached = regexpCache[pattern]
|
|
140
|
-
if (maybeCached) {
|
|
141
|
-
return maybeCached
|
|
135
|
+
if (arg.length === 1) {
|
|
136
|
+
const pattern = arg[0]
|
|
137
|
+
const path = pattern instanceof RegExp ? `(${pattern})` : pattern
|
|
138
|
+
const matchers = [{ path }]
|
|
139
|
+
matchers.hasStarPath = path === '*'
|
|
140
|
+
matchers.hasSlashPath = path === '/'
|
|
141
|
+
return matchers
|
|
142
142
|
}
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
143
|
+
|
|
144
|
+
// hasStarPath/hasSlashPath cache the lookups isFastStar/isFastSlash
|
|
145
|
+
// would otherwise re-run on every request.
|
|
146
|
+
let hasStarPath = false
|
|
147
|
+
let hasSlashPath = false
|
|
148
|
+
const matchers = arg.map(pattern => {
|
|
149
|
+
const isRegExp = pattern instanceof RegExp
|
|
150
|
+
const path = isRegExp ? `(${pattern})` : pattern
|
|
151
|
+
if (path === '*') {
|
|
152
|
+
hasStarPath = true
|
|
153
|
+
} else if (path === '/') {
|
|
154
|
+
hasSlashPath = true
|
|
155
|
+
}
|
|
156
|
+
return {
|
|
157
|
+
path,
|
|
158
|
+
regex: isRegExp ? pattern : compile?.(pattern),
|
|
159
|
+
}
|
|
160
|
+
})
|
|
161
|
+
matchers.hasStarPath = hasStarPath
|
|
162
|
+
matchers.hasSlashPath = hasSlashPath
|
|
163
|
+
return matchers
|
|
146
164
|
}
|
|
147
165
|
|
|
148
166
|
function wrapMethod (original) {
|
|
@@ -218,9 +236,9 @@ function createWrapRouterMethod (name) {
|
|
|
218
236
|
return wrapMethod
|
|
219
237
|
}
|
|
220
238
|
|
|
221
|
-
const wrapRouterMethod = createWrapRouterMethod('router')
|
|
222
|
-
|
|
223
239
|
addHook({ name: 'router', versions: ['>=1 <2'] }, Router => {
|
|
240
|
+
const wrapRouterMethod = createWrapRouterMethod('router', getCompileToRegexp())
|
|
241
|
+
|
|
224
242
|
shimmer.wrap(Router.prototype, 'use', wrapRouterMethod)
|
|
225
243
|
shimmer.wrap(Router.prototype, 'route', wrapRouterMethod)
|
|
226
244
|
|
|
@@ -230,6 +248,8 @@ addHook({ name: 'router', versions: ['>=1 <2'] }, Router => {
|
|
|
230
248
|
const queryParserReadCh = channel('datadog:query:read:finish')
|
|
231
249
|
|
|
232
250
|
addHook({ name: 'router', versions: ['>=2'] }, Router => {
|
|
251
|
+
const wrapRouterMethod = createWrapRouterMethod('router', getCompileToRegexp())
|
|
252
|
+
|
|
233
253
|
const WrappedRouter = shimmer.wrapFunction(Router, function (originalRouter) {
|
|
234
254
|
return function wrappedMethod () {
|
|
235
255
|
const router = originalRouter.apply(this, arguments)
|
|
@@ -11,8 +11,11 @@ const {
|
|
|
11
11
|
VITEST_WORKER_TRACE_PAYLOAD_CODE,
|
|
12
12
|
VITEST_WORKER_LOGS_PAYLOAD_CODE,
|
|
13
13
|
DYNAMIC_NAME_RE,
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
getTestSuitePath,
|
|
15
|
+
recordAttemptToFixExecution,
|
|
16
|
+
collectTestOptimizationSummariesFromTraces,
|
|
17
|
+
logAttemptToFixTestExecution,
|
|
18
|
+
logTestOptimizationSummary,
|
|
16
19
|
} = require('../../dd-trace/src/plugins/util/test')
|
|
17
20
|
const { addHook, channel } = require('./helpers/instrument')
|
|
18
21
|
|
|
@@ -49,6 +52,7 @@ const codeCoverageReportCh = channel('ci:vitest:coverage-report')
|
|
|
49
52
|
|
|
50
53
|
const taskToCtx = new WeakMap()
|
|
51
54
|
const taskToStatuses = new WeakMap()
|
|
55
|
+
const attemptToFixTaskToStatuses = new WeakMap()
|
|
52
56
|
const originalHookFns = new WeakMap()
|
|
53
57
|
const newTasks = new WeakSet()
|
|
54
58
|
const dynamicNameTasks = new WeakSet()
|
|
@@ -57,6 +61,8 @@ const disabledTasks = new WeakSet()
|
|
|
57
61
|
const quarantinedTasks = new WeakSet()
|
|
58
62
|
const attemptToFixTasks = new WeakSet()
|
|
59
63
|
const modifiedTasks = new WeakSet()
|
|
64
|
+
const attemptToFixExecutions = new Map()
|
|
65
|
+
const loggedAttemptToFixTests = new Set()
|
|
60
66
|
let isRetryReasonEfd = false
|
|
61
67
|
let isRetryReasonAttemptToFix = false
|
|
62
68
|
const switchedStatuses = new WeakSet()
|
|
@@ -255,6 +261,29 @@ function getTestName (task) {
|
|
|
255
261
|
return testName
|
|
256
262
|
}
|
|
257
263
|
|
|
264
|
+
function getFinalAttemptToFixStatus (task, state, isSwitchedStatus, testCtx) {
|
|
265
|
+
if (isSwitchedStatus && attemptToFixTasks.has(task) && testCtx?.status) {
|
|
266
|
+
return testCtx.status
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
return state === 'fail' ? 'fail' : 'pass'
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
function recordFinalAttemptToFixExecution (task, status, providedContext) {
|
|
273
|
+
const statuses = attemptToFixTaskToStatuses.get(task)
|
|
274
|
+
if (statuses && statuses.length <= providedContext.testManagementAttemptToFixRetries) {
|
|
275
|
+
statuses.push(status)
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
recordAttemptToFixExecution(attemptToFixExecutions, {
|
|
279
|
+
testSuite: getTestSuitePath(task.file.filepath, process.cwd()),
|
|
280
|
+
testName: getTestName(task),
|
|
281
|
+
status,
|
|
282
|
+
isDisabled: disabledTasks.has(task),
|
|
283
|
+
isQuarantined: quarantinedTasks.has(task),
|
|
284
|
+
})
|
|
285
|
+
}
|
|
286
|
+
|
|
258
287
|
/**
|
|
259
288
|
* Wraps a function so it runs inside the current test span context.
|
|
260
289
|
* @param {object} task
|
|
@@ -480,7 +509,8 @@ function getFinishWrapper (exitOrClose) {
|
|
|
480
509
|
onFinish,
|
|
481
510
|
})
|
|
482
511
|
|
|
483
|
-
|
|
512
|
+
logTestOptimizationSummary({ attemptToFixExecutions, newTestsWithDynamicNames })
|
|
513
|
+
loggedAttemptToFixTests.clear()
|
|
484
514
|
|
|
485
515
|
await flushPromise
|
|
486
516
|
|
|
@@ -542,7 +572,10 @@ function threadHandler (thread) {
|
|
|
542
572
|
workerProcess.on('message', (message) => {
|
|
543
573
|
if (message.__tinypool_worker_message__ && message.data) {
|
|
544
574
|
if (message.interprocessCode === VITEST_WORKER_TRACE_PAYLOAD_CODE) {
|
|
545
|
-
|
|
575
|
+
collectTestOptimizationSummariesFromTraces(message.data, {
|
|
576
|
+
newTestsWithDynamicNames,
|
|
577
|
+
attemptToFixExecutions,
|
|
578
|
+
})
|
|
546
579
|
workerReportTraceCh.publish(message.data)
|
|
547
580
|
} else if (message.interprocessCode === VITEST_WORKER_LOGS_PAYLOAD_CODE) {
|
|
548
581
|
workerReportLogsCh.publish(message.data)
|
|
@@ -584,7 +617,10 @@ function getWrappedOn (on) {
|
|
|
584
617
|
if (message.type !== 'Buffer' && Array.isArray(message)) {
|
|
585
618
|
const [interprocessCode, data] = message
|
|
586
619
|
if (interprocessCode === VITEST_WORKER_TRACE_PAYLOAD_CODE) {
|
|
587
|
-
|
|
620
|
+
collectTestOptimizationSummariesFromTraces(data, {
|
|
621
|
+
newTestsWithDynamicNames,
|
|
622
|
+
attemptToFixExecutions,
|
|
623
|
+
})
|
|
588
624
|
workerReportTraceCh.publish(data)
|
|
589
625
|
} else if (interprocessCode === VITEST_WORKER_LOGS_PAYLOAD_CODE) {
|
|
590
626
|
workerReportLogsCh.publish(data)
|
|
@@ -658,7 +694,7 @@ function wrapVitestTestRunner (VitestTestRunner) {
|
|
|
658
694
|
isRetryReasonAttemptToFix = task.repeats !== testManagementAttemptToFixRetries
|
|
659
695
|
task.repeats = testManagementAttemptToFixRetries
|
|
660
696
|
attemptToFixTasks.add(task)
|
|
661
|
-
|
|
697
|
+
attemptToFixTaskToStatuses.set(task, [])
|
|
662
698
|
}
|
|
663
699
|
},
|
|
664
700
|
})
|
|
@@ -726,15 +762,17 @@ function wrapVitestTestRunner (VitestTestRunner) {
|
|
|
726
762
|
|
|
727
763
|
if (isTestManagementTestsEnabled) {
|
|
728
764
|
const isAttemptingToFix = attemptToFixTasks.has(task)
|
|
729
|
-
const isDisabled = disabledTasks.has(task)
|
|
730
765
|
const isQuarantined = quarantinedTasks.has(task)
|
|
731
766
|
|
|
732
|
-
if (isAttemptingToFix
|
|
733
|
-
|
|
767
|
+
if (isAttemptingToFix) {
|
|
768
|
+
const statuses = attemptToFixTaskToStatuses.get(task)
|
|
769
|
+
if (task.result.state === 'pass' && statuses?.includes('fail')) {
|
|
734
770
|
switchedStatuses.add(task)
|
|
771
|
+
task.result.state = 'fail'
|
|
735
772
|
}
|
|
736
|
-
|
|
737
|
-
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
if (!isAttemptingToFix && isQuarantined) {
|
|
738
776
|
if (task.result.state === 'fail') {
|
|
739
777
|
switchedStatuses.add(task)
|
|
740
778
|
}
|
|
@@ -822,10 +860,9 @@ function wrapVitestTestRunner (VitestTestRunner) {
|
|
|
822
860
|
|
|
823
861
|
const lastExecutionStatus = task.result.state
|
|
824
862
|
const isAtf = attemptToFixTasks.has(task)
|
|
825
|
-
const isQuarantinedOrDisabledAtf = isAtf && (quarantinedTasks.has(task) || disabledTasks.has(task))
|
|
826
863
|
const shouldTrackStatuses = isEarlyFlakeDetectionEnabled || isAtf
|
|
827
|
-
const shouldFlipStatus = isEarlyFlakeDetectionEnabled ||
|
|
828
|
-
const statuses = taskToStatuses.get(task)
|
|
864
|
+
const shouldFlipStatus = isEarlyFlakeDetectionEnabled || isAtf
|
|
865
|
+
const statuses = isAtf ? attemptToFixTaskToStatuses.get(task) : taskToStatuses.get(task)
|
|
829
866
|
|
|
830
867
|
// These clauses handle task.repeats, whether EFD is enabled or not
|
|
831
868
|
// The only thing that EFD does is to forcefully pass the test if it has passed at least once
|
|
@@ -842,7 +879,7 @@ function wrapVitestTestRunner (VitestTestRunner) {
|
|
|
842
879
|
} else {
|
|
843
880
|
testPassCh.publish({ task, ...ctx.currentStore })
|
|
844
881
|
}
|
|
845
|
-
if (shouldTrackStatuses) {
|
|
882
|
+
if (shouldTrackStatuses && statuses) {
|
|
846
883
|
statuses.push(lastExecutionStatus)
|
|
847
884
|
}
|
|
848
885
|
if (shouldFlipStatus) {
|
|
@@ -855,7 +892,7 @@ function wrapVitestTestRunner (VitestTestRunner) {
|
|
|
855
892
|
}
|
|
856
893
|
}
|
|
857
894
|
} else if (numRepetition === task.repeats) {
|
|
858
|
-
if (shouldTrackStatuses) {
|
|
895
|
+
if (shouldTrackStatuses && statuses) {
|
|
859
896
|
statuses.push(lastExecutionStatus)
|
|
860
897
|
}
|
|
861
898
|
|
|
@@ -893,6 +930,14 @@ function wrapVitestTestRunner (VitestTestRunner) {
|
|
|
893
930
|
}
|
|
894
931
|
taskToCtx.set(task, ctx)
|
|
895
932
|
|
|
933
|
+
if (attemptToFixTasks.has(task)) {
|
|
934
|
+
logAttemptToFixTestExecution(
|
|
935
|
+
getTestSuitePath(task.file.filepath, process.cwd()),
|
|
936
|
+
testName,
|
|
937
|
+
loggedAttemptToFixTests
|
|
938
|
+
)
|
|
939
|
+
}
|
|
940
|
+
|
|
896
941
|
testStartCh.runStores(ctx, () => {})
|
|
897
942
|
|
|
898
943
|
// Wrap the test function so it runs inside the test span context.
|
|
@@ -960,11 +1005,11 @@ function wrapVitestTestRunner (VitestTestRunner) {
|
|
|
960
1005
|
let attemptToFixPassed = false
|
|
961
1006
|
let attemptToFixFailed = false
|
|
962
1007
|
if (attemptToFixTasks.has(task)) {
|
|
963
|
-
const statuses =
|
|
1008
|
+
const statuses = attemptToFixTaskToStatuses.get(task)
|
|
964
1009
|
if (statuses.length === testManagementAttemptToFixRetries) {
|
|
965
|
-
if (statuses.every(status => status === 'pass')) {
|
|
1010
|
+
if (status === 'pass' && statuses.every(status => status === 'pass')) {
|
|
966
1011
|
attemptToFixPassed = true
|
|
967
|
-
} else if (statuses.includes('fail')) {
|
|
1012
|
+
} else if (status === 'fail' || statuses.includes('fail')) {
|
|
968
1013
|
attemptToFixFailed = true
|
|
969
1014
|
}
|
|
970
1015
|
}
|
|
@@ -1165,9 +1210,16 @@ addHook({
|
|
|
1165
1210
|
// We have to trick vitest into thinking that the test has passed
|
|
1166
1211
|
// but we want to report it as failed if it did fail
|
|
1167
1212
|
const isSwitchedStatus = switchedStatuses.has(task)
|
|
1213
|
+
const providedContext = getProvidedContext()
|
|
1168
1214
|
|
|
1169
1215
|
if (result) {
|
|
1170
1216
|
const { state, duration, errors } = result
|
|
1217
|
+
const testError = errors?.[0]
|
|
1218
|
+
if (attemptToFixTasks.has(task)) {
|
|
1219
|
+
const status = getFinalAttemptToFixStatus(task, state, isSwitchedStatus, testCtx)
|
|
1220
|
+
recordFinalAttemptToFixExecution(task, status, providedContext)
|
|
1221
|
+
}
|
|
1222
|
+
|
|
1171
1223
|
if (state === 'skip') { // programmatic skip
|
|
1172
1224
|
testSkipCh.publish({
|
|
1173
1225
|
testName: getTestName(task),
|
|
@@ -1177,24 +1229,19 @@ addHook({
|
|
|
1177
1229
|
})
|
|
1178
1230
|
} else if (state === 'pass' && !isSwitchedStatus) {
|
|
1179
1231
|
if (testCtx) {
|
|
1232
|
+
const isSkippedByTestManagement =
|
|
1233
|
+
!attemptToFixTasks.has(task) && (disabledTasks.has(task) || quarantinedTasks.has(task))
|
|
1180
1234
|
testPassCh.publish({
|
|
1181
1235
|
task,
|
|
1182
|
-
finalStatus:
|
|
1183
|
-
disabledTasks.has(task) || quarantinedTasks.has(task) ? 'skip' : 'pass',
|
|
1236
|
+
finalStatus: isSkippedByTestManagement ? 'skip' : 'pass',
|
|
1184
1237
|
...testCtx.currentStore,
|
|
1185
1238
|
})
|
|
1186
1239
|
}
|
|
1187
1240
|
} else if (state === 'fail' || isSwitchedStatus) {
|
|
1188
|
-
let testError
|
|
1189
|
-
|
|
1190
|
-
if (errors?.length) {
|
|
1191
|
-
testError = errors[0]
|
|
1192
|
-
}
|
|
1193
|
-
|
|
1194
1241
|
let hasFailedAllRetries = false
|
|
1195
1242
|
let attemptToFixFailed = false
|
|
1196
1243
|
if (attemptToFixTasks.has(task)) {
|
|
1197
|
-
const statuses =
|
|
1244
|
+
const statuses = attemptToFixTaskToStatuses.get(task)
|
|
1198
1245
|
if (statuses.includes('fail')) {
|
|
1199
1246
|
attemptToFixFailed = true
|
|
1200
1247
|
}
|
|
@@ -1204,7 +1251,6 @@ addHook({
|
|
|
1204
1251
|
}
|
|
1205
1252
|
|
|
1206
1253
|
// Check if all EFD retries failed
|
|
1207
|
-
const providedContext = getProvidedContext()
|
|
1208
1254
|
const isEfdRetry =
|
|
1209
1255
|
providedContext.isEarlyFlakeDetectionEnabled && (newTasks.has(task) || modifiedTasks.has(task))
|
|
1210
1256
|
if (isEfdRetry) {
|
|
@@ -1232,7 +1278,7 @@ addHook({
|
|
|
1232
1278
|
|
|
1233
1279
|
let finalStatus
|
|
1234
1280
|
if (isSwitchedStatus) {
|
|
1235
|
-
if (disabledTasks.has(task) || quarantinedTasks.has(task)) {
|
|
1281
|
+
if (!attemptToFixTasks.has(task) && (disabledTasks.has(task) || quarantinedTasks.has(task))) {
|
|
1236
1282
|
finalStatus = 'skip'
|
|
1237
1283
|
} else if (isAtrRetry || isEfdRetry) {
|
|
1238
1284
|
finalStatus = hasFailedAllRetries ? 'fail' : 'pass'
|
|
@@ -149,7 +149,7 @@ class BaseAwsSdkPlugin extends ClientPlugin {
|
|
|
149
149
|
}
|
|
150
150
|
this.addResponseTags(span, response)
|
|
151
151
|
|
|
152
|
-
if (this._tracerConfig?.
|
|
152
|
+
if (this._tracerConfig?.DD_TRACE_AWS_ADD_SPAN_POINTERS) {
|
|
153
153
|
this.addSpanPointers(span, response)
|
|
154
154
|
}
|
|
155
155
|
})
|
|
@@ -118,7 +118,7 @@ class DynamoDb extends BaseAwsSdkPlugin {
|
|
|
118
118
|
return this.dynamoPrimaryKeyConfig
|
|
119
119
|
}
|
|
120
120
|
|
|
121
|
-
const configStr = this._tracerConfig?.
|
|
121
|
+
const configStr = this._tracerConfig?.DD_TRACE_DYNAMODB_TABLE_PRIMARY_KEYS
|
|
122
122
|
if (!configStr) {
|
|
123
123
|
log.warn(
|
|
124
124
|
// eslint-disable-next-line @stylistic/max-len
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
+
const log = require('../../dd-trace/src/log')
|
|
3
4
|
const ConsumerPlugin = require('../../dd-trace/src/plugins/consumer')
|
|
4
5
|
const { getMessageSize } = require('../../dd-trace/src/datastreams')
|
|
5
6
|
|
|
@@ -73,8 +74,8 @@ class BullmqConsumerPlugin extends ConsumerPlugin {
|
|
|
73
74
|
job.opts.telemetry.metadata = JSON.stringify(metadata)
|
|
74
75
|
|
|
75
76
|
return ddCarrier
|
|
76
|
-
} catch {
|
|
77
|
-
|
|
77
|
+
} catch (error) {
|
|
78
|
+
log.warn('bullmq: skipping _datadog extract on malformed telemetry.metadata: %s', error.message)
|
|
78
79
|
}
|
|
79
80
|
}
|
|
80
81
|
}
|
|
@@ -1,8 +1,22 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
+
const log = require('../../dd-trace/src/log')
|
|
3
4
|
const ProducerPlugin = require('../../dd-trace/src/plugins/producer')
|
|
4
5
|
const { DsmPathwayCodec, getMessageSize } = require('../../dd-trace/src/datastreams')
|
|
5
6
|
|
|
7
|
+
// Customer-controlled metadata may be malformed JSON. Returning a fresh `{}`
|
|
8
|
+
// on parse failure keeps the publish path alive instead of throwing into
|
|
9
|
+
// `Queue.add` / `Queue.addBulk`.
|
|
10
|
+
function parseTelemetryMetadata (raw) {
|
|
11
|
+
if (!raw) return {}
|
|
12
|
+
try {
|
|
13
|
+
return JSON.parse(raw)
|
|
14
|
+
} catch (error) {
|
|
15
|
+
log.warn('bullmq: ignoring malformed telemetry.metadata: %s', error.message)
|
|
16
|
+
return {}
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
6
20
|
class BaseBullmqProducerPlugin extends ProducerPlugin {
|
|
7
21
|
static id = 'bullmq'
|
|
8
22
|
|
|
@@ -45,9 +59,9 @@ class BaseBullmqProducerPlugin extends ProducerPlugin {
|
|
|
45
59
|
_injectIntoOpts (span, opts) {
|
|
46
60
|
const carrier = {}
|
|
47
61
|
this.tracer.inject(span, 'text_map', carrier)
|
|
48
|
-
const
|
|
49
|
-
|
|
50
|
-
opts.telemetry = { metadata: JSON.stringify(
|
|
62
|
+
const metadata = parseTelemetryMetadata(opts.telemetry?.metadata)
|
|
63
|
+
metadata._datadog = carrier
|
|
64
|
+
opts.telemetry = { metadata: JSON.stringify(metadata), omitContext: true }
|
|
51
65
|
}
|
|
52
66
|
|
|
53
67
|
setProducerCheckpoint (span, ctx) {
|
|
@@ -56,10 +70,10 @@ class BaseBullmqProducerPlugin extends ProducerPlugin {
|
|
|
56
70
|
const dataStreamsContext = this.tracer.setCheckpoint(edgeTags, span, payloadSize)
|
|
57
71
|
|
|
58
72
|
if (optsTarget && typeof optsTarget === 'object') {
|
|
59
|
-
const
|
|
60
|
-
DsmPathwayCodec.encode(dataStreamsContext,
|
|
61
|
-
if (!
|
|
62
|
-
optsTarget.telemetry = { metadata: JSON.stringify(
|
|
73
|
+
const metadata = parseTelemetryMetadata(optsTarget.telemetry?.metadata)
|
|
74
|
+
DsmPathwayCodec.encode(dataStreamsContext, metadata._datadog || metadata)
|
|
75
|
+
if (!metadata._datadog) metadata._datadog = {}
|
|
76
|
+
optsTarget.telemetry = { metadata: JSON.stringify(metadata), omitContext: true }
|
|
63
77
|
}
|
|
64
78
|
}
|
|
65
79
|
|
|
@@ -161,10 +175,10 @@ class QueueAddBulkPlugin extends BaseBullmqProducerPlugin {
|
|
|
161
175
|
const payloadSize = getMessageSize(job.data)
|
|
162
176
|
const dataStreamsContext = this.tracer.setCheckpoint(edgeTags, span, payloadSize)
|
|
163
177
|
job.opts = job.opts || {}
|
|
164
|
-
const
|
|
165
|
-
DsmPathwayCodec.encode(dataStreamsContext,
|
|
166
|
-
if (!
|
|
167
|
-
job.opts.telemetry = { metadata: JSON.stringify(
|
|
178
|
+
const metadata = parseTelemetryMetadata(job.opts.telemetry?.metadata)
|
|
179
|
+
DsmPathwayCodec.encode(dataStreamsContext, metadata._datadog || metadata)
|
|
180
|
+
if (!metadata._datadog) metadata._datadog = {}
|
|
181
|
+
job.opts.telemetry = { metadata: JSON.stringify(metadata), omitContext: true }
|
|
168
182
|
}
|
|
169
183
|
}
|
|
170
184
|
}
|
|
@@ -55,7 +55,9 @@ const {
|
|
|
55
55
|
TEST_IS_MODIFIED,
|
|
56
56
|
TEST_HAS_DYNAMIC_NAME,
|
|
57
57
|
DYNAMIC_NAME_RE,
|
|
58
|
-
|
|
58
|
+
recordAttemptToFixExecution,
|
|
59
|
+
logAttemptToFixTestExecution,
|
|
60
|
+
logTestOptimizationSummary,
|
|
59
61
|
getPullRequestBaseBranch,
|
|
60
62
|
TEST_FINAL_STATUS,
|
|
61
63
|
} = require('../../dd-trace/src/plugins/util/test')
|
|
@@ -278,9 +280,8 @@ function getFinalStatus ({
|
|
|
278
280
|
isQuarantined,
|
|
279
281
|
isDisabled,
|
|
280
282
|
}) {
|
|
281
|
-
// If the test is quarantined or disabled,
|
|
282
|
-
|
|
283
|
-
if (isQuarantined || isDisabled || status === 'skip') {
|
|
283
|
+
// If the test is quarantined or disabled, its final status is skip unless attempt-to-fix takes precedence.
|
|
284
|
+
if (status === 'skip' || (retryKind !== FINAL_STATUS_RETRY_KIND.atf && (isQuarantined || isDisabled))) {
|
|
284
285
|
return 'skip'
|
|
285
286
|
}
|
|
286
287
|
|
|
@@ -322,6 +323,8 @@ class CypressPlugin {
|
|
|
322
323
|
isImpactedTestsEnabled = false
|
|
323
324
|
modifiedFiles = []
|
|
324
325
|
newTestsWithDynamicNames = new Set()
|
|
326
|
+
attemptToFixExecutions = new Map()
|
|
327
|
+
loggedAttemptToFixTests = new Set()
|
|
325
328
|
|
|
326
329
|
constructor () {
|
|
327
330
|
const {
|
|
@@ -394,6 +397,8 @@ class CypressPlugin {
|
|
|
394
397
|
this.testManagementTests = undefined
|
|
395
398
|
this.isImpactedTestsEnabled = false
|
|
396
399
|
this.modifiedFiles = []
|
|
400
|
+
this.attemptToFixExecutions = new Map()
|
|
401
|
+
this.loggedAttemptToFixTests = new Set()
|
|
397
402
|
this.activeTestSpan = null
|
|
398
403
|
this.testSuiteSpan = null
|
|
399
404
|
this.testModuleSpan = null
|
|
@@ -800,7 +805,10 @@ class CypressPlugin {
|
|
|
800
805
|
this.testSessionSpan.setTag(TEST_MANAGEMENT_ENABLED, 'true')
|
|
801
806
|
}
|
|
802
807
|
|
|
803
|
-
|
|
808
|
+
logTestOptimizationSummary({
|
|
809
|
+
attemptToFixExecutions: this.attemptToFixExecutions,
|
|
810
|
+
newTestsWithDynamicNames: this.newTestsWithDynamicNames,
|
|
811
|
+
})
|
|
804
812
|
|
|
805
813
|
this.testModuleSpan.finish()
|
|
806
814
|
this.ciVisEvent(TELEMETRY_EVENT_FINISHED, 'module')
|
|
@@ -939,10 +947,10 @@ class CypressPlugin {
|
|
|
939
947
|
if (cypressTest.displayError) {
|
|
940
948
|
latestError = new Error(cypressTest.displayError)
|
|
941
949
|
}
|
|
942
|
-
// Update test status - but NOT for quarantined tests where we intentionally
|
|
950
|
+
// Update test status - but NOT for non-ATF quarantined tests where we intentionally
|
|
943
951
|
// report 'fail' to Datadog even though Cypress sees it as 'pass'
|
|
944
952
|
const isQuarantinedTest = finishedTest.testSpan?.context()?._tags?.[TEST_MANAGEMENT_IS_QUARANTINED] === 'true'
|
|
945
|
-
if (cypressTestStatus !== finishedTest.testStatus && !isQuarantinedTest) {
|
|
953
|
+
if (cypressTestStatus !== finishedTest.testStatus && (!isQuarantinedTest || finishedTest.isAttemptToFix)) {
|
|
946
954
|
finishedTest.testSpan.setTag(TEST_STATUS, cypressTestStatus)
|
|
947
955
|
finishedTest.testSpan.setTag('error', latestError)
|
|
948
956
|
}
|
|
@@ -1050,6 +1058,10 @@ class CypressPlugin {
|
|
|
1050
1058
|
return { shouldSkip: true }
|
|
1051
1059
|
}
|
|
1052
1060
|
|
|
1061
|
+
if (isAttemptToFix) {
|
|
1062
|
+
logAttemptToFixTestExecution(testSuite, testName, this.loggedAttemptToFixTests)
|
|
1063
|
+
}
|
|
1064
|
+
|
|
1053
1065
|
// For disabled tests (not attemptToFix), skip them
|
|
1054
1066
|
if (!isAttemptToFix && isDisabled) {
|
|
1055
1067
|
return { shouldSkip: true }
|
|
@@ -1090,6 +1102,7 @@ class CypressPlugin {
|
|
|
1090
1102
|
isAttemptToFix,
|
|
1091
1103
|
isModified,
|
|
1092
1104
|
isQuarantined: isQuarantinedFromSupport,
|
|
1105
|
+
isDisabled: isDisabledFromSupport,
|
|
1093
1106
|
} = test
|
|
1094
1107
|
if (coverage && this.isCodeCoverageEnabled && this.tracer._tracer._exporter?.exportCoverage) {
|
|
1095
1108
|
const coverageFiles = getCoveredFilenamesFromCoverage(coverage)
|
|
@@ -1119,6 +1132,7 @@ class CypressPlugin {
|
|
|
1119
1132
|
this.testStatuses[testName] = [testStatus]
|
|
1120
1133
|
}
|
|
1121
1134
|
const testStatuses = this.testStatuses[testName]
|
|
1135
|
+
const activeSpanTags = this.activeTestSpan.context()._tags
|
|
1122
1136
|
|
|
1123
1137
|
if (error) {
|
|
1124
1138
|
this.activeTestSpan.setTag('error', error)
|
|
@@ -1194,6 +1208,13 @@ class CypressPlugin {
|
|
|
1194
1208
|
this.activeTestSpan.setTag(TEST_MANAGEMENT_ATTEMPT_TO_FIX_PASSED, 'true')
|
|
1195
1209
|
}
|
|
1196
1210
|
}
|
|
1211
|
+
recordAttemptToFixExecution(this.attemptToFixExecutions, {
|
|
1212
|
+
testSuite,
|
|
1213
|
+
testName,
|
|
1214
|
+
status: testStatus,
|
|
1215
|
+
isDisabled: activeSpanTags[TEST_MANAGEMENT_IS_DISABLED] === 'true',
|
|
1216
|
+
isQuarantined: activeSpanTags[TEST_MANAGEMENT_IS_QUARANTINED] === 'true',
|
|
1217
|
+
})
|
|
1197
1218
|
}
|
|
1198
1219
|
// ATR: set TEST_HAS_FAILED_ALL_RETRIES when all auto test retries were exhausted and every attempt failed
|
|
1199
1220
|
if (this.isFlakyTestRetriesEnabled && !isAttemptToFix && !isEfdRetry &&
|
|
@@ -1207,6 +1228,9 @@ class CypressPlugin {
|
|
|
1207
1228
|
if (isQuarantinedFromSupport) {
|
|
1208
1229
|
this.activeTestSpan.setTag(TEST_MANAGEMENT_IS_QUARANTINED, 'true')
|
|
1209
1230
|
}
|
|
1231
|
+
if (isDisabledFromSupport) {
|
|
1232
|
+
this.activeTestSpan.setTag(TEST_MANAGEMENT_IS_DISABLED, 'true')
|
|
1233
|
+
}
|
|
1210
1234
|
|
|
1211
1235
|
const finishedTest = {
|
|
1212
1236
|
testName,
|
|
@@ -1222,13 +1246,12 @@ class CypressPlugin {
|
|
|
1222
1246
|
this.finishedTestsByFile[testSuite] = [finishedTest]
|
|
1223
1247
|
}
|
|
1224
1248
|
// test spans are finished at after:spec
|
|
1225
|
-
const activeSpanTags = this.activeTestSpan.context()._tags
|
|
1226
1249
|
this.ciVisEvent(TELEMETRY_EVENT_FINISHED, 'test', {
|
|
1227
1250
|
hasCodeOwners: !!activeSpanTags[TEST_CODE_OWNERS],
|
|
1228
1251
|
isNew,
|
|
1229
1252
|
isRum: isRUMActive,
|
|
1230
1253
|
browserDriver: 'cypress',
|
|
1231
|
-
isQuarantined:
|
|
1254
|
+
isQuarantined: activeSpanTags[TEST_MANAGEMENT_IS_QUARANTINED] === 'true',
|
|
1232
1255
|
isModified,
|
|
1233
1256
|
isDisabled: activeSpanTags[TEST_MANAGEMENT_IS_DISABLED] === 'true',
|
|
1234
1257
|
})
|