dd-trace 5.9.0 → 5.11.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/index.d.ts +15 -0
- package/package.json +3 -2
- package/packages/datadog-instrumentations/src/fetch.js +6 -45
- package/packages/datadog-instrumentations/src/helpers/fetch.js +17 -0
- package/packages/datadog-instrumentations/src/helpers/hooks.js +3 -1
- package/packages/datadog-instrumentations/src/jest.js +161 -14
- package/packages/datadog-instrumentations/src/kafkajs.js +4 -7
- package/packages/datadog-instrumentations/src/mongoose.js +2 -1
- package/packages/datadog-instrumentations/src/oracledb.js +1 -1
- package/packages/datadog-instrumentations/src/otel-sdk-trace.js +6 -1
- package/packages/datadog-instrumentations/src/selenium.js +69 -0
- package/packages/datadog-plugin-cucumber/src/index.js +2 -2
- package/packages/datadog-plugin-cypress/src/cypress-plugin.js +2 -2
- package/packages/datadog-plugin-cypress/src/support.js +19 -3
- package/packages/datadog-plugin-fetch/src/index.js +17 -11
- package/packages/datadog-plugin-jest/src/index.js +7 -2
- package/packages/datadog-plugin-mocha/src/index.js +4 -5
- package/packages/datadog-plugin-openai/src/services.js +2 -1
- package/packages/datadog-plugin-playwright/src/index.js +2 -2
- package/packages/datadog-plugin-selenium/src/index.js +71 -0
- package/packages/dd-trace/src/appsec/iast/analyzers/analyzers.js +1 -0
- package/packages/dd-trace/src/appsec/iast/analyzers/hardcoded-base-analyzer.js +70 -0
- package/packages/dd-trace/src/appsec/iast/analyzers/hardcoded-password-analyzer.js +14 -0
- package/packages/dd-trace/src/appsec/iast/analyzers/hardcoded-password-rules.js +12 -0
- package/packages/dd-trace/src/appsec/iast/analyzers/hardcoded-rule-type.js +6 -0
- package/packages/dd-trace/src/appsec/iast/analyzers/hardcoded-secret-analyzer.js +5 -50
- package/packages/dd-trace/src/appsec/iast/analyzers/hardcoded-secret-rules.js +742 -0
- package/packages/dd-trace/src/appsec/iast/analyzers/hardcoded-secrets-rules.js +539 -66
- package/packages/dd-trace/src/appsec/iast/analyzers/weak-hash-analyzer.js +6 -2
- package/packages/dd-trace/src/appsec/iast/vulnerabilities.js +1 -0
- package/packages/dd-trace/src/appsec/reporter.js +11 -10
- package/packages/dd-trace/src/appsec/telemetry.js +36 -7
- package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +4 -2
- package/packages/dd-trace/src/ci-visibility/exporters/git/git_metadata.js +9 -2
- package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +4 -1
- package/packages/dd-trace/src/config.js +97 -10
- package/packages/dd-trace/src/dogstatsd.js +13 -11
- package/packages/dd-trace/src/index.js +5 -1
- package/packages/dd-trace/src/noop/dogstatsd.js +11 -0
- package/packages/dd-trace/src/noop/proxy.js +3 -0
- package/packages/dd-trace/src/opentracing/propagation/text_map.js +10 -4
- package/packages/dd-trace/src/opentracing/span.js +2 -0
- package/packages/dd-trace/src/plugins/index.js +2 -0
- package/packages/dd-trace/src/plugins/util/git.js +33 -11
- package/packages/dd-trace/src/plugins/util/test.js +34 -3
- package/packages/dd-trace/src/profiling/config.js +8 -4
- package/packages/dd-trace/src/profiling/exporters/agent.js +5 -3
- package/packages/dd-trace/src/profiling/profiler.js +4 -0
- package/packages/dd-trace/src/profiling/ssi-telemetry-mock-profiler.js +33 -0
- package/packages/dd-trace/src/profiling/ssi-telemetry.js +167 -0
- package/packages/dd-trace/src/proxy.js +7 -1
- package/packages/dd-trace/src/tagger.js +13 -3
- package/packages/dd-trace/src/telemetry/index.js +5 -4
- package/packages/dd-trace/src/telemetry/metrics.js +2 -2
package/index.d.ts
CHANGED
|
@@ -187,6 +187,7 @@ interface Plugins {
|
|
|
187
187
|
"restify": tracer.plugins.restify;
|
|
188
188
|
"rhea": tracer.plugins.rhea;
|
|
189
189
|
"router": tracer.plugins.router;
|
|
190
|
+
"selenium": tracer.plugins.selenium;
|
|
190
191
|
"sharedb": tracer.plugins.sharedb;
|
|
191
192
|
"tedious": tracer.plugins.tedious;
|
|
192
193
|
"winston": tracer.plugins.winston;
|
|
@@ -789,6 +790,14 @@ declare namespace tracer {
|
|
|
789
790
|
*/
|
|
790
791
|
gauge(stat: string, value?: number, tags?: { [tag: string]: string|number }): void
|
|
791
792
|
|
|
793
|
+
/**
|
|
794
|
+
* Sets a histogram value, optionally specifying tags.
|
|
795
|
+
* @param {string} stat The dot-separated metric name.
|
|
796
|
+
* @param {number} value The amount to increment the stat by.
|
|
797
|
+
* @param {[tag:string]:string|number} tags Tags to pass along, such as `{ foo: 'bar' }`. Values are combined with config.tags.
|
|
798
|
+
*/
|
|
799
|
+
histogram(stat: string, value?: number, tags?: { [tag: string]: string|number }): void
|
|
800
|
+
|
|
792
801
|
/**
|
|
793
802
|
* Forces any unsent metrics to be sent
|
|
794
803
|
*
|
|
@@ -1728,6 +1737,12 @@ declare namespace tracer {
|
|
|
1728
1737
|
*/
|
|
1729
1738
|
interface router extends Integration {}
|
|
1730
1739
|
|
|
1740
|
+
/**
|
|
1741
|
+
* This plugin automatically instruments the
|
|
1742
|
+
* [selenium-webdriver](https://www.npmjs.com/package/selenium-webdriver) module.
|
|
1743
|
+
*/
|
|
1744
|
+
interface selenium extends Integration {}
|
|
1745
|
+
|
|
1731
1746
|
/**
|
|
1732
1747
|
* This plugin automatically instruments the
|
|
1733
1748
|
* [sharedb](https://github.com/share/sharedb) module.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dd-trace",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.11.0",
|
|
4
4
|
"description": "Datadog APM tracing client for JavaScript",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"typings": "index.d.ts",
|
|
@@ -36,6 +36,7 @@
|
|
|
36
36
|
"test:integration:cucumber": "mocha --colors --timeout 30000 \"integration-tests/cucumber/*.spec.js\"",
|
|
37
37
|
"test:integration:cypress": "mocha --colors --timeout 30000 \"integration-tests/cypress/*.spec.js\"",
|
|
38
38
|
"test:integration:playwright": "mocha --colors --timeout 30000 \"integration-tests/playwright/*.spec.js\"",
|
|
39
|
+
"test:integration:selenium": "mocha --colors --timeout 30000 \"integration-tests/selenium/*.spec.js\"",
|
|
39
40
|
"test:integration:profiler": "mocha --colors --timeout 90000 \"integration-tests/profiler/*.spec.js\"",
|
|
40
41
|
"test:integration:serverless": "mocha --colors --timeout 30000 \"integration-tests/serverless/*.spec.js\"",
|
|
41
42
|
"test:integration:plugins": "mocha --colors --exit -r \"packages/dd-trace/test/setup/mocha.js\" \"packages/datadog-plugin-@($(echo $PLUGINS))/test/integration-test/**/*.spec.js\"",
|
|
@@ -69,7 +70,7 @@
|
|
|
69
70
|
"node": ">=18"
|
|
70
71
|
},
|
|
71
72
|
"dependencies": {
|
|
72
|
-
"@datadog/native-appsec": "7.1.
|
|
73
|
+
"@datadog/native-appsec": "7.1.1",
|
|
73
74
|
"@datadog/native-iast-rewriter": "2.3.0",
|
|
74
75
|
"@datadog/native-iast-taint-tracking": "1.7.0",
|
|
75
76
|
"@datadog/native-metrics": "^2.0.0",
|
|
@@ -1,51 +1,12 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
3
|
const shimmer = require('../../datadog-shimmer')
|
|
4
|
-
const {
|
|
5
|
-
|
|
6
|
-
const startChannel = channel('apm:fetch:request:start')
|
|
7
|
-
const finishChannel = channel('apm:fetch:request:finish')
|
|
8
|
-
const errorChannel = channel('apm:fetch:request:error')
|
|
9
|
-
|
|
10
|
-
function wrapFetch (fetch, Request) {
|
|
11
|
-
if (typeof fetch !== 'function') return fetch
|
|
12
|
-
|
|
13
|
-
return function (input, init) {
|
|
14
|
-
if (!startChannel.hasSubscribers) return fetch.apply(this, arguments)
|
|
15
|
-
|
|
16
|
-
const req = new Request(input, init)
|
|
17
|
-
const headers = req.headers
|
|
18
|
-
const message = { req, headers }
|
|
19
|
-
|
|
20
|
-
return startChannel.runStores(message, () => {
|
|
21
|
-
// Request object is read-only so we need new objects to change headers.
|
|
22
|
-
arguments[0] = message.req
|
|
23
|
-
arguments[1] = { headers: message.headers }
|
|
24
|
-
|
|
25
|
-
return fetch.apply(this, arguments)
|
|
26
|
-
.then(
|
|
27
|
-
res => {
|
|
28
|
-
message.res = res
|
|
29
|
-
|
|
30
|
-
finishChannel.publish(message)
|
|
31
|
-
|
|
32
|
-
return res
|
|
33
|
-
},
|
|
34
|
-
err => {
|
|
35
|
-
if (err.name !== 'AbortError') {
|
|
36
|
-
message.error = err
|
|
37
|
-
errorChannel.publish(message)
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
finishChannel.publish(message)
|
|
41
|
-
|
|
42
|
-
throw err
|
|
43
|
-
}
|
|
44
|
-
)
|
|
45
|
-
})
|
|
46
|
-
}
|
|
47
|
-
}
|
|
4
|
+
const { tracingChannel } = require('dc-polyfill')
|
|
5
|
+
const { createWrapFetch } = require('./helpers/fetch')
|
|
48
6
|
|
|
49
7
|
if (globalThis.fetch) {
|
|
50
|
-
|
|
8
|
+
const ch = tracingChannel('apm:fetch:request')
|
|
9
|
+
const wrapFetch = createWrapFetch(globalThis.Request, ch)
|
|
10
|
+
|
|
11
|
+
globalThis.fetch = shimmer.wrap(fetch, wrapFetch(fetch))
|
|
51
12
|
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
exports.createWrapFetch = function createWrapFetch (Request, ch) {
|
|
4
|
+
return function wrapFetch (fetch) {
|
|
5
|
+
if (typeof fetch !== 'function') return fetch
|
|
6
|
+
|
|
7
|
+
return function (input, init) {
|
|
8
|
+
if (!ch.start.hasSubscribers) return fetch.apply(this, arguments)
|
|
9
|
+
|
|
10
|
+
const req = new Request(input, init)
|
|
11
|
+
const headers = req.headers
|
|
12
|
+
const ctx = { req, headers }
|
|
13
|
+
|
|
14
|
+
return ch.tracePromise(() => fetch.call(this, req, { headers: ctx.headers }), ctx)
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -58,6 +58,7 @@ module.exports = {
|
|
|
58
58
|
'jest-environment-node': () => require('../jest'),
|
|
59
59
|
'jest-environment-jsdom': () => require('../jest'),
|
|
60
60
|
'jest-jasmine2': () => require('../jest'),
|
|
61
|
+
'jest-runtime': () => require('../jest'),
|
|
61
62
|
'jest-worker': () => require('../jest'),
|
|
62
63
|
knex: () => require('../knex'),
|
|
63
64
|
koa: () => require('../koa'),
|
|
@@ -103,8 +104,9 @@ module.exports = {
|
|
|
103
104
|
restify: () => require('../restify'),
|
|
104
105
|
rhea: () => require('../rhea'),
|
|
105
106
|
router: () => require('../router'),
|
|
106
|
-
|
|
107
|
+
'selenium-webdriver': () => require('../selenium'),
|
|
107
108
|
sequelize: () => require('../sequelize'),
|
|
109
|
+
sharedb: () => require('../sharedb'),
|
|
108
110
|
tedious: () => require('../tedious'),
|
|
109
111
|
when: () => require('../when'),
|
|
110
112
|
winston: () => require('../winston')
|
|
@@ -11,7 +11,8 @@ const {
|
|
|
11
11
|
getTestSuitePath,
|
|
12
12
|
getTestParametersString,
|
|
13
13
|
addEfdStringToTestName,
|
|
14
|
-
removeEfdStringFromTestName
|
|
14
|
+
removeEfdStringFromTestName,
|
|
15
|
+
getIsFaultyEarlyFlakeDetection
|
|
15
16
|
} = require('../../dd-trace/src/plugins/util/test')
|
|
16
17
|
const {
|
|
17
18
|
getFormattedJestTestParameters,
|
|
@@ -44,11 +45,14 @@ const knownTestsCh = channel('ci:jest:known-tests')
|
|
|
44
45
|
|
|
45
46
|
const itrSkippedSuitesCh = channel('ci:jest:itr:skipped-suites')
|
|
46
47
|
|
|
48
|
+
// Message sent by jest's main process to workers to run a test suite (=test file)
|
|
49
|
+
// https://github.com/jestjs/jest/blob/1d682f21c7a35da4d3ab3a1436a357b980ebd0fa/packages/jest-worker/src/types.ts#L37
|
|
50
|
+
const CHILD_MESSAGE_CALL = 1
|
|
47
51
|
// Maximum time we'll wait for the tracer to flush
|
|
48
52
|
const FLUSH_TIMEOUT = 10000
|
|
49
53
|
|
|
50
54
|
let skippableSuites = []
|
|
51
|
-
let knownTests =
|
|
55
|
+
let knownTests = {}
|
|
52
56
|
let isCodeCoverageEnabled = false
|
|
53
57
|
let isSuitesSkippingEnabled = false
|
|
54
58
|
let isUserCodeCoverageEnabled = false
|
|
@@ -58,6 +62,8 @@ let hasUnskippableSuites = false
|
|
|
58
62
|
let hasForcedToRunSuites = false
|
|
59
63
|
let isEarlyFlakeDetectionEnabled = false
|
|
60
64
|
let earlyFlakeDetectionNumRetries = 0
|
|
65
|
+
let earlyFlakeDetectionFaultyThreshold = 30
|
|
66
|
+
let isEarlyFlakeDetectionFaulty = false
|
|
61
67
|
let hasFilteredSkippableSuites = false
|
|
62
68
|
|
|
63
69
|
const sessionAsyncResource = new AsyncResource('bound-anonymous-fn')
|
|
@@ -73,6 +79,7 @@ const specStatusToTestStatus = {
|
|
|
73
79
|
const asyncResources = new WeakMap()
|
|
74
80
|
const originalTestFns = new WeakMap()
|
|
75
81
|
const retriedTestsToNumAttempts = new Map()
|
|
82
|
+
const newTestsTestStatuses = new Map()
|
|
76
83
|
|
|
77
84
|
// based on https://github.com/facebook/jest/blob/main/packages/jest-circus/src/formatNodeAssertErrors.ts#L41
|
|
78
85
|
function formatJestError (errors) {
|
|
@@ -101,6 +108,13 @@ function getTestEnvironmentOptions (config) {
|
|
|
101
108
|
return {}
|
|
102
109
|
}
|
|
103
110
|
|
|
111
|
+
function getEfdStats (testStatuses) {
|
|
112
|
+
return testStatuses.reduce((acc, testStatus) => {
|
|
113
|
+
acc[testStatus]++
|
|
114
|
+
return acc
|
|
115
|
+
}, { pass: 0, fail: 0 })
|
|
116
|
+
}
|
|
117
|
+
|
|
104
118
|
function getWrappedEnvironment (BaseEnvironment, jestVersion) {
|
|
105
119
|
return class DatadogEnvironment extends BaseEnvironment {
|
|
106
120
|
constructor (config, context) {
|
|
@@ -110,6 +124,7 @@ function getWrappedEnvironment (BaseEnvironment, jestVersion) {
|
|
|
110
124
|
this.testSuite = getTestSuitePath(context.testPath, rootDir)
|
|
111
125
|
this.nameToParams = {}
|
|
112
126
|
this.global._ddtrace = global._ddtrace
|
|
127
|
+
this.hasSnapshotTests = undefined
|
|
113
128
|
|
|
114
129
|
this.displayName = config.projectConfig?.displayName?.name
|
|
115
130
|
this.testEnvironmentOptions = getTestEnvironmentOptions(config)
|
|
@@ -123,9 +138,12 @@ function getWrappedEnvironment (BaseEnvironment, jestVersion) {
|
|
|
123
138
|
this.isEarlyFlakeDetectionEnabled = this.testEnvironmentOptions._ddIsEarlyFlakeDetectionEnabled
|
|
124
139
|
|
|
125
140
|
if (this.isEarlyFlakeDetectionEnabled) {
|
|
141
|
+
const hasKnownTests = !!knownTests.jest
|
|
126
142
|
earlyFlakeDetectionNumRetries = this.testEnvironmentOptions._ddEarlyFlakeDetectionNumRetries
|
|
127
143
|
try {
|
|
128
|
-
this.knownTestsForThisSuite =
|
|
144
|
+
this.knownTestsForThisSuite = hasKnownTests
|
|
145
|
+
? (knownTests.jest[this.testSuite] || [])
|
|
146
|
+
: this.getKnownTestsForSuite(this.testEnvironmentOptions._ddKnownTests)
|
|
129
147
|
} catch (e) {
|
|
130
148
|
// If there has been an error parsing the tests, we'll disable Early Flake Deteciton
|
|
131
149
|
this.isEarlyFlakeDetectionEnabled = false
|
|
@@ -133,6 +151,21 @@ function getWrappedEnvironment (BaseEnvironment, jestVersion) {
|
|
|
133
151
|
}
|
|
134
152
|
}
|
|
135
153
|
|
|
154
|
+
getHasSnapshotTests () {
|
|
155
|
+
if (this.hasSnapshotTests !== undefined) {
|
|
156
|
+
return this.hasSnapshotTests
|
|
157
|
+
}
|
|
158
|
+
let hasSnapshotTests = true
|
|
159
|
+
try {
|
|
160
|
+
const { _snapshotData } = this.context.expect.getState().snapshotState
|
|
161
|
+
hasSnapshotTests = Object.keys(_snapshotData).length > 0
|
|
162
|
+
} catch (e) {
|
|
163
|
+
// if we can't be sure, we'll err on the side of caution and assume it has snapshots
|
|
164
|
+
}
|
|
165
|
+
this.hasSnapshotTests = hasSnapshotTests
|
|
166
|
+
return hasSnapshotTests
|
|
167
|
+
}
|
|
168
|
+
|
|
136
169
|
// Function that receives a list of known tests for a test service and
|
|
137
170
|
// returns the ones that belong to the current suite
|
|
138
171
|
getKnownTestsForSuite (knownTests) {
|
|
@@ -145,7 +178,7 @@ function getWrappedEnvironment (BaseEnvironment, jestVersion) {
|
|
|
145
178
|
if (typeof knownTestsForSuite === 'string') {
|
|
146
179
|
knownTestsForSuite = JSON.parse(knownTestsForSuite)
|
|
147
180
|
}
|
|
148
|
-
return knownTestsForSuite
|
|
181
|
+
return knownTestsForSuite
|
|
149
182
|
}
|
|
150
183
|
|
|
151
184
|
// Add the `add_test` event we don't have the test object yet, so
|
|
@@ -217,6 +250,13 @@ function getWrappedEnvironment (BaseEnvironment, jestVersion) {
|
|
|
217
250
|
const isSkipped = event.mode === 'todo' || event.mode === 'skip'
|
|
218
251
|
if (isNew && !isSkipped && !retriedTestsToNumAttempts.has(testName)) {
|
|
219
252
|
retriedTestsToNumAttempts.set(testName, 0)
|
|
253
|
+
// Retrying snapshots has proven to be problematic, so we'll skip them for now
|
|
254
|
+
// We'll still detect new tests, but we won't retry them.
|
|
255
|
+
// TODO: do not bail out of EFD with the whole test suite
|
|
256
|
+
if (this.getHasSnapshotTests()) {
|
|
257
|
+
log.warn('Early flake detection is disabled for suites with snapshots')
|
|
258
|
+
return
|
|
259
|
+
}
|
|
220
260
|
for (let retryIndex = 0; retryIndex < earlyFlakeDetectionNumRetries; retryIndex++) {
|
|
221
261
|
if (this.global.test) {
|
|
222
262
|
this.global.test(addEfdStringToTestName(event.testName, retryIndex), event.fn, event.timeout)
|
|
@@ -242,6 +282,19 @@ function getWrappedEnvironment (BaseEnvironment, jestVersion) {
|
|
|
242
282
|
})
|
|
243
283
|
// restore in case it is retried
|
|
244
284
|
event.test.fn = originalTestFns.get(event.test)
|
|
285
|
+
// We'll store the test statuses of the retries
|
|
286
|
+
if (this.isEarlyFlakeDetectionEnabled) {
|
|
287
|
+
const testName = getJestTestName(event.test)
|
|
288
|
+
const originalTestName = removeEfdStringFromTestName(testName)
|
|
289
|
+
const isNewTest = retriedTestsToNumAttempts.has(originalTestName)
|
|
290
|
+
if (isNewTest) {
|
|
291
|
+
if (newTestsTestStatuses.has(originalTestName)) {
|
|
292
|
+
newTestsTestStatuses.get(originalTestName).push(status)
|
|
293
|
+
} else {
|
|
294
|
+
newTestsTestStatuses.set(originalTestName, [status])
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
}
|
|
245
298
|
})
|
|
246
299
|
}
|
|
247
300
|
if (event.name === 'test_skip' || event.name === 'test_todo') {
|
|
@@ -285,7 +338,7 @@ function applySuiteSkipping (originalTests, rootDir, frameworkVersion) {
|
|
|
285
338
|
numSkippedSuites = jestSuitesToRun.skippedSuites.length
|
|
286
339
|
|
|
287
340
|
itrSkippedSuitesCh.publish({ skippedSuites: jestSuitesToRun.skippedSuites, frameworkVersion })
|
|
288
|
-
|
|
341
|
+
|
|
289
342
|
return jestSuitesToRun.suitesToRun
|
|
290
343
|
}
|
|
291
344
|
|
|
@@ -383,6 +436,7 @@ function cliWrapper (cli, jestVersion) {
|
|
|
383
436
|
isSuitesSkippingEnabled = libraryConfig.isSuitesSkippingEnabled
|
|
384
437
|
isEarlyFlakeDetectionEnabled = libraryConfig.isEarlyFlakeDetectionEnabled
|
|
385
438
|
earlyFlakeDetectionNumRetries = libraryConfig.earlyFlakeDetectionNumRetries
|
|
439
|
+
earlyFlakeDetectionFaultyThreshold = libraryConfig.earlyFlakeDetectionFaultyThreshold
|
|
386
440
|
}
|
|
387
441
|
} catch (err) {
|
|
388
442
|
log.error(err)
|
|
@@ -497,6 +551,7 @@ function cliWrapper (cli, jestVersion) {
|
|
|
497
551
|
hasForcedToRunSuites,
|
|
498
552
|
error,
|
|
499
553
|
isEarlyFlakeDetectionEnabled,
|
|
554
|
+
isEarlyFlakeDetectionFaulty,
|
|
500
555
|
onDone
|
|
501
556
|
})
|
|
502
557
|
})
|
|
@@ -508,6 +563,28 @@ function cliWrapper (cli, jestVersion) {
|
|
|
508
563
|
|
|
509
564
|
numSkippedSuites = 0
|
|
510
565
|
|
|
566
|
+
/**
|
|
567
|
+
* If Early Flake Detection (EFD) is enabled the logic is as follows:
|
|
568
|
+
* - If all attempts for a test are failing, the test has failed and we will let the test process fail.
|
|
569
|
+
* - If just a single attempt passes, we will prevent the test process from failing.
|
|
570
|
+
* The rationale behind is the following: you may still be able to block your CI pipeline by gating
|
|
571
|
+
* on flakiness (the test will be considered flaky), but you may choose to unblock the pipeline too.
|
|
572
|
+
*/
|
|
573
|
+
|
|
574
|
+
if (isEarlyFlakeDetectionEnabled) {
|
|
575
|
+
let numFailedTestsToIgnore = 0
|
|
576
|
+
for (const testStatuses of newTestsTestStatuses.values()) {
|
|
577
|
+
const { pass, fail } = getEfdStats(testStatuses)
|
|
578
|
+
if (pass > 0) { // as long as one passes, we'll consider the test passed
|
|
579
|
+
numFailedTestsToIgnore += fail
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
// If every test that failed was an EFD retry, we'll consider the suite passed
|
|
583
|
+
if (numFailedTestsToIgnore !== 0 && result.results.numFailedTests === numFailedTestsToIgnore) {
|
|
584
|
+
result.results.success = true
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
|
|
511
588
|
return result
|
|
512
589
|
})
|
|
513
590
|
|
|
@@ -619,7 +696,6 @@ function configureTestEnvironment (readConfigsResult) {
|
|
|
619
696
|
// because `jestAdapterWrapper` runs in a different process. We have to go through `testEnvironmentOptions`
|
|
620
697
|
configs.forEach(config => {
|
|
621
698
|
config.testEnvironmentOptions._ddTestCodeCoverageEnabled = isCodeCoverageEnabled
|
|
622
|
-
config.testEnvironmentOptions._ddKnownTests = knownTests
|
|
623
699
|
})
|
|
624
700
|
|
|
625
701
|
isUserCodeCoverageEnabled = !!readConfigsResult.globalConfig.collectCoverage
|
|
@@ -712,13 +788,26 @@ addHook({
|
|
|
712
788
|
const SearchSource = searchSourcePackage.default ? searchSourcePackage.default : searchSourcePackage
|
|
713
789
|
|
|
714
790
|
shimmer.wrap(SearchSource.prototype, 'getTestPaths', getTestPaths => async function () {
|
|
715
|
-
|
|
716
|
-
return getTestPaths.apply(this, arguments)
|
|
717
|
-
}
|
|
718
|
-
|
|
791
|
+
const testPaths = await getTestPaths.apply(this, arguments)
|
|
719
792
|
const [{ rootDir, shard }] = arguments
|
|
720
793
|
|
|
721
|
-
if (
|
|
794
|
+
if (isEarlyFlakeDetectionEnabled) {
|
|
795
|
+
const projectSuites = testPaths.tests.map(test => getTestSuitePath(test.path, test.context.config.rootDir))
|
|
796
|
+
const isFaulty =
|
|
797
|
+
getIsFaultyEarlyFlakeDetection(projectSuites, knownTests.jest || {}, earlyFlakeDetectionFaultyThreshold)
|
|
798
|
+
if (isFaulty) {
|
|
799
|
+
log.error('Early flake detection is disabled because the number of new suites is too high.')
|
|
800
|
+
isEarlyFlakeDetectionEnabled = false
|
|
801
|
+
const testEnvironmentOptions = testPaths.tests[0]?.context?.config?.testEnvironmentOptions
|
|
802
|
+
// Project config is shared among all tests, so we can modify it here
|
|
803
|
+
if (testEnvironmentOptions) {
|
|
804
|
+
testEnvironmentOptions._ddIsEarlyFlakeDetectionEnabled = false
|
|
805
|
+
}
|
|
806
|
+
isEarlyFlakeDetectionFaulty = true
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
if (shard?.shardCount > 1 || !isSuitesSkippingEnabled || !skippableSuites.length) {
|
|
722
811
|
// If the user is using jest sharding, we want to apply the filtering of tests in the shard process.
|
|
723
812
|
// The reason for this is the following:
|
|
724
813
|
// The tests for different shards are likely being run in different CI jobs so
|
|
@@ -726,10 +815,8 @@ addHook({
|
|
|
726
815
|
// If the skippable endpoint is returning different suites and we filter the list of tests here,
|
|
727
816
|
// the base list of tests that is used for sharding might be different,
|
|
728
817
|
// causing the shards to potentially run the same suite.
|
|
729
|
-
return
|
|
818
|
+
return testPaths
|
|
730
819
|
}
|
|
731
|
-
|
|
732
|
-
const testPaths = await getTestPaths.apply(this, arguments)
|
|
733
820
|
const { tests } = testPaths
|
|
734
821
|
|
|
735
822
|
const suitesToRun = applySuiteSkipping(tests, rootDir, frameworkVersion)
|
|
@@ -789,12 +876,72 @@ if (DD_MAJOR < 4) {
|
|
|
789
876
|
}, jasmineAsyncInstallWraper)
|
|
790
877
|
}
|
|
791
878
|
|
|
879
|
+
const LIBRARIES_BYPASSING_JEST_REQUIRE_ENGINE = [
|
|
880
|
+
'selenium-webdriver'
|
|
881
|
+
]
|
|
882
|
+
|
|
883
|
+
function shouldBypassJestRequireEngine (moduleName) {
|
|
884
|
+
return (
|
|
885
|
+
LIBRARIES_BYPASSING_JEST_REQUIRE_ENGINE.some(library => moduleName.includes(library))
|
|
886
|
+
)
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
addHook({
|
|
890
|
+
name: 'jest-runtime',
|
|
891
|
+
versions: ['>=24.8.0']
|
|
892
|
+
}, (runtimePackage) => {
|
|
893
|
+
const Runtime = runtimePackage.default ? runtimePackage.default : runtimePackage
|
|
894
|
+
|
|
895
|
+
shimmer.wrap(Runtime.prototype, 'requireModuleOrMock', requireModuleOrMock => function (from, moduleName) {
|
|
896
|
+
// TODO: do this for every library that we instrument
|
|
897
|
+
if (shouldBypassJestRequireEngine(moduleName)) {
|
|
898
|
+
// To bypass jest's own require engine
|
|
899
|
+
return this._requireCoreModule(moduleName)
|
|
900
|
+
}
|
|
901
|
+
return requireModuleOrMock.apply(this, arguments)
|
|
902
|
+
})
|
|
903
|
+
|
|
904
|
+
return runtimePackage
|
|
905
|
+
})
|
|
906
|
+
|
|
792
907
|
addHook({
|
|
793
908
|
name: 'jest-worker',
|
|
794
909
|
versions: ['>=24.9.0'],
|
|
795
910
|
file: 'build/workers/ChildProcessWorker.js'
|
|
796
911
|
}, (childProcessWorker) => {
|
|
797
912
|
const ChildProcessWorker = childProcessWorker.default
|
|
913
|
+
shimmer.wrap(ChildProcessWorker.prototype, 'send', send => function (request) {
|
|
914
|
+
if (!isEarlyFlakeDetectionEnabled) {
|
|
915
|
+
return send.apply(this, arguments)
|
|
916
|
+
}
|
|
917
|
+
const [type] = request
|
|
918
|
+
// eslint-disable-next-line
|
|
919
|
+
// https://github.com/jestjs/jest/blob/1d682f21c7a35da4d3ab3a1436a357b980ebd0fa/packages/jest-worker/src/workers/ChildProcessWorker.ts#L424
|
|
920
|
+
if (type === CHILD_MESSAGE_CALL) {
|
|
921
|
+
// This is the message that the main process sends to the worker to run a test suite (=test file).
|
|
922
|
+
// In here we modify the config.testEnvironmentOptions to include the known tests for the suite.
|
|
923
|
+
// This way the suite only knows about the tests that are part of it.
|
|
924
|
+
const args = request[request.length - 1]
|
|
925
|
+
if (args.length > 1) {
|
|
926
|
+
return send.apply(this, arguments)
|
|
927
|
+
}
|
|
928
|
+
if (!args[0]?.config) {
|
|
929
|
+
return send.apply(this, arguments)
|
|
930
|
+
}
|
|
931
|
+
const [{ globalConfig, config, path: testSuiteAbsolutePath }] = args
|
|
932
|
+
const testSuite = getTestSuitePath(testSuiteAbsolutePath, globalConfig.rootDir || process.cwd())
|
|
933
|
+
const suiteKnownTests = knownTests.jest?.[testSuite] || []
|
|
934
|
+
args[0].config = {
|
|
935
|
+
...config,
|
|
936
|
+
testEnvironmentOptions: {
|
|
937
|
+
...config.testEnvironmentOptions,
|
|
938
|
+
_ddKnownTests: suiteKnownTests
|
|
939
|
+
}
|
|
940
|
+
}
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
return send.apply(this, arguments)
|
|
944
|
+
})
|
|
798
945
|
shimmer.wrap(ChildProcessWorker.prototype, '_onMessage', _onMessage => function () {
|
|
799
946
|
const [code, data] = arguments[0]
|
|
800
947
|
if (code === JEST_WORKER_TRACE_PAYLOAD_CODE) { // datadog trace payload
|
|
@@ -68,7 +68,10 @@ addHook({ name: 'kafkajs', file: 'src/index.js', versions: ['>=1.4'] }, (BaseKaf
|
|
|
68
68
|
const result = send.apply(this, arguments)
|
|
69
69
|
|
|
70
70
|
result.then(
|
|
71
|
-
innerAsyncResource.bind(
|
|
71
|
+
innerAsyncResource.bind(res => {
|
|
72
|
+
producerFinishCh.publish(undefined)
|
|
73
|
+
producerCommitCh.publish(res)
|
|
74
|
+
}),
|
|
72
75
|
innerAsyncResource.bind(err => {
|
|
73
76
|
if (err) {
|
|
74
77
|
producerErrorCh.publish(err)
|
|
@@ -77,12 +80,6 @@ addHook({ name: 'kafkajs', file: 'src/index.js', versions: ['>=1.4'] }, (BaseKaf
|
|
|
77
80
|
})
|
|
78
81
|
)
|
|
79
82
|
|
|
80
|
-
result.then(res => {
|
|
81
|
-
if (producerCommitCh.hasSubscribers) {
|
|
82
|
-
producerCommitCh.publish(res)
|
|
83
|
-
}
|
|
84
|
-
})
|
|
85
|
-
|
|
86
83
|
return result
|
|
87
84
|
} catch (e) {
|
|
88
85
|
producerErrorCh.publish(e)
|
|
@@ -21,7 +21,8 @@ addHook({
|
|
|
21
21
|
name: 'mongoose',
|
|
22
22
|
versions: ['>=4.6.4 <5', '5', '6', '>=7']
|
|
23
23
|
}, mongoose => {
|
|
24
|
-
|
|
24
|
+
// As of Mongoose 7, custom promise libraries are no longer supported and mongoose.Promise may be undefined
|
|
25
|
+
if (mongoose.Promise && mongoose.Promise !== global.Promise) {
|
|
25
26
|
shimmer.wrap(mongoose.Promise.prototype, 'then', wrapThen)
|
|
26
27
|
}
|
|
27
28
|
|
|
@@ -21,7 +21,7 @@ function finish (err) {
|
|
|
21
21
|
finishChannel.publish(undefined)
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
addHook({ name: 'oracledb', versions: ['5'] }, oracledb => {
|
|
24
|
+
addHook({ name: 'oracledb', versions: ['>=5'] }, oracledb => {
|
|
25
25
|
shimmer.wrap(oracledb.Connection.prototype, 'execute', execute => {
|
|
26
26
|
return function wrappedExecute (dbQuery, ...args) {
|
|
27
27
|
if (!startChannel.hasSubscribers) {
|
|
@@ -4,7 +4,12 @@ const { addHook } = require('./helpers/instrument')
|
|
|
4
4
|
const shimmer = require('../../datadog-shimmer')
|
|
5
5
|
const tracer = require('../../dd-trace')
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
const otelSdkEnabled = process.env.DD_TRACE_OTEL_ENABLED ||
|
|
8
|
+
process.env.OTEL_SDK_DISABLED
|
|
9
|
+
? !process.env.OTEL_SDK_DISABLED
|
|
10
|
+
: undefined
|
|
11
|
+
|
|
12
|
+
if (otelSdkEnabled) {
|
|
8
13
|
addHook({
|
|
9
14
|
name: '@opentelemetry/sdk-trace-node',
|
|
10
15
|
file: 'build/src/NodeTracerProvider.js',
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
const { addHook, channel } = require('./helpers/instrument')
|
|
2
|
+
const shimmer = require('../../datadog-shimmer')
|
|
3
|
+
|
|
4
|
+
const ciSeleniumDriverGetStartCh = channel('ci:selenium:driver:get')
|
|
5
|
+
|
|
6
|
+
const RUM_STOP_SESSION_SCRIPT = `
|
|
7
|
+
if (window.DD_RUM && window.DD_RUM.stopSession) {
|
|
8
|
+
window.DD_RUM.stopSession();
|
|
9
|
+
return true;
|
|
10
|
+
} else {
|
|
11
|
+
return false;
|
|
12
|
+
}
|
|
13
|
+
`
|
|
14
|
+
const IS_RUM_ACTIVE_SCRIPT = 'return !!window.DD_RUM'
|
|
15
|
+
|
|
16
|
+
const DD_CIVISIBILITY_RUM_FLUSH_WAIT_MILLIS = 500
|
|
17
|
+
const DD_CIVISIBILITY_TEST_EXECUTION_ID_COOKIE_NAME = 'datadog-ci-visibility-test-execution-id'
|
|
18
|
+
|
|
19
|
+
// TODO: can we increase the supported version range?
|
|
20
|
+
addHook({
|
|
21
|
+
name: 'selenium-webdriver',
|
|
22
|
+
versions: ['>=4.11.0']
|
|
23
|
+
}, (seleniumPackage, seleniumVersion) => {
|
|
24
|
+
// TODO: do not turn this into async. Use promises
|
|
25
|
+
shimmer.wrap(seleniumPackage.WebDriver.prototype, 'get', get => async function () {
|
|
26
|
+
let traceId
|
|
27
|
+
const setTraceId = (inputTraceId) => {
|
|
28
|
+
traceId = inputTraceId
|
|
29
|
+
}
|
|
30
|
+
const getResult = await get.apply(this, arguments)
|
|
31
|
+
|
|
32
|
+
const isRumActive = await this.executeScript(IS_RUM_ACTIVE_SCRIPT)
|
|
33
|
+
const capabilities = await this.getCapabilities()
|
|
34
|
+
|
|
35
|
+
ciSeleniumDriverGetStartCh.publish({
|
|
36
|
+
setTraceId,
|
|
37
|
+
seleniumVersion,
|
|
38
|
+
browserName: capabilities.getBrowserName(),
|
|
39
|
+
browserVersion: capabilities.getBrowserVersion(),
|
|
40
|
+
isRumActive
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
await this.manage().addCookie({
|
|
44
|
+
name: DD_CIVISIBILITY_TEST_EXECUTION_ID_COOKIE_NAME,
|
|
45
|
+
value: traceId
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
return getResult
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
shimmer.wrap(seleniumPackage.WebDriver.prototype, 'quit', quit => async function () {
|
|
52
|
+
const isRumActive = await this.executeScript(RUM_STOP_SESSION_SCRIPT)
|
|
53
|
+
|
|
54
|
+
if (isRumActive) {
|
|
55
|
+
// We'll have time for RUM to flush the events (there's no callback to know when it's done)
|
|
56
|
+
await new Promise(resolve => {
|
|
57
|
+
setTimeout(() => {
|
|
58
|
+
resolve()
|
|
59
|
+
}, DD_CIVISIBILITY_RUM_FLUSH_WAIT_MILLIS)
|
|
60
|
+
})
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
await this.manage().deleteCookie(DD_CIVISIBILITY_TEST_EXECUTION_ID_COOKIE_NAME)
|
|
64
|
+
|
|
65
|
+
return quit.apply(this, arguments)
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
return seleniumPackage
|
|
69
|
+
})
|
|
@@ -16,7 +16,7 @@ const {
|
|
|
16
16
|
TEST_CODE_OWNERS,
|
|
17
17
|
ITR_CORRELATION_ID,
|
|
18
18
|
TEST_SOURCE_FILE,
|
|
19
|
-
|
|
19
|
+
TEST_EARLY_FLAKE_ENABLED,
|
|
20
20
|
TEST_IS_NEW,
|
|
21
21
|
TEST_IS_RETRY
|
|
22
22
|
} = require('../../dd-trace/src/plugins/util/test')
|
|
@@ -68,7 +68,7 @@ class CucumberPlugin extends CiPlugin {
|
|
|
68
68
|
}
|
|
69
69
|
)
|
|
70
70
|
if (isEarlyFlakeDetectionEnabled) {
|
|
71
|
-
this.testSessionSpan.setTag(
|
|
71
|
+
this.testSessionSpan.setTag(TEST_EARLY_FLAKE_ENABLED, 'true')
|
|
72
72
|
}
|
|
73
73
|
|
|
74
74
|
this.testSessionSpan.setTag(TEST_STATUS, status)
|
|
@@ -28,7 +28,7 @@ const {
|
|
|
28
28
|
TEST_SOURCE_FILE,
|
|
29
29
|
TEST_IS_NEW,
|
|
30
30
|
TEST_IS_RETRY,
|
|
31
|
-
|
|
31
|
+
TEST_EARLY_FLAKE_ENABLED
|
|
32
32
|
} = require('../../dd-trace/src/plugins/util/test')
|
|
33
33
|
const { isMarkedAsUnskippable } = require('../../datadog-plugin-jest/src/util')
|
|
34
34
|
const { ORIGIN_KEY, COMPONENT } = require('../../dd-trace/src/constants')
|
|
@@ -364,7 +364,7 @@ class CypressPlugin {
|
|
|
364
364
|
getTestModuleCommonTags(this.command, this.frameworkVersion, TEST_FRAMEWORK_NAME)
|
|
365
365
|
|
|
366
366
|
if (this.isEarlyFlakeDetectionEnabled) {
|
|
367
|
-
testSessionSpanMetadata[
|
|
367
|
+
testSessionSpanMetadata[TEST_EARLY_FLAKE_ENABLED] = 'true'
|
|
368
368
|
}
|
|
369
369
|
|
|
370
370
|
this.testSessionSpan = this.tracer.startSpan(`${TEST_FRAMEWORK_NAME}.test_session`, {
|