dd-trace 5.36.0 → 5.37.1
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 +15 -14
- 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/blocked_templates.js +3 -3
- 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/index.js +1 -1
- 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 +17 -4
- package/packages/dd-trace/src/debugger/devtools_client/breakpoints.js +17 -10
- package/packages/dd-trace/src/debugger/devtools_client/source-maps.js +64 -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 +20 -7
- 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
|
@@ -26,6 +26,27 @@ const testToStartLine = new WeakMap()
|
|
|
26
26
|
const testFileToSuiteAr = new Map()
|
|
27
27
|
const wrappedFunctions = new WeakSet()
|
|
28
28
|
const newTests = {}
|
|
29
|
+
const testsQuarantined = new Set()
|
|
30
|
+
|
|
31
|
+
function isQuarantinedTest (test, testsToQuarantine) {
|
|
32
|
+
const testSuite = getTestSuitePath(test.file, process.cwd())
|
|
33
|
+
const testName = test.fullTitle()
|
|
34
|
+
|
|
35
|
+
const isQuarantined = (testsToQuarantine
|
|
36
|
+
.mocha
|
|
37
|
+
?.suites
|
|
38
|
+
?.[testSuite]
|
|
39
|
+
?.tests
|
|
40
|
+
?.[testName]
|
|
41
|
+
?.properties
|
|
42
|
+
?.quarantined) ?? false
|
|
43
|
+
|
|
44
|
+
if (isQuarantined) {
|
|
45
|
+
testsQuarantined.add(test)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return isQuarantined
|
|
49
|
+
}
|
|
29
50
|
|
|
30
51
|
function isNewTest (test, knownTests) {
|
|
31
52
|
const testSuite = getTestSuitePath(test.file, process.cwd())
|
|
@@ -171,7 +192,8 @@ function getOnTestHandler (isMain) {
|
|
|
171
192
|
file: testSuiteAbsolutePath,
|
|
172
193
|
title,
|
|
173
194
|
_ddIsNew: isNew,
|
|
174
|
-
_ddIsEfdRetry: isEfdRetry
|
|
195
|
+
_ddIsEfdRetry: isEfdRetry,
|
|
196
|
+
_ddIsQuarantined: isQuarantined
|
|
175
197
|
} = test
|
|
176
198
|
|
|
177
199
|
const testInfo = {
|
|
@@ -187,6 +209,7 @@ function getOnTestHandler (isMain) {
|
|
|
187
209
|
|
|
188
210
|
testInfo.isNew = isNew
|
|
189
211
|
testInfo.isEfdRetry = isEfdRetry
|
|
212
|
+
testInfo.isQuarantined = isQuarantined
|
|
190
213
|
// We want to store the result of the new tests
|
|
191
214
|
if (isNew) {
|
|
192
215
|
const testFullName = getTestFullName(test)
|
|
@@ -360,6 +383,15 @@ function getRunTestsWrapper (runTests, config) {
|
|
|
360
383
|
}
|
|
361
384
|
})
|
|
362
385
|
}
|
|
386
|
+
|
|
387
|
+
if (config.isQuarantinedTestsEnabled) {
|
|
388
|
+
suite.tests.forEach(test => {
|
|
389
|
+
if (isQuarantinedTest(test, config.quarantinedTests)) {
|
|
390
|
+
test._ddIsQuarantined = true
|
|
391
|
+
}
|
|
392
|
+
})
|
|
393
|
+
}
|
|
394
|
+
|
|
363
395
|
return runTests.apply(this, arguments)
|
|
364
396
|
}
|
|
365
397
|
}
|
|
@@ -384,5 +416,6 @@ module.exports = {
|
|
|
384
416
|
getOnPendingHandler,
|
|
385
417
|
testFileToSuiteAr,
|
|
386
418
|
getRunTestsWrapper,
|
|
387
|
-
newTests
|
|
419
|
+
newTests,
|
|
420
|
+
testsQuarantined
|
|
388
421
|
}
|
|
@@ -33,6 +33,13 @@ addHook({
|
|
|
33
33
|
delete this.options._ddIsEfdEnabled
|
|
34
34
|
delete this.options._ddKnownTests
|
|
35
35
|
delete this.options._ddEfdNumRetries
|
|
36
|
+
delete this.options._ddQuarantinedTests
|
|
37
|
+
}
|
|
38
|
+
if (this.options._ddIsQuarantinedEnabled) {
|
|
39
|
+
config.isQuarantinedEnabled = true
|
|
40
|
+
config.quarantinedTests = this.options._ddQuarantinedTests
|
|
41
|
+
delete this.options._ddIsQuarantinedEnabled
|
|
42
|
+
delete this.options._ddQuarantinedTests
|
|
36
43
|
}
|
|
37
44
|
return run.apply(this, arguments)
|
|
38
45
|
})
|
|
@@ -6,14 +6,14 @@ const {
|
|
|
6
6
|
AsyncResource
|
|
7
7
|
} = require('./helpers/instrument')
|
|
8
8
|
const shimmer = require('../../datadog-shimmer')
|
|
9
|
-
const
|
|
9
|
+
const satisfies = require('semifies')
|
|
10
10
|
|
|
11
11
|
function wrapConnection (Connection, version) {
|
|
12
12
|
const startCh = channel('apm:mysql2:query:start')
|
|
13
13
|
const finishCh = channel('apm:mysql2:query:finish')
|
|
14
14
|
const errorCh = channel('apm:mysql2:query:error')
|
|
15
15
|
const startOuterQueryCh = channel('datadog:mysql2:outerquery:start')
|
|
16
|
-
const shouldEmitEndAfterQueryAbort =
|
|
16
|
+
const shouldEmitEndAfterQueryAbort = satisfies(version, '>=1.3.3')
|
|
17
17
|
|
|
18
18
|
shimmer.wrap(Connection.prototype, 'addCommand', addCommand => function (cmd) {
|
|
19
19
|
if (!startCh.hasSubscribers) return addCommand.apply(this, arguments)
|
|
@@ -154,7 +154,7 @@ function wrapConnection (Connection, version) {
|
|
|
154
154
|
}
|
|
155
155
|
function wrapPool (Pool, version) {
|
|
156
156
|
const startOuterQueryCh = channel('datadog:mysql2:outerquery:start')
|
|
157
|
-
const shouldEmitEndAfterQueryAbort =
|
|
157
|
+
const shouldEmitEndAfterQueryAbort = satisfies(version, '>=1.3.3')
|
|
158
158
|
|
|
159
159
|
shimmer.wrap(Pool.prototype, 'query', query => function (sql, values, cb) {
|
|
160
160
|
if (!startOuterQueryCh.hasSubscribers) return query.apply(this, arguments)
|
|
@@ -97,6 +97,14 @@ const V4_PACKAGE_SHIMS = [
|
|
|
97
97
|
targetClass: 'Translations',
|
|
98
98
|
baseResource: 'audio.translations',
|
|
99
99
|
methods: ['create']
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
file: 'resources/chat/completions/completions.js',
|
|
103
|
+
targetClass: 'Completions',
|
|
104
|
+
baseResource: 'chat.completions',
|
|
105
|
+
methods: ['create'],
|
|
106
|
+
streamedResponse: true,
|
|
107
|
+
versions: ['>=4.85.0']
|
|
100
108
|
}
|
|
101
109
|
]
|
|
102
110
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const
|
|
1
|
+
const satisfies = require('semifies')
|
|
2
2
|
|
|
3
3
|
const { addHook, channel, AsyncResource } = require('./helpers/instrument')
|
|
4
4
|
const shimmer = require('../../datadog-shimmer')
|
|
@@ -13,6 +13,7 @@ const testSessionFinishCh = channel('ci:playwright:session:finish')
|
|
|
13
13
|
|
|
14
14
|
const libraryConfigurationCh = channel('ci:playwright:library-configuration')
|
|
15
15
|
const knownTestsCh = channel('ci:playwright:known-tests')
|
|
16
|
+
const quarantinedTestsCh = channel('ci:playwright:quarantined-tests')
|
|
16
17
|
|
|
17
18
|
const testSuiteStartCh = channel('ci:playwright:test-suite:start')
|
|
18
19
|
const testSuiteFinishCh = channel('ci:playwright:test-suite:finish')
|
|
@@ -41,8 +42,24 @@ let earlyFlakeDetectionNumRetries = 0
|
|
|
41
42
|
let isFlakyTestRetriesEnabled = false
|
|
42
43
|
let flakyTestRetriesCount = 0
|
|
43
44
|
let knownTests = {}
|
|
45
|
+
let isQuarantinedTestsEnabled = false
|
|
46
|
+
let quarantinedTests = {}
|
|
44
47
|
let rootDir = ''
|
|
45
|
-
const
|
|
48
|
+
const MINIMUM_SUPPORTED_VERSION_RANGE_EFD = '>=1.38.0'
|
|
49
|
+
|
|
50
|
+
function isQuarantineTest (test) {
|
|
51
|
+
const testName = getTestFullname(test)
|
|
52
|
+
const testSuite = getTestSuitePath(test._requireFile, rootDir)
|
|
53
|
+
|
|
54
|
+
return quarantinedTests
|
|
55
|
+
?.playwright
|
|
56
|
+
?.suites
|
|
57
|
+
?.[testSuite]
|
|
58
|
+
?.tests
|
|
59
|
+
?.[testName]
|
|
60
|
+
?.properties
|
|
61
|
+
?.quarantined
|
|
62
|
+
}
|
|
46
63
|
|
|
47
64
|
function isNewTest (test) {
|
|
48
65
|
const testSuite = getTestSuitePath(test._requireFile, rootDir)
|
|
@@ -296,6 +313,7 @@ function testEndHandler (test, annotations, testStatus, error, isTimeout) {
|
|
|
296
313
|
error,
|
|
297
314
|
extraTags: annotationTags,
|
|
298
315
|
isNew: test._ddIsNew,
|
|
316
|
+
isQuarantined: test._ddIsQuarantined,
|
|
299
317
|
isEfdRetry: test._ddIsEfdRetry
|
|
300
318
|
})
|
|
301
319
|
})
|
|
@@ -424,14 +442,16 @@ function runnerHook (runnerExport, playwrightVersion) {
|
|
|
424
442
|
earlyFlakeDetectionNumRetries = libraryConfig.earlyFlakeDetectionNumRetries
|
|
425
443
|
isFlakyTestRetriesEnabled = libraryConfig.isFlakyTestRetriesEnabled
|
|
426
444
|
flakyTestRetriesCount = libraryConfig.flakyTestRetriesCount
|
|
445
|
+
isQuarantinedTestsEnabled = libraryConfig.isQuarantinedTestsEnabled
|
|
427
446
|
}
|
|
428
447
|
} catch (e) {
|
|
429
448
|
isEarlyFlakeDetectionEnabled = false
|
|
430
449
|
isKnownTestsEnabled = false
|
|
450
|
+
isQuarantinedTestsEnabled = false
|
|
431
451
|
log.error('Playwright session start error', e)
|
|
432
452
|
}
|
|
433
453
|
|
|
434
|
-
if (isKnownTestsEnabled &&
|
|
454
|
+
if (isKnownTestsEnabled && satisfies(playwrightVersion, MINIMUM_SUPPORTED_VERSION_RANGE_EFD)) {
|
|
435
455
|
try {
|
|
436
456
|
const { err, knownTests: receivedKnownTests } = await getChannelPromise(knownTestsCh)
|
|
437
457
|
if (!err) {
|
|
@@ -447,6 +467,20 @@ function runnerHook (runnerExport, playwrightVersion) {
|
|
|
447
467
|
}
|
|
448
468
|
}
|
|
449
469
|
|
|
470
|
+
if (isQuarantinedTestsEnabled && satisfies(playwrightVersion, MINIMUM_SUPPORTED_VERSION_RANGE_EFD)) {
|
|
471
|
+
try {
|
|
472
|
+
const { err, quarantinedTests: receivedQuarantinedTests } = await getChannelPromise(quarantinedTestsCh)
|
|
473
|
+
if (!err) {
|
|
474
|
+
quarantinedTests = receivedQuarantinedTests
|
|
475
|
+
} else {
|
|
476
|
+
isQuarantinedTestsEnabled = false
|
|
477
|
+
}
|
|
478
|
+
} catch (err) {
|
|
479
|
+
isQuarantinedTestsEnabled = false
|
|
480
|
+
log.error('Playwright quarantined tests error', err)
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
|
|
450
484
|
const projects = getProjectsFromRunner(this)
|
|
451
485
|
|
|
452
486
|
if (isFlakyTestRetriesEnabled && flakyTestRetriesCount > 0) {
|
|
@@ -479,6 +513,7 @@ function runnerHook (runnerExport, playwrightVersion) {
|
|
|
479
513
|
testSessionFinishCh.publish({
|
|
480
514
|
status: STATUS_TO_TEST_STATUS[sessionStatus],
|
|
481
515
|
isEarlyFlakeDetectionEnabled,
|
|
516
|
+
isQuarantinedTestsEnabled,
|
|
482
517
|
onDone
|
|
483
518
|
})
|
|
484
519
|
})
|
|
@@ -487,6 +522,8 @@ function runnerHook (runnerExport, playwrightVersion) {
|
|
|
487
522
|
startedSuites = []
|
|
488
523
|
remainingTestsByFile = {}
|
|
489
524
|
|
|
525
|
+
// TODO: we can trick playwright into thinking the session passed by returning
|
|
526
|
+
// 'passed' here. We might be able to use this for both EFD and Quarantined tests.
|
|
490
527
|
return runAllTestsReturn
|
|
491
528
|
})
|
|
492
529
|
|
|
@@ -540,7 +577,7 @@ addHook({
|
|
|
540
577
|
addHook({
|
|
541
578
|
name: 'playwright',
|
|
542
579
|
file: 'lib/common/suiteUtils.js',
|
|
543
|
-
versions: [
|
|
580
|
+
versions: [MINIMUM_SUPPORTED_VERSION_RANGE_EFD]
|
|
544
581
|
}, suiteUtilsPackage => {
|
|
545
582
|
// We grab `applyRepeatEachIndex` to use it later
|
|
546
583
|
// `applyRepeatEachIndex` needs to be applied to a cloned suite
|
|
@@ -552,31 +589,42 @@ addHook({
|
|
|
552
589
|
addHook({
|
|
553
590
|
name: 'playwright',
|
|
554
591
|
file: 'lib/runner/loadUtils.js',
|
|
555
|
-
versions: [
|
|
592
|
+
versions: [MINIMUM_SUPPORTED_VERSION_RANGE_EFD]
|
|
556
593
|
}, (loadUtilsPackage) => {
|
|
557
594
|
const oldCreateRootSuite = loadUtilsPackage.createRootSuite
|
|
558
595
|
|
|
559
596
|
async function newCreateRootSuite () {
|
|
597
|
+
if (!isKnownTestsEnabled && !isQuarantinedTestsEnabled) {
|
|
598
|
+
return oldCreateRootSuite.apply(this, arguments)
|
|
599
|
+
}
|
|
560
600
|
const rootSuite = await oldCreateRootSuite.apply(this, arguments)
|
|
561
|
-
|
|
562
|
-
|
|
601
|
+
|
|
602
|
+
const allTests = rootSuite.allTests()
|
|
603
|
+
|
|
604
|
+
if (isQuarantinedTestsEnabled) {
|
|
605
|
+
const testsToBeIgnored = allTests.filter(isQuarantineTest)
|
|
606
|
+
testsToBeIgnored.forEach(test => {
|
|
607
|
+
test._ddIsQuarantined = true
|
|
608
|
+
test.expectedStatus = 'skipped'
|
|
609
|
+
})
|
|
563
610
|
}
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
.filter(isNewTest)
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
611
|
+
|
|
612
|
+
if (isKnownTestsEnabled) {
|
|
613
|
+
const newTests = allTests.filter(isNewTest)
|
|
614
|
+
|
|
615
|
+
newTests.forEach(newTest => {
|
|
616
|
+
newTest._ddIsNew = true
|
|
617
|
+
if (isEarlyFlakeDetectionEnabled && newTest.expectedStatus !== 'skipped') {
|
|
618
|
+
const fileSuite = getSuiteType(newTest, 'file')
|
|
619
|
+
const projectSuite = getSuiteType(newTest, 'project')
|
|
620
|
+
for (let repeatEachIndex = 0; repeatEachIndex < earlyFlakeDetectionNumRetries; repeatEachIndex++) {
|
|
621
|
+
const copyFileSuite = deepCloneSuite(fileSuite, isNewTest)
|
|
622
|
+
applyRepeatEachIndex(projectSuite._fullProject, copyFileSuite, repeatEachIndex + 1)
|
|
623
|
+
projectSuite._addSuite(copyFileSuite)
|
|
624
|
+
}
|
|
577
625
|
}
|
|
578
|
-
}
|
|
579
|
-
}
|
|
626
|
+
})
|
|
627
|
+
}
|
|
580
628
|
|
|
581
629
|
return rootSuite
|
|
582
630
|
}
|
|
@@ -9,6 +9,7 @@ const testPassCh = channel('ci:vitest:test:pass')
|
|
|
9
9
|
const testErrorCh = channel('ci:vitest:test:error')
|
|
10
10
|
const testSkipCh = channel('ci:vitest:test:skip')
|
|
11
11
|
const isNewTestCh = channel('ci:vitest:test:is-new')
|
|
12
|
+
const isQuarantinedCh = channel('ci:vitest:test:is-quarantined')
|
|
12
13
|
|
|
13
14
|
// test suite hooks
|
|
14
15
|
const testSuiteStartCh = channel('ci:vitest:test-suite:start')
|
|
@@ -21,10 +22,12 @@ const testSessionFinishCh = channel('ci:vitest:session:finish')
|
|
|
21
22
|
const libraryConfigurationCh = channel('ci:vitest:library-configuration')
|
|
22
23
|
const knownTestsCh = channel('ci:vitest:known-tests')
|
|
23
24
|
const isEarlyFlakeDetectionFaultyCh = channel('ci:vitest:is-early-flake-detection-faulty')
|
|
25
|
+
const quarantinedTestsCh = channel('ci:vitest:quarantined-tests')
|
|
24
26
|
|
|
25
27
|
const taskToAsync = new WeakMap()
|
|
26
28
|
const taskToStatuses = new WeakMap()
|
|
27
29
|
const newTasks = new WeakSet()
|
|
30
|
+
const quarantinedTasks = new WeakSet()
|
|
28
31
|
let isRetryReasonEfd = false
|
|
29
32
|
const switchedStatuses = new WeakSet()
|
|
30
33
|
const sessionAsyncResource = new AsyncResource('bound-anonymous-fn')
|
|
@@ -46,7 +49,9 @@ function getProvidedContext () {
|
|
|
46
49
|
_ddIsDiEnabled,
|
|
47
50
|
_ddKnownTests: knownTests,
|
|
48
51
|
_ddEarlyFlakeDetectionNumRetries: numRepeats,
|
|
49
|
-
_ddIsKnownTestsEnabled: isKnownTestsEnabled
|
|
52
|
+
_ddIsKnownTestsEnabled: isKnownTestsEnabled,
|
|
53
|
+
_ddIsQuarantinedTestsEnabled: isQuarantinedTestsEnabled,
|
|
54
|
+
_ddQuarantinedTests: quarantinedTests
|
|
50
55
|
} = globalThis.__vitest_worker__.providedContext
|
|
51
56
|
|
|
52
57
|
return {
|
|
@@ -54,7 +59,9 @@ function getProvidedContext () {
|
|
|
54
59
|
isEarlyFlakeDetectionEnabled: _ddIsEarlyFlakeDetectionEnabled,
|
|
55
60
|
knownTests,
|
|
56
61
|
numRepeats,
|
|
57
|
-
isKnownTestsEnabled
|
|
62
|
+
isKnownTestsEnabled,
|
|
63
|
+
isQuarantinedTestsEnabled,
|
|
64
|
+
quarantinedTests
|
|
58
65
|
}
|
|
59
66
|
} catch (e) {
|
|
60
67
|
log.error('Vitest workers could not parse provided context, so some features will not work.')
|
|
@@ -63,7 +70,9 @@ function getProvidedContext () {
|
|
|
63
70
|
isEarlyFlakeDetectionEnabled: false,
|
|
64
71
|
knownTests: {},
|
|
65
72
|
numRepeats: 0,
|
|
66
|
-
isKnownTestsEnabled: false
|
|
73
|
+
isKnownTestsEnabled: false,
|
|
74
|
+
isQuarantinedTestsEnabled: false,
|
|
75
|
+
quarantinedTests: {}
|
|
67
76
|
}
|
|
68
77
|
}
|
|
69
78
|
}
|
|
@@ -158,8 +167,10 @@ function getSortWrapper (sort) {
|
|
|
158
167
|
let earlyFlakeDetectionNumRetries = 0
|
|
159
168
|
let isEarlyFlakeDetectionFaulty = false
|
|
160
169
|
let isKnownTestsEnabled = false
|
|
170
|
+
let isQuarantinedTestsEnabled = false
|
|
161
171
|
let isDiEnabled = false
|
|
162
172
|
let knownTests = {}
|
|
173
|
+
let quarantinedTests = {}
|
|
163
174
|
|
|
164
175
|
try {
|
|
165
176
|
const { err, libraryConfig } = await getChannelPromise(libraryConfigurationCh)
|
|
@@ -170,6 +181,7 @@ function getSortWrapper (sort) {
|
|
|
170
181
|
earlyFlakeDetectionNumRetries = libraryConfig.earlyFlakeDetectionNumRetries
|
|
171
182
|
isDiEnabled = libraryConfig.isDiEnabled
|
|
172
183
|
isKnownTestsEnabled = libraryConfig.isKnownTestsEnabled
|
|
184
|
+
isQuarantinedTestsEnabled = libraryConfig.isQuarantinedTestsEnabled
|
|
173
185
|
}
|
|
174
186
|
} catch (e) {
|
|
175
187
|
isFlakyTestRetriesEnabled = false
|
|
@@ -229,6 +241,23 @@ function getSortWrapper (sort) {
|
|
|
229
241
|
}
|
|
230
242
|
}
|
|
231
243
|
|
|
244
|
+
if (isQuarantinedTestsEnabled) {
|
|
245
|
+
const { err, quarantinedTests: receivedQuarantinedTests } = await getChannelPromise(quarantinedTestsCh)
|
|
246
|
+
if (!err) {
|
|
247
|
+
quarantinedTests = receivedQuarantinedTests
|
|
248
|
+
try {
|
|
249
|
+
const workspaceProject = this.ctx.getCoreWorkspaceProject()
|
|
250
|
+
workspaceProject._provided._ddIsQuarantinedTestsEnabled = isQuarantinedTestsEnabled
|
|
251
|
+
workspaceProject._provided._ddQuarantinedTests = quarantinedTests
|
|
252
|
+
} catch (e) {
|
|
253
|
+
log.warn('Could not send quarantined tests to workers so Quarantine will not work.')
|
|
254
|
+
}
|
|
255
|
+
} else {
|
|
256
|
+
isQuarantinedTestsEnabled = false
|
|
257
|
+
log.error('Could not get quarantined tests.')
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
232
261
|
let testCodeCoverageLinesTotal
|
|
233
262
|
|
|
234
263
|
if (this.ctx.coverageProvider?.generateCoverage) {
|
|
@@ -263,6 +292,7 @@ function getSortWrapper (sort) {
|
|
|
263
292
|
error,
|
|
264
293
|
isEarlyFlakeDetectionEnabled,
|
|
265
294
|
isEarlyFlakeDetectionFaulty,
|
|
295
|
+
isQuarantinedTestsEnabled,
|
|
266
296
|
onFinish
|
|
267
297
|
})
|
|
268
298
|
})
|
|
@@ -332,7 +362,7 @@ addHook({
|
|
|
332
362
|
|
|
333
363
|
// `onAfterRunTask` is run after all repetitions or attempts are run
|
|
334
364
|
shimmer.wrap(VitestTestRunner.prototype, 'onAfterRunTask', onAfterRunTask => async function (task) {
|
|
335
|
-
const { isEarlyFlakeDetectionEnabled } = getProvidedContext()
|
|
365
|
+
const { isEarlyFlakeDetectionEnabled, isQuarantinedTestsEnabled } = getProvidedContext()
|
|
336
366
|
|
|
337
367
|
if (isEarlyFlakeDetectionEnabled && taskToStatuses.has(task)) {
|
|
338
368
|
const statuses = taskToStatuses.get(task)
|
|
@@ -345,6 +375,12 @@ addHook({
|
|
|
345
375
|
}
|
|
346
376
|
}
|
|
347
377
|
|
|
378
|
+
if (isQuarantinedTestsEnabled) {
|
|
379
|
+
if (quarantinedTasks.has(task)) {
|
|
380
|
+
task.result.state = 'pass'
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
348
384
|
return onAfterRunTask.apply(this, arguments)
|
|
349
385
|
})
|
|
350
386
|
|
|
@@ -356,17 +392,34 @@ addHook({
|
|
|
356
392
|
}
|
|
357
393
|
const testName = getTestName(task)
|
|
358
394
|
let isNew = false
|
|
395
|
+
let isQuarantined = false
|
|
359
396
|
|
|
360
397
|
const {
|
|
361
398
|
isKnownTestsEnabled,
|
|
362
399
|
isEarlyFlakeDetectionEnabled,
|
|
363
|
-
isDiEnabled
|
|
400
|
+
isDiEnabled,
|
|
401
|
+
isQuarantinedTestsEnabled,
|
|
402
|
+
quarantinedTests
|
|
364
403
|
} = getProvidedContext()
|
|
365
404
|
|
|
366
405
|
if (isKnownTestsEnabled) {
|
|
367
406
|
isNew = newTasks.has(task)
|
|
368
407
|
}
|
|
369
408
|
|
|
409
|
+
if (isQuarantinedTestsEnabled) {
|
|
410
|
+
isQuarantinedCh.publish({
|
|
411
|
+
quarantinedTests,
|
|
412
|
+
testSuiteAbsolutePath: task.file.filepath,
|
|
413
|
+
testName,
|
|
414
|
+
onDone: (isTestQuarantined) => {
|
|
415
|
+
isQuarantined = isTestQuarantined
|
|
416
|
+
if (isTestQuarantined) {
|
|
417
|
+
quarantinedTasks.add(task)
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
})
|
|
421
|
+
}
|
|
422
|
+
|
|
370
423
|
const { retry: numAttempt, repeats: numRepetition } = retryInfo
|
|
371
424
|
|
|
372
425
|
// We finish the previous test here because we know it has failed already
|
|
@@ -448,7 +501,8 @@ addHook({
|
|
|
448
501
|
isRetry: numAttempt > 0 || numRepetition > 0,
|
|
449
502
|
isRetryReasonEfd,
|
|
450
503
|
isNew,
|
|
451
|
-
mightHitProbe: isDiEnabled && numAttempt > 0
|
|
504
|
+
mightHitProbe: isDiEnabled && numAttempt > 0,
|
|
505
|
+
isQuarantined
|
|
452
506
|
})
|
|
453
507
|
})
|
|
454
508
|
return onBeforeTryTask.apply(this, arguments)
|
|
@@ -27,7 +27,9 @@ const {
|
|
|
27
27
|
TEST_MODULE_ID,
|
|
28
28
|
TEST_SUITE,
|
|
29
29
|
CUCUMBER_IS_PARALLEL,
|
|
30
|
-
TEST_RETRY_REASON
|
|
30
|
+
TEST_RETRY_REASON,
|
|
31
|
+
TEST_MANAGEMENT_ENABLED,
|
|
32
|
+
TEST_MANAGEMENT_IS_QUARANTINED
|
|
31
33
|
} = require('../../dd-trace/src/plugins/util/test')
|
|
32
34
|
const { RESOURCE_NAME } = require('../../../ext/tags')
|
|
33
35
|
const { COMPONENT, ERROR_MESSAGE } = require('../../dd-trace/src/constants')
|
|
@@ -47,6 +49,7 @@ const {
|
|
|
47
49
|
const id = require('../../dd-trace/src/id')
|
|
48
50
|
|
|
49
51
|
const BREAKPOINT_HIT_GRACE_PERIOD_MS = 200
|
|
52
|
+
const BREAKPOINT_SET_GRACE_PERIOD_MS = 200
|
|
50
53
|
const isCucumberWorker = !!process.env.CUCUMBER_WORKER_ID
|
|
51
54
|
|
|
52
55
|
function getTestSuiteTags (testSuiteSpan) {
|
|
@@ -83,6 +86,7 @@ class CucumberPlugin extends CiPlugin {
|
|
|
83
86
|
hasForcedToRunSuites,
|
|
84
87
|
isEarlyFlakeDetectionEnabled,
|
|
85
88
|
isEarlyFlakeDetectionFaulty,
|
|
89
|
+
isQuarantinedTestsEnabled,
|
|
86
90
|
isParallel
|
|
87
91
|
}) => {
|
|
88
92
|
const { isSuitesSkippingEnabled, isCodeCoverageEnabled } = this.libraryConfig || {}
|
|
@@ -109,6 +113,9 @@ class CucumberPlugin extends CiPlugin {
|
|
|
109
113
|
if (isParallel) {
|
|
110
114
|
this.testSessionSpan.setTag(CUCUMBER_IS_PARALLEL, 'true')
|
|
111
115
|
}
|
|
116
|
+
if (isQuarantinedTestsEnabled) {
|
|
117
|
+
this.testSessionSpan.setTag(TEST_MANAGEMENT_ENABLED, 'true')
|
|
118
|
+
}
|
|
112
119
|
|
|
113
120
|
this.testSessionSpan.setTag(TEST_STATUS, status)
|
|
114
121
|
this.testModuleSpan.setTag(TEST_STATUS, status)
|
|
@@ -251,7 +258,12 @@ class CucumberPlugin extends CiPlugin {
|
|
|
251
258
|
const { file, line, stackIndex } = probeInformation
|
|
252
259
|
this.runningTestProbe = { file, line }
|
|
253
260
|
this.testErrorStackIndex = stackIndex
|
|
254
|
-
|
|
261
|
+
const waitUntil = Date.now() + BREAKPOINT_SET_GRACE_PERIOD_MS
|
|
262
|
+
while (Date.now() < waitUntil) {
|
|
263
|
+
// TODO: To avoid a race condition, we should wait until `probeInformation.setProbePromise` has resolved.
|
|
264
|
+
// However, Cucumber doesn't have a mechanism for waiting asyncrounously here, so for now, we'll have to
|
|
265
|
+
// fall back to a fixed syncronous delay.
|
|
266
|
+
}
|
|
255
267
|
}
|
|
256
268
|
}
|
|
257
269
|
span.setTag(TEST_STATUS, 'fail')
|
|
@@ -311,7 +323,8 @@ class CucumberPlugin extends CiPlugin {
|
|
|
311
323
|
errorMessage,
|
|
312
324
|
isNew,
|
|
313
325
|
isEfdRetry,
|
|
314
|
-
isFlakyRetry
|
|
326
|
+
isFlakyRetry,
|
|
327
|
+
isQuarantined
|
|
315
328
|
}) => {
|
|
316
329
|
const span = storage('legacy').getStore().span
|
|
317
330
|
const statusTag = isStep ? 'step.status' : TEST_STATUS
|
|
@@ -340,6 +353,10 @@ class CucumberPlugin extends CiPlugin {
|
|
|
340
353
|
span.setTag(TEST_IS_RETRY, 'true')
|
|
341
354
|
}
|
|
342
355
|
|
|
356
|
+
if (isQuarantined) {
|
|
357
|
+
span.setTag(TEST_MANAGEMENT_IS_QUARANTINED, 'true')
|
|
358
|
+
}
|
|
359
|
+
|
|
343
360
|
span.finish()
|
|
344
361
|
if (!isStep) {
|
|
345
362
|
const spanTags = span.context()._tags
|
|
@@ -33,7 +33,9 @@ const {
|
|
|
33
33
|
TEST_SESSION_NAME,
|
|
34
34
|
TEST_LEVEL_EVENT_TYPES,
|
|
35
35
|
TEST_RETRY_REASON,
|
|
36
|
-
DD_TEST_IS_USER_PROVIDED_SERVICE
|
|
36
|
+
DD_TEST_IS_USER_PROVIDED_SERVICE,
|
|
37
|
+
TEST_MANAGEMENT_IS_QUARANTINED,
|
|
38
|
+
TEST_MANAGEMENT_ENABLED
|
|
37
39
|
} = require('../../dd-trace/src/plugins/util/test')
|
|
38
40
|
const { isMarkedAsUnskippable } = require('../../datadog-plugin-jest/src/util')
|
|
39
41
|
const { ORIGIN_KEY, COMPONENT } = require('../../dd-trace/src/constants')
|
|
@@ -152,6 +154,20 @@ function getKnownTests (tracer, testConfiguration) {
|
|
|
152
154
|
})
|
|
153
155
|
}
|
|
154
156
|
|
|
157
|
+
function getQuarantinedTests (tracer, testConfiguration) {
|
|
158
|
+
return new Promise(resolve => {
|
|
159
|
+
if (!tracer._tracer._exporter?.getQuarantinedTests) {
|
|
160
|
+
return resolve({ err: new Error('Test Optimization was not initialized correctly') })
|
|
161
|
+
}
|
|
162
|
+
tracer._tracer._exporter.getQuarantinedTests(testConfiguration, (err, quarantinedTests) => {
|
|
163
|
+
resolve({
|
|
164
|
+
err,
|
|
165
|
+
quarantinedTests
|
|
166
|
+
})
|
|
167
|
+
})
|
|
168
|
+
})
|
|
169
|
+
}
|
|
170
|
+
|
|
155
171
|
function getSuiteStatus (suiteStats) {
|
|
156
172
|
if (!suiteStats) {
|
|
157
173
|
return 'skip'
|
|
@@ -240,7 +256,8 @@ class CypressPlugin {
|
|
|
240
256
|
earlyFlakeDetectionNumRetries,
|
|
241
257
|
isFlakyTestRetriesEnabled,
|
|
242
258
|
flakyTestRetriesCount,
|
|
243
|
-
isKnownTestsEnabled
|
|
259
|
+
isKnownTestsEnabled,
|
|
260
|
+
isQuarantinedTestsEnabled
|
|
244
261
|
}
|
|
245
262
|
} = libraryConfigurationResponse
|
|
246
263
|
this.isSuitesSkippingEnabled = isSuitesSkippingEnabled
|
|
@@ -251,12 +268,24 @@ class CypressPlugin {
|
|
|
251
268
|
if (isFlakyTestRetriesEnabled) {
|
|
252
269
|
this.cypressConfig.retries.runMode = flakyTestRetriesCount
|
|
253
270
|
}
|
|
271
|
+
this.isQuarantinedTestsEnabled = isQuarantinedTestsEnabled
|
|
254
272
|
}
|
|
255
273
|
return this.cypressConfig
|
|
256
274
|
})
|
|
257
275
|
return this.libraryConfigurationPromise
|
|
258
276
|
}
|
|
259
277
|
|
|
278
|
+
getIsQuarantinedTest (testSuite, testName) {
|
|
279
|
+
return this.quarantinedTests
|
|
280
|
+
?.cypress
|
|
281
|
+
?.suites
|
|
282
|
+
?.[testSuite]
|
|
283
|
+
?.tests
|
|
284
|
+
?.[testName]
|
|
285
|
+
?.properties
|
|
286
|
+
?.quarantined
|
|
287
|
+
}
|
|
288
|
+
|
|
260
289
|
getTestSuiteSpan ({ testSuite, testSuiteAbsolutePath }) {
|
|
261
290
|
const testSuiteSpanMetadata =
|
|
262
291
|
getTestSuiteCommonTags(this.command, this.frameworkVersion, testSuite, TEST_FRAMEWORK_NAME)
|
|
@@ -351,10 +380,6 @@ class CypressPlugin {
|
|
|
351
380
|
})
|
|
352
381
|
}
|
|
353
382
|
|
|
354
|
-
isNewTest (testName, testSuite) {
|
|
355
|
-
return !this.knownTestsByTestSuite?.[testSuite]?.includes(testName)
|
|
356
|
-
}
|
|
357
|
-
|
|
358
383
|
async beforeRun (details) {
|
|
359
384
|
// We need to make sure that the plugin is initialized before running the tests
|
|
360
385
|
// This is for the case where the user has not returned the promise from the init function
|
|
@@ -393,6 +418,19 @@ class CypressPlugin {
|
|
|
393
418
|
}
|
|
394
419
|
}
|
|
395
420
|
|
|
421
|
+
if (this.isQuarantinedTestsEnabled) {
|
|
422
|
+
const quarantinedTestsResponse = await getQuarantinedTests(
|
|
423
|
+
this.tracer,
|
|
424
|
+
this.testConfiguration
|
|
425
|
+
)
|
|
426
|
+
if (quarantinedTestsResponse.err) {
|
|
427
|
+
log.error('Cypress quarantined tests response error', quarantinedTestsResponse.err)
|
|
428
|
+
this.isQuarantinedTestsEnabled = false
|
|
429
|
+
} else {
|
|
430
|
+
this.quarantinedTests = quarantinedTestsResponse.quarantinedTests
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
|
|
396
434
|
// `details.specs` are test files
|
|
397
435
|
details.specs?.forEach(({ absolute, relative }) => {
|
|
398
436
|
const isUnskippableSuite = isMarkedAsUnskippable({ path: absolute })
|
|
@@ -471,6 +509,10 @@ class CypressPlugin {
|
|
|
471
509
|
}
|
|
472
510
|
)
|
|
473
511
|
|
|
512
|
+
if (this.isQuarantinedTestsEnabled) {
|
|
513
|
+
this.testSessionSpan.setTag(TEST_MANAGEMENT_ENABLED, 'true')
|
|
514
|
+
}
|
|
515
|
+
|
|
474
516
|
this.testModuleSpan.finish()
|
|
475
517
|
this.ciVisEvent(TELEMETRY_EVENT_FINISHED, 'module')
|
|
476
518
|
this.testSessionSpan.finish()
|
|
@@ -545,6 +587,13 @@ class CypressPlugin {
|
|
|
545
587
|
if (this.itrCorrelationId) {
|
|
546
588
|
skippedTestSpan.setTag(ITR_CORRELATION_ID, this.itrCorrelationId)
|
|
547
589
|
}
|
|
590
|
+
|
|
591
|
+
const isQuarantined = this.getIsQuarantinedTest(spec.relative, cypressTestName)
|
|
592
|
+
|
|
593
|
+
if (isQuarantined) {
|
|
594
|
+
skippedTestSpan.setTag(TEST_MANAGEMENT_IS_QUARANTINED, 'true')
|
|
595
|
+
}
|
|
596
|
+
|
|
548
597
|
skippedTestSpan.finish()
|
|
549
598
|
})
|
|
550
599
|
|
|
@@ -648,6 +697,7 @@ class CypressPlugin {
|
|
|
648
697
|
})
|
|
649
698
|
const isUnskippable = this.unskippableSuites.includes(testSuite)
|
|
650
699
|
const isForcedToRun = shouldSkip && isUnskippable
|
|
700
|
+
const isQuarantined = this.getIsQuarantinedTest(testSuite, testName)
|
|
651
701
|
|
|
652
702
|
// skip test
|
|
653
703
|
if (shouldSkip && !isUnskippable) {
|
|
@@ -656,6 +706,12 @@ class CypressPlugin {
|
|
|
656
706
|
return { shouldSkip: true }
|
|
657
707
|
}
|
|
658
708
|
|
|
709
|
+
// TODO: I haven't found a way to trick cypress into ignoring a test
|
|
710
|
+
// The way we'll implement quarantine in cypress is by skipping the test altogether
|
|
711
|
+
if (isQuarantined) {
|
|
712
|
+
return { shouldSkip: true }
|
|
713
|
+
}
|
|
714
|
+
|
|
659
715
|
if (!this.activeTestSpan) {
|
|
660
716
|
this.activeTestSpan = this.getTestSpan({
|
|
661
717
|
testName,
|
|
@@ -681,7 +737,8 @@ class CypressPlugin {
|
|
|
681
737
|
testSuiteAbsolutePath,
|
|
682
738
|
testName,
|
|
683
739
|
isNew,
|
|
684
|
-
isEfdRetry
|
|
740
|
+
isEfdRetry,
|
|
741
|
+
isQuarantined
|
|
685
742
|
} = test
|
|
686
743
|
if (coverage && this.isCodeCoverageEnabled && this.tracer._tracer._exporter?.exportCoverage) {
|
|
687
744
|
const coverageFiles = getCoveredFilenamesFromCoverage(coverage)
|
|
@@ -720,6 +777,9 @@ class CypressPlugin {
|
|
|
720
777
|
this.activeTestSpan.setTag(TEST_RETRY_REASON, 'efd')
|
|
721
778
|
}
|
|
722
779
|
}
|
|
780
|
+
if (isQuarantined) {
|
|
781
|
+
this.activeTestSpan.setTag(TEST_MANAGEMENT_IS_QUARANTINED, 'true')
|
|
782
|
+
}
|
|
723
783
|
const finishedTest = {
|
|
724
784
|
testName,
|
|
725
785
|
testStatus,
|