dd-trace 5.99.0 → 5.100.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE-3rdparty.csv +0 -1
- package/package.json +24 -5
- package/packages/datadog-instrumentations/src/cucumber.js +69 -5
- package/packages/datadog-instrumentations/src/express.js +3 -2
- package/packages/datadog-instrumentations/src/helpers/hooks.js +1 -0
- package/packages/datadog-instrumentations/src/hono.js +15 -4
- package/packages/datadog-instrumentations/src/jest.js +89 -63
- package/packages/datadog-instrumentations/src/mocha/main.js +18 -22
- package/packages/datadog-instrumentations/src/mocha/utils.js +114 -96
- package/packages/datadog-instrumentations/src/mocha/worker.js +2 -2
- package/packages/datadog-instrumentations/src/path-to-regexp.js +44 -0
- package/packages/datadog-instrumentations/src/playwright.js +108 -18
- package/packages/datadog-instrumentations/src/router.js +53 -33
- package/packages/datadog-instrumentations/src/vitest.js +76 -30
- package/packages/datadog-plugin-aws-sdk/src/base.js +1 -1
- package/packages/datadog-plugin-aws-sdk/src/services/dynamodb.js +1 -1
- package/packages/datadog-plugin-bullmq/src/consumer.js +3 -2
- package/packages/datadog-plugin-bullmq/src/producer.js +25 -11
- package/packages/datadog-plugin-cypress/src/cypress-plugin.js +32 -9
- package/packages/datadog-plugin-cypress/src/support.js +22 -21
- package/packages/datadog-plugin-dd-trace-api/src/index.js +1 -1
- package/packages/datadog-plugin-graphql/src/utils.js +2 -2
- package/packages/datadog-plugin-grpc/src/client.js +1 -1
- package/packages/datadog-plugin-grpc/src/server.js +1 -1
- package/packages/datadog-plugin-memcached/src/index.js +1 -1
- package/packages/datadog-plugin-mongodb-core/src/index.js +2 -3
- package/packages/datadog-plugin-playwright/src/index.js +6 -0
- package/packages/datadog-plugin-router/src/index.js +13 -0
- package/packages/dd-trace/index.js +4 -3
- package/packages/dd-trace/src/aiguard/sdk.js +2 -2
- package/packages/dd-trace/src/appsec/blocking.js +18 -6
- package/packages/dd-trace/src/appsec/graphql.js +1 -1
- package/packages/dd-trace/src/baggage.js +26 -13
- package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +1 -1
- package/packages/dd-trace/src/config/generated-config-types.d.ts +45 -69
- package/packages/dd-trace/src/config/index.js +13 -12
- package/packages/dd-trace/src/config/normalize-service.js +31 -0
- package/packages/dd-trace/src/config/supported-configurations.json +31 -76
- package/packages/dd-trace/src/debugger/config.js +1 -1
- package/packages/dd-trace/src/dogstatsd.js +5 -8
- package/packages/dd-trace/src/encode/0.4.js +1 -1
- package/packages/dd-trace/src/encode/tags-processors.js +3 -3
- package/packages/dd-trace/src/exporter.js +1 -1
- package/packages/dd-trace/src/git_metadata_tagger.js +1 -1
- package/packages/dd-trace/src/heap_snapshots.js +4 -4
- package/packages/dd-trace/src/llmobs/constants/tags.js +3 -0
- package/packages/dd-trace/src/llmobs/sdk.js +21 -1
- package/packages/dd-trace/src/llmobs/span_processor.js +14 -1
- package/packages/dd-trace/src/llmobs/writers/base.js +7 -1
- package/packages/dd-trace/src/llmobs/writers/spans.js +1 -1
- package/packages/dd-trace/src/openfeature/eval-metrics-hook.js +2 -2
- package/packages/dd-trace/src/opentelemetry/context_manager.js +11 -8
- package/packages/dd-trace/src/opentelemetry/logs/index.js +5 -5
- package/packages/dd-trace/src/opentelemetry/metrics/index.js +6 -6
- package/packages/dd-trace/src/opentelemetry/span-helpers.js +170 -0
- package/packages/dd-trace/src/opentelemetry/span.js +14 -42
- package/packages/dd-trace/src/opentelemetry/trace/otlp_http_trace_exporter.js +1 -1
- package/packages/dd-trace/src/opentelemetry/tracer.js +11 -36
- package/packages/dd-trace/src/opentracing/propagation/text_map.js +44 -23
- package/packages/dd-trace/src/opentracing/propagation/tracestate.js +42 -12
- package/packages/dd-trace/src/opentracing/span.js +4 -3
- package/packages/dd-trace/src/plugin_manager.js +6 -6
- package/packages/dd-trace/src/plugins/log_plugin.js +1 -1
- package/packages/dd-trace/src/plugins/util/ci.js +119 -32
- package/packages/dd-trace/src/plugins/util/test.js +295 -29
- package/packages/dd-trace/src/profiling/ssi-heuristics.js +2 -2
- package/packages/dd-trace/src/propagation-hash/index.js +1 -1
- package/packages/dd-trace/src/proxy.js +9 -9
- package/packages/dd-trace/src/runtime_metrics/runtime_metrics.js +1 -1
- package/packages/dd-trace/src/span_processor.js +1 -1
- package/packages/dd-trace/src/telemetry/telemetry.js +7 -5
- package/packages/dd-trace/src/tracer_metadata.js +1 -1
- package/vendor/dist/path-to-regexp/LICENSE +0 -21
- package/vendor/dist/path-to-regexp/index.js +0 -1
|
@@ -12,7 +12,9 @@ const {
|
|
|
12
12
|
PLAYWRIGHT_WORKER_TRACE_PAYLOAD_CODE,
|
|
13
13
|
getIsFaultyEarlyFlakeDetection,
|
|
14
14
|
DYNAMIC_NAME_RE,
|
|
15
|
-
|
|
15
|
+
recordAttemptToFixExecution,
|
|
16
|
+
logAttemptToFixTestExecution,
|
|
17
|
+
logTestOptimizationSummary,
|
|
16
18
|
} = require('../../dd-trace/src/plugins/util/test')
|
|
17
19
|
const log = require('../../dd-trace/src/log')
|
|
18
20
|
const {
|
|
@@ -76,9 +78,10 @@ let testManagementAttemptToFixRetries = 0
|
|
|
76
78
|
let testManagementTests = {}
|
|
77
79
|
let isImpactedTestsEnabled = false
|
|
78
80
|
let modifiedFiles = {}
|
|
79
|
-
const quarantinedOrDisabledTestsAttemptToFix = []
|
|
80
81
|
let quarantinedButNotAttemptToFixFqns = new Set()
|
|
81
82
|
const newTestsWithDynamicNames = new Set()
|
|
83
|
+
const attemptToFixExecutions = new Map()
|
|
84
|
+
const loggedAttemptToFixTests = new Set()
|
|
82
85
|
let rootDir = ''
|
|
83
86
|
let sessionProjects = []
|
|
84
87
|
|
|
@@ -290,6 +293,32 @@ function testWillRetry (test, testStatus) {
|
|
|
290
293
|
return testStatus === 'fail' && test.results.length <= test.retries
|
|
291
294
|
}
|
|
292
295
|
|
|
296
|
+
function getFinalStatus ({
|
|
297
|
+
isFinalExecution,
|
|
298
|
+
isDisabled,
|
|
299
|
+
isQuarantined,
|
|
300
|
+
isAtrRetry,
|
|
301
|
+
isEfdManagedTest,
|
|
302
|
+
isAttemptToFix,
|
|
303
|
+
hasFailedAllRetries,
|
|
304
|
+
hasFailedAttemptToFixRetries,
|
|
305
|
+
testStatus,
|
|
306
|
+
}) {
|
|
307
|
+
if (!isFinalExecution) {
|
|
308
|
+
return
|
|
309
|
+
}
|
|
310
|
+
if (isDisabled || isQuarantined || testStatus === 'skip') {
|
|
311
|
+
return 'skip'
|
|
312
|
+
}
|
|
313
|
+
if (isAtrRetry || isEfdManagedTest) {
|
|
314
|
+
return hasFailedAllRetries ? 'fail' : 'pass'
|
|
315
|
+
}
|
|
316
|
+
if (isAttemptToFix) {
|
|
317
|
+
return hasFailedAttemptToFixRetries ? 'fail' : 'pass'
|
|
318
|
+
}
|
|
319
|
+
return testStatus
|
|
320
|
+
}
|
|
321
|
+
|
|
293
322
|
function getTestFullname (test) {
|
|
294
323
|
let parent = test.parent
|
|
295
324
|
const names = [test.title]
|
|
@@ -337,6 +366,11 @@ function testBeginHandler (test, browserName, shouldCreateTestSpan) {
|
|
|
337
366
|
// We disable retries by default if attemptToFix is true
|
|
338
367
|
if (getTestProperties(test).attemptToFix) {
|
|
339
368
|
test.retries = 0
|
|
369
|
+
logAttemptToFixTestExecution(
|
|
370
|
+
getTestSuitePath(testSuiteAbsolutePath, rootDir),
|
|
371
|
+
getTestFullname(test),
|
|
372
|
+
loggedAttemptToFixTests
|
|
373
|
+
)
|
|
340
374
|
}
|
|
341
375
|
|
|
342
376
|
// this handles tests that do not go through the worker process (because they're skipped)
|
|
@@ -399,6 +433,20 @@ function testEndHandler ({
|
|
|
399
433
|
|
|
400
434
|
const testProperties = getTestProperties(test)
|
|
401
435
|
|
|
436
|
+
if (testProperties.attemptToFix) {
|
|
437
|
+
test._ddHasFailedAttemptToFixRetries = false
|
|
438
|
+
test._ddHasFailedAllRetries = false
|
|
439
|
+
test._ddHasPassedAttemptToFixRetries = false
|
|
440
|
+
|
|
441
|
+
recordAttemptToFixExecution(attemptToFixExecutions, {
|
|
442
|
+
testSuite: getTestSuitePath(test._requireFile, rootDir),
|
|
443
|
+
testName: getTestFullname(test),
|
|
444
|
+
status: testStatus,
|
|
445
|
+
isDisabled: testProperties.disabled,
|
|
446
|
+
isQuarantined: testProperties.quarantined,
|
|
447
|
+
})
|
|
448
|
+
}
|
|
449
|
+
|
|
402
450
|
if (testStatuses.length === testManagementAttemptToFixRetries + 1 && testProperties.attemptToFix) {
|
|
403
451
|
if (testStatuses.includes('fail')) {
|
|
404
452
|
test._ddHasFailedAttemptToFixRetries = true
|
|
@@ -431,10 +479,26 @@ function testEndHandler ({
|
|
|
431
479
|
if (shouldCreateTestSpan) {
|
|
432
480
|
const testResult = results.at(-1)
|
|
433
481
|
const testCtx = testToCtx.get(test)
|
|
482
|
+
const isEfdManagedTest = (test._ddIsNew || test._ddIsModified) &&
|
|
483
|
+
!test._ddIsAttemptToFix &&
|
|
484
|
+
isEarlyFlakeDetectionEnabled
|
|
434
485
|
const isAtrRetry = testResult?.retry > 0 &&
|
|
435
486
|
isFlakyTestRetriesEnabled &&
|
|
436
487
|
!test._ddIsAttemptToFix &&
|
|
437
488
|
!test._ddIsEfdRetry
|
|
489
|
+
|
|
490
|
+
const finalStatus = getFinalStatus({
|
|
491
|
+
isFinalExecution: !testWillRetry(test, testStatus),
|
|
492
|
+
isDisabled: test._ddIsDisabled,
|
|
493
|
+
isQuarantined: test._ddIsQuarantined,
|
|
494
|
+
isAtrRetry,
|
|
495
|
+
isEfdManagedTest,
|
|
496
|
+
isAttemptToFix: test._ddIsAttemptToFix,
|
|
497
|
+
hasFailedAllRetries: test._ddHasFailedAllRetries,
|
|
498
|
+
hasFailedAttemptToFixRetries: test._ddHasFailedAttemptToFixRetries,
|
|
499
|
+
testStatus,
|
|
500
|
+
})
|
|
501
|
+
|
|
438
502
|
// if there is no testCtx, the skipped test will be created later
|
|
439
503
|
if (testCtx) {
|
|
440
504
|
testFinishCh.publish({
|
|
@@ -454,6 +518,7 @@ function testEndHandler ({
|
|
|
454
518
|
hasFailedAttemptToFixRetries: test._ddHasFailedAttemptToFixRetries,
|
|
455
519
|
isAtrRetry,
|
|
456
520
|
isModified: test._ddIsModified,
|
|
521
|
+
finalStatus,
|
|
457
522
|
...testCtx.currentStore,
|
|
458
523
|
})
|
|
459
524
|
}
|
|
@@ -597,11 +662,12 @@ function dispatcherHookNew (dispatcherExport, runWrapper) {
|
|
|
597
662
|
|
|
598
663
|
const isTimeout = status === 'timedOut'
|
|
599
664
|
const shouldCreateTestSpan = test.expectedStatus === 'skipped'
|
|
665
|
+
const testStatus = STATUS_TO_TEST_STATUS[status]
|
|
600
666
|
testEndHandler(
|
|
601
667
|
{
|
|
602
668
|
test,
|
|
603
669
|
annotations,
|
|
604
|
-
testStatus
|
|
670
|
+
testStatus,
|
|
605
671
|
error: errors && errors[0],
|
|
606
672
|
isTimeout,
|
|
607
673
|
shouldCreateTestSpan,
|
|
@@ -613,6 +679,27 @@ function dispatcherHookNew (dispatcherExport, runWrapper) {
|
|
|
613
679
|
isFlakyTestRetriesEnabled &&
|
|
614
680
|
!test._ddIsAttemptToFix &&
|
|
615
681
|
!test._ddIsEfdRetry
|
|
682
|
+
|
|
683
|
+
// EFD retries (new or modified tests) are implemented as clones with retries=0,
|
|
684
|
+
// so testWillRetry always returns false for them. Instead, we track how many
|
|
685
|
+
// executions have been reported via testsToTestStatuses (updated by testEndHandler
|
|
686
|
+
// above) and mark the execution final once the count reaches the expected total.
|
|
687
|
+
// This mirrors how ATF finality is detected and centralizes the decision in the
|
|
688
|
+
// main process, so workers only need to act on the _ddIsFinalExecution flag.
|
|
689
|
+
const isEfdManagedTest = (test._ddIsNew || test._ddIsModified) &&
|
|
690
|
+
!test._ddIsAttemptToFix &&
|
|
691
|
+
isEarlyFlakeDetectionEnabled
|
|
692
|
+
let isFinalExecution
|
|
693
|
+
if (isEfdManagedTest) {
|
|
694
|
+
const testFqn = getTestFullyQualifiedName(test)
|
|
695
|
+
const efdTestStatuses = testsToTestStatuses.get(testFqn) || []
|
|
696
|
+
isFinalExecution = efdTestStatuses.length === earlyFlakeDetectionNumRetries + 1
|
|
697
|
+
} else if (test._ddIsAttemptToFix) {
|
|
698
|
+
isFinalExecution = !!(test._ddHasPassedAttemptToFixRetries || test._ddHasFailedAttemptToFixRetries)
|
|
699
|
+
} else {
|
|
700
|
+
isFinalExecution = !testWillRetry(test, testStatus)
|
|
701
|
+
}
|
|
702
|
+
|
|
616
703
|
// We want to send the ddProperties to the worker
|
|
617
704
|
worker.process.send({
|
|
618
705
|
type: 'ddProperties',
|
|
@@ -629,6 +716,8 @@ function dispatcherHookNew (dispatcherExport, runWrapper) {
|
|
|
629
716
|
_ddHasFailedAttemptToFixRetries: test._ddHasFailedAttemptToFixRetries,
|
|
630
717
|
_ddIsAtrRetry: isAtrRetry,
|
|
631
718
|
_ddIsModified: test._ddIsModified,
|
|
719
|
+
_ddIsFinalExecution: isFinalExecution,
|
|
720
|
+
_ddIsEfdManagedTest: isEfdManagedTest,
|
|
632
721
|
},
|
|
633
722
|
})
|
|
634
723
|
})
|
|
@@ -766,7 +855,6 @@ function runAllTestsWrapper (runAllTests, playwrightVersion) {
|
|
|
766
855
|
|
|
767
856
|
if (isTestManagementTestsEnabled && sessionStatus === 'failed') {
|
|
768
857
|
let totalFailedTestCount = 0
|
|
769
|
-
let totalAttemptToFixFailedTestCount = 0
|
|
770
858
|
let totalPureQuarantinedFailedTestCount = 0
|
|
771
859
|
|
|
772
860
|
for (const [fqn, testStatuses] of testsToTestStatuses.entries()) {
|
|
@@ -780,16 +868,7 @@ function runAllTestsWrapper (runAllTests, playwrightVersion) {
|
|
|
780
868
|
}
|
|
781
869
|
}
|
|
782
870
|
|
|
783
|
-
|
|
784
|
-
const testFqn = getTestFullyQualifiedName(test)
|
|
785
|
-
const testStatuses = testsToTestStatuses.get(testFqn)
|
|
786
|
-
// Only count as failed if the final status (after retries) is 'fail'
|
|
787
|
-
if (testStatuses && testStatuses[testStatuses.length - 1] === 'fail') {
|
|
788
|
-
totalAttemptToFixFailedTestCount += 1
|
|
789
|
-
}
|
|
790
|
-
}
|
|
791
|
-
|
|
792
|
-
const totalIgnorableFailures = totalAttemptToFixFailedTestCount + totalPureQuarantinedFailedTestCount
|
|
871
|
+
const totalIgnorableFailures = totalPureQuarantinedFailedTestCount
|
|
793
872
|
|
|
794
873
|
if (totalFailedTestCount > 0 && totalFailedTestCount === totalIgnorableFailures) {
|
|
795
874
|
runAllTestsReturn = 'passed'
|
|
@@ -797,7 +876,8 @@ function runAllTestsWrapper (runAllTests, playwrightVersion) {
|
|
|
797
876
|
}
|
|
798
877
|
}
|
|
799
878
|
|
|
800
|
-
|
|
879
|
+
logTestOptimizationSummary({ attemptToFixExecutions, newTestsWithDynamicNames })
|
|
880
|
+
loggedAttemptToFixTests.clear()
|
|
801
881
|
|
|
802
882
|
const flushWait = new Promise(resolve => {
|
|
803
883
|
onDone = resolve
|
|
@@ -975,9 +1055,6 @@ addHook({
|
|
|
975
1055
|
if (!fileSuitesWithManagedTestsToProjects.has(fileSuite)) {
|
|
976
1056
|
fileSuitesWithManagedTestsToProjects.set(fileSuite, getSuiteType(test, 'project'))
|
|
977
1057
|
}
|
|
978
|
-
if (testProperties.disabled || testProperties.quarantined) {
|
|
979
|
-
quarantinedOrDisabledTestsAttemptToFix.push(test)
|
|
980
|
-
}
|
|
981
1058
|
}
|
|
982
1059
|
}
|
|
983
1060
|
applyRetriesToTests(
|
|
@@ -1275,6 +1352,18 @@ addHook({
|
|
|
1275
1352
|
// Wait for the properties to be received
|
|
1276
1353
|
await ddPropertiesPromise
|
|
1277
1354
|
|
|
1355
|
+
const finalStatus = getFinalStatus({
|
|
1356
|
+
isFinalExecution: test._ddIsFinalExecution,
|
|
1357
|
+
isDisabled: test._ddIsDisabled,
|
|
1358
|
+
isQuarantined: test._ddIsQuarantined,
|
|
1359
|
+
isAtrRetry: test._ddIsAtrRetry,
|
|
1360
|
+
isEfdManagedTest: test._ddIsEfdManagedTest,
|
|
1361
|
+
isAttemptToFix: test._ddIsAttemptToFix,
|
|
1362
|
+
hasFailedAllRetries: test._ddHasFailedAllRetries,
|
|
1363
|
+
hasFailedAttemptToFixRetries: test._ddHasFailedAttemptToFixRetries,
|
|
1364
|
+
testStatus: STATUS_TO_TEST_STATUS[status],
|
|
1365
|
+
})
|
|
1366
|
+
|
|
1278
1367
|
testFinishCh.publish({
|
|
1279
1368
|
testStatus: STATUS_TO_TEST_STATUS[status],
|
|
1280
1369
|
steps: steps.filter(step => step.testId === testId),
|
|
@@ -1294,6 +1383,7 @@ addHook({
|
|
|
1294
1383
|
isAtrRetry: test._ddIsAtrRetry,
|
|
1295
1384
|
isModified: test._ddIsModified,
|
|
1296
1385
|
onDone,
|
|
1386
|
+
finalStatus,
|
|
1297
1387
|
...testCtx.currentStore,
|
|
1298
1388
|
})
|
|
1299
1389
|
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
3
|
const METHODS = [...require('http').METHODS.map(v => v.toLowerCase()), 'all']
|
|
4
|
-
const pathToRegExp = require('../../../vendor/dist/path-to-regexp')
|
|
5
4
|
const shimmer = require('../../datadog-shimmer')
|
|
6
5
|
const { addHook, channel } = require('./helpers/instrument')
|
|
6
|
+
const { getCompileToRegexp } = require('./path-to-regexp')
|
|
7
7
|
|
|
8
8
|
const {
|
|
9
9
|
getRouterMountPaths,
|
|
@@ -19,15 +19,22 @@ const {
|
|
|
19
19
|
} = require('./helpers/router-helper')
|
|
20
20
|
|
|
21
21
|
function isFastStar (layer, matchers) {
|
|
22
|
-
return layer.regexp?.fast_star ?? matchers.
|
|
22
|
+
return layer.regexp?.fast_star ?? matchers.hasStarPath
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
function isFastSlash (layer, matchers) {
|
|
26
|
-
return layer.regexp?.fast_slash ?? matchers.
|
|
26
|
+
return layer.regexp?.fast_slash ?? matchers.hasSlashPath
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
// TODO: Move this function to a shared file between Express and Router
|
|
30
|
-
|
|
30
|
+
/**
|
|
31
|
+
* @param {string} name Channel namespace (`apm:<name>:middleware:*`).
|
|
32
|
+
* @param {((pattern: string | RegExp) => RegExp | undefined) | undefined} compile
|
|
33
|
+
* Host-resolved path-to-regexp compile adapter, or undefined when the host
|
|
34
|
+
* instance ships no path-to-regexp. Captured here so each express/router
|
|
35
|
+
* instance keeps the dialect it actually loaded.
|
|
36
|
+
*/
|
|
37
|
+
function createWrapRouterMethod (name, compile) {
|
|
31
38
|
const enterChannel = channel(`apm:${name}:middleware:enter`)
|
|
32
39
|
const exitChannel = channel(`apm:${name}:middleware:exit`)
|
|
33
40
|
const finishChannel = channel(`apm:${name}:middleware:finish`)
|
|
@@ -35,8 +42,6 @@ function createWrapRouterMethod (name) {
|
|
|
35
42
|
const nextChannel = channel(`apm:${name}:middleware:next`)
|
|
36
43
|
const routeAddedChannel = channel(`apm:${name}:route:added`)
|
|
37
44
|
|
|
38
|
-
const regexpCache = Object.create(null)
|
|
39
|
-
|
|
40
45
|
function wrapLayerHandle (layer, original) {
|
|
41
46
|
original._name = original._name || layer.name
|
|
42
47
|
|
|
@@ -55,13 +60,16 @@ function createWrapRouterMethod (name) {
|
|
|
55
60
|
|
|
56
61
|
let route
|
|
57
62
|
|
|
58
|
-
if (matchers) {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
63
|
+
if (matchers?.length && !isFastStar(layer, matchers) && !isFastSlash(layer, matchers)) {
|
|
64
|
+
if (matchers.length === 1) {
|
|
65
|
+
// The host already matched this layer; the lone pattern is the route.
|
|
66
|
+
route = matchers[0].path
|
|
67
|
+
} else {
|
|
68
|
+
for (const matcher of matchers) {
|
|
69
|
+
if (matcher.regex?.test(layer.path)) {
|
|
70
|
+
route = matcher.path
|
|
71
|
+
break
|
|
72
|
+
}
|
|
65
73
|
}
|
|
66
74
|
}
|
|
67
75
|
}
|
|
@@ -124,25 +132,35 @@ function createWrapRouterMethod (name) {
|
|
|
124
132
|
return []
|
|
125
133
|
}
|
|
126
134
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
},
|
|
135
|
-
}))
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
function cachedPathToRegExp (pattern) {
|
|
139
|
-
const maybeCached = regexpCache[pattern]
|
|
140
|
-
if (maybeCached) {
|
|
141
|
-
return maybeCached
|
|
135
|
+
if (arg.length === 1) {
|
|
136
|
+
const pattern = arg[0]
|
|
137
|
+
const path = pattern instanceof RegExp ? `(${pattern})` : pattern
|
|
138
|
+
const matchers = [{ path }]
|
|
139
|
+
matchers.hasStarPath = path === '*'
|
|
140
|
+
matchers.hasSlashPath = path === '/'
|
|
141
|
+
return matchers
|
|
142
142
|
}
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
143
|
+
|
|
144
|
+
// hasStarPath/hasSlashPath cache the lookups isFastStar/isFastSlash
|
|
145
|
+
// would otherwise re-run on every request.
|
|
146
|
+
let hasStarPath = false
|
|
147
|
+
let hasSlashPath = false
|
|
148
|
+
const matchers = arg.map(pattern => {
|
|
149
|
+
const isRegExp = pattern instanceof RegExp
|
|
150
|
+
const path = isRegExp ? `(${pattern})` : pattern
|
|
151
|
+
if (path === '*') {
|
|
152
|
+
hasStarPath = true
|
|
153
|
+
} else if (path === '/') {
|
|
154
|
+
hasSlashPath = true
|
|
155
|
+
}
|
|
156
|
+
return {
|
|
157
|
+
path,
|
|
158
|
+
regex: isRegExp ? pattern : compile?.(pattern),
|
|
159
|
+
}
|
|
160
|
+
})
|
|
161
|
+
matchers.hasStarPath = hasStarPath
|
|
162
|
+
matchers.hasSlashPath = hasSlashPath
|
|
163
|
+
return matchers
|
|
146
164
|
}
|
|
147
165
|
|
|
148
166
|
function wrapMethod (original) {
|
|
@@ -218,9 +236,9 @@ function createWrapRouterMethod (name) {
|
|
|
218
236
|
return wrapMethod
|
|
219
237
|
}
|
|
220
238
|
|
|
221
|
-
const wrapRouterMethod = createWrapRouterMethod('router')
|
|
222
|
-
|
|
223
239
|
addHook({ name: 'router', versions: ['>=1 <2'] }, Router => {
|
|
240
|
+
const wrapRouterMethod = createWrapRouterMethod('router', getCompileToRegexp())
|
|
241
|
+
|
|
224
242
|
shimmer.wrap(Router.prototype, 'use', wrapRouterMethod)
|
|
225
243
|
shimmer.wrap(Router.prototype, 'route', wrapRouterMethod)
|
|
226
244
|
|
|
@@ -230,6 +248,8 @@ addHook({ name: 'router', versions: ['>=1 <2'] }, Router => {
|
|
|
230
248
|
const queryParserReadCh = channel('datadog:query:read:finish')
|
|
231
249
|
|
|
232
250
|
addHook({ name: 'router', versions: ['>=2'] }, Router => {
|
|
251
|
+
const wrapRouterMethod = createWrapRouterMethod('router', getCompileToRegexp())
|
|
252
|
+
|
|
233
253
|
const WrappedRouter = shimmer.wrapFunction(Router, function (originalRouter) {
|
|
234
254
|
return function wrappedMethod () {
|
|
235
255
|
const router = originalRouter.apply(this, arguments)
|
|
@@ -11,8 +11,11 @@ const {
|
|
|
11
11
|
VITEST_WORKER_TRACE_PAYLOAD_CODE,
|
|
12
12
|
VITEST_WORKER_LOGS_PAYLOAD_CODE,
|
|
13
13
|
DYNAMIC_NAME_RE,
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
getTestSuitePath,
|
|
15
|
+
recordAttemptToFixExecution,
|
|
16
|
+
collectTestOptimizationSummariesFromTraces,
|
|
17
|
+
logAttemptToFixTestExecution,
|
|
18
|
+
logTestOptimizationSummary,
|
|
16
19
|
} = require('../../dd-trace/src/plugins/util/test')
|
|
17
20
|
const { addHook, channel } = require('./helpers/instrument')
|
|
18
21
|
|
|
@@ -49,6 +52,7 @@ const codeCoverageReportCh = channel('ci:vitest:coverage-report')
|
|
|
49
52
|
|
|
50
53
|
const taskToCtx = new WeakMap()
|
|
51
54
|
const taskToStatuses = new WeakMap()
|
|
55
|
+
const attemptToFixTaskToStatuses = new WeakMap()
|
|
52
56
|
const originalHookFns = new WeakMap()
|
|
53
57
|
const newTasks = new WeakSet()
|
|
54
58
|
const dynamicNameTasks = new WeakSet()
|
|
@@ -57,6 +61,8 @@ const disabledTasks = new WeakSet()
|
|
|
57
61
|
const quarantinedTasks = new WeakSet()
|
|
58
62
|
const attemptToFixTasks = new WeakSet()
|
|
59
63
|
const modifiedTasks = new WeakSet()
|
|
64
|
+
const attemptToFixExecutions = new Map()
|
|
65
|
+
const loggedAttemptToFixTests = new Set()
|
|
60
66
|
let isRetryReasonEfd = false
|
|
61
67
|
let isRetryReasonAttemptToFix = false
|
|
62
68
|
const switchedStatuses = new WeakSet()
|
|
@@ -255,6 +261,29 @@ function getTestName (task) {
|
|
|
255
261
|
return testName
|
|
256
262
|
}
|
|
257
263
|
|
|
264
|
+
function getFinalAttemptToFixStatus (task, state, isSwitchedStatus, testCtx) {
|
|
265
|
+
if (isSwitchedStatus && attemptToFixTasks.has(task) && testCtx?.status) {
|
|
266
|
+
return testCtx.status
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
return state === 'fail' ? 'fail' : 'pass'
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
function recordFinalAttemptToFixExecution (task, status, providedContext) {
|
|
273
|
+
const statuses = attemptToFixTaskToStatuses.get(task)
|
|
274
|
+
if (statuses && statuses.length <= providedContext.testManagementAttemptToFixRetries) {
|
|
275
|
+
statuses.push(status)
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
recordAttemptToFixExecution(attemptToFixExecutions, {
|
|
279
|
+
testSuite: getTestSuitePath(task.file.filepath, process.cwd()),
|
|
280
|
+
testName: getTestName(task),
|
|
281
|
+
status,
|
|
282
|
+
isDisabled: disabledTasks.has(task),
|
|
283
|
+
isQuarantined: quarantinedTasks.has(task),
|
|
284
|
+
})
|
|
285
|
+
}
|
|
286
|
+
|
|
258
287
|
/**
|
|
259
288
|
* Wraps a function so it runs inside the current test span context.
|
|
260
289
|
* @param {object} task
|
|
@@ -480,7 +509,8 @@ function getFinishWrapper (exitOrClose) {
|
|
|
480
509
|
onFinish,
|
|
481
510
|
})
|
|
482
511
|
|
|
483
|
-
|
|
512
|
+
logTestOptimizationSummary({ attemptToFixExecutions, newTestsWithDynamicNames })
|
|
513
|
+
loggedAttemptToFixTests.clear()
|
|
484
514
|
|
|
485
515
|
await flushPromise
|
|
486
516
|
|
|
@@ -542,7 +572,10 @@ function threadHandler (thread) {
|
|
|
542
572
|
workerProcess.on('message', (message) => {
|
|
543
573
|
if (message.__tinypool_worker_message__ && message.data) {
|
|
544
574
|
if (message.interprocessCode === VITEST_WORKER_TRACE_PAYLOAD_CODE) {
|
|
545
|
-
|
|
575
|
+
collectTestOptimizationSummariesFromTraces(message.data, {
|
|
576
|
+
newTestsWithDynamicNames,
|
|
577
|
+
attemptToFixExecutions,
|
|
578
|
+
})
|
|
546
579
|
workerReportTraceCh.publish(message.data)
|
|
547
580
|
} else if (message.interprocessCode === VITEST_WORKER_LOGS_PAYLOAD_CODE) {
|
|
548
581
|
workerReportLogsCh.publish(message.data)
|
|
@@ -584,7 +617,10 @@ function getWrappedOn (on) {
|
|
|
584
617
|
if (message.type !== 'Buffer' && Array.isArray(message)) {
|
|
585
618
|
const [interprocessCode, data] = message
|
|
586
619
|
if (interprocessCode === VITEST_WORKER_TRACE_PAYLOAD_CODE) {
|
|
587
|
-
|
|
620
|
+
collectTestOptimizationSummariesFromTraces(data, {
|
|
621
|
+
newTestsWithDynamicNames,
|
|
622
|
+
attemptToFixExecutions,
|
|
623
|
+
})
|
|
588
624
|
workerReportTraceCh.publish(data)
|
|
589
625
|
} else if (interprocessCode === VITEST_WORKER_LOGS_PAYLOAD_CODE) {
|
|
590
626
|
workerReportLogsCh.publish(data)
|
|
@@ -658,7 +694,7 @@ function wrapVitestTestRunner (VitestTestRunner) {
|
|
|
658
694
|
isRetryReasonAttemptToFix = task.repeats !== testManagementAttemptToFixRetries
|
|
659
695
|
task.repeats = testManagementAttemptToFixRetries
|
|
660
696
|
attemptToFixTasks.add(task)
|
|
661
|
-
|
|
697
|
+
attemptToFixTaskToStatuses.set(task, [])
|
|
662
698
|
}
|
|
663
699
|
},
|
|
664
700
|
})
|
|
@@ -726,15 +762,17 @@ function wrapVitestTestRunner (VitestTestRunner) {
|
|
|
726
762
|
|
|
727
763
|
if (isTestManagementTestsEnabled) {
|
|
728
764
|
const isAttemptingToFix = attemptToFixTasks.has(task)
|
|
729
|
-
const isDisabled = disabledTasks.has(task)
|
|
730
765
|
const isQuarantined = quarantinedTasks.has(task)
|
|
731
766
|
|
|
732
|
-
if (isAttemptingToFix
|
|
733
|
-
|
|
767
|
+
if (isAttemptingToFix) {
|
|
768
|
+
const statuses = attemptToFixTaskToStatuses.get(task)
|
|
769
|
+
if (task.result.state === 'pass' && statuses?.includes('fail')) {
|
|
734
770
|
switchedStatuses.add(task)
|
|
771
|
+
task.result.state = 'fail'
|
|
735
772
|
}
|
|
736
|
-
|
|
737
|
-
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
if (!isAttemptingToFix && isQuarantined) {
|
|
738
776
|
if (task.result.state === 'fail') {
|
|
739
777
|
switchedStatuses.add(task)
|
|
740
778
|
}
|
|
@@ -822,10 +860,9 @@ function wrapVitestTestRunner (VitestTestRunner) {
|
|
|
822
860
|
|
|
823
861
|
const lastExecutionStatus = task.result.state
|
|
824
862
|
const isAtf = attemptToFixTasks.has(task)
|
|
825
|
-
const isQuarantinedOrDisabledAtf = isAtf && (quarantinedTasks.has(task) || disabledTasks.has(task))
|
|
826
863
|
const shouldTrackStatuses = isEarlyFlakeDetectionEnabled || isAtf
|
|
827
|
-
const shouldFlipStatus = isEarlyFlakeDetectionEnabled ||
|
|
828
|
-
const statuses = taskToStatuses.get(task)
|
|
864
|
+
const shouldFlipStatus = isEarlyFlakeDetectionEnabled || isAtf
|
|
865
|
+
const statuses = isAtf ? attemptToFixTaskToStatuses.get(task) : taskToStatuses.get(task)
|
|
829
866
|
|
|
830
867
|
// These clauses handle task.repeats, whether EFD is enabled or not
|
|
831
868
|
// The only thing that EFD does is to forcefully pass the test if it has passed at least once
|
|
@@ -842,7 +879,7 @@ function wrapVitestTestRunner (VitestTestRunner) {
|
|
|
842
879
|
} else {
|
|
843
880
|
testPassCh.publish({ task, ...ctx.currentStore })
|
|
844
881
|
}
|
|
845
|
-
if (shouldTrackStatuses) {
|
|
882
|
+
if (shouldTrackStatuses && statuses) {
|
|
846
883
|
statuses.push(lastExecutionStatus)
|
|
847
884
|
}
|
|
848
885
|
if (shouldFlipStatus) {
|
|
@@ -855,7 +892,7 @@ function wrapVitestTestRunner (VitestTestRunner) {
|
|
|
855
892
|
}
|
|
856
893
|
}
|
|
857
894
|
} else if (numRepetition === task.repeats) {
|
|
858
|
-
if (shouldTrackStatuses) {
|
|
895
|
+
if (shouldTrackStatuses && statuses) {
|
|
859
896
|
statuses.push(lastExecutionStatus)
|
|
860
897
|
}
|
|
861
898
|
|
|
@@ -893,6 +930,14 @@ function wrapVitestTestRunner (VitestTestRunner) {
|
|
|
893
930
|
}
|
|
894
931
|
taskToCtx.set(task, ctx)
|
|
895
932
|
|
|
933
|
+
if (attemptToFixTasks.has(task)) {
|
|
934
|
+
logAttemptToFixTestExecution(
|
|
935
|
+
getTestSuitePath(task.file.filepath, process.cwd()),
|
|
936
|
+
testName,
|
|
937
|
+
loggedAttemptToFixTests
|
|
938
|
+
)
|
|
939
|
+
}
|
|
940
|
+
|
|
896
941
|
testStartCh.runStores(ctx, () => {})
|
|
897
942
|
|
|
898
943
|
// Wrap the test function so it runs inside the test span context.
|
|
@@ -960,11 +1005,11 @@ function wrapVitestTestRunner (VitestTestRunner) {
|
|
|
960
1005
|
let attemptToFixPassed = false
|
|
961
1006
|
let attemptToFixFailed = false
|
|
962
1007
|
if (attemptToFixTasks.has(task)) {
|
|
963
|
-
const statuses =
|
|
1008
|
+
const statuses = attemptToFixTaskToStatuses.get(task)
|
|
964
1009
|
if (statuses.length === testManagementAttemptToFixRetries) {
|
|
965
|
-
if (statuses.every(status => status === 'pass')) {
|
|
1010
|
+
if (status === 'pass' && statuses.every(status => status === 'pass')) {
|
|
966
1011
|
attemptToFixPassed = true
|
|
967
|
-
} else if (statuses.includes('fail')) {
|
|
1012
|
+
} else if (status === 'fail' || statuses.includes('fail')) {
|
|
968
1013
|
attemptToFixFailed = true
|
|
969
1014
|
}
|
|
970
1015
|
}
|
|
@@ -1165,9 +1210,16 @@ addHook({
|
|
|
1165
1210
|
// We have to trick vitest into thinking that the test has passed
|
|
1166
1211
|
// but we want to report it as failed if it did fail
|
|
1167
1212
|
const isSwitchedStatus = switchedStatuses.has(task)
|
|
1213
|
+
const providedContext = getProvidedContext()
|
|
1168
1214
|
|
|
1169
1215
|
if (result) {
|
|
1170
1216
|
const { state, duration, errors } = result
|
|
1217
|
+
const testError = errors?.[0]
|
|
1218
|
+
if (attemptToFixTasks.has(task)) {
|
|
1219
|
+
const status = getFinalAttemptToFixStatus(task, state, isSwitchedStatus, testCtx)
|
|
1220
|
+
recordFinalAttemptToFixExecution(task, status, providedContext)
|
|
1221
|
+
}
|
|
1222
|
+
|
|
1171
1223
|
if (state === 'skip') { // programmatic skip
|
|
1172
1224
|
testSkipCh.publish({
|
|
1173
1225
|
testName: getTestName(task),
|
|
@@ -1177,24 +1229,19 @@ addHook({
|
|
|
1177
1229
|
})
|
|
1178
1230
|
} else if (state === 'pass' && !isSwitchedStatus) {
|
|
1179
1231
|
if (testCtx) {
|
|
1232
|
+
const isSkippedByTestManagement =
|
|
1233
|
+
!attemptToFixTasks.has(task) && (disabledTasks.has(task) || quarantinedTasks.has(task))
|
|
1180
1234
|
testPassCh.publish({
|
|
1181
1235
|
task,
|
|
1182
|
-
finalStatus:
|
|
1183
|
-
disabledTasks.has(task) || quarantinedTasks.has(task) ? 'skip' : 'pass',
|
|
1236
|
+
finalStatus: isSkippedByTestManagement ? 'skip' : 'pass',
|
|
1184
1237
|
...testCtx.currentStore,
|
|
1185
1238
|
})
|
|
1186
1239
|
}
|
|
1187
1240
|
} else if (state === 'fail' || isSwitchedStatus) {
|
|
1188
|
-
let testError
|
|
1189
|
-
|
|
1190
|
-
if (errors?.length) {
|
|
1191
|
-
testError = errors[0]
|
|
1192
|
-
}
|
|
1193
|
-
|
|
1194
1241
|
let hasFailedAllRetries = false
|
|
1195
1242
|
let attemptToFixFailed = false
|
|
1196
1243
|
if (attemptToFixTasks.has(task)) {
|
|
1197
|
-
const statuses =
|
|
1244
|
+
const statuses = attemptToFixTaskToStatuses.get(task)
|
|
1198
1245
|
if (statuses.includes('fail')) {
|
|
1199
1246
|
attemptToFixFailed = true
|
|
1200
1247
|
}
|
|
@@ -1204,7 +1251,6 @@ addHook({
|
|
|
1204
1251
|
}
|
|
1205
1252
|
|
|
1206
1253
|
// Check if all EFD retries failed
|
|
1207
|
-
const providedContext = getProvidedContext()
|
|
1208
1254
|
const isEfdRetry =
|
|
1209
1255
|
providedContext.isEarlyFlakeDetectionEnabled && (newTasks.has(task) || modifiedTasks.has(task))
|
|
1210
1256
|
if (isEfdRetry) {
|
|
@@ -1232,7 +1278,7 @@ addHook({
|
|
|
1232
1278
|
|
|
1233
1279
|
let finalStatus
|
|
1234
1280
|
if (isSwitchedStatus) {
|
|
1235
|
-
if (disabledTasks.has(task) || quarantinedTasks.has(task)) {
|
|
1281
|
+
if (!attemptToFixTasks.has(task) && (disabledTasks.has(task) || quarantinedTasks.has(task))) {
|
|
1236
1282
|
finalStatus = 'skip'
|
|
1237
1283
|
} else if (isAtrRetry || isEfdRetry) {
|
|
1238
1284
|
finalStatus = hasFailedAllRetries ? 'fail' : 'pass'
|
|
@@ -149,7 +149,7 @@ class BaseAwsSdkPlugin extends ClientPlugin {
|
|
|
149
149
|
}
|
|
150
150
|
this.addResponseTags(span, response)
|
|
151
151
|
|
|
152
|
-
if (this._tracerConfig?.
|
|
152
|
+
if (this._tracerConfig?.DD_TRACE_AWS_ADD_SPAN_POINTERS) {
|
|
153
153
|
this.addSpanPointers(span, response)
|
|
154
154
|
}
|
|
155
155
|
})
|
|
@@ -118,7 +118,7 @@ class DynamoDb extends BaseAwsSdkPlugin {
|
|
|
118
118
|
return this.dynamoPrimaryKeyConfig
|
|
119
119
|
}
|
|
120
120
|
|
|
121
|
-
const configStr = this._tracerConfig?.
|
|
121
|
+
const configStr = this._tracerConfig?.DD_TRACE_DYNAMODB_TABLE_PRIMARY_KEYS
|
|
122
122
|
if (!configStr) {
|
|
123
123
|
log.warn(
|
|
124
124
|
// eslint-disable-next-line @stylistic/max-len
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
+
const log = require('../../dd-trace/src/log')
|
|
3
4
|
const ConsumerPlugin = require('../../dd-trace/src/plugins/consumer')
|
|
4
5
|
const { getMessageSize } = require('../../dd-trace/src/datastreams')
|
|
5
6
|
|
|
@@ -73,8 +74,8 @@ class BullmqConsumerPlugin extends ConsumerPlugin {
|
|
|
73
74
|
job.opts.telemetry.metadata = JSON.stringify(metadata)
|
|
74
75
|
|
|
75
76
|
return ddCarrier
|
|
76
|
-
} catch {
|
|
77
|
-
|
|
77
|
+
} catch (error) {
|
|
78
|
+
log.warn('bullmq: skipping _datadog extract on malformed telemetry.metadata: %s', error.message)
|
|
78
79
|
}
|
|
79
80
|
}
|
|
80
81
|
}
|