dd-trace 5.106.0 → 5.107.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.d.ts +20 -1
- package/package.json +5 -7
- package/packages/datadog-core/src/storage.js +47 -48
- package/packages/datadog-esbuild/index.js +6 -1
- package/packages/datadog-instrumentations/src/ai.js +12 -3
- package/packages/datadog-instrumentations/src/body-parser.js +5 -2
- package/packages/datadog-instrumentations/src/connect.js +3 -2
- package/packages/datadog-instrumentations/src/cookie-parser.js +3 -2
- package/packages/datadog-instrumentations/src/cucumber.js +7 -0
- package/packages/datadog-instrumentations/src/express-mongo-sanitize.js +7 -5
- package/packages/datadog-instrumentations/src/express-session.js +12 -11
- package/packages/datadog-instrumentations/src/express.js +24 -20
- package/packages/datadog-instrumentations/src/fastify.js +18 -6
- package/packages/datadog-instrumentations/src/helpers/openai-ai-guard.js +27 -12
- package/packages/datadog-instrumentations/src/http/client.js +9 -12
- package/packages/datadog-instrumentations/src/http/server.js +30 -16
- package/packages/datadog-instrumentations/src/http2/client.js +15 -12
- package/packages/datadog-instrumentations/src/http2/server.js +15 -8
- package/packages/datadog-instrumentations/src/jest/bail-reporter.js +42 -0
- package/packages/datadog-instrumentations/src/jest.js +143 -73
- package/packages/datadog-instrumentations/src/mocha/main.js +43 -8
- package/packages/datadog-instrumentations/src/mocha/utils.js +128 -17
- package/packages/datadog-instrumentations/src/multer.js +3 -2
- package/packages/datadog-instrumentations/src/mysql2.js +34 -0
- package/packages/datadog-instrumentations/src/net.js +8 -6
- package/packages/datadog-instrumentations/src/openai.js +19 -7
- package/packages/datadog-instrumentations/src/pg.js +19 -0
- package/packages/datadog-instrumentations/src/router.js +12 -10
- package/packages/datadog-instrumentations/src/vitest.js +29 -4
- package/packages/datadog-plugin-aws-sdk/src/base.js +0 -3
- package/packages/datadog-plugin-aws-sdk/src/services/sqs.js +62 -11
- package/packages/datadog-plugin-cucumber/src/index.js +2 -0
- package/packages/datadog-plugin-cypress/src/support.js +31 -1
- package/packages/datadog-plugin-http/src/client.js +0 -3
- package/packages/datadog-plugin-http/src/server.js +11 -1
- package/packages/datadog-plugin-mocha/src/index.js +2 -0
- package/packages/datadog-plugin-pg/src/index.js +10 -0
- package/packages/dd-trace/src/aiguard/index.js +34 -15
- package/packages/dd-trace/src/aiguard/sdk.js +34 -3
- package/packages/dd-trace/src/aiguard/tags.js +6 -0
- package/packages/dd-trace/src/ci-visibility/exporters/agentless/index.js +1 -1
- package/packages/dd-trace/src/config/defaults.js +14 -0
- package/packages/dd-trace/src/config/generated-config-types.d.ts +1 -1
- package/packages/dd-trace/src/config/helper.js +1 -0
- package/packages/dd-trace/src/config/index.js +5 -9
- package/packages/dd-trace/src/config/parsers.js +8 -0
- package/packages/dd-trace/src/config/supported-configurations.json +13 -6
- package/packages/dd-trace/src/crashtracking/crashtracker.js +2 -2
- package/packages/dd-trace/src/datastreams/writer.js +1 -2
- package/packages/dd-trace/src/debugger/config.js +1 -1
- package/packages/dd-trace/src/debugger/devtools_client/config.js +3 -2
- package/packages/dd-trace/src/debugger/index.js +1 -2
- package/packages/dd-trace/src/dogstatsd.js +2 -3
- package/packages/dd-trace/src/encode/0.4.js +49 -41
- package/packages/dd-trace/src/encode/agentless-json.js +5 -1
- package/packages/dd-trace/src/encode/tags-processors.js +14 -0
- package/packages/dd-trace/src/exporters/agent/index.js +1 -2
- package/packages/dd-trace/src/exporters/agentless/index.js +6 -10
- package/packages/dd-trace/src/exporters/common/buffering-exporter.js +1 -2
- package/packages/dd-trace/src/exporters/common/request.js +26 -0
- package/packages/dd-trace/src/exporters/span-stats/index.js +1 -2
- package/packages/dd-trace/src/llmobs/plugins/genai/index.js +4 -0
- package/packages/dd-trace/src/llmobs/plugins/genai/util.js +45 -0
- package/packages/dd-trace/src/llmobs/sdk.js +4 -1
- package/packages/dd-trace/src/llmobs/span_processor.js +17 -1
- package/packages/dd-trace/src/llmobs/tagger.js +5 -3
- package/packages/dd-trace/src/llmobs/util.js +54 -0
- package/packages/dd-trace/src/llmobs/writers/base.js +1 -2
- package/packages/dd-trace/src/llmobs/writers/util.js +1 -2
- package/packages/dd-trace/src/openfeature/writers/base.js +1 -10
- package/packages/dd-trace/src/openfeature/writers/util.js +1 -2
- package/packages/dd-trace/src/opentelemetry/metrics/instruments.js +26 -13
- package/packages/dd-trace/src/opentelemetry/metrics/meter.js +7 -10
- package/packages/dd-trace/src/opentelemetry/metrics/periodic_metric_reader.js +92 -0
- package/packages/dd-trace/src/opentelemetry/trace/otlp_transformer.js +3 -2
- package/packages/dd-trace/src/opentracing/span.js +23 -18
- package/packages/dd-trace/src/opentracing/tracer.js +16 -12
- package/packages/dd-trace/src/plugins/ci_plugin.js +131 -46
- package/packages/dd-trace/src/priority_sampler.js +6 -5
- package/packages/dd-trace/src/profiling/config.js +1 -2
- package/packages/dd-trace/src/proxy.js +13 -10
- package/packages/dd-trace/src/remote_config/index.js +1 -2
- package/packages/dd-trace/src/runtime_metrics/client.js +30 -0
- package/packages/dd-trace/src/runtime_metrics/index.js +12 -2
- package/packages/dd-trace/src/runtime_metrics/otlp_runtime_metrics.js +284 -0
- package/packages/dd-trace/src/runtime_metrics/runtime_metrics.js +2 -11
- package/packages/dd-trace/src/service-naming/source-resolver.js +5 -1
- package/packages/dd-trace/src/span_format.js +33 -25
- package/packages/dd-trace/src/span_stats.js +1 -1
- package/packages/dd-trace/src/startup-log.js +1 -2
- package/packages/dd-trace/src/telemetry/send-data.js +1 -1
- package/packages/dd-trace/src/tracer.js +1 -1
- package/vendor/dist/@apm-js-collab/code-transformer/index.js +2 -2
- package/vendor/dist/shell-quote/index.js +1 -1
- package/packages/dd-trace/src/agent/url.js +0 -28
- package/scripts/preinstall.js +0 -34
|
@@ -86,7 +86,10 @@ const CHILD_MESSAGE_CALL = 1
|
|
|
86
86
|
|
|
87
87
|
// Maximum time we'll wait for the tracer to flush
|
|
88
88
|
const FLUSH_TIMEOUT = 10_000
|
|
89
|
+
const JEST_SESSION_STATE = Symbol.for('dd-trace:jest:session')
|
|
90
|
+
const JEST_BAIL_REPORTER_PATH = require.resolve('./jest/bail-reporter')
|
|
89
91
|
const isJestWorker = !!getEnvironmentVariable('JEST_WORKER_ID')
|
|
92
|
+
const jestSessionState = globalThis[JEST_SESSION_STATE] || (globalThis[JEST_SESSION_STATE] = {})
|
|
90
93
|
|
|
91
94
|
// https://github.com/jestjs/jest/blob/41f842a46bb2691f828c3a5f27fc1d6290495b82/packages/jest-circus/src/types.ts#L9C8-L9C54
|
|
92
95
|
const RETRY_TIMES = Symbol.for('RETRY_TIMES')
|
|
@@ -166,6 +169,7 @@ const MINIMUM_JEST_COVERAGE_BACKFILL_VERSION = '>=28.0.0'
|
|
|
166
169
|
const atrSuppressedErrors = new Map()
|
|
167
170
|
let hasWarnedDeprecatedJestVersion = false
|
|
168
171
|
let isJestCoverageBackfillSupported = false
|
|
172
|
+
let hasFinishedTestSession = false
|
|
169
173
|
|
|
170
174
|
// Track quarantined tests whose errors were suppressed, keyed by "suite › testName"
|
|
171
175
|
const quarantinedFailingTests = new Set()
|
|
@@ -1218,6 +1222,25 @@ function getCoverageBackfillRequire (CoverageReporter) {
|
|
|
1218
1222
|
return require
|
|
1219
1223
|
}
|
|
1220
1224
|
|
|
1225
|
+
function addDatadogBailReporter (globalConfig) {
|
|
1226
|
+
if (!globalConfig.bail) return globalConfig
|
|
1227
|
+
|
|
1228
|
+
const reporters = globalConfig.reporters || [['default', {}]]
|
|
1229
|
+
for (const [reporter] of reporters) {
|
|
1230
|
+
if (reporter === JEST_BAIL_REPORTER_PATH) {
|
|
1231
|
+
return globalConfig
|
|
1232
|
+
}
|
|
1233
|
+
}
|
|
1234
|
+
|
|
1235
|
+
return {
|
|
1236
|
+
...globalConfig,
|
|
1237
|
+
reporters: [
|
|
1238
|
+
...reporters,
|
|
1239
|
+
[JEST_BAIL_REPORTER_PATH, {}],
|
|
1240
|
+
],
|
|
1241
|
+
}
|
|
1242
|
+
}
|
|
1243
|
+
|
|
1221
1244
|
function getTestContexts (tests) {
|
|
1222
1245
|
if (!tests?.length) return
|
|
1223
1246
|
|
|
@@ -1283,6 +1306,115 @@ function applySkippedCoverageToJestCoverageMap (coverageMap, rootDir) {
|
|
|
1283
1306
|
)
|
|
1284
1307
|
}
|
|
1285
1308
|
|
|
1309
|
+
function getSessionFinishError (results) {
|
|
1310
|
+
const numFailedTestSuites = results?.numFailedTestSuites || 0
|
|
1311
|
+
const numFailedTests = results?.numFailedTests || 0
|
|
1312
|
+
|
|
1313
|
+
return new Error(`Failed test suites: ${numFailedTestSuites}. Failed tests: ${numFailedTests}`)
|
|
1314
|
+
}
|
|
1315
|
+
|
|
1316
|
+
function getTestSessionCoveragePayload (results, fallbackRootDir) {
|
|
1317
|
+
const payload = {}
|
|
1318
|
+
if (!shouldReportCodeCoverageLinesPct()) return payload
|
|
1319
|
+
|
|
1320
|
+
try {
|
|
1321
|
+
const coverageMap = results?.coverageMap || lastCoverageMap
|
|
1322
|
+
const coverageRootDir = lastCoverageMapRootDir ||
|
|
1323
|
+
repositoryRoot ||
|
|
1324
|
+
fallbackRootDir ||
|
|
1325
|
+
process.cwd()
|
|
1326
|
+
if (isSuitesSkipped) {
|
|
1327
|
+
applySkippedCoverageToJestCoverageMap(coverageMap, coverageRootDir)
|
|
1328
|
+
}
|
|
1329
|
+
payload.testCodeCoverageLinesTotal = getTestCoverageLinesPercentage(
|
|
1330
|
+
coverageMap,
|
|
1331
|
+
undefined,
|
|
1332
|
+
coverageRootDir
|
|
1333
|
+
)
|
|
1334
|
+
if (isTiaCoverageBackfillEnabled()) {
|
|
1335
|
+
payload.testSessionCoverageFiles = getExecutableFilesFromCoverage(coverageMap).map(({ filename, bitmap }) => ({
|
|
1336
|
+
filename: getTestSuitePath(filename, coverageRootDir),
|
|
1337
|
+
bitmap,
|
|
1338
|
+
}))
|
|
1339
|
+
}
|
|
1340
|
+
} catch {
|
|
1341
|
+
// ignore errors
|
|
1342
|
+
}
|
|
1343
|
+
|
|
1344
|
+
return payload
|
|
1345
|
+
}
|
|
1346
|
+
|
|
1347
|
+
function getNumBailFailures (results) {
|
|
1348
|
+
const numFailedTests = results?.numFailedTests || 0
|
|
1349
|
+
const numFailedSuites = results?.numRuntimeErrorTestSuites === undefined
|
|
1350
|
+
? (numFailedTests === 0 ? results?.numFailedTestSuites || 0 : 0)
|
|
1351
|
+
: results.numRuntimeErrorTestSuites
|
|
1352
|
+
|
|
1353
|
+
return numFailedTests + numFailedSuites
|
|
1354
|
+
}
|
|
1355
|
+
|
|
1356
|
+
function shouldFinishBailTestSession (globalConfig, results) {
|
|
1357
|
+
return !!globalConfig?.bail && getNumBailFailures(results) >= globalConfig.bail
|
|
1358
|
+
}
|
|
1359
|
+
|
|
1360
|
+
async function waitForTestSessionFinish (payload) {
|
|
1361
|
+
if (!testSessionFinishCh.hasSubscribers || hasFinishedTestSession) return
|
|
1362
|
+
|
|
1363
|
+
hasFinishedTestSession = true
|
|
1364
|
+
|
|
1365
|
+
let timeoutId
|
|
1366
|
+
|
|
1367
|
+
const flushPromise = new Promise((resolve) => {
|
|
1368
|
+
payload.onDone = () => {
|
|
1369
|
+
clearTimeout(timeoutId)
|
|
1370
|
+
resolve()
|
|
1371
|
+
}
|
|
1372
|
+
})
|
|
1373
|
+
|
|
1374
|
+
const timeoutPromise = new Promise((resolve) => {
|
|
1375
|
+
timeoutId = realSetTimeout(() => {
|
|
1376
|
+
resolve('timeout')
|
|
1377
|
+
}, FLUSH_TIMEOUT)
|
|
1378
|
+
timeoutId.unref?.()
|
|
1379
|
+
})
|
|
1380
|
+
|
|
1381
|
+
testSessionFinishCh.publish(payload)
|
|
1382
|
+
|
|
1383
|
+
const waitingResult = await Promise.race([flushPromise, timeoutPromise])
|
|
1384
|
+
|
|
1385
|
+
if (waitingResult === 'timeout') {
|
|
1386
|
+
log.error('Timeout waiting for the tracer to flush')
|
|
1387
|
+
}
|
|
1388
|
+
}
|
|
1389
|
+
|
|
1390
|
+
function getTestSessionFinishPayload (status, error, extra = {}) {
|
|
1391
|
+
return {
|
|
1392
|
+
status,
|
|
1393
|
+
isSuitesSkipped,
|
|
1394
|
+
isSuitesSkippingEnabled,
|
|
1395
|
+
isCodeCoverageEnabled,
|
|
1396
|
+
isCoverageReportUploadEnabled,
|
|
1397
|
+
numSkippedSuites,
|
|
1398
|
+
hasUnskippableSuites,
|
|
1399
|
+
hasForcedToRunSuites,
|
|
1400
|
+
error,
|
|
1401
|
+
isEarlyFlakeDetectionEnabled,
|
|
1402
|
+
isEarlyFlakeDetectionFaulty,
|
|
1403
|
+
isTestManagementTestsEnabled,
|
|
1404
|
+
...extra,
|
|
1405
|
+
}
|
|
1406
|
+
}
|
|
1407
|
+
|
|
1408
|
+
async function finishBailTestSession (results, fallbackRootDir) {
|
|
1409
|
+
await waitForTestSessionFinish(getTestSessionFinishPayload(
|
|
1410
|
+
'fail',
|
|
1411
|
+
getSessionFinishError(results),
|
|
1412
|
+
getTestSessionCoveragePayload(results, fallbackRootDir)
|
|
1413
|
+
))
|
|
1414
|
+
}
|
|
1415
|
+
|
|
1416
|
+
jestSessionState.finishBailTestSession = finishBailTestSession
|
|
1417
|
+
|
|
1286
1418
|
function reporterDispatcherWrapper (reporterDispatcherPackage) {
|
|
1287
1419
|
const ReporterDispatcher = reporterDispatcherPackage.default ?? reporterDispatcherPackage
|
|
1288
1420
|
if (ReporterDispatcher?.prototype?.onRunComplete) {
|
|
@@ -1351,7 +1483,11 @@ function wrapCoverageReporter (CoverageReporter, hookMeta) {
|
|
|
1351
1483
|
}
|
|
1352
1484
|
lastCoverageMap = coverageMap
|
|
1353
1485
|
lastCoverageMapRootDir = rootDir
|
|
1354
|
-
|
|
1486
|
+
const result = await onRunComplete.call(this, coverageContexts, results)
|
|
1487
|
+
if (shouldFinishBailTestSession(this._globalConfig, results)) {
|
|
1488
|
+
await finishBailTestSession(results, rootDir)
|
|
1489
|
+
}
|
|
1490
|
+
return result
|
|
1355
1491
|
})
|
|
1356
1492
|
}
|
|
1357
1493
|
|
|
@@ -1462,12 +1598,12 @@ function getCliWrapper (isNewJestVersion) {
|
|
|
1462
1598
|
)
|
|
1463
1599
|
}
|
|
1464
1600
|
return shimmer.wrap(cli, 'runCLI', runCLI => async function () {
|
|
1465
|
-
let onDone
|
|
1466
1601
|
if (!libraryConfigurationCh.hasSubscribers) {
|
|
1467
1602
|
return runCLI.apply(this, arguments)
|
|
1468
1603
|
}
|
|
1469
1604
|
|
|
1470
1605
|
resetSuiteSkippingRunState()
|
|
1606
|
+
hasFinishedTestSession = false
|
|
1471
1607
|
|
|
1472
1608
|
try {
|
|
1473
1609
|
const { err, libraryConfig } = await getChannelPromise(libraryConfigurationCh, {
|
|
@@ -1576,7 +1712,6 @@ function getCliWrapper (isNewJestVersion) {
|
|
|
1576
1712
|
|
|
1577
1713
|
const {
|
|
1578
1714
|
results: {
|
|
1579
|
-
coverageMap: resultCoverageMap,
|
|
1580
1715
|
numFailedTestSuites,
|
|
1581
1716
|
numFailedTests,
|
|
1582
1717
|
numRuntimeErrorTestSuites = 0,
|
|
@@ -1591,36 +1726,6 @@ function getCliWrapper (isNewJestVersion) {
|
|
|
1591
1726
|
const hasRunLevelFailure = runExecError != null || wasInterrupted === true
|
|
1592
1727
|
const mustNotFlipSuccess = hasSuiteLevelFailures || hasRunLevelFailure
|
|
1593
1728
|
|
|
1594
|
-
let testCodeCoverageLinesTotal
|
|
1595
|
-
let testSessionCoverageFiles
|
|
1596
|
-
const shouldReportTestSessionCoverage = isTiaCoverageBackfillEnabled()
|
|
1597
|
-
|
|
1598
|
-
if (shouldReportCodeCoverageLinesPct()) {
|
|
1599
|
-
try {
|
|
1600
|
-
const coverageMap = resultCoverageMap || lastCoverageMap
|
|
1601
|
-
const coverageRootDir = lastCoverageMapRootDir ||
|
|
1602
|
-
repositoryRoot ||
|
|
1603
|
-
result.globalConfig?.rootDir ||
|
|
1604
|
-
process.cwd()
|
|
1605
|
-
if (isSuitesSkipped) {
|
|
1606
|
-
applySkippedCoverageToJestCoverageMap(coverageMap, coverageRootDir)
|
|
1607
|
-
}
|
|
1608
|
-
testCodeCoverageLinesTotal = getTestCoverageLinesPercentage(
|
|
1609
|
-
coverageMap,
|
|
1610
|
-
undefined,
|
|
1611
|
-
coverageRootDir
|
|
1612
|
-
)
|
|
1613
|
-
if (shouldReportTestSessionCoverage) {
|
|
1614
|
-
testSessionCoverageFiles = getExecutableFilesFromCoverage(coverageMap).map(({ filename, bitmap }) => ({
|
|
1615
|
-
filename: getTestSuitePath(filename, coverageRootDir),
|
|
1616
|
-
bitmap,
|
|
1617
|
-
}))
|
|
1618
|
-
}
|
|
1619
|
-
} catch {
|
|
1620
|
-
// ignore errors
|
|
1621
|
-
}
|
|
1622
|
-
}
|
|
1623
|
-
|
|
1624
1729
|
/**
|
|
1625
1730
|
* If Early Flake Detection (EFD) is enabled the logic is as follows:
|
|
1626
1731
|
* - If all attempts for a test are failing, the test has failed and we will let the test process fail.
|
|
@@ -1774,46 +1879,9 @@ function getCliWrapper (isNewJestVersion) {
|
|
|
1774
1879
|
error = new Error(`Failed test suites: ${numFailedTestSuites}. Failed tests: ${numFailedTests}`)
|
|
1775
1880
|
}
|
|
1776
1881
|
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
const flushPromise = new Promise((resolve) => {
|
|
1781
|
-
onDone = () => {
|
|
1782
|
-
clearTimeout(timeoutId)
|
|
1783
|
-
resolve()
|
|
1784
|
-
}
|
|
1785
|
-
})
|
|
1786
|
-
|
|
1787
|
-
const timeoutPromise = new Promise((resolve) => {
|
|
1788
|
-
timeoutId = realSetTimeout(() => {
|
|
1789
|
-
resolve('timeout')
|
|
1790
|
-
}, FLUSH_TIMEOUT)
|
|
1791
|
-
timeoutId.unref?.()
|
|
1792
|
-
})
|
|
1793
|
-
|
|
1794
|
-
testSessionFinishCh.publish({
|
|
1795
|
-
status,
|
|
1796
|
-
isSuitesSkipped,
|
|
1797
|
-
isSuitesSkippingEnabled,
|
|
1798
|
-
isCodeCoverageEnabled,
|
|
1799
|
-
isCoverageReportUploadEnabled,
|
|
1800
|
-
testCodeCoverageLinesTotal,
|
|
1801
|
-
testSessionCoverageFiles,
|
|
1802
|
-
numSkippedSuites,
|
|
1803
|
-
hasUnskippableSuites,
|
|
1804
|
-
hasForcedToRunSuites,
|
|
1805
|
-
error,
|
|
1806
|
-
isEarlyFlakeDetectionEnabled,
|
|
1807
|
-
isEarlyFlakeDetectionFaulty,
|
|
1808
|
-
isTestManagementTestsEnabled,
|
|
1809
|
-
onDone,
|
|
1810
|
-
})
|
|
1811
|
-
|
|
1812
|
-
const waitingResult = await Promise.race([flushPromise, timeoutPromise])
|
|
1813
|
-
|
|
1814
|
-
if (waitingResult === 'timeout') {
|
|
1815
|
-
log.error('Timeout waiting for the tracer to flush')
|
|
1816
|
-
}
|
|
1882
|
+
await waitForTestSessionFinish(getTestSessionFinishPayload(status, error, {
|
|
1883
|
+
...getTestSessionCoveragePayload(result.results, result.globalConfig?.rootDir),
|
|
1884
|
+
}))
|
|
1817
1885
|
|
|
1818
1886
|
if (codeCoverageReportCh.hasSubscribers) {
|
|
1819
1887
|
const rootDir = result.globalConfig?.rootDir || process.cwd()
|
|
@@ -2100,6 +2168,8 @@ function configureTestEnvironment (readConfigsResult) {
|
|
|
2100
2168
|
log.warn("Jest's '--forceExit' flag has been passed. This may cause loss of data.")
|
|
2101
2169
|
}
|
|
2102
2170
|
|
|
2171
|
+
readConfigsResult.globalConfig = addDatadogBailReporter(readConfigsResult.globalConfig)
|
|
2172
|
+
|
|
2103
2173
|
if (isSuitesSkippingEnabled) {
|
|
2104
2174
|
// If suite skipping is enabled, we pass `passWithNoTests` in case every test gets skipped.
|
|
2105
2175
|
const globalConfig = {
|
|
@@ -24,6 +24,7 @@ const {
|
|
|
24
24
|
collectTestOptimizationSummariesFromTraces,
|
|
25
25
|
logTestOptimizationSummary,
|
|
26
26
|
getTestOptimizationRequestResults,
|
|
27
|
+
isModifiedTest,
|
|
27
28
|
} = require('../../../dd-trace/src/plugins/util/test')
|
|
28
29
|
|
|
29
30
|
const {
|
|
@@ -39,6 +40,7 @@ const {
|
|
|
39
40
|
getOnPendingHandler,
|
|
40
41
|
testFileToSuiteCtx,
|
|
41
42
|
newTests,
|
|
43
|
+
efdTests,
|
|
42
44
|
testsQuarantined,
|
|
43
45
|
getTestFullName,
|
|
44
46
|
getRunTestsWrapper,
|
|
@@ -191,6 +193,23 @@ function getCoverageRootDir () {
|
|
|
191
193
|
return config.repositoryRoot || process.cwd()
|
|
192
194
|
}
|
|
193
195
|
|
|
196
|
+
/**
|
|
197
|
+
* Recomputes whether a parallel worker result belongs to a modified suite.
|
|
198
|
+
*
|
|
199
|
+
* In parallel mode, `_ddIsModified` is set on Mocha Test objects inside the worker.
|
|
200
|
+
* The main process receives `Test.prototype.serialize()` output for test events,
|
|
201
|
+
* and that fixed serialization drops custom properties. We still need modified-test
|
|
202
|
+
* bookkeeping in the main process for EFD failure suppression, so infer it again
|
|
203
|
+
* from the suite path.
|
|
204
|
+
*
|
|
205
|
+
* @param {string} testSuiteAbsolutePath
|
|
206
|
+
* @returns {boolean}
|
|
207
|
+
*/
|
|
208
|
+
function isModifiedTestSuite (testSuiteAbsolutePath) {
|
|
209
|
+
const testPath = getTestSuitePath(testSuiteAbsolutePath, getCoverageRootDir())
|
|
210
|
+
return isModifiedTest(testPath, null, null, config.modifiedFiles, 'mocha')
|
|
211
|
+
}
|
|
212
|
+
|
|
194
213
|
function shouldReportCodeCoverageLinesPct (hasBackfilledCoverage) {
|
|
195
214
|
return !isSuitesSkipped || hasBackfilledCoverage
|
|
196
215
|
}
|
|
@@ -252,12 +271,13 @@ function getOnEndHandler (isParallel) {
|
|
|
252
271
|
* The rationale behind is the following: you may still be able to block your CI pipeline by gating
|
|
253
272
|
* on flakiness (the test will be considered flaky), but you may choose to unblock the pipeline too.
|
|
254
273
|
*/
|
|
255
|
-
for (const tests of Object.values(
|
|
256
|
-
const
|
|
257
|
-
const
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
this.failures -=
|
|
274
|
+
for (const tests of Object.values(efdTests)) {
|
|
275
|
+
const failingEfdTests = tests.filter(test => isTestFailed(test))
|
|
276
|
+
const areAllEfdTestsFailing = failingEfdTests.length === tests.length
|
|
277
|
+
const nonQuarantinedFailingEfdTests = failingEfdTests.filter(test => !testsQuarantined.has(test))
|
|
278
|
+
if (nonQuarantinedFailingEfdTests.length && !areAllEfdTestsFailing) {
|
|
279
|
+
this.stats.failures -= nonQuarantinedFailingEfdTests.length
|
|
280
|
+
this.failures -= nonQuarantinedFailingEfdTests.length
|
|
261
281
|
}
|
|
262
282
|
}
|
|
263
283
|
}
|
|
@@ -1139,10 +1159,15 @@ addHook({
|
|
|
1139
1159
|
.events
|
|
1140
1160
|
.filter(event => event.eventName === 'test end')
|
|
1141
1161
|
.map(event => event.data)
|
|
1162
|
+
const isModified = config.isImpactedTestsEnabled && isModifiedTestSuite(testSuiteAbsolutePath)
|
|
1142
1163
|
|
|
1143
1164
|
for (const test of tests) {
|
|
1165
|
+
const testProperties = getTestProperties(test, config.testManagementTests)
|
|
1166
|
+
const isAttemptToFix = config.isTestManagementTestsEnabled && testProperties.isAttemptToFix
|
|
1167
|
+
|
|
1144
1168
|
// `newTests` is filled in the worker process, so we need to use the test results to fill it here too.
|
|
1145
|
-
|
|
1169
|
+
const isNew = config.isKnownTestsEnabled && isNewTest(test, config.knownTests)
|
|
1170
|
+
if (isNew) {
|
|
1146
1171
|
const testFullName = getTestFullName(test)
|
|
1147
1172
|
const tests = newTests[testFullName]
|
|
1148
1173
|
|
|
@@ -1152,8 +1177,18 @@ addHook({
|
|
|
1152
1177
|
newTests[testFullName] = [test]
|
|
1153
1178
|
}
|
|
1154
1179
|
}
|
|
1180
|
+
// `efdTests` is filled in the worker process, so we need to use the test results to fill it here too.
|
|
1181
|
+
if (!isAttemptToFix && (isNew || isModified)) {
|
|
1182
|
+
const testFullName = getTestFullName(test)
|
|
1183
|
+
const tests = efdTests[testFullName]
|
|
1184
|
+
|
|
1185
|
+
if (tests) {
|
|
1186
|
+
tests.push(test)
|
|
1187
|
+
} else {
|
|
1188
|
+
efdTests[testFullName] = [test]
|
|
1189
|
+
}
|
|
1190
|
+
}
|
|
1155
1191
|
// `testsQuarantined` is filled in the worker process, so we need to use the test results to fill it here too.
|
|
1156
|
-
const testProperties = getTestProperties(test, config.testManagementTests)
|
|
1157
1192
|
if (config.isTestManagementTestsEnabled && testProperties.isQuarantined && !testProperties.isAttemptToFix) {
|
|
1158
1193
|
testsQuarantined.add(test)
|
|
1159
1194
|
}
|
|
@@ -35,6 +35,7 @@ const testToStartLine = new WeakMap()
|
|
|
35
35
|
const testFileToSuiteCtx = new Map()
|
|
36
36
|
const wrappedFunctions = new WeakSet()
|
|
37
37
|
const newTests = {}
|
|
38
|
+
const efdTests = {}
|
|
38
39
|
const newTestsWithDynamicNames = new Set()
|
|
39
40
|
const testsAttemptToFix = new Set()
|
|
40
41
|
const testsQuarantined = new Set()
|
|
@@ -129,14 +130,61 @@ function wrapOriginalEfdTest (test, slowTestRetries) {
|
|
|
129
130
|
})
|
|
130
131
|
}
|
|
131
132
|
|
|
133
|
+
/**
|
|
134
|
+
* Disables Mocha's native retry mechanism for Datadog-managed clone retries.
|
|
135
|
+
* @param {{ retries?: (count: number) => void }} test
|
|
136
|
+
* @returns {void}
|
|
137
|
+
*/
|
|
138
|
+
function disableMochaRetries (test) {
|
|
139
|
+
if (typeof test.retries === 'function') {
|
|
140
|
+
test.retries(0)
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Checks whether a runnable belongs to a Datadog-managed clone retry feature.
|
|
146
|
+
* @param {{
|
|
147
|
+
* _ddIsAttemptToFix?: boolean,
|
|
148
|
+
* _ddIsEfdRetry?: boolean,
|
|
149
|
+
* _ddIsModified?: boolean,
|
|
150
|
+
* _ddIsNew?: boolean
|
|
151
|
+
* }} test
|
|
152
|
+
* @param {{ isEarlyFlakeDetectionEnabled?: boolean }} config
|
|
153
|
+
* @returns {boolean}
|
|
154
|
+
*/
|
|
155
|
+
function isDatadogManagedRetryTest (test, config) {
|
|
156
|
+
return test._ddIsAttemptToFix ||
|
|
157
|
+
test._ddIsEfdRetry ||
|
|
158
|
+
(config.isEarlyFlakeDetectionEnabled && (test._ddIsNew || test._ddIsModified))
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Checks whether a runnable belongs to an Early Flake Detection execution.
|
|
163
|
+
* @param {{
|
|
164
|
+
* _ddIsAttemptToFix?: boolean,
|
|
165
|
+
* _ddIsEfdRetry?: boolean,
|
|
166
|
+
* _ddIsModified?: boolean,
|
|
167
|
+
* _ddIsNew?: boolean
|
|
168
|
+
* }} test
|
|
169
|
+
* @param {{ isEarlyFlakeDetectionEnabled?: boolean }} config
|
|
170
|
+
* @returns {boolean}
|
|
171
|
+
*/
|
|
172
|
+
function isEarlyFlakeDetectionTest (test, config) {
|
|
173
|
+
return !test._ddIsAttemptToFix &&
|
|
174
|
+
config.isEarlyFlakeDetectionEnabled &&
|
|
175
|
+
(test._ddIsEfdRetry || test._ddIsNew || test._ddIsModified)
|
|
176
|
+
}
|
|
177
|
+
|
|
132
178
|
function retryTest (test, numRetries, tags, slowTestRetries) {
|
|
133
179
|
const suite = test.parent
|
|
134
180
|
const isEfdRetry = tags.includes('_ddIsEfdRetry')
|
|
181
|
+
disableMochaRetries(test)
|
|
135
182
|
if (isEfdRetry) {
|
|
136
183
|
wrapOriginalEfdTest(test, slowTestRetries)
|
|
137
184
|
}
|
|
138
185
|
for (let retryIndex = 0; retryIndex < numRetries; retryIndex++) {
|
|
139
186
|
const clonedTest = test.clone()
|
|
187
|
+
disableMochaRetries(clonedTest)
|
|
140
188
|
suite.addTest(clonedTest)
|
|
141
189
|
if (isEfdRetry) {
|
|
142
190
|
clonedTest._ddEfdRetryIndex = retryIndex + 1
|
|
@@ -205,6 +253,21 @@ function getTestFullName (test) {
|
|
|
205
253
|
return `mocha.${getTestSuitePath(test.file, process.cwd())}.${test.fullTitle()}`
|
|
206
254
|
}
|
|
207
255
|
|
|
256
|
+
/**
|
|
257
|
+
* Records every attempt for a test grouped by its full test name.
|
|
258
|
+
* @param {Record<string, Array<{ file: string, fullTitle: () => string }>>} testsByFullName
|
|
259
|
+
* @param {{ file: string, fullTitle: () => string }} test
|
|
260
|
+
* @returns {void}
|
|
261
|
+
*/
|
|
262
|
+
function recordTestAttempt (testsByFullName, test) {
|
|
263
|
+
const testFullName = getTestFullName(test)
|
|
264
|
+
if (testsByFullName[testFullName]) {
|
|
265
|
+
testsByFullName[testFullName].push(test)
|
|
266
|
+
} else {
|
|
267
|
+
testsByFullName[testFullName] = [test]
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
208
271
|
function getTestStatus (test) {
|
|
209
272
|
if (test.isPending()) {
|
|
210
273
|
return 'skip'
|
|
@@ -230,14 +293,38 @@ function getTestContext (test) {
|
|
|
230
293
|
return testToContext.get(key)
|
|
231
294
|
}
|
|
232
295
|
|
|
296
|
+
/**
|
|
297
|
+
* Copies Test Management metadata from Mocha's original runnable to its native retry clone.
|
|
298
|
+
* @param {{
|
|
299
|
+
* _retriedTest?: {
|
|
300
|
+
* _ddIsDisabled?: boolean,
|
|
301
|
+
* _ddIsQuarantined?: boolean
|
|
302
|
+
* },
|
|
303
|
+
* _ddIsDisabled?: boolean,
|
|
304
|
+
* _ddIsQuarantined?: boolean
|
|
305
|
+
* }} test
|
|
306
|
+
*/
|
|
307
|
+
function inheritDatadogPropertiesFromRetriedTest (test) {
|
|
308
|
+
const retriedTest = test._retriedTest
|
|
309
|
+
if (!retriedTest) return
|
|
310
|
+
|
|
311
|
+
if (retriedTest._ddIsDisabled) {
|
|
312
|
+
test._ddIsDisabled = true
|
|
313
|
+
}
|
|
314
|
+
if (retriedTest._ddIsQuarantined) {
|
|
315
|
+
test._ddIsQuarantined = true
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
if (test._ddIsQuarantined && !test._ddIsAttemptToFix) {
|
|
319
|
+
testsQuarantined.add(test)
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
233
323
|
function runnableWrapper (RunnablePackage, libraryConfig) {
|
|
234
324
|
shimmer.wrap(RunnablePackage.prototype, 'run', run => function (...args) {
|
|
235
325
|
if (!testFinishCh.hasSubscribers) {
|
|
236
326
|
return run.apply(this, args)
|
|
237
327
|
}
|
|
238
|
-
if (libraryConfig?.isFlakyTestRetriesEnabled) {
|
|
239
|
-
this.retries(libraryConfig?.flakyTestRetriesCount)
|
|
240
|
-
}
|
|
241
328
|
// The reason why the wrapping logic is here is because we need to cover
|
|
242
329
|
// `afterEach` and `beforeEach` hooks as well.
|
|
243
330
|
// It can't be done in `getOnTestHandler` because it's only called for tests.
|
|
@@ -245,6 +332,7 @@ function runnableWrapper (RunnablePackage, libraryConfig) {
|
|
|
245
332
|
const isAfterEach = this.parent._afterEach.includes(this)
|
|
246
333
|
|
|
247
334
|
const isTestHook = isBeforeEach || isAfterEach
|
|
335
|
+
const test = isTestHook ? this.ctx.currentTest : this
|
|
248
336
|
|
|
249
337
|
// we restore the original user defined function
|
|
250
338
|
if (wrappedFunctions.has(this.fn)) {
|
|
@@ -253,8 +341,20 @@ function runnableWrapper (RunnablePackage, libraryConfig) {
|
|
|
253
341
|
wrappedFunctions.delete(this.fn)
|
|
254
342
|
}
|
|
255
343
|
|
|
344
|
+
if (isDatadogManagedRetryTest(test, libraryConfig)) {
|
|
345
|
+
disableMochaRetries(this)
|
|
346
|
+
if (typeof args[0] === 'function') {
|
|
347
|
+
const onRunnableFinished = args[0]
|
|
348
|
+
args[0] = function () {
|
|
349
|
+
disableMochaRetries(test)
|
|
350
|
+
return onRunnableFinished.apply(this, arguments)
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
} else if (libraryConfig?.isFlakyTestRetriesEnabled) {
|
|
354
|
+
this.retries(libraryConfig.flakyTestRetriesCount)
|
|
355
|
+
}
|
|
356
|
+
|
|
256
357
|
if (isTestHook || this.type === 'test') {
|
|
257
|
-
const test = isTestHook ? this.ctx.currentTest : this
|
|
258
358
|
const ctx = getTestContext(test)
|
|
259
359
|
|
|
260
360
|
if (ctx) {
|
|
@@ -288,6 +388,8 @@ function getOnTestHandler (isMain) {
|
|
|
288
388
|
wrappedFunctions.delete(test.fn)
|
|
289
389
|
}
|
|
290
390
|
|
|
391
|
+
inheritDatadogPropertiesFromRetriedTest(test)
|
|
392
|
+
|
|
291
393
|
const {
|
|
292
394
|
file: testSuiteAbsolutePath,
|
|
293
395
|
title,
|
|
@@ -340,12 +442,10 @@ function getOnTestHandler (isMain) {
|
|
|
340
442
|
}
|
|
341
443
|
// We want to store the result of the new tests
|
|
342
444
|
if (isNew) {
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
newTests[testFullName] = [test]
|
|
348
|
-
}
|
|
445
|
+
recordTestAttempt(newTests, test)
|
|
446
|
+
}
|
|
447
|
+
if (!isAttemptToFix && (isNew || isModified)) {
|
|
448
|
+
recordTestAttempt(efdTests, test)
|
|
349
449
|
}
|
|
350
450
|
|
|
351
451
|
if (!isAttemptToFix && isDisabled) {
|
|
@@ -371,13 +471,16 @@ function getFinalStatus ({
|
|
|
371
471
|
hasPassedAnyEfdAttempt,
|
|
372
472
|
isQuarantined,
|
|
373
473
|
isDisabled,
|
|
474
|
+
isFinalAttempt,
|
|
374
475
|
}) {
|
|
375
476
|
// Note that intermediate executions DO NOT report a final status tag
|
|
376
477
|
|
|
377
|
-
// Intermediate
|
|
478
|
+
// Intermediate executions must not carry a final status, regardless of quarantine/disabled state
|
|
479
|
+
const isExternalIntermediateExecution = !isEfdRetry && !isAttemptToFix && !isFinalAttempt
|
|
378
480
|
const isIntermediateExecution =
|
|
379
481
|
(isEfdRetry && !isLastEfdRetry) ||
|
|
380
|
-
(isAttemptToFix && !isLastAttemptToFix)
|
|
482
|
+
(isAttemptToFix && !isLastAttemptToFix) ||
|
|
483
|
+
isExternalIntermediateExecution
|
|
381
484
|
if (isIntermediateExecution) {
|
|
382
485
|
return
|
|
383
486
|
}
|
|
@@ -411,8 +514,7 @@ function getTestFinishInfo (test, status, config, error) {
|
|
|
411
514
|
|
|
412
515
|
const testName = getTestFullName(test)
|
|
413
516
|
if (
|
|
414
|
-
config
|
|
415
|
-
(test._ddIsNew || test._ddIsModified) &&
|
|
517
|
+
isEarlyFlakeDetectionTest(test, config) &&
|
|
416
518
|
!test._ddIsEfdRetry &&
|
|
417
519
|
!efdRetryCountByTestFullName.has(testName)
|
|
418
520
|
) {
|
|
@@ -434,8 +536,7 @@ function getTestFinishInfo (test, status, config, error) {
|
|
|
434
536
|
|
|
435
537
|
// Needed for the getFinalStatus call. This is because EFD does NOT tag as
|
|
436
538
|
// EFD retry the first run of the test. It only tags as retries the clones
|
|
437
|
-
const isEfdRetry =
|
|
438
|
-
test._ddIsEfdRetry || ((test._ddIsNew || test._ddIsModified) && config.isEarlyFlakeDetectionEnabled)
|
|
539
|
+
const isEfdRetry = isEarlyFlakeDetectionTest(test, config)
|
|
439
540
|
|
|
440
541
|
if (test._ddIsAttemptToFix && isLastAttempt) {
|
|
441
542
|
if (testStatuses.includes('fail')) {
|
|
@@ -463,6 +564,7 @@ function getTestFinishInfo (test, status, config, error) {
|
|
|
463
564
|
const isAtrRetry = config.isFlakyTestRetriesEnabled &&
|
|
464
565
|
!test._ddIsAttemptToFix &&
|
|
465
566
|
!test._ddIsEfdRetry
|
|
567
|
+
const isFinalAttempt = status !== 'fail' || test._currentRetry >= test._retries
|
|
466
568
|
|
|
467
569
|
const { isFlakyTestRetriesEnabled } = config
|
|
468
570
|
const { _ddIsAttemptToFix, _ddIsQuarantined, _ddIsDisabled } = test
|
|
@@ -480,6 +582,7 @@ function getTestFinishInfo (test, status, config, error) {
|
|
|
480
582
|
hasPassedAnyEfdAttempt: testStatuses.includes('pass'),
|
|
481
583
|
isQuarantined: _ddIsQuarantined,
|
|
482
584
|
isDisabled: _ddIsDisabled,
|
|
585
|
+
isFinalAttempt,
|
|
483
586
|
})
|
|
484
587
|
|
|
485
588
|
if (_ddIsAttemptToFix) {
|
|
@@ -661,7 +764,14 @@ function getOnTestRetryHandler (config) {
|
|
|
661
764
|
config.isFlakyTestRetriesEnabled &&
|
|
662
765
|
!test._ddIsAttemptToFix &&
|
|
663
766
|
!test._ddIsEfdRetry
|
|
664
|
-
testRetryCh.publish({
|
|
767
|
+
testRetryCh.publish({
|
|
768
|
+
isFirstAttempt,
|
|
769
|
+
err,
|
|
770
|
+
willBeRetried,
|
|
771
|
+
test,
|
|
772
|
+
isAtrRetry,
|
|
773
|
+
...ctx.currentStore,
|
|
774
|
+
})
|
|
665
775
|
}
|
|
666
776
|
const key = getTestToContextKey(test)
|
|
667
777
|
testToContext.delete(key)
|
|
@@ -796,6 +906,7 @@ module.exports = {
|
|
|
796
906
|
testFileToSuiteCtx,
|
|
797
907
|
getRunTestsWrapper,
|
|
798
908
|
newTests,
|
|
909
|
+
efdTests,
|
|
799
910
|
newTestsWithDynamicNames,
|
|
800
911
|
testsQuarantined,
|
|
801
912
|
testsAttemptToFix,
|
|
@@ -6,7 +6,8 @@ const { channel, addHook, AsyncResource } = require('./helpers/instrument')
|
|
|
6
6
|
const multerReadCh = channel('datadog:multer:read:finish')
|
|
7
7
|
|
|
8
8
|
function publishRequestBodyAndNext (req, res, next) {
|
|
9
|
-
|
|
9
|
+
// Mirror next's name/arity so wrapCallback skips its per-call identity rewrite.
|
|
10
|
+
return shimmer.wrapCallback(next, original => function next (_error) {
|
|
10
11
|
if (multerReadCh.hasSubscribers && req) {
|
|
11
12
|
const abortController = new AbortController()
|
|
12
13
|
const body = req.body
|
|
@@ -16,7 +17,7 @@ function publishRequestBodyAndNext (req, res, next) {
|
|
|
16
17
|
if (abortController.signal.aborted) return
|
|
17
18
|
}
|
|
18
19
|
|
|
19
|
-
return
|
|
20
|
+
return original.apply(this, arguments)
|
|
20
21
|
})
|
|
21
22
|
}
|
|
22
23
|
|