dd-trace 5.36.0 → 5.37.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 +2 -1
- package/index.d.ts +5 -0
- package/loader-hook.mjs +0 -4
- package/package.json +14 -13
- package/packages/datadog-instrumentations/src/cucumber.js +54 -1
- package/packages/datadog-instrumentations/src/helpers/instrument.js +2 -2
- package/packages/datadog-instrumentations/src/helpers/register.js +2 -2
- package/packages/datadog-instrumentations/src/jest.js +103 -5
- package/packages/datadog-instrumentations/src/mocha/main.js +46 -4
- package/packages/datadog-instrumentations/src/mocha/utils.js +35 -2
- package/packages/datadog-instrumentations/src/mocha/worker.js +7 -0
- package/packages/datadog-instrumentations/src/mysql2.js +3 -3
- package/packages/datadog-instrumentations/src/openai.js +8 -0
- package/packages/datadog-instrumentations/src/playwright.js +70 -22
- package/packages/datadog-instrumentations/src/vitest.js +60 -6
- package/packages/datadog-plugin-cucumber/src/index.js +20 -3
- package/packages/datadog-plugin-cypress/src/cypress-plugin.js +67 -7
- package/packages/datadog-plugin-dd-trace-api/src/index.js +1 -3
- package/packages/datadog-plugin-graphql/src/utils.js +8 -1
- package/packages/datadog-plugin-jest/src/index.js +12 -2
- package/packages/datadog-plugin-mocha/src/index.js +22 -3
- package/packages/datadog-plugin-mongodb-core/src/index.js +28 -2
- package/packages/datadog-plugin-openai/src/tracing.js +1 -2
- package/packages/datadog-plugin-playwright/src/index.js +31 -5
- package/packages/datadog-plugin-vitest/src/index.js +25 -1
- package/packages/dd-trace/src/appsec/iast/analyzers/code-injection-analyzer.js +2 -6
- package/packages/dd-trace/src/appsec/iast/analyzers/injection-analyzer.js +15 -1
- package/packages/dd-trace/src/appsec/iast/analyzers/nosql-injection-mongodb-analyzer.js +11 -24
- package/packages/dd-trace/src/appsec/iast/analyzers/sql-injection-analyzer.js +2 -6
- package/packages/dd-trace/src/appsec/iast/analyzers/stored-injection-analyzer.js +11 -0
- package/packages/dd-trace/src/appsec/iast/analyzers/template-injection-analyzer.js +2 -6
- package/packages/dd-trace/src/appsec/iast/analyzers/vulnerability-analyzer.js +22 -2
- package/packages/dd-trace/src/appsec/iast/index.js +2 -0
- package/packages/dd-trace/src/appsec/iast/security-controls/index.js +187 -0
- package/packages/dd-trace/src/appsec/iast/security-controls/parser.js +96 -0
- package/packages/dd-trace/src/appsec/iast/taint-tracking/operations.js +2 -2
- package/packages/dd-trace/src/appsec/iast/taint-tracking/secure-marks-generator.js +1 -1
- package/packages/dd-trace/src/appsec/iast/taint-tracking/secure-marks.js +28 -0
- package/packages/dd-trace/src/appsec/iast/telemetry/iast-metric.js +5 -0
- package/packages/dd-trace/src/appsec/iast/utils.js +24 -0
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/index.js +8 -13
- package/packages/dd-trace/src/appsec/iast/vulnerability-reporter.js +1 -0
- package/packages/dd-trace/src/appsec/rasp/command_injection.js +4 -4
- package/packages/dd-trace/src/appsec/rasp/lfi.js +2 -2
- package/packages/dd-trace/src/appsec/rasp/sql_injection.js +2 -2
- package/packages/dd-trace/src/appsec/rasp/ssrf.js +2 -2
- package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/worker/index.js +17 -49
- package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +20 -2
- package/packages/dd-trace/src/ci-visibility/quarantined-tests/get-quarantined-tests.js +62 -0
- package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +5 -2
- package/packages/dd-trace/src/config.js +16 -3
- package/packages/dd-trace/src/debugger/devtools_client/breakpoints.js +14 -7
- package/packages/dd-trace/src/debugger/devtools_client/source-maps.js +50 -0
- package/packages/dd-trace/src/debugger/devtools_client/state.js +38 -10
- package/packages/dd-trace/src/iitm.js +2 -2
- package/packages/dd-trace/src/llmobs/tagger.js +12 -2
- package/packages/dd-trace/src/opentracing/span.js +2 -2
- package/packages/dd-trace/src/plugins/ci_plugin.js +16 -3
- package/packages/dd-trace/src/plugins/database.js +14 -4
- package/packages/dd-trace/src/plugins/util/inferred_proxy.js +1 -3
- package/packages/dd-trace/src/plugins/util/test.js +6 -4
- package/packages/dd-trace/src/proxy.js +5 -1
- package/packages/dd-trace/src/ritm.js +2 -1
- package/packages/dd-trace/src/spanleak.js +0 -1
- package/packages/memwatch/package.json +0 -9
package/LICENSE-3rdparty.csv
CHANGED
|
@@ -27,7 +27,7 @@ require,protobufjs,BSD-3-Clause,Copyright 2016 Daniel Wirtz
|
|
|
27
27
|
require,tlhunter-sorted-set,MIT,Copyright (c) 2023 Datadog Inc.
|
|
28
28
|
require,retry,MIT,Copyright 2011 Tim Koschützki Felix Geisendörfer
|
|
29
29
|
require,rfdc,MIT,Copyright 2019 David Mark Clements
|
|
30
|
-
require,
|
|
30
|
+
require,semifies,Apache license 2.0,Copyright Authors
|
|
31
31
|
require,shell-quote,mit,Copyright (c) 2013 James Halliday
|
|
32
32
|
require,source-map,BSD-3-Clause,Copyright (c) 2009-2011, Mozilla Foundation and contributors
|
|
33
33
|
require,ttl-set,MIT,Copyright (c) 2024 Thomas Watson
|
|
@@ -68,6 +68,7 @@ dev,nock,MIT,Copyright 2017 Pedro Teixeira and other contributors
|
|
|
68
68
|
dev,nyc,ISC,Copyright 2015 Contributors
|
|
69
69
|
dev,proxyquire,MIT,Copyright 2013 Thorsten Lorenz
|
|
70
70
|
dev,rimraf,ISC,Copyright Isaac Z. Schlueter and Contributors
|
|
71
|
+
dev,semver,ISC,Copyright Isaac Z. Schlueter and Contributors
|
|
71
72
|
dev,sinon,BSD-3-Clause,Copyright 2010-2017 Christian Johansen
|
|
72
73
|
dev,sinon-chai,WTFPL and BSD-2-Clause,Copyright 2004 Sam Hocevar 2012–2017 Domenic Denicola
|
|
73
74
|
dev,tap,ISC,Copyright 2011-2022 Isaac Z. Schlueter and Contributors
|
package/index.d.ts
CHANGED
|
@@ -2226,6 +2226,11 @@ declare namespace tracer {
|
|
|
2226
2226
|
*/
|
|
2227
2227
|
redactionValuePattern?: string,
|
|
2228
2228
|
|
|
2229
|
+
/**
|
|
2230
|
+
* Allows to enable security controls.
|
|
2231
|
+
*/
|
|
2232
|
+
securityControlsConfiguration?: string,
|
|
2233
|
+
|
|
2229
2234
|
/**
|
|
2230
2235
|
* Specifies the verbosity of the sent telemetry. Default 'INFORMATION'
|
|
2231
2236
|
*/
|
package/loader-hook.mjs
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dd-trace",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.37.0",
|
|
4
4
|
"description": "Datadog APM tracing client for JavaScript",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"typings": "index.d.ts",
|
|
@@ -12,8 +12,8 @@
|
|
|
12
12
|
"bench:e2e:ci-visibility": "node benchmark/e2e-ci/benchmark-run.js",
|
|
13
13
|
"type:doc": "cd docs && yarn && yarn build",
|
|
14
14
|
"type:test": "cd docs && yarn && yarn test",
|
|
15
|
-
"lint": "node scripts/check_licenses.js && eslint . && yarn audit",
|
|
16
|
-
"lint:fix": "node scripts/check_licenses.js && eslint . --fix && yarn audit",
|
|
15
|
+
"lint": "node scripts/check_licenses.js && eslint . --max-warnings 0 && yarn audit",
|
|
16
|
+
"lint:fix": "node scripts/check_licenses.js && eslint . --max-warnings 0 --fix && yarn audit",
|
|
17
17
|
"release:proposal": "node scripts/release/proposal",
|
|
18
18
|
"services": "node ./scripts/install_plugin_modules && node packages/dd-trace/test/setup/services",
|
|
19
19
|
"test": "SERVICES=* yarn services && mocha --expose-gc 'packages/dd-trace/test/setup/node.js' 'packages/*/test/**/*.spec.js'",
|
|
@@ -84,7 +84,7 @@
|
|
|
84
84
|
"@datadog/libdatadog": "^0.4.0",
|
|
85
85
|
"@datadog/native-appsec": "8.4.0",
|
|
86
86
|
"@datadog/native-iast-rewriter": "2.8.0",
|
|
87
|
-
"@datadog/native-iast-taint-tracking": "3.
|
|
87
|
+
"@datadog/native-iast-taint-tracking": "3.3.0",
|
|
88
88
|
"@datadog/native-metrics": "^3.1.0",
|
|
89
89
|
"@datadog/pprof": "5.5.1",
|
|
90
90
|
"@datadog/sketches-js": "^2.1.0",
|
|
@@ -108,7 +108,7 @@
|
|
|
108
108
|
"protobufjs": "^7.2.5",
|
|
109
109
|
"retry": "^0.13.1",
|
|
110
110
|
"rfdc": "^1.3.1",
|
|
111
|
-
"
|
|
111
|
+
"semifies": "^1.0.0",
|
|
112
112
|
"shell-quote": "^1.8.1",
|
|
113
113
|
"source-map": "^0.7.4",
|
|
114
114
|
"tlhunter-sorted-set": "^0.1.0",
|
|
@@ -116,10 +116,10 @@
|
|
|
116
116
|
},
|
|
117
117
|
"devDependencies": {
|
|
118
118
|
"@apollo/server": "^4.11.0",
|
|
119
|
-
"@eslint/eslintrc": "^3.
|
|
120
|
-
"@eslint/js": "^
|
|
119
|
+
"@eslint/eslintrc": "^3.2.0",
|
|
120
|
+
"@eslint/js": "^9.19.0",
|
|
121
121
|
"@msgpack/msgpack": "^3.0.0-beta3",
|
|
122
|
-
"@stylistic/eslint-plugin-js": "^
|
|
122
|
+
"@stylistic/eslint-plugin-js": "^3.0.1",
|
|
123
123
|
"@types/node": "^16.0.0",
|
|
124
124
|
"autocannon": "^4.5.2",
|
|
125
125
|
"aws-sdk": "^2.1446.0",
|
|
@@ -132,12 +132,12 @@
|
|
|
132
132
|
"cli-table3": "^0.6.3",
|
|
133
133
|
"dotenv": "16.3.1",
|
|
134
134
|
"esbuild": "0.16.12",
|
|
135
|
-
"eslint": "^
|
|
135
|
+
"eslint": "^9.19.0",
|
|
136
136
|
"eslint-config-standard": "^17.1.0",
|
|
137
|
-
"eslint-plugin-import": "^2.
|
|
138
|
-
"eslint-plugin-mocha": "^10.
|
|
139
|
-
"eslint-plugin-n": "^
|
|
140
|
-
"eslint-plugin-promise": "^
|
|
137
|
+
"eslint-plugin-import": "^2.31.0",
|
|
138
|
+
"eslint-plugin-mocha": "^10.5.0",
|
|
139
|
+
"eslint-plugin-n": "^17.15.1",
|
|
140
|
+
"eslint-plugin-promise": "^7.2.1",
|
|
141
141
|
"express": "^4.21.2",
|
|
142
142
|
"get-port": "^3.2.0",
|
|
143
143
|
"glob": "^7.1.6",
|
|
@@ -152,6 +152,7 @@
|
|
|
152
152
|
"nyc": "^15.1.0",
|
|
153
153
|
"proxyquire": "^1.8.0",
|
|
154
154
|
"rimraf": "^3.0.0",
|
|
155
|
+
"semver": "^7.5.4",
|
|
155
156
|
"sinon": "^16.1.3",
|
|
156
157
|
"sinon-chai": "^3.7.0",
|
|
157
158
|
"tap": "^16.3.7",
|
|
@@ -22,6 +22,7 @@ const knownTestsCh = channel('ci:cucumber:known-tests')
|
|
|
22
22
|
const skippableSuitesCh = channel('ci:cucumber:test-suite:skippable')
|
|
23
23
|
const sessionStartCh = channel('ci:cucumber:session:start')
|
|
24
24
|
const sessionFinishCh = channel('ci:cucumber:session:finish')
|
|
25
|
+
const quarantinedTestsCh = channel('ci:cucumber:quarantined-tests')
|
|
25
26
|
|
|
26
27
|
const workerReportTraceCh = channel('ci:cucumber:worker-report:trace')
|
|
27
28
|
|
|
@@ -71,6 +72,8 @@ let earlyFlakeDetectionFaultyThreshold = 0
|
|
|
71
72
|
let isEarlyFlakeDetectionFaulty = false
|
|
72
73
|
let isFlakyTestRetriesEnabled = false
|
|
73
74
|
let isKnownTestsEnabled = false
|
|
75
|
+
let isQuarantinedTestsEnabled = false
|
|
76
|
+
let quarantinedTests = {}
|
|
74
77
|
let numTestRetries = 0
|
|
75
78
|
let knownTests = []
|
|
76
79
|
let skippedSuites = []
|
|
@@ -117,6 +120,17 @@ function isNewTest (testSuite, testName) {
|
|
|
117
120
|
return !testsForSuite.includes(testName)
|
|
118
121
|
}
|
|
119
122
|
|
|
123
|
+
function isQuarantinedTest (testSuite, testName) {
|
|
124
|
+
return quarantinedTests
|
|
125
|
+
?.cucumber
|
|
126
|
+
?.suites
|
|
127
|
+
?.[testSuite]
|
|
128
|
+
?.tests
|
|
129
|
+
?.[testName]
|
|
130
|
+
?.properties
|
|
131
|
+
?.quarantined
|
|
132
|
+
}
|
|
133
|
+
|
|
120
134
|
function getTestStatusFromRetries (testStatuses) {
|
|
121
135
|
if (testStatuses.every(status => status === 'fail')) {
|
|
122
136
|
return 'fail'
|
|
@@ -293,12 +307,17 @@ function wrapRun (pl, isLatestVersion) {
|
|
|
293
307
|
}
|
|
294
308
|
let isNew = false
|
|
295
309
|
let isEfdRetry = false
|
|
310
|
+
let isQuarantined = false
|
|
296
311
|
if (isKnownTestsEnabled && status !== 'skip') {
|
|
297
312
|
const numRetries = numRetriesByPickleId.get(this.pickle.id)
|
|
298
313
|
|
|
299
314
|
isNew = numRetries !== undefined
|
|
300
315
|
isEfdRetry = numRetries > 0
|
|
301
316
|
}
|
|
317
|
+
if (isQuarantinedTestsEnabled) {
|
|
318
|
+
const testSuitePath = getTestSuitePath(testFileAbsolutePath, process.cwd())
|
|
319
|
+
isQuarantined = isQuarantinedTest(testSuitePath, this.pickle.name)
|
|
320
|
+
}
|
|
302
321
|
const attemptAsyncResource = numAttemptToAsyncResource.get(numAttempt)
|
|
303
322
|
|
|
304
323
|
const error = getErrorFromCucumberResult(result)
|
|
@@ -307,7 +326,15 @@ function wrapRun (pl, isLatestVersion) {
|
|
|
307
326
|
await promises.hitBreakpointPromise
|
|
308
327
|
}
|
|
309
328
|
attemptAsyncResource.runInAsyncScope(() => {
|
|
310
|
-
testFinishCh.publish({
|
|
329
|
+
testFinishCh.publish({
|
|
330
|
+
status,
|
|
331
|
+
skipReason,
|
|
332
|
+
error,
|
|
333
|
+
isNew,
|
|
334
|
+
isEfdRetry,
|
|
335
|
+
isFlakyRetry: numAttempt > 0,
|
|
336
|
+
isQuarantined
|
|
337
|
+
})
|
|
311
338
|
})
|
|
312
339
|
})
|
|
313
340
|
return promise
|
|
@@ -396,6 +423,7 @@ function getWrappedStart (start, frameworkVersion, isParallel = false, isCoordin
|
|
|
396
423
|
isFlakyTestRetriesEnabled = configurationResponse.libraryConfig?.isFlakyTestRetriesEnabled
|
|
397
424
|
numTestRetries = configurationResponse.libraryConfig?.flakyTestRetriesCount
|
|
398
425
|
isKnownTestsEnabled = configurationResponse.libraryConfig?.isKnownTestsEnabled
|
|
426
|
+
isQuarantinedTestsEnabled = configurationResponse.libraryConfig?.isQuarantinedTestsEnabled
|
|
399
427
|
|
|
400
428
|
if (isKnownTestsEnabled) {
|
|
401
429
|
const knownTestsResponse = await getChannelPromise(knownTestsCh)
|
|
@@ -453,6 +481,15 @@ function getWrappedStart (start, frameworkVersion, isParallel = false, isCoordin
|
|
|
453
481
|
}
|
|
454
482
|
}
|
|
455
483
|
|
|
484
|
+
if (isQuarantinedTestsEnabled) {
|
|
485
|
+
const quarantinedTestsResponse = await getChannelPromise(quarantinedTestsCh)
|
|
486
|
+
if (!quarantinedTestsResponse.err) {
|
|
487
|
+
quarantinedTests = quarantinedTestsResponse.quarantinedTests
|
|
488
|
+
} else {
|
|
489
|
+
isQuarantinedTestsEnabled = false
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
|
|
456
493
|
const processArgv = process.argv.slice(2).join(' ')
|
|
457
494
|
const command = process.env.npm_lifecycle_script || `cucumber-js ${processArgv}`
|
|
458
495
|
|
|
@@ -500,6 +537,7 @@ function getWrappedStart (start, frameworkVersion, isParallel = false, isCoordin
|
|
|
500
537
|
hasForcedToRunSuites: isForcedToRun,
|
|
501
538
|
isEarlyFlakeDetectionEnabled,
|
|
502
539
|
isEarlyFlakeDetectionFaulty,
|
|
540
|
+
isQuarantinedTestsEnabled,
|
|
503
541
|
isParallel
|
|
504
542
|
})
|
|
505
543
|
})
|
|
@@ -536,6 +574,7 @@ function getWrappedRunTestCase (runTestCaseFunction, isNewerCucumberVersion = fa
|
|
|
536
574
|
}
|
|
537
575
|
|
|
538
576
|
let isNew = false
|
|
577
|
+
let isQuarantined = false
|
|
539
578
|
|
|
540
579
|
if (isKnownTestsEnabled) {
|
|
541
580
|
isNew = isNewTest(testSuitePath, pickle.name)
|
|
@@ -543,6 +582,10 @@ function getWrappedRunTestCase (runTestCaseFunction, isNewerCucumberVersion = fa
|
|
|
543
582
|
numRetriesByPickleId.set(pickle.id, 0)
|
|
544
583
|
}
|
|
545
584
|
}
|
|
585
|
+
if (isQuarantinedTestsEnabled) {
|
|
586
|
+
isQuarantined = isQuarantinedTest(testSuitePath, pickle.name)
|
|
587
|
+
}
|
|
588
|
+
|
|
546
589
|
// TODO: for >=11 we could use `runTestCaseResult` instead of accumulating results in `lastStatusByPickleId`
|
|
547
590
|
let runTestCaseResult = await runTestCaseFunction.apply(this, arguments)
|
|
548
591
|
|
|
@@ -557,6 +600,7 @@ function getWrappedRunTestCase (runTestCaseFunction, isNewerCucumberVersion = fa
|
|
|
557
600
|
}
|
|
558
601
|
let testStatus = lastTestStatus
|
|
559
602
|
let shouldBePassedByEFD = false
|
|
603
|
+
let shouldBePassedByQuarantine = false
|
|
560
604
|
if (isNew && isEarlyFlakeDetectionEnabled) {
|
|
561
605
|
/**
|
|
562
606
|
* If Early Flake Detection (EFD) is enabled the logic is as follows:
|
|
@@ -573,6 +617,11 @@ function getWrappedRunTestCase (runTestCaseFunction, isNewerCucumberVersion = fa
|
|
|
573
617
|
}
|
|
574
618
|
}
|
|
575
619
|
|
|
620
|
+
if (isQuarantinedTestsEnabled && isQuarantined) {
|
|
621
|
+
this.success = true
|
|
622
|
+
shouldBePassedByQuarantine = true
|
|
623
|
+
}
|
|
624
|
+
|
|
576
625
|
if (!pickleResultByFile[testFileAbsolutePath]) {
|
|
577
626
|
pickleResultByFile[testFileAbsolutePath] = [testStatus]
|
|
578
627
|
} else {
|
|
@@ -604,6 +653,10 @@ function getWrappedRunTestCase (runTestCaseFunction, isNewerCucumberVersion = fa
|
|
|
604
653
|
return shouldBePassedByEFD
|
|
605
654
|
}
|
|
606
655
|
|
|
656
|
+
if (isNewerCucumberVersion && isQuarantinedTestsEnabled && isQuarantined) {
|
|
657
|
+
return shouldBePassedByQuarantine
|
|
658
|
+
}
|
|
659
|
+
|
|
607
660
|
return runTestCaseResult
|
|
608
661
|
}
|
|
609
662
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
3
|
const dc = require('dc-polyfill')
|
|
4
|
-
const
|
|
4
|
+
const satisfies = require('semifies')
|
|
5
5
|
const instrumentations = require('./instrumentations')
|
|
6
6
|
const { AsyncResource } = require('async_hooks')
|
|
7
7
|
|
|
@@ -36,7 +36,7 @@ exports.addHook = function addHook ({ name, versions, file, filePattern }, hook)
|
|
|
36
36
|
|
|
37
37
|
// AsyncResource.bind exists and binds `this` properly only from 17.8.0 and up.
|
|
38
38
|
// https://nodejs.org/api/async_context.html#asyncresourcebindfn-thisarg
|
|
39
|
-
if (
|
|
39
|
+
if (satisfies(process.versions.node, '>=17.8.0')) {
|
|
40
40
|
exports.AsyncResource = AsyncResource
|
|
41
41
|
} else {
|
|
42
42
|
exports.AsyncResource = class extends AsyncResource {
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const { channel } = require('dc-polyfill')
|
|
4
4
|
const path = require('path')
|
|
5
|
-
const
|
|
5
|
+
const satisfies = require('semifies')
|
|
6
6
|
const Hook = require('./hook')
|
|
7
7
|
const requirePackageJson = require('../../../dd-trace/src/require-package-json')
|
|
8
8
|
const log = require('../../../dd-trace/src/log')
|
|
@@ -155,7 +155,7 @@ for (const packageName of names) {
|
|
|
155
155
|
}
|
|
156
156
|
|
|
157
157
|
function matchVersion (version, ranges) {
|
|
158
|
-
return !version || (ranges && ranges.some(range =>
|
|
158
|
+
return !version || (ranges && ranges.some(range => satisfies(version, range)))
|
|
159
159
|
}
|
|
160
160
|
|
|
161
161
|
function getVersion (moduleBaseDir) {
|
|
@@ -43,6 +43,7 @@ const testErrCh = channel('ci:jest:test:err')
|
|
|
43
43
|
const skippableSuitesCh = channel('ci:jest:test-suite:skippable')
|
|
44
44
|
const libraryConfigurationCh = channel('ci:jest:library-configuration')
|
|
45
45
|
const knownTestsCh = channel('ci:jest:known-tests')
|
|
46
|
+
const quarantinedTestsCh = channel('ci:jest:quarantined-tests')
|
|
46
47
|
|
|
47
48
|
const itrSkippedSuitesCh = channel('ci:jest:itr:skipped-suites')
|
|
48
49
|
|
|
@@ -70,6 +71,8 @@ let earlyFlakeDetectionFaultyThreshold = 30
|
|
|
70
71
|
let isEarlyFlakeDetectionFaulty = false
|
|
71
72
|
let hasFilteredSkippableSuites = false
|
|
72
73
|
let isKnownTestsEnabled = false
|
|
74
|
+
let isQuarantinedTestsEnabled = false
|
|
75
|
+
let quarantinedTests = {}
|
|
73
76
|
|
|
74
77
|
const sessionAsyncResource = new AsyncResource('bound-anonymous-fn')
|
|
75
78
|
|
|
@@ -140,6 +143,7 @@ function getWrappedEnvironment (BaseEnvironment, jestVersion) {
|
|
|
140
143
|
this.flakyTestRetriesCount = this.testEnvironmentOptions._ddFlakyTestRetriesCount
|
|
141
144
|
this.isDiEnabled = this.testEnvironmentOptions._ddIsDiEnabled
|
|
142
145
|
this.isKnownTestsEnabled = this.testEnvironmentOptions._ddIsKnownTestsEnabled
|
|
146
|
+
this.isQuarantinedTestsEnabled = this.testEnvironmentOptions._ddIsQuarantinedTestsEnabled
|
|
143
147
|
|
|
144
148
|
if (this.isKnownTestsEnabled) {
|
|
145
149
|
try {
|
|
@@ -161,6 +165,18 @@ function getWrappedEnvironment (BaseEnvironment, jestVersion) {
|
|
|
161
165
|
this.global[RETRY_TIMES] = this.flakyTestRetriesCount
|
|
162
166
|
}
|
|
163
167
|
}
|
|
168
|
+
|
|
169
|
+
if (this.isQuarantinedTestsEnabled) {
|
|
170
|
+
try {
|
|
171
|
+
const hasQuarantinedTests = !!quarantinedTests.jest
|
|
172
|
+
this.quarantinedTestsForThisSuite = hasQuarantinedTests
|
|
173
|
+
? this.getQuarantinedTestsForSuite(quarantinedTests.jest.suites[this.testSuite].tests)
|
|
174
|
+
: this.getQuarantinedTestsForSuite(this.testEnvironmentOptions._ddQuarantinedTests)
|
|
175
|
+
} catch (e) {
|
|
176
|
+
log.error('Error parsing quarantined tests', e)
|
|
177
|
+
this.isQuarantinedTestsEnabled = false
|
|
178
|
+
}
|
|
179
|
+
}
|
|
164
180
|
}
|
|
165
181
|
|
|
166
182
|
getHasSnapshotTests () {
|
|
@@ -193,8 +209,25 @@ function getWrappedEnvironment (BaseEnvironment, jestVersion) {
|
|
|
193
209
|
return knownTestsForSuite
|
|
194
210
|
}
|
|
195
211
|
|
|
212
|
+
getQuarantinedTestsForSuite (quaratinedTests) {
|
|
213
|
+
if (this.quarantinedTestsForThisSuite) {
|
|
214
|
+
return this.quarantinedTestsForThisSuite
|
|
215
|
+
}
|
|
216
|
+
let quarantinedTestsForSuite = quaratinedTests
|
|
217
|
+
// If jest is using workers, quarantined tests are serialized to json.
|
|
218
|
+
// If jest runs in band, they are not.
|
|
219
|
+
if (typeof quarantinedTestsForSuite === 'string') {
|
|
220
|
+
quarantinedTestsForSuite = JSON.parse(quarantinedTestsForSuite)
|
|
221
|
+
}
|
|
222
|
+
return Object.entries(quarantinedTestsForSuite).reduce((acc, [testName, { properties }]) => {
|
|
223
|
+
if (properties?.quarantined) {
|
|
224
|
+
acc.push(testName)
|
|
225
|
+
}
|
|
226
|
+
return acc
|
|
227
|
+
}, [])
|
|
228
|
+
}
|
|
229
|
+
|
|
196
230
|
// Add the `add_test` event we don't have the test object yet, so
|
|
197
|
-
// we use its describe block to get the full name
|
|
198
231
|
getTestNameFromAddTestEvent (event, state) {
|
|
199
232
|
const describeSuffix = getJestTestName(state.currentDescribeBlock)
|
|
200
233
|
const fullTestName = describeSuffix ? `${describeSuffix} ${event.testName}` : event.testName
|
|
@@ -303,6 +336,12 @@ function getWrappedEnvironment (BaseEnvironment, jestVersion) {
|
|
|
303
336
|
}
|
|
304
337
|
}
|
|
305
338
|
}
|
|
339
|
+
let isQuarantined = false
|
|
340
|
+
|
|
341
|
+
if (this.isQuarantinedTestsEnabled) {
|
|
342
|
+
const testName = getJestTestName(event.test)
|
|
343
|
+
isQuarantined = this.quarantinedTestsForThisSuite?.includes(testName)
|
|
344
|
+
}
|
|
306
345
|
|
|
307
346
|
const promises = {}
|
|
308
347
|
const numRetries = this.global[RETRY_TIMES]
|
|
@@ -337,7 +376,7 @@ function getWrappedEnvironment (BaseEnvironment, jestVersion) {
|
|
|
337
376
|
testFinishCh.publish({
|
|
338
377
|
status,
|
|
339
378
|
testStartLine: getTestLineStart(event.test.asyncError, this.testSuite),
|
|
340
|
-
|
|
379
|
+
isQuarantined
|
|
341
380
|
})
|
|
342
381
|
})
|
|
343
382
|
|
|
@@ -485,6 +524,7 @@ function cliWrapper (cli, jestVersion) {
|
|
|
485
524
|
earlyFlakeDetectionNumRetries = libraryConfig.earlyFlakeDetectionNumRetries
|
|
486
525
|
earlyFlakeDetectionFaultyThreshold = libraryConfig.earlyFlakeDetectionFaultyThreshold
|
|
487
526
|
isKnownTestsEnabled = libraryConfig.isKnownTestsEnabled
|
|
527
|
+
isQuarantinedTestsEnabled = libraryConfig.isQuarantinedTestsEnabled
|
|
488
528
|
}
|
|
489
529
|
} catch (err) {
|
|
490
530
|
log.error('Jest library configuration error', err)
|
|
@@ -532,6 +572,25 @@ function cliWrapper (cli, jestVersion) {
|
|
|
532
572
|
}
|
|
533
573
|
}
|
|
534
574
|
|
|
575
|
+
if (isQuarantinedTestsEnabled) {
|
|
576
|
+
const quarantinedTestsPromise = new Promise((resolve) => {
|
|
577
|
+
onDone = resolve
|
|
578
|
+
})
|
|
579
|
+
|
|
580
|
+
sessionAsyncResource.runInAsyncScope(() => {
|
|
581
|
+
quarantinedTestsCh.publish({ onDone })
|
|
582
|
+
})
|
|
583
|
+
|
|
584
|
+
try {
|
|
585
|
+
const { err, quarantinedTests: receivedQuarantinedTests } = await quarantinedTestsPromise
|
|
586
|
+
if (!err) {
|
|
587
|
+
quarantinedTests = receivedQuarantinedTests
|
|
588
|
+
}
|
|
589
|
+
} catch (err) {
|
|
590
|
+
log.error('Jest quarantined tests error', err)
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
|
|
535
594
|
const processArgv = process.argv.slice(2).join(' ')
|
|
536
595
|
sessionAsyncResource.runInAsyncScope(() => {
|
|
537
596
|
testSessionStartCh.publish({ command: `jest ${processArgv}`, frameworkVersion: jestVersion })
|
|
@@ -601,6 +660,7 @@ function cliWrapper (cli, jestVersion) {
|
|
|
601
660
|
error,
|
|
602
661
|
isEarlyFlakeDetectionEnabled,
|
|
603
662
|
isEarlyFlakeDetectionFaulty,
|
|
663
|
+
isQuarantinedTestsEnabled,
|
|
604
664
|
onDone
|
|
605
665
|
})
|
|
606
666
|
})
|
|
@@ -634,6 +694,37 @@ function cliWrapper (cli, jestVersion) {
|
|
|
634
694
|
}
|
|
635
695
|
}
|
|
636
696
|
|
|
697
|
+
if (isQuarantinedTestsEnabled) {
|
|
698
|
+
const failedTests = result
|
|
699
|
+
.results
|
|
700
|
+
.testResults.flatMap(({ testResults, testFilePath: testSuiteAbsolutePath }) => (
|
|
701
|
+
testResults.map(({ fullName: testName, status }) => ({ testName, testSuiteAbsolutePath, status }))
|
|
702
|
+
))
|
|
703
|
+
.filter(({ status }) => status === 'failed')
|
|
704
|
+
|
|
705
|
+
let numFailedQuarantinedTests = 0
|
|
706
|
+
|
|
707
|
+
for (const { testName, testSuiteAbsolutePath } of failedTests) {
|
|
708
|
+
const testSuite = getTestSuitePath(testSuiteAbsolutePath, result.globalConfig.rootDir)
|
|
709
|
+
const isQuarantined = quarantinedTests
|
|
710
|
+
?.jest
|
|
711
|
+
?.suites
|
|
712
|
+
?.[testSuite]
|
|
713
|
+
?.tests
|
|
714
|
+
?.[testName]
|
|
715
|
+
?.properties
|
|
716
|
+
?.quarantined
|
|
717
|
+
if (isQuarantined) {
|
|
718
|
+
numFailedQuarantinedTests++
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
// If every test that failed was quarantined, we'll consider the suite passed
|
|
723
|
+
if (numFailedQuarantinedTests !== 0 && result.results.numFailedTests === numFailedQuarantinedTests) {
|
|
724
|
+
result.results.success = true
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
|
|
637
728
|
return result
|
|
638
729
|
})
|
|
639
730
|
|
|
@@ -825,6 +916,8 @@ addHook({
|
|
|
825
916
|
_ddFlakyTestRetriesCount,
|
|
826
917
|
_ddIsDiEnabled,
|
|
827
918
|
_ddIsKnownTestsEnabled,
|
|
919
|
+
_ddIsQuarantinedTestsEnabled,
|
|
920
|
+
_ddQuarantinedTests,
|
|
828
921
|
...restOfTestEnvironmentOptions
|
|
829
922
|
} = testEnvironmentOptions
|
|
830
923
|
|
|
@@ -936,8 +1029,9 @@ addHook({
|
|
|
936
1029
|
})
|
|
937
1030
|
|
|
938
1031
|
/*
|
|
939
|
-
* This hook does
|
|
1032
|
+
* This hook does three things:
|
|
940
1033
|
* - Pass known tests to the workers.
|
|
1034
|
+
* - Pass quarantined tests to the workers.
|
|
941
1035
|
* - Receive trace, coverage and logs payloads from the workers.
|
|
942
1036
|
*/
|
|
943
1037
|
addHook({
|
|
@@ -947,7 +1041,7 @@ addHook({
|
|
|
947
1041
|
}, (childProcessWorker) => {
|
|
948
1042
|
const ChildProcessWorker = childProcessWorker.default
|
|
949
1043
|
shimmer.wrap(ChildProcessWorker.prototype, 'send', send => function (request) {
|
|
950
|
-
if (!isKnownTestsEnabled) {
|
|
1044
|
+
if (!isKnownTestsEnabled && !isQuarantinedTestsEnabled) {
|
|
951
1045
|
return send.apply(this, arguments)
|
|
952
1046
|
}
|
|
953
1047
|
const [type] = request
|
|
@@ -967,11 +1061,15 @@ addHook({
|
|
|
967
1061
|
const [{ globalConfig, config, path: testSuiteAbsolutePath }] = args
|
|
968
1062
|
const testSuite = getTestSuitePath(testSuiteAbsolutePath, globalConfig.rootDir || process.cwd())
|
|
969
1063
|
const suiteKnownTests = knownTests.jest?.[testSuite] || []
|
|
1064
|
+
|
|
1065
|
+
const suiteQuarantinedTests = quarantinedTests.jest?.suites?.[testSuite]?.tests || {}
|
|
1066
|
+
|
|
970
1067
|
args[0].config = {
|
|
971
1068
|
...config,
|
|
972
1069
|
testEnvironmentOptions: {
|
|
973
1070
|
...config.testEnvironmentOptions,
|
|
974
|
-
_ddKnownTests: suiteKnownTests
|
|
1071
|
+
_ddKnownTests: suiteKnownTests,
|
|
1072
|
+
_ddQuarantinedTests: suiteQuarantinedTests
|
|
975
1073
|
}
|
|
976
1074
|
}
|
|
977
1075
|
}
|
|
@@ -27,6 +27,7 @@ const {
|
|
|
27
27
|
getOnPendingHandler,
|
|
28
28
|
testFileToSuiteAr,
|
|
29
29
|
newTests,
|
|
30
|
+
testsQuarantined,
|
|
30
31
|
getTestFullName,
|
|
31
32
|
getRunTestsWrapper
|
|
32
33
|
} = require('./utils')
|
|
@@ -61,6 +62,7 @@ const testSuiteCodeCoverageCh = channel('ci:mocha:test-suite:code-coverage')
|
|
|
61
62
|
const libraryConfigurationCh = channel('ci:mocha:library-configuration')
|
|
62
63
|
const knownTestsCh = channel('ci:mocha:known-tests')
|
|
63
64
|
const skippableSuitesCh = channel('ci:mocha:test-suite:skippable')
|
|
65
|
+
const quarantinedTestsCh = channel('ci:mocha:quarantined-tests')
|
|
64
66
|
const workerReportTraceCh = channel('ci:mocha:worker-report:trace')
|
|
65
67
|
const testSessionStartCh = channel('ci:mocha:session:start')
|
|
66
68
|
const testSessionFinishCh = channel('ci:mocha:session:finish')
|
|
@@ -135,6 +137,18 @@ function getOnEndHandler (isParallel) {
|
|
|
135
137
|
}
|
|
136
138
|
}
|
|
137
139
|
|
|
140
|
+
// We subtract the errors from quarantined tests from the total number of failures
|
|
141
|
+
if (config.isQuarantinedTestsEnabled) {
|
|
142
|
+
let numFailedQuarantinedTests = 0
|
|
143
|
+
for (const test of testsQuarantined) {
|
|
144
|
+
if (isTestFailed(test)) {
|
|
145
|
+
numFailedQuarantinedTests++
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
this.stats.failures -= numFailedQuarantinedTests
|
|
149
|
+
this.failures -= numFailedQuarantinedTests
|
|
150
|
+
}
|
|
151
|
+
|
|
138
152
|
if (status === 'fail') {
|
|
139
153
|
error = new Error(`Failed tests: ${this.failures}.`)
|
|
140
154
|
}
|
|
@@ -165,6 +179,7 @@ function getOnEndHandler (isParallel) {
|
|
|
165
179
|
error,
|
|
166
180
|
isEarlyFlakeDetectionEnabled: config.isEarlyFlakeDetectionEnabled,
|
|
167
181
|
isEarlyFlakeDetectionFaulty: config.isEarlyFlakeDetectionFaulty,
|
|
182
|
+
isQuarantinedTestsEnabled: config.isQuarantinedTestsEnabled,
|
|
168
183
|
isParallel
|
|
169
184
|
})
|
|
170
185
|
})
|
|
@@ -173,6 +188,22 @@ function getOnEndHandler (isParallel) {
|
|
|
173
188
|
function getExecutionConfiguration (runner, isParallel, onFinishRequest) {
|
|
174
189
|
const mochaRunAsyncResource = new AsyncResource('bound-anonymous-fn')
|
|
175
190
|
|
|
191
|
+
const onReceivedQuarantinedTests = ({ err, quarantinedTests: receivedQuarantinedTests }) => {
|
|
192
|
+
if (err) {
|
|
193
|
+
config.quarantinedTests = {}
|
|
194
|
+
config.isQuarantinedTestsEnabled = false
|
|
195
|
+
} else {
|
|
196
|
+
config.quarantinedTests = receivedQuarantinedTests
|
|
197
|
+
}
|
|
198
|
+
if (config.isSuitesSkippingEnabled) {
|
|
199
|
+
skippableSuitesCh.publish({
|
|
200
|
+
onDone: mochaRunAsyncResource.bind(onReceivedSkippableSuites)
|
|
201
|
+
})
|
|
202
|
+
} else {
|
|
203
|
+
onFinishRequest()
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
176
207
|
const onReceivedSkippableSuites = ({ err, skippableSuites, itrCorrelationId: responseItrCorrelationId }) => {
|
|
177
208
|
if (err) {
|
|
178
209
|
suitesToSkip = []
|
|
@@ -205,8 +236,11 @@ function getExecutionConfiguration (runner, isParallel, onFinishRequest) {
|
|
|
205
236
|
} else {
|
|
206
237
|
config.knownTests = knownTests
|
|
207
238
|
}
|
|
208
|
-
|
|
209
|
-
|
|
239
|
+
if (config.isQuarantinedTestsEnabled) {
|
|
240
|
+
quarantinedTestsCh.publish({
|
|
241
|
+
onDone: mochaRunAsyncResource.bind(onReceivedQuarantinedTests)
|
|
242
|
+
})
|
|
243
|
+
} else if (config.isSuitesSkippingEnabled) {
|
|
210
244
|
skippableSuitesCh.publish({
|
|
211
245
|
onDone: mochaRunAsyncResource.bind(onReceivedSkippableSuites)
|
|
212
246
|
})
|
|
@@ -224,15 +258,20 @@ function getExecutionConfiguration (runner, isParallel, onFinishRequest) {
|
|
|
224
258
|
config.earlyFlakeDetectionNumRetries = libraryConfig.earlyFlakeDetectionNumRetries
|
|
225
259
|
config.earlyFlakeDetectionFaultyThreshold = libraryConfig.earlyFlakeDetectionFaultyThreshold
|
|
226
260
|
config.isKnownTestsEnabled = libraryConfig.isKnownTestsEnabled
|
|
227
|
-
// ITR
|
|
261
|
+
// ITR, auto test retries and quarantine are not supported in parallel mode yet
|
|
228
262
|
config.isSuitesSkippingEnabled = !isParallel && libraryConfig.isSuitesSkippingEnabled
|
|
229
263
|
config.isFlakyTestRetriesEnabled = !isParallel && libraryConfig.isFlakyTestRetriesEnabled
|
|
230
264
|
config.flakyTestRetriesCount = !isParallel && libraryConfig.flakyTestRetriesCount
|
|
265
|
+
config.isQuarantinedTestsEnabled = !isParallel && libraryConfig.isQuarantinedTestsEnabled
|
|
231
266
|
|
|
232
267
|
if (config.isKnownTestsEnabled) {
|
|
233
268
|
knownTestsCh.publish({
|
|
234
269
|
onDone: mochaRunAsyncResource.bind(onReceivedKnownTests)
|
|
235
270
|
})
|
|
271
|
+
} else if (config.isQuarantinedTestsEnabled) {
|
|
272
|
+
quarantinedTestsCh.publish({
|
|
273
|
+
onDone: mochaRunAsyncResource.bind(onReceivedQuarantinedTests)
|
|
274
|
+
})
|
|
236
275
|
} else if (config.isSuitesSkippingEnabled) {
|
|
237
276
|
skippableSuitesCh.publish({
|
|
238
277
|
onDone: mochaRunAsyncResource.bind(onReceivedSkippableSuites)
|
|
@@ -357,7 +396,7 @@ addHook({
|
|
|
357
396
|
|
|
358
397
|
this.once('end', getOnEndHandler(false))
|
|
359
398
|
|
|
360
|
-
this.on('test', getOnTestHandler(true
|
|
399
|
+
this.on('test', getOnTestHandler(true))
|
|
361
400
|
|
|
362
401
|
this.on('test end', getOnTestEndHandler())
|
|
363
402
|
|
|
@@ -579,6 +618,7 @@ addHook({
|
|
|
579
618
|
|
|
580
619
|
const testPath = getTestSuitePath(testSuiteAbsolutePath, process.cwd())
|
|
581
620
|
const testSuiteKnownTests = config.knownTests.mocha?.[testPath] || []
|
|
621
|
+
const testSuiteQuarantinedTests = config.quarantinedTests?.modules?.mocha?.suites?.[testPath] || []
|
|
582
622
|
|
|
583
623
|
// We pass the known tests for the test file to the worker
|
|
584
624
|
const testFileResult = await run.apply(
|
|
@@ -589,6 +629,8 @@ addHook({
|
|
|
589
629
|
...workerArgs,
|
|
590
630
|
_ddEfdNumRetries: config.earlyFlakeDetectionNumRetries,
|
|
591
631
|
_ddIsEfdEnabled: config.isEarlyFlakeDetectionEnabled,
|
|
632
|
+
_ddIsQuarantinedEnabled: config.isQuarantinedTestsEnabled,
|
|
633
|
+
_ddQuarantinedTests: testSuiteQuarantinedTests,
|
|
592
634
|
_ddKnownTests: {
|
|
593
635
|
mocha: {
|
|
594
636
|
[testPath]: testSuiteKnownTests
|