dd-trace 5.73.0 → 5.74.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/index.d.ts +28 -0
- package/package.json +6 -3
- package/packages/datadog-core/src/utils/src/set.js +5 -1
- package/packages/datadog-esbuild/index.js +104 -36
- package/packages/datadog-esbuild/src/utils.js +198 -0
- package/packages/datadog-instrumentations/src/express-session.js +1 -0
- package/packages/datadog-instrumentations/src/express.js +82 -0
- package/packages/datadog-instrumentations/src/helpers/router-helper.js +238 -0
- package/packages/datadog-instrumentations/src/jest.js +2 -1
- package/packages/datadog-instrumentations/src/playwright.js +110 -56
- package/packages/datadog-instrumentations/src/router.js +63 -6
- package/packages/datadog-instrumentations/src/ws.js +3 -3
- package/packages/datadog-plugin-express/src/code_origin.js +2 -0
- package/packages/datadog-plugin-ws/src/close.js +1 -1
- package/packages/dd-trace/src/config-helper.js +3 -1
- package/packages/dd-trace/src/config.js +437 -446
- package/packages/dd-trace/src/config_defaults.js +5 -12
- package/packages/dd-trace/src/plugins/util/ci.js +3 -2
- package/packages/dd-trace/src/plugins/util/stacktrace.js +16 -1
- package/packages/dd-trace/src/supported-configurations.json +1 -0
- package/packages/dd-trace/src/telemetry/endpoints.js +27 -1
- package/packages/dd-trace/src/telemetry/index.js +16 -13
- package/scripts/preinstall.js +3 -1
- package/version.js +2 -1
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { channel } = require('./instrument')
|
|
4
|
+
const shimmer = require('../../../datadog-shimmer')
|
|
5
|
+
|
|
6
|
+
const routerMountPaths = new WeakMap() // to track mount paths for router instances
|
|
7
|
+
const layerMatchers = new WeakMap() // to store layer matchers
|
|
8
|
+
const appMountedRouters = new WeakSet() // to track routers mounted via app.use()
|
|
9
|
+
|
|
10
|
+
const METHODS = [...require('http').METHODS.map(v => v.toLowerCase()), 'all']
|
|
11
|
+
|
|
12
|
+
const routeAddedChannel = channel('apm:express:route:added')
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Joins two URL path segments into a single path
|
|
16
|
+
*
|
|
17
|
+
* @param {string} base - The base path
|
|
18
|
+
* @param {string} path - The path to append
|
|
19
|
+
* @returns {string|null} The joined path or null if the combination would create an invalid route in Express
|
|
20
|
+
*/
|
|
21
|
+
function joinPath (base, path) {
|
|
22
|
+
if (!base || base === '/') return path || '/'
|
|
23
|
+
if (!path || path === '/') return base
|
|
24
|
+
|
|
25
|
+
// Express does not normalize paths without leading slashes.
|
|
26
|
+
// If either path doesn't start with '/', we should skip this combination.
|
|
27
|
+
// Allow only empty string for path
|
|
28
|
+
if (path !== '' && !path.startsWith('/')) return null
|
|
29
|
+
if (!base.startsWith('/')) return null
|
|
30
|
+
|
|
31
|
+
// Handle duplicate slashes when base ends with / and path starts with /
|
|
32
|
+
if (base.endsWith('/') && path.startsWith('/')) {
|
|
33
|
+
return base + path.slice(1)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return base + path
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Normalize route definitions coming from Express into a string representation
|
|
40
|
+
function normalizeRoutePath (path) {
|
|
41
|
+
if (path == null) return null
|
|
42
|
+
if (typeof path === 'string') return path
|
|
43
|
+
if (path instanceof RegExp) return path.toString()
|
|
44
|
+
|
|
45
|
+
return String(path)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Recursively publish every route reachable from the router.
|
|
49
|
+
function collectRoutesFromRouter (router, prefix) {
|
|
50
|
+
if (!router?.stack?.length) return
|
|
51
|
+
|
|
52
|
+
for (const layer of router.stack) {
|
|
53
|
+
if (layer.route) {
|
|
54
|
+
// This layer has a direct route
|
|
55
|
+
const route = layer.route
|
|
56
|
+
|
|
57
|
+
const fullPaths = getRouteFullPaths(route, prefix)
|
|
58
|
+
|
|
59
|
+
for (const fullPath of fullPaths) {
|
|
60
|
+
for (const [method, enabled] of Object.entries(route.methods || {})) {
|
|
61
|
+
if (!enabled) continue
|
|
62
|
+
routeAddedChannel.publish({
|
|
63
|
+
method: normalizeMethodName(method),
|
|
64
|
+
path: fullPath
|
|
65
|
+
})
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
} else if (layer.handle?.stack?.length) {
|
|
69
|
+
// This layer contains a nested router
|
|
70
|
+
// Extract mount path from layer
|
|
71
|
+
const mountPath = typeof layer.path === 'string'
|
|
72
|
+
? layer.path
|
|
73
|
+
: getLayerMatchers(layer)?.[0]?.path || ''
|
|
74
|
+
|
|
75
|
+
const nestedPrefix = joinPath(prefix, mountPath)
|
|
76
|
+
if (nestedPrefix === null) continue
|
|
77
|
+
|
|
78
|
+
// Set the mount path for the nested router
|
|
79
|
+
setRouterMountPath(layer.handle, nestedPrefix)
|
|
80
|
+
markAppMounted(layer.handle)
|
|
81
|
+
// Recursively collect from nested routers
|
|
82
|
+
collectRoutesFromRouter(layer.handle, nestedPrefix)
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Flatten any route definition into an array of normalized path strings.
|
|
88
|
+
function normalizeRoutePaths (path) {
|
|
89
|
+
if (path == null) return []
|
|
90
|
+
|
|
91
|
+
if (Array.isArray(path) === false) {
|
|
92
|
+
const normalized = normalizeRoutePath(path)
|
|
93
|
+
return [normalized]
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const paths = path.flat(Infinity)
|
|
97
|
+
const result = []
|
|
98
|
+
for (const _path of paths) {
|
|
99
|
+
const normalized = normalizeRoutePath(_path)
|
|
100
|
+
if (normalized !== null) {
|
|
101
|
+
result.push(normalized)
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return result
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function setRouterMountPath (router, mountPath) {
|
|
109
|
+
if (!router || typeof mountPath !== 'string') return
|
|
110
|
+
const existing = routerMountPaths.get(router)
|
|
111
|
+
if (existing) {
|
|
112
|
+
existing.add(mountPath)
|
|
113
|
+
} else {
|
|
114
|
+
routerMountPaths.set(router, new Set([mountPath]))
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function getRouterMountPaths (router) {
|
|
119
|
+
const paths = routerMountPaths.get(router)
|
|
120
|
+
if (!paths) return []
|
|
121
|
+
return [...paths]
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function setLayerMatchers (layer, matchers) {
|
|
125
|
+
layerMatchers.set(layer, matchers)
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function getLayerMatchers (layer) {
|
|
129
|
+
return layerMatchers.get(layer)
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function normalizeMethodName (method) {
|
|
133
|
+
return method === '_all' || method === 'all' ? '*' : method
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function getRouteFullPaths (route, prefix) {
|
|
137
|
+
if (!route) return []
|
|
138
|
+
|
|
139
|
+
const routePaths = normalizeRoutePaths(route.path)
|
|
140
|
+
const pathsToPublish = routePaths.length ? routePaths : ['']
|
|
141
|
+
|
|
142
|
+
return pathsToPublish
|
|
143
|
+
.map(routePath => joinPath(prefix, routePath))
|
|
144
|
+
.filter(path => path !== null) // Filter out invalid path combinations
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function markAppMounted (router) {
|
|
148
|
+
if (router) appMountedRouters.add(router)
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function isAppMounted (router) {
|
|
152
|
+
return appMountedRouters.has(router)
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Normalise the optional mount argument provided to app.use()/router.use().
|
|
157
|
+
* Express accepts strings, regex, arrays (possibly nested), or
|
|
158
|
+
* no mount path at all; this helper returns the flattened set of paths along
|
|
159
|
+
* with the index where actual middleware arguments start.
|
|
160
|
+
*/
|
|
161
|
+
function extractMountPaths (path) {
|
|
162
|
+
const hasMount = typeof path === 'string' || path instanceof RegExp || Array.isArray(path)
|
|
163
|
+
|
|
164
|
+
if (!hasMount) {
|
|
165
|
+
return { mountPaths: ['/'], startIdx: 0 }
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const paths = normalizeRoutePaths(path)
|
|
169
|
+
return {
|
|
170
|
+
mountPaths: paths.length ? paths : ['/'],
|
|
171
|
+
startIdx: 1
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Detect cycle router graphs.
|
|
177
|
+
*/
|
|
178
|
+
function hasRouterCycle (router, stack = new Set()) {
|
|
179
|
+
if (!router?.stack?.length) return false
|
|
180
|
+
if (stack.has(router)) return true
|
|
181
|
+
|
|
182
|
+
stack.add(router)
|
|
183
|
+
|
|
184
|
+
for (const layer of router.stack) {
|
|
185
|
+
if (!layer?.route && layer?.handle?.stack?.length) {
|
|
186
|
+
const hasCycle = hasRouterCycle(layer.handle, stack)
|
|
187
|
+
if (hasCycle) {
|
|
188
|
+
return true
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
stack.delete(router)
|
|
194
|
+
return false
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function wrapRouteMethodsAndPublish (route, paths, publish) {
|
|
198
|
+
if (!route || !paths.length) return
|
|
199
|
+
|
|
200
|
+
const filteredPaths = paths.filter(Boolean)
|
|
201
|
+
if (!filteredPaths.length) return
|
|
202
|
+
|
|
203
|
+
const uniquePaths = new Set(filteredPaths)
|
|
204
|
+
|
|
205
|
+
METHODS.forEach(method => {
|
|
206
|
+
if (typeof route[method] !== 'function') return
|
|
207
|
+
|
|
208
|
+
shimmer.wrap(route, method, (originalMethod) => function wrappedRouteMethod (...args) {
|
|
209
|
+
const normalizedMethod = normalizeMethodName(method)
|
|
210
|
+
|
|
211
|
+
for (const path of uniquePaths) {
|
|
212
|
+
publish({
|
|
213
|
+
method: normalizedMethod,
|
|
214
|
+
path
|
|
215
|
+
})
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
return originalMethod.apply(this, args)
|
|
219
|
+
})
|
|
220
|
+
})
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
module.exports = {
|
|
224
|
+
setRouterMountPath,
|
|
225
|
+
getRouterMountPaths,
|
|
226
|
+
joinPath,
|
|
227
|
+
setLayerMatchers,
|
|
228
|
+
getLayerMatchers,
|
|
229
|
+
markAppMounted,
|
|
230
|
+
isAppMounted,
|
|
231
|
+
normalizeRoutePath,
|
|
232
|
+
normalizeRoutePaths,
|
|
233
|
+
getRouteFullPaths,
|
|
234
|
+
wrapRouteMethodsAndPublish,
|
|
235
|
+
extractMountPaths,
|
|
236
|
+
hasRouterCycle,
|
|
237
|
+
collectRoutesFromRouter
|
|
238
|
+
}
|
|
@@ -1335,7 +1335,7 @@ addHook({
|
|
|
1335
1335
|
}
|
|
1336
1336
|
const returnedValue = requireModuleOrMock.apply(this, arguments)
|
|
1337
1337
|
if (process.exitCode === 1) {
|
|
1338
|
-
if (this.loggedReferenceErrors
|
|
1338
|
+
if (this.loggedReferenceErrors?.size > 0) {
|
|
1339
1339
|
const errorMessage = [...this.loggedReferenceErrors][0]
|
|
1340
1340
|
testSuiteErrorCh.publish({
|
|
1341
1341
|
errorMessage,
|
|
@@ -1427,6 +1427,7 @@ function enqueueWrapper (enqueue) {
|
|
|
1427
1427
|
if (worker && !wrappedWorkers.has(worker)) {
|
|
1428
1428
|
shimmer.wrap(worker._child, 'send', sendWrapper)
|
|
1429
1429
|
shimmer.wrap(worker, '_onMessage', onMessageWrapper)
|
|
1430
|
+
worker._child.removeAllListeners('message')
|
|
1430
1431
|
worker._child.on('message', worker._onMessage.bind(worker))
|
|
1431
1432
|
wrappedWorkers.add(worker)
|
|
1432
1433
|
}
|
|
@@ -7,7 +7,8 @@ const shimmer = require('../../datadog-shimmer')
|
|
|
7
7
|
const {
|
|
8
8
|
parseAnnotations,
|
|
9
9
|
getTestSuitePath,
|
|
10
|
-
PLAYWRIGHT_WORKER_TRACE_PAYLOAD_CODE
|
|
10
|
+
PLAYWRIGHT_WORKER_TRACE_PAYLOAD_CODE,
|
|
11
|
+
getIsFaultyEarlyFlakeDetection
|
|
11
12
|
} = require('../../dd-trace/src/plugins/util/test')
|
|
12
13
|
const log = require('../../dd-trace/src/log')
|
|
13
14
|
const { DD_MAJOR } = require('../../../version')
|
|
@@ -52,6 +53,7 @@ let isKnownTestsEnabled = false
|
|
|
52
53
|
let isEarlyFlakeDetectionEnabled = false
|
|
53
54
|
let earlyFlakeDetectionNumRetries = 0
|
|
54
55
|
let isEarlyFlakeDetectionFaulty = false
|
|
56
|
+
let earlyFlakeDetectionFaultyThreshold = 0
|
|
55
57
|
let isFlakyTestRetriesEnabled = false
|
|
56
58
|
let flakyTestRetriesCount = 0
|
|
57
59
|
let knownTests = {}
|
|
@@ -111,8 +113,10 @@ function deepCloneSuite (suite, filterTest, tags = []) {
|
|
|
111
113
|
if (filterTest(entry)) {
|
|
112
114
|
const copiedTest = entry._clone()
|
|
113
115
|
tags.forEach(tag => {
|
|
114
|
-
|
|
115
|
-
|
|
116
|
+
const resolvedTag = typeof tag === 'function' ? tag(entry) : tag
|
|
117
|
+
|
|
118
|
+
if (resolvedTag) {
|
|
119
|
+
copiedTest[resolvedTag] = true
|
|
116
120
|
}
|
|
117
121
|
})
|
|
118
122
|
copy._addTest(copiedTest)
|
|
@@ -542,6 +546,7 @@ function runAllTestsWrapper (runAllTests, playwrightVersion) {
|
|
|
542
546
|
isKnownTestsEnabled = libraryConfig.isKnownTestsEnabled
|
|
543
547
|
isEarlyFlakeDetectionEnabled = libraryConfig.isEarlyFlakeDetectionEnabled
|
|
544
548
|
earlyFlakeDetectionNumRetries = libraryConfig.earlyFlakeDetectionNumRetries
|
|
549
|
+
earlyFlakeDetectionFaultyThreshold = libraryConfig.earlyFlakeDetectionFaultyThreshold
|
|
545
550
|
isFlakyTestRetriesEnabled = libraryConfig.isFlakyTestRetriesEnabled
|
|
546
551
|
flakyTestRetriesCount = libraryConfig.flakyTestRetriesCount
|
|
547
552
|
isTestManagementTestsEnabled = libraryConfig.isTestManagementEnabled
|
|
@@ -761,6 +766,30 @@ addHook({
|
|
|
761
766
|
return suiteUtilsPackage
|
|
762
767
|
})
|
|
763
768
|
|
|
769
|
+
/**
|
|
770
|
+
* We could repeat the logic of `applyRepeatEachIndex` here, but it'd be more risky
|
|
771
|
+
* as playwright could change it at any time.
|
|
772
|
+
*
|
|
773
|
+
* `applyRepeatEachIndex` goes through all the tests in a suite and applies the "repeat" logic
|
|
774
|
+
* for a single repeat index.
|
|
775
|
+
*
|
|
776
|
+
* This means that the clone logic is cumbersome:
|
|
777
|
+
* - we grab the unique file suites that have new tests
|
|
778
|
+
* - we store its project suite
|
|
779
|
+
* - we clone each of these file suites for each repeat index
|
|
780
|
+
* - we execute `applyRepeatEachIndex` for each of these cloned file suites
|
|
781
|
+
* - we add the cloned file suites to the project suite
|
|
782
|
+
*/
|
|
783
|
+
function applyRetriesToTests (fileSuitesWithTestsToRetry, filterTest, tagsToApply, numRetries) {
|
|
784
|
+
for (const [fileSuite, projectSuite] of fileSuitesWithTestsToRetry.entries()) {
|
|
785
|
+
for (let repeatEachIndex = 1; repeatEachIndex <= numRetries; repeatEachIndex++) {
|
|
786
|
+
const copyFileSuite = deepCloneSuite(fileSuite, filterTest, tagsToApply)
|
|
787
|
+
applyRepeatEachIndex(projectSuite._fullProject, copyFileSuite, repeatEachIndex + 1)
|
|
788
|
+
projectSuite._addSuite(copyFileSuite)
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
|
|
764
793
|
addHook({
|
|
765
794
|
name: 'playwright',
|
|
766
795
|
file: 'lib/runner/loadUtils.js',
|
|
@@ -780,87 +809,112 @@ addHook({
|
|
|
780
809
|
const allTests = rootSuite.allTests()
|
|
781
810
|
|
|
782
811
|
if (isTestManagementTestsEnabled) {
|
|
812
|
+
const fileSuitesWithManagedTestsToProjects = new Map()
|
|
783
813
|
for (const test of allTests) {
|
|
784
814
|
const testProperties = getTestProperties(test)
|
|
815
|
+
// Disabled tests are skipped and not retried
|
|
785
816
|
if (testProperties.disabled) {
|
|
786
817
|
test._ddIsDisabled = true
|
|
787
|
-
|
|
818
|
+
test.expectedStatus = 'skipped'
|
|
819
|
+
continue
|
|
820
|
+
}
|
|
821
|
+
if (testProperties.quarantined) {
|
|
788
822
|
test._ddIsQuarantined = true
|
|
823
|
+
if (!testProperties.attemptToFix) {
|
|
824
|
+
// Do not skip quarantined tests, let them run and overwrite results post-run if they fail
|
|
825
|
+
const testFqn = getTestFullyQualifiedName(test)
|
|
826
|
+
quarantinedButNotAttemptToFixFqns.add(testFqn)
|
|
827
|
+
}
|
|
789
828
|
}
|
|
790
829
|
if (testProperties.attemptToFix) {
|
|
791
830
|
test._ddIsAttemptToFix = true
|
|
792
831
|
const fileSuite = getSuiteType(test, 'file')
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
const copyFileSuite = deepCloneSuite(fileSuite, isAttemptToFix, [
|
|
797
|
-
testProperties.disabled && '_ddIsDisabled',
|
|
798
|
-
testProperties.quarantined && '_ddIsQuarantined',
|
|
799
|
-
'_ddIsAttemptToFix',
|
|
800
|
-
'_ddIsAttemptToFixRetry'
|
|
801
|
-
])
|
|
802
|
-
applyRepeatEachIndex(projectSuite._fullProject, copyFileSuite, repeatEachIndex + 1)
|
|
803
|
-
projectSuite._addSuite(copyFileSuite)
|
|
832
|
+
|
|
833
|
+
if (!fileSuitesWithManagedTestsToProjects.has(fileSuite)) {
|
|
834
|
+
fileSuitesWithManagedTestsToProjects.set(fileSuite, getSuiteType(test, 'project'))
|
|
804
835
|
}
|
|
805
836
|
if (testProperties.disabled || testProperties.quarantined) {
|
|
806
837
|
quarantinedOrDisabledTestsAttemptToFix.push(test)
|
|
807
838
|
}
|
|
808
|
-
} else if (testProperties.disabled) {
|
|
809
|
-
test.expectedStatus = 'skipped'
|
|
810
|
-
} else if (testProperties.quarantined) {
|
|
811
|
-
// Do not skip quarantined tests, let them run and overwrite results post-run if they fail
|
|
812
|
-
const testFqn = getTestFullyQualifiedName(test)
|
|
813
|
-
quarantinedButNotAttemptToFixFqns.add(testFqn)
|
|
814
839
|
}
|
|
815
840
|
}
|
|
841
|
+
applyRetriesToTests(
|
|
842
|
+
fileSuitesWithManagedTestsToProjects,
|
|
843
|
+
(test) => test._ddIsAttemptToFix,
|
|
844
|
+
[
|
|
845
|
+
(test) => test._ddIsQuarantined && '_ddIsQuarantined',
|
|
846
|
+
'_ddIsAttemptToFix',
|
|
847
|
+
'_ddIsAttemptToFixRetry'
|
|
848
|
+
],
|
|
849
|
+
testManagementAttemptToFixRetries
|
|
850
|
+
)
|
|
816
851
|
}
|
|
817
852
|
|
|
818
853
|
if (isImpactedTestsEnabled) {
|
|
819
|
-
|
|
820
|
-
|
|
854
|
+
const impactedTests = allTests.filter(test => {
|
|
855
|
+
let isImpacted = false
|
|
856
|
+
isModifiedCh.publish({
|
|
821
857
|
filePath: test._requireFile,
|
|
822
|
-
modifiedFiles
|
|
858
|
+
modifiedFiles,
|
|
859
|
+
onDone: (isModified) => { isImpacted = isModified }
|
|
823
860
|
})
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
const copyFileSuite = deepCloneSuite(fileSuite, isModifiedTest, [
|
|
835
|
-
isNew && '_ddIsNew',
|
|
836
|
-
'_ddIsModified',
|
|
837
|
-
'_ddIsEfdRetry'
|
|
838
|
-
])
|
|
839
|
-
applyRepeatEachIndex(projectSuite._fullProject, copyFileSuite, repeatEachIndex + 1)
|
|
840
|
-
projectSuite._addSuite(copyFileSuite)
|
|
861
|
+
return isImpacted
|
|
862
|
+
})
|
|
863
|
+
|
|
864
|
+
const fileSuitesWithImpactedTestsToProjects = new Map()
|
|
865
|
+
impactedTests.forEach(impactedTest => {
|
|
866
|
+
impactedTest._ddIsModified = true
|
|
867
|
+
if (isEarlyFlakeDetectionEnabled && impactedTest.expectedStatus !== 'skipped') {
|
|
868
|
+
const fileSuite = getSuiteType(impactedTest, 'file')
|
|
869
|
+
if (!fileSuitesWithImpactedTestsToProjects.has(fileSuite)) {
|
|
870
|
+
fileSuitesWithImpactedTestsToProjects.set(fileSuite, getSuiteType(impactedTest, 'project'))
|
|
841
871
|
}
|
|
842
872
|
}
|
|
843
|
-
})
|
|
873
|
+
})
|
|
874
|
+
// If something change in the file, all tests in the file are impacted, hence the () => true filter
|
|
875
|
+
applyRetriesToTests(
|
|
876
|
+
fileSuitesWithImpactedTestsToProjects,
|
|
877
|
+
() => true,
|
|
878
|
+
[
|
|
879
|
+
'_ddIsModified',
|
|
880
|
+
'_ddIsEfdRetry',
|
|
881
|
+
(test) => (isKnownTestsEnabled && isNewTest(test) ? '_ddIsNew' : null)
|
|
882
|
+
],
|
|
883
|
+
earlyFlakeDetectionNumRetries
|
|
884
|
+
)
|
|
844
885
|
}
|
|
845
886
|
|
|
846
887
|
if (isKnownTestsEnabled) {
|
|
847
888
|
const newTests = allTests.filter(isNewTest)
|
|
848
889
|
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
890
|
+
const isFaulty = getIsFaultyEarlyFlakeDetection(
|
|
891
|
+
allTests.map(test => getTestSuitePath(test._requireFile, rootDir)),
|
|
892
|
+
knownTests.playwright,
|
|
893
|
+
earlyFlakeDetectionFaultyThreshold
|
|
894
|
+
)
|
|
895
|
+
|
|
896
|
+
if (isFaulty) {
|
|
897
|
+
isEarlyFlakeDetectionEnabled = false
|
|
898
|
+
isKnownTestsEnabled = false
|
|
899
|
+
isEarlyFlakeDetectionFaulty = true
|
|
900
|
+
} else {
|
|
901
|
+
const fileSuitesWithNewTestsToProjects = new Map()
|
|
902
|
+
newTests.forEach(newTest => {
|
|
903
|
+
newTest._ddIsNew = true
|
|
904
|
+
if (isEarlyFlakeDetectionEnabled && newTest.expectedStatus !== 'skipped' && !newTest._ddIsModified) {
|
|
905
|
+
const fileSuite = getSuiteType(newTest, 'file')
|
|
906
|
+
if (!fileSuitesWithNewTestsToProjects.has(fileSuite)) {
|
|
907
|
+
fileSuitesWithNewTestsToProjects.set(fileSuite, getSuiteType(newTest, 'project'))
|
|
908
|
+
}
|
|
862
909
|
}
|
|
863
|
-
}
|
|
910
|
+
})
|
|
911
|
+
|
|
912
|
+
applyRetriesToTests(
|
|
913
|
+
fileSuitesWithNewTestsToProjects,
|
|
914
|
+
isNewTest,
|
|
915
|
+
['_ddIsNew', '_ddIsEfdRetry'],
|
|
916
|
+
earlyFlakeDetectionNumRetries
|
|
917
|
+
)
|
|
864
918
|
}
|
|
865
919
|
}
|
|
866
920
|
|
|
@@ -5,6 +5,19 @@ const pathToRegExp = require('path-to-regexp')
|
|
|
5
5
|
const shimmer = require('../../datadog-shimmer')
|
|
6
6
|
const { addHook, channel } = require('./helpers/instrument')
|
|
7
7
|
|
|
8
|
+
const {
|
|
9
|
+
getRouterMountPaths,
|
|
10
|
+
joinPath,
|
|
11
|
+
getLayerMatchers,
|
|
12
|
+
setLayerMatchers,
|
|
13
|
+
isAppMounted,
|
|
14
|
+
setRouterMountPath,
|
|
15
|
+
extractMountPaths,
|
|
16
|
+
getRouteFullPaths,
|
|
17
|
+
wrapRouteMethodsAndPublish,
|
|
18
|
+
collectRoutesFromRouter
|
|
19
|
+
} = require('./helpers/router-helper')
|
|
20
|
+
|
|
8
21
|
function isFastStar (layer, matchers) {
|
|
9
22
|
return layer.regexp?.fast_star ?? matchers.some(matcher => matcher.path === '*')
|
|
10
23
|
}
|
|
@@ -22,7 +35,6 @@ function createWrapRouterMethod (name) {
|
|
|
22
35
|
const nextChannel = channel(`apm:${name}:middleware:next`)
|
|
23
36
|
const routeAddedChannel = channel(`apm:${name}:route:added`)
|
|
24
37
|
|
|
25
|
-
const layerMatchers = new WeakMap()
|
|
26
38
|
const regexpCache = Object.create(null)
|
|
27
39
|
|
|
28
40
|
function wrapLayerHandle (layer, original) {
|
|
@@ -31,7 +43,7 @@ function createWrapRouterMethod (name) {
|
|
|
31
43
|
return shimmer.wrapFunction(original, original => function () {
|
|
32
44
|
if (!enterChannel.hasSubscribers) return original.apply(this, arguments)
|
|
33
45
|
|
|
34
|
-
const matchers =
|
|
46
|
+
const matchers = getLayerMatchers(layer)
|
|
35
47
|
const lastIndex = arguments.length - 1
|
|
36
48
|
const name = original._name || original.name
|
|
37
49
|
const req = arguments[arguments.length > 3 ? 1 : 0]
|
|
@@ -78,7 +90,7 @@ function createWrapRouterMethod (name) {
|
|
|
78
90
|
layer.handle = wrapLayerHandle(layer, layer.handle)
|
|
79
91
|
}
|
|
80
92
|
|
|
81
|
-
|
|
93
|
+
setLayerMatchers(layer, matchers)
|
|
82
94
|
|
|
83
95
|
if (layer.route) {
|
|
84
96
|
METHODS.forEach(method => {
|
|
@@ -115,7 +127,7 @@ function createWrapRouterMethod (name) {
|
|
|
115
127
|
return arg.map(pattern => ({
|
|
116
128
|
path: pattern instanceof RegExp ? `(${pattern})` : pattern,
|
|
117
129
|
test: layer => {
|
|
118
|
-
const matchers =
|
|
130
|
+
const matchers = getLayerMatchers(layer)
|
|
119
131
|
return !isFastStar(layer, matchers) &&
|
|
120
132
|
!isFastSlash(layer, matchers) &&
|
|
121
133
|
cachedPathToRegExp(pattern).test(layer.path)
|
|
@@ -134,12 +146,12 @@ function createWrapRouterMethod (name) {
|
|
|
134
146
|
}
|
|
135
147
|
|
|
136
148
|
function wrapMethod (original) {
|
|
137
|
-
return shimmer.wrapFunction(original, original => function methodWithTrace (fn) {
|
|
149
|
+
return shimmer.wrapFunction(original, original => function methodWithTrace (fn, ...otherArgs) {
|
|
138
150
|
let offset = 0
|
|
139
151
|
if (this.stack) {
|
|
140
152
|
offset = Array.isArray(this.stack) ? this.stack.length : 1
|
|
141
153
|
}
|
|
142
|
-
const router = original.
|
|
154
|
+
const router = original.call(this, fn, ...otherArgs)
|
|
143
155
|
|
|
144
156
|
if (typeof this.stack === 'function') {
|
|
145
157
|
this.stack = [{ handle: this.stack }]
|
|
@@ -149,6 +161,51 @@ function createWrapRouterMethod (name) {
|
|
|
149
161
|
routeAddedChannel.publish({ topOfStackFunc: methodWithTrace, layer: this.stack.at(-1) })
|
|
150
162
|
}
|
|
151
163
|
|
|
164
|
+
// Publish only if this router was mounted by app.use() (prevents early '/sub/...')
|
|
165
|
+
if (routeAddedChannel.hasSubscribers && isAppMounted(this) && this.stack?.length > offset) {
|
|
166
|
+
// Handle nested router mounting for 'use' method
|
|
167
|
+
if (original.name === 'use' && otherArgs.length >= 1) {
|
|
168
|
+
const { mountPaths, startIdx } = extractMountPaths(fn)
|
|
169
|
+
|
|
170
|
+
if (mountPaths.length) {
|
|
171
|
+
const parentPaths = getRouterMountPaths(this)
|
|
172
|
+
const callArgs = [fn, ...otherArgs]
|
|
173
|
+
|
|
174
|
+
for (let i = startIdx; i < callArgs.length; i++) {
|
|
175
|
+
const nestedRouter = callArgs[i]
|
|
176
|
+
|
|
177
|
+
if (!nestedRouter || typeof nestedRouter !== 'function') continue
|
|
178
|
+
|
|
179
|
+
for (const parentPath of parentPaths) {
|
|
180
|
+
for (const normalizedMountPath of mountPaths) {
|
|
181
|
+
const fullMountPath = joinPath(parentPath, normalizedMountPath)
|
|
182
|
+
if (fullMountPath === null) continue
|
|
183
|
+
|
|
184
|
+
setRouterMountPath(nestedRouter, fullMountPath)
|
|
185
|
+
collectRoutesFromRouter(nestedRouter, fullMountPath)
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const mountPaths = getRouterMountPaths(this)
|
|
193
|
+
|
|
194
|
+
if (mountPaths.length) {
|
|
195
|
+
const layer = this.stack.at(-1)
|
|
196
|
+
|
|
197
|
+
if (layer?.route) {
|
|
198
|
+
const route = layer.route
|
|
199
|
+
|
|
200
|
+
const fullPaths = mountPaths.flatMap(mountPath => getRouteFullPaths(route, mountPath))
|
|
201
|
+
|
|
202
|
+
wrapRouteMethodsAndPublish(route, fullPaths, (payload) => {
|
|
203
|
+
routeAddedChannel.publish(payload)
|
|
204
|
+
})
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
152
209
|
if (this.stack.length > offset) {
|
|
153
210
|
wrapStack(this.stack.slice(offset), extractMatchers(fn))
|
|
154
211
|
}
|
|
@@ -41,7 +41,7 @@ function wrapSend (send) {
|
|
|
41
41
|
|
|
42
42
|
const [data, options, cb] = arguments
|
|
43
43
|
|
|
44
|
-
const ctx = { data, socket: this._sender
|
|
44
|
+
const ctx = { data, socket: this._sender?._socket }
|
|
45
45
|
|
|
46
46
|
return typeof cb === 'function'
|
|
47
47
|
? producerCh.traceCallback(send, undefined, ctx, this, data, options, cb)
|
|
@@ -70,7 +70,7 @@ function createWrappedHandler (handler) {
|
|
|
70
70
|
return function wrappedMessageHandler (data, binary) {
|
|
71
71
|
const byteLength = dataLength(data)
|
|
72
72
|
|
|
73
|
-
const ctx = { data, binary, socket: this._sender
|
|
73
|
+
const ctx = { data, binary, socket: this._sender?._socket, byteLength }
|
|
74
74
|
|
|
75
75
|
return receiverCh.traceSync(handler, ctx, this, data, binary)
|
|
76
76
|
}
|
|
@@ -93,7 +93,7 @@ function wrapClose (close) {
|
|
|
93
93
|
// if both are true then the self is sending the close event
|
|
94
94
|
const isPeerClose = this._closeFrameReceived === true && this._closeFrameSent === false
|
|
95
95
|
|
|
96
|
-
const ctx = { code, data, socket: this._sender
|
|
96
|
+
const ctx = { code, data, socket: this._sender?._socket, isPeerClose }
|
|
97
97
|
|
|
98
98
|
return closeCh.traceSync(close, ctx, this, ...arguments)
|
|
99
99
|
}
|
|
@@ -19,6 +19,7 @@ class ExpressCodeOriginForSpansPlugin extends Plugin {
|
|
|
19
19
|
})
|
|
20
20
|
|
|
21
21
|
this.addSub('apm:express:route:added', ({ topOfStackFunc, layer }) => {
|
|
22
|
+
if (!layer) return
|
|
22
23
|
if (layerTags.has(layer)) return
|
|
23
24
|
layerTags.set(layer, entryTags(topOfStackFunc))
|
|
24
25
|
})
|
|
@@ -30,6 +31,7 @@ class ExpressCodeOriginForSpansPlugin extends Plugin {
|
|
|
30
31
|
})
|
|
31
32
|
|
|
32
33
|
this.addSub('apm:router:route:added', ({ topOfStackFunc, layer }) => {
|
|
34
|
+
if (!layer) return
|
|
33
35
|
if (layerTags.has(layer)) return
|
|
34
36
|
layerTags.set(layer, entryTags(topOfStackFunc))
|
|
35
37
|
})
|
|
@@ -17,7 +17,7 @@ class WSClosePlugin extends TracingPlugin {
|
|
|
17
17
|
if (!traceWebsocketMessagesEnabled) return
|
|
18
18
|
|
|
19
19
|
const { code, data, socket, isPeerClose } = ctx
|
|
20
|
-
if (!socket
|
|
20
|
+
if (!socket?.spanContext) return
|
|
21
21
|
|
|
22
22
|
const spanKind = isPeerClose ? 'consumer' : 'producer'
|
|
23
23
|
const spanTags = socket.spanContext.spanTags
|
|
@@ -37,7 +37,9 @@ for (const deprecation of Object.keys(deprecations)) {
|
|
|
37
37
|
module.exports = {
|
|
38
38
|
/**
|
|
39
39
|
* Returns the environment variables that are supported by the tracer
|
|
40
|
-
* (including all non-Datadog/OTEL specific environment variables)
|
|
40
|
+
* (including all non-Datadog/OTEL specific environment variables).
|
|
41
|
+
*
|
|
42
|
+
* This should only be called once in config.js to avoid copying the object frequently.
|
|
41
43
|
*
|
|
42
44
|
* @returns {TracerEnv} The environment variables
|
|
43
45
|
*/
|