dd-trace 5.103.0 → 5.104.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 +25 -3
- package/package.json +4 -3
- package/packages/datadog-instrumentations/src/aws-sdk.js +2 -2
- package/packages/datadog-instrumentations/src/cassandra-driver.js +5 -2
- package/packages/datadog-instrumentations/src/cucumber.js +103 -30
- package/packages/datadog-instrumentations/src/elasticsearch.js +4 -4
- package/packages/datadog-instrumentations/src/graphql.js +0 -5
- package/packages/datadog-instrumentations/src/grpc/client.js +48 -32
- package/packages/datadog-instrumentations/src/helpers/callback-instrumentor.js +1 -1
- package/packages/datadog-instrumentations/src/helpers/kafka.js +17 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/compiler.js +3 -2
- package/packages/datadog-instrumentations/src/helpers/rewriter/index.js +19 -5
- package/packages/datadog-instrumentations/src/helpers/rewriter/transforms.js +14 -13
- package/packages/datadog-instrumentations/src/http/client.js +2 -2
- package/packages/datadog-instrumentations/src/ioredis.js +3 -3
- package/packages/datadog-instrumentations/src/jest.js +33 -36
- package/packages/datadog-instrumentations/src/kafkajs.js +25 -6
- package/packages/datadog-instrumentations/src/mariadb.js +1 -1
- package/packages/datadog-instrumentations/src/memcached.js +2 -1
- package/packages/datadog-instrumentations/src/mocha/main.js +272 -91
- package/packages/datadog-instrumentations/src/mocha/utils.js +48 -8
- package/packages/datadog-instrumentations/src/mongodb-core.js +1 -1
- package/packages/datadog-instrumentations/src/mongoose.js +10 -12
- package/packages/datadog-instrumentations/src/mysql.js +2 -2
- package/packages/datadog-instrumentations/src/mysql2.js +1 -1
- package/packages/datadog-instrumentations/src/pg.js +1 -1
- package/packages/datadog-instrumentations/src/playwright.js +22 -5
- package/packages/datadog-instrumentations/src/router.js +4 -2
- package/packages/datadog-instrumentations/src/vitest.js +246 -149
- package/packages/datadog-plugin-cypress/src/cypress-plugin.js +26 -19
- package/packages/datadog-plugin-elasticsearch/src/index.js +28 -8
- package/packages/datadog-plugin-graphql/src/utils.js +4 -1
- package/packages/datadog-plugin-kafkajs/src/producer.js +32 -0
- package/packages/datadog-plugin-mongodb-core/src/index.js +54 -19
- package/packages/datadog-plugin-redis/src/index.js +37 -2
- package/packages/datadog-plugin-undici/src/index.js +19 -0
- package/packages/datadog-plugin-vitest/src/index.js +19 -7
- package/packages/datadog-shimmer/src/shimmer.js +35 -0
- package/packages/dd-trace/src/appsec/blocking.js +2 -2
- package/packages/dd-trace/src/appsec/index.js +10 -3
- package/packages/dd-trace/src/appsec/reporter.js +19 -5
- package/packages/dd-trace/src/ci-visibility/requests/request.js +3 -1
- package/packages/dd-trace/src/ci-visibility/test-api-manual/test-api-manual-plugin.js +5 -3
- package/packages/dd-trace/src/config/generated-config-types.d.ts +1 -0
- package/packages/dd-trace/src/config/supported-configurations.json +9 -0
- package/packages/dd-trace/src/crashtracking/crashtracker.js +15 -3
- package/packages/dd-trace/src/datastreams/context.js +4 -2
- package/packages/dd-trace/src/encode/agentless-ci-visibility.js +26 -19
- package/packages/dd-trace/src/exporters/common/agents.js +3 -1
- package/packages/dd-trace/src/exporters/common/request.js +3 -1
- package/packages/dd-trace/src/id.js +17 -4
- package/packages/dd-trace/src/lambda/handler.js +2 -4
- package/packages/dd-trace/src/llmobs/sdk.js +10 -0
- package/packages/dd-trace/src/log/writer.js +3 -1
- package/packages/dd-trace/src/noop/span.js +3 -1
- package/packages/dd-trace/src/openfeature/writers/exposures.js +51 -20
- package/packages/dd-trace/src/opentelemetry/metrics/periodic_metric_reader.js +1 -1
- package/packages/dd-trace/src/plugins/apollo.js +3 -1
- package/packages/dd-trace/src/plugins/ci_plugin.js +3 -13
- package/packages/dd-trace/src/plugins/log_plugin.js +3 -1
- package/packages/dd-trace/src/plugins/tracing.js +5 -3
- package/packages/dd-trace/src/plugins/util/git.js +3 -1
- package/packages/dd-trace/src/plugins/util/test.js +82 -0
- package/packages/dd-trace/src/plugins/util/web.js +11 -0
- package/packages/dd-trace/src/scope.js +7 -5
- package/packages/dd-trace/src/service-naming/extra-services.js +14 -0
- package/vendor/dist/opentracing/LICENSE +0 -201
- package/vendor/dist/opentracing/binary_carrier.d.ts +0 -11
- package/vendor/dist/opentracing/constants.d.ts +0 -61
- package/vendor/dist/opentracing/examples/demo/demo.d.ts +0 -2
- package/vendor/dist/opentracing/ext/tags.d.ts +0 -90
- package/vendor/dist/opentracing/functions.d.ts +0 -20
- package/vendor/dist/opentracing/global_tracer.d.ts +0 -14
- package/vendor/dist/opentracing/index.d.ts +0 -12
- package/vendor/dist/opentracing/index.js +0 -1
- package/vendor/dist/opentracing/mock_tracer/index.d.ts +0 -5
- package/vendor/dist/opentracing/mock_tracer/mock_context.d.ts +0 -13
- package/vendor/dist/opentracing/mock_tracer/mock_report.d.ts +0 -16
- package/vendor/dist/opentracing/mock_tracer/mock_span.d.ts +0 -50
- package/vendor/dist/opentracing/mock_tracer/mock_tracer.d.ts +0 -26
- package/vendor/dist/opentracing/noop.d.ts +0 -8
- package/vendor/dist/opentracing/reference.d.ts +0 -33
- package/vendor/dist/opentracing/span.d.ts +0 -147
- package/vendor/dist/opentracing/span_context.d.ts +0 -26
- package/vendor/dist/opentracing/test/api_compatibility.d.ts +0 -16
- package/vendor/dist/opentracing/test/mocktracer_implemenation.d.ts +0 -3
- package/vendor/dist/opentracing/test/noop_implementation.d.ts +0 -4
- package/vendor/dist/opentracing/test/opentracing_api.d.ts +0 -3
- package/vendor/dist/opentracing/test/unittest.d.ts +0 -2
- package/vendor/dist/opentracing/tracer.d.ts +0 -127
|
@@ -18,6 +18,7 @@ const {
|
|
|
18
18
|
getIsFaultyEarlyFlakeDetection,
|
|
19
19
|
collectTestOptimizationSummariesFromTraces,
|
|
20
20
|
logTestOptimizationSummary,
|
|
21
|
+
getTestOptimizationRequestResults,
|
|
21
22
|
} = require('../../../dd-trace/src/plugins/util/test')
|
|
22
23
|
|
|
23
24
|
const {
|
|
@@ -109,6 +110,29 @@ function isTestFailed (test) {
|
|
|
109
110
|
return false
|
|
110
111
|
}
|
|
111
112
|
|
|
113
|
+
function getRootSuiteStatus (rootTests) {
|
|
114
|
+
let status = 'pass'
|
|
115
|
+
if (rootTests.every(t => t.isPending())) {
|
|
116
|
+
status = 'skip'
|
|
117
|
+
} else {
|
|
118
|
+
for (const test of rootTests) {
|
|
119
|
+
if (test.state === 'failed' || test.timedOut || test._ddHookFailed) {
|
|
120
|
+
status = 'fail'
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return status
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function haveRootTestsFinished (rootTests) {
|
|
128
|
+
for (const test of rootTests) {
|
|
129
|
+
if (!test.isPending() && !test.state && !test.timedOut && !test._ddHookFailed) {
|
|
130
|
+
return false
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
return true
|
|
134
|
+
}
|
|
135
|
+
|
|
112
136
|
function getFilteredSuites (originalSuites) {
|
|
113
137
|
return originalSuites.reduce((acc, suite) => {
|
|
114
138
|
const testPath = getTestSuitePath(suite.file, process.cwd())
|
|
@@ -226,11 +250,38 @@ function getOnEndHandler (isParallel) {
|
|
|
226
250
|
}
|
|
227
251
|
}
|
|
228
252
|
|
|
253
|
+
function getRunStoresPromise (channelToPublishTo, ctx) {
|
|
254
|
+
return new Promise(resolve => {
|
|
255
|
+
channelToPublishTo.runStores({ ...ctx, onDone: resolve }, () => {})
|
|
256
|
+
})
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
function applyKnownTestsResponse ({ err, knownTests }) {
|
|
260
|
+
if (err) {
|
|
261
|
+
config.knownTests = []
|
|
262
|
+
config.isEarlyFlakeDetectionEnabled = false
|
|
263
|
+
config.isKnownTestsEnabled = false
|
|
264
|
+
} else {
|
|
265
|
+
config.knownTests = knownTests
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
function applyTestManagementTestsResponse ({ err, testManagementTests: receivedTestManagementTests }) {
|
|
270
|
+
if (err) {
|
|
271
|
+
config.testManagementTests = {}
|
|
272
|
+
config.isTestManagementTestsEnabled = false
|
|
273
|
+
config.testManagementAttemptToFixRetries = 0
|
|
274
|
+
} else {
|
|
275
|
+
config.testManagementTests = receivedTestManagementTests
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
229
279
|
function getExecutionConfiguration (runner, isParallel, frameworkVersion, onFinishRequest) {
|
|
230
280
|
const ctx = {
|
|
231
281
|
isParallel,
|
|
232
282
|
frameworkVersion,
|
|
233
283
|
}
|
|
284
|
+
let skippableSuitesResponse
|
|
234
285
|
|
|
235
286
|
const onReceivedSkippableSuites = ({ err, skippableSuites, itrCorrelationId: responseItrCorrelationId }) => {
|
|
236
287
|
if (err) {
|
|
@@ -256,6 +307,16 @@ function getExecutionConfiguration (runner, isParallel, frameworkVersion, onFini
|
|
|
256
307
|
})
|
|
257
308
|
}
|
|
258
309
|
|
|
310
|
+
const requestSkippableSuites = () => {
|
|
311
|
+
if (skippableSuitesResponse) {
|
|
312
|
+
onReceivedSkippableSuites(skippableSuitesResponse)
|
|
313
|
+
return
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
ctx.onDone = onReceivedSkippableSuites
|
|
317
|
+
skippableSuitesCh.runStores(ctx, () => {})
|
|
318
|
+
}
|
|
319
|
+
|
|
259
320
|
const onReceivedImpactedTests = ({ err, modifiedFiles: receivedModifiedFiles }) => {
|
|
260
321
|
if (err) {
|
|
261
322
|
config.modifiedFiles = []
|
|
@@ -264,8 +325,7 @@ function getExecutionConfiguration (runner, isParallel, frameworkVersion, onFini
|
|
|
264
325
|
config.modifiedFiles = receivedModifiedFiles
|
|
265
326
|
}
|
|
266
327
|
if (config.isSuitesSkippingEnabled) {
|
|
267
|
-
|
|
268
|
-
skippableSuitesCh.runStores(ctx, () => {})
|
|
328
|
+
requestSkippableSuites()
|
|
269
329
|
} else {
|
|
270
330
|
mochaGlobalRunCh.runStores(ctx, () => {
|
|
271
331
|
onFinishRequest()
|
|
@@ -273,44 +333,12 @@ function getExecutionConfiguration (runner, isParallel, frameworkVersion, onFini
|
|
|
273
333
|
}
|
|
274
334
|
}
|
|
275
335
|
|
|
276
|
-
const
|
|
277
|
-
if (err) {
|
|
278
|
-
config.testManagementTests = {}
|
|
279
|
-
config.isTestManagementTestsEnabled = false
|
|
280
|
-
config.testManagementAttemptToFixRetries = 0
|
|
281
|
-
} else {
|
|
282
|
-
config.testManagementTests = receivedTestManagementTests
|
|
283
|
-
}
|
|
336
|
+
const continueAfterTestRequests = () => {
|
|
284
337
|
if (config.isImpactedTestsEnabled) {
|
|
285
338
|
ctx.onDone = onReceivedImpactedTests
|
|
286
339
|
modifiedFilesCh.runStores(ctx, () => {})
|
|
287
340
|
} else if (config.isSuitesSkippingEnabled) {
|
|
288
|
-
|
|
289
|
-
skippableSuitesCh.runStores(ctx, () => {})
|
|
290
|
-
} else {
|
|
291
|
-
mochaGlobalRunCh.runStores(ctx, () => {
|
|
292
|
-
onFinishRequest()
|
|
293
|
-
})
|
|
294
|
-
}
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
const onReceivedKnownTests = ({ err, knownTests }) => {
|
|
298
|
-
if (err) {
|
|
299
|
-
config.knownTests = []
|
|
300
|
-
config.isEarlyFlakeDetectionEnabled = false
|
|
301
|
-
config.isKnownTestsEnabled = false
|
|
302
|
-
} else {
|
|
303
|
-
config.knownTests = knownTests
|
|
304
|
-
}
|
|
305
|
-
if (config.isTestManagementTestsEnabled) {
|
|
306
|
-
ctx.onDone = onReceivedTestManagementTests
|
|
307
|
-
testManagementTestsCh.runStores(ctx, () => {})
|
|
308
|
-
} else if (config.isImpactedTestsEnabled) {
|
|
309
|
-
ctx.onDone = onReceivedImpactedTests
|
|
310
|
-
modifiedFilesCh.runStores(ctx, () => {})
|
|
311
|
-
} else if (config.isSuitesSkippingEnabled) {
|
|
312
|
-
ctx.onDone = onReceivedSkippableSuites
|
|
313
|
-
skippableSuitesCh.runStores(ctx, () => {})
|
|
341
|
+
requestSkippableSuites()
|
|
314
342
|
} else {
|
|
315
343
|
mochaGlobalRunCh.runStores(ctx, () => {
|
|
316
344
|
onFinishRequest()
|
|
@@ -336,23 +364,30 @@ function getExecutionConfiguration (runner, isParallel, frameworkVersion, onFini
|
|
|
336
364
|
config.isFlakyTestRetriesEnabled = libraryConfig.isFlakyTestRetriesEnabled
|
|
337
365
|
config.flakyTestRetriesCount = libraryConfig.flakyTestRetriesCount
|
|
338
366
|
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
367
|
+
getTestOptimizationRequestResults({
|
|
368
|
+
isKnownTestsEnabled: config.isKnownTestsEnabled,
|
|
369
|
+
isTestManagementTestsEnabled: config.isTestManagementTestsEnabled,
|
|
370
|
+
isSuitesSkippingEnabled: config.isSuitesSkippingEnabled,
|
|
371
|
+
getKnownTests: () => getRunStoresPromise(knownTestsCh, ctx),
|
|
372
|
+
getTestManagementTests: () => getRunStoresPromise(testManagementTestsCh, ctx),
|
|
373
|
+
getSkippableSuites: () => getRunStoresPromise(skippableSuitesCh, ctx),
|
|
374
|
+
}).then(requestResults => {
|
|
375
|
+
const {
|
|
376
|
+
knownTestsResponse,
|
|
377
|
+
testManagementTestsResponse,
|
|
378
|
+
skippableSuitesResponse: requestSkippableSuitesResponse,
|
|
379
|
+
} = requestResults
|
|
380
|
+
|
|
381
|
+
if (knownTestsResponse) {
|
|
382
|
+
applyKnownTestsResponse(knownTestsResponse)
|
|
383
|
+
}
|
|
384
|
+
if (testManagementTestsResponse) {
|
|
385
|
+
applyTestManagementTestsResponse(testManagementTestsResponse)
|
|
386
|
+
}
|
|
387
|
+
skippableSuitesResponse = requestSkippableSuitesResponse
|
|
388
|
+
|
|
389
|
+
continueAfterTestRequests()
|
|
390
|
+
})
|
|
356
391
|
}
|
|
357
392
|
|
|
358
393
|
ctx.onDone = onReceivedConfiguration
|
|
@@ -473,22 +508,166 @@ addHook({
|
|
|
473
508
|
// Populated during the root 'suite' event so the normal finish path can include them
|
|
474
509
|
// in mixed-file status calculation.
|
|
475
510
|
const rootTestsByFile = new Map()
|
|
511
|
+
// Counts how many original tests per pure-root file still need their final attempt.
|
|
512
|
+
// Hits zero when the last test's lifecycle completes, triggering the suite finish.
|
|
513
|
+
const rootPendingCountByFile = new Map()
|
|
514
|
+
const rootFinalizationPendingCountByFile = new Map()
|
|
515
|
+
const rootFallbackPendingFiles = new Set()
|
|
516
|
+
const rootFinalizationPendingTests = new WeakSet()
|
|
517
|
+
let pendingRootFinalizations = 0
|
|
518
|
+
let hasEnded = false
|
|
519
|
+
let hasFinishedRun = false
|
|
520
|
+
let endRunner
|
|
521
|
+
|
|
522
|
+
function updateRootTestForFinalAttempt (test) {
|
|
523
|
+
if (!test._retriedTest) return
|
|
524
|
+
|
|
525
|
+
const rootTests = rootTestsByFile.get(test.file)
|
|
526
|
+
if (!rootTests) return
|
|
527
|
+
|
|
528
|
+
const retriedTestIndex = rootTests.indexOf(test._retriedTest)
|
|
529
|
+
if (retriedTestIndex !== -1) {
|
|
530
|
+
rootTests[retriedTestIndex] = test
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
function finishRunIfReady () {
|
|
535
|
+
if (hasFinishedRun) return
|
|
536
|
+
if (hasEnded && pendingRootFinalizations === 0) {
|
|
537
|
+
hasFinishedRun = true
|
|
538
|
+
onEnd.call(endRunner)
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
function incrementPendingRootFinalization (test) {
|
|
543
|
+
if (!rootPendingCountByFile.has(test.file) || rootFinalizationPendingTests.has(test)) return
|
|
544
|
+
|
|
545
|
+
rootFinalizationPendingTests.add(test)
|
|
546
|
+
pendingRootFinalizations++
|
|
547
|
+
rootFinalizationPendingCountByFile.set(
|
|
548
|
+
test.file,
|
|
549
|
+
(rootFinalizationPendingCountByFile.get(test.file) || 0) + 1
|
|
550
|
+
)
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
function decrementPendingRootFinalization (test) {
|
|
554
|
+
if (!rootFinalizationPendingTests.has(test)) return
|
|
555
|
+
|
|
556
|
+
rootFinalizationPendingTests.delete(test)
|
|
557
|
+
pendingRootFinalizations--
|
|
558
|
+
|
|
559
|
+
const remaining = rootFinalizationPendingCountByFile.get(test.file) - 1
|
|
560
|
+
if (remaining > 0) {
|
|
561
|
+
rootFinalizationPendingCountByFile.set(test.file, remaining)
|
|
562
|
+
} else {
|
|
563
|
+
rootFinalizationPendingCountByFile.delete(test.file)
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
if (!rootFinalizationPendingCountByFile.has(test.file) && rootFallbackPendingFiles.delete(test.file)) {
|
|
567
|
+
finishRootSuiteFallbackForFile(test.file)
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
finishRunIfReady()
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
function finishRootSuiteForFile (file) {
|
|
574
|
+
const remaining = rootPendingCountByFile.get(file) - 1
|
|
575
|
+
if (remaining > 0) {
|
|
576
|
+
rootPendingCountByFile.set(file, remaining)
|
|
577
|
+
return
|
|
578
|
+
}
|
|
579
|
+
rootPendingCountByFile.delete(file)
|
|
580
|
+
|
|
581
|
+
const ctx = testFileToSuiteCtx.get(file)
|
|
582
|
+
if (!ctx) {
|
|
583
|
+
log.warn('No ctx found for suite', file)
|
|
584
|
+
return
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
const rootTests = rootTestsByFile.get(file) || []
|
|
588
|
+
const status = getRootSuiteStatus(rootTests)
|
|
589
|
+
|
|
590
|
+
if (global.__coverage__) {
|
|
591
|
+
const coverageFiles = getCoveredFilenamesFromCoverage(global.__coverage__)
|
|
592
|
+
testSuiteCodeCoverageCh.publish({ coverageFiles, suiteFile: file })
|
|
593
|
+
mergeCoverage(global.__coverage__, originalCoverageMap)
|
|
594
|
+
resetCoverage(global.__coverage__)
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
testSuiteFinishCh.publish({ status, ...ctx.currentStore }, () => {})
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
function finishRootSuiteFallbackForFile (file) {
|
|
601
|
+
const ctx = testFileToSuiteCtx.get(file)
|
|
602
|
+
if (!ctx || !rootPendingCountByFile.has(file)) return
|
|
603
|
+
|
|
604
|
+
const rootTests = rootTestsByFile.get(file) || []
|
|
605
|
+
const status = haveRootTestsFinished(rootTests) ? getRootSuiteStatus(rootTests) : 'fail'
|
|
606
|
+
rootPendingCountByFile.delete(file)
|
|
607
|
+
testSuiteFinishCh.publish({ status, ...ctx.currentStore }, () => {})
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
function finishRootSuiteAfterFinalAttempt (test) {
|
|
611
|
+
if (!test._ddIsFinalAttempt || !rootPendingCountByFile.has(test.file)) return
|
|
612
|
+
|
|
613
|
+
updateRootTestForFinalAttempt(test)
|
|
614
|
+
finishRootSuiteForFile(test.file)
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
const onEnd = getOnEndHandler(false)
|
|
476
618
|
|
|
477
619
|
this.once('start', getOnStartHandler(frameworkVersion))
|
|
478
620
|
|
|
479
|
-
this.once('end',
|
|
621
|
+
this.once('end', function () {
|
|
622
|
+
hasEnded = true
|
|
623
|
+
endRunner = this
|
|
624
|
+
finishRunIfReady()
|
|
625
|
+
})
|
|
626
|
+
|
|
627
|
+
// The job of this listener is to
|
|
628
|
+
// initialize the suite span tag in correct order
|
|
629
|
+
// (that is suiteA -> testA ... -> suiteB -> testB
|
|
630
|
+
// instead of suiteA -> suiteB -> testA -> ... -> testB)
|
|
631
|
+
// when the suite has tests that are in the top level
|
|
632
|
+
// (no describe(...))
|
|
633
|
+
this.on('test', function (test) {
|
|
634
|
+
const ctx = testFileToSuiteCtx.get(test.file)
|
|
635
|
+
if (ctx?._pendingRootStart) {
|
|
636
|
+
ctx._pendingRootStart = false
|
|
637
|
+
testSuiteStartCh.runStores(ctx, () => {})
|
|
638
|
+
}
|
|
639
|
+
})
|
|
480
640
|
|
|
481
641
|
this.on('test', getOnTestHandler(true))
|
|
482
642
|
|
|
483
|
-
this.on('test end', getOnTestEndHandler(config
|
|
643
|
+
this.on('test end', getOnTestEndHandler(config, {
|
|
644
|
+
onStart: incrementPendingRootFinalization,
|
|
645
|
+
onFinish: function (test) {
|
|
646
|
+
finishRootSuiteAfterFinalAttempt(test)
|
|
647
|
+
decrementPendingRootFinalization(test)
|
|
648
|
+
},
|
|
649
|
+
}))
|
|
484
650
|
|
|
485
651
|
this.on('retry', getOnTestRetryHandler(config))
|
|
486
652
|
|
|
487
653
|
// If the hook passes, 'hook end' will be emitted. Otherwise, 'fail' will be emitted
|
|
488
654
|
this.on('hook end', getOnHookEndHandler(config))
|
|
489
655
|
|
|
656
|
+
this.on('hook end', function (hook) {
|
|
657
|
+
const test = hook.ctx?.currentTest
|
|
658
|
+
if (!test) return
|
|
659
|
+
finishRootSuiteAfterFinalAttempt(test)
|
|
660
|
+
})
|
|
661
|
+
|
|
490
662
|
this.on('fail', getOnFailHandler(true, config))
|
|
491
663
|
|
|
664
|
+
this.on('fail', function (testOrHook) {
|
|
665
|
+
if (testOrHook.type !== 'hook') return
|
|
666
|
+
const test = testOrHook.ctx?.currentTest
|
|
667
|
+
if (!test) return
|
|
668
|
+
finishRootSuiteAfterFinalAttempt(test)
|
|
669
|
+
})
|
|
670
|
+
|
|
492
671
|
this.on('pending', getOnPendingHandler())
|
|
493
672
|
|
|
494
673
|
this.on('suite', function (suite) {
|
|
@@ -503,7 +682,13 @@ addHook({
|
|
|
503
682
|
if (suite.root && suite.tests.length > 0) {
|
|
504
683
|
const files = new Set(suite.tests.map(test => test.file).filter(Boolean))
|
|
505
684
|
for (const file of files) {
|
|
506
|
-
|
|
685
|
+
const testsForFile = suite.tests.filter(t => t.file === file)
|
|
686
|
+
rootTestsByFile.set(file, testsForFile)
|
|
687
|
+
// Only track the countdown for pure root-level files.
|
|
688
|
+
// Mixed files are finished by the normal 'suite end' path.
|
|
689
|
+
if (!suitesByTestFile[file]) {
|
|
690
|
+
rootPendingCountByFile.set(file, testsForFile.length)
|
|
691
|
+
}
|
|
507
692
|
if (testFileToSuiteCtx.get(file)) continue
|
|
508
693
|
const isUnskippable = unskippableSuites.includes(file)
|
|
509
694
|
isForcedToRun = isUnskippable && suitesToSkip.includes(getTestSuitePath(file, process.cwd()))
|
|
@@ -512,9 +697,9 @@ addHook({
|
|
|
512
697
|
isUnskippable,
|
|
513
698
|
isForcedToRun,
|
|
514
699
|
itrCorrelationId,
|
|
700
|
+
_pendingRootStart: true, // Now the suite start fires lazily on the first test event for this file
|
|
515
701
|
}
|
|
516
702
|
testFileToSuiteCtx.set(file, ctx)
|
|
517
|
-
testSuiteStartCh.runStores(ctx, () => {})
|
|
518
703
|
}
|
|
519
704
|
}
|
|
520
705
|
return
|
|
@@ -536,42 +721,38 @@ addHook({
|
|
|
536
721
|
|
|
537
722
|
this.on('suite end', function (suite) {
|
|
538
723
|
if (suite.root) {
|
|
539
|
-
//
|
|
540
|
-
|
|
724
|
+
// Normal case: pure root-level files are finished by the 'test end' / 'hook end'
|
|
725
|
+
// listeners via finishRootSuiteForFile. Two edge cases remain here:
|
|
726
|
+
//
|
|
727
|
+
// 1. All-pending: no 'test' event fired, _pendingRootStart is still true.
|
|
728
|
+
// Start and immediately finish with 'skip'.
|
|
729
|
+
//
|
|
730
|
+
// 2. Aborted mid-run (e.g. a beforeEach hook failure): Mocha skips remaining
|
|
731
|
+
// tests and jumps straight to 'suite end'. rootPendingCountByFile still has
|
|
732
|
+
// a nonzero count for the file because the last tests never ran. Finish it
|
|
733
|
+
// as failed now.
|
|
734
|
+
//
|
|
735
|
+
// 3. Async finalization lagged behind Mocha's synchronous events (e.g. DI retry
|
|
736
|
+
// wait): all tests have Mocha terminal state, but the final-attempt callback
|
|
737
|
+
// did not run before root 'suite end'. Finish from the observed test states.
|
|
738
|
+
const processedFiles = new Set()
|
|
541
739
|
for (const test of suite.tests) {
|
|
542
|
-
if (!test.file) continue
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
status = 'skip'
|
|
556
|
-
} else {
|
|
557
|
-
for (const test of tests) {
|
|
558
|
-
if (test.state === 'failed' || test.timedOut) {
|
|
559
|
-
status = 'fail'
|
|
560
|
-
break
|
|
561
|
-
}
|
|
740
|
+
if (!test.file || processedFiles.has(test.file)) continue
|
|
741
|
+
processedFiles.add(test.file)
|
|
742
|
+
if (suitesByTestFile[test.file]) continue // mixed: handled by normal path
|
|
743
|
+
const ctx = testFileToSuiteCtx.get(test.file)
|
|
744
|
+
if (!ctx) continue
|
|
745
|
+
if (ctx._pendingRootStart) {
|
|
746
|
+
ctx._pendingRootStart = false
|
|
747
|
+
testSuiteStartCh.runStores(ctx, () => {})
|
|
748
|
+
testSuiteFinishCh.publish({ status: 'skip', ...ctx.currentStore }, () => {})
|
|
749
|
+
} else if (rootPendingCountByFile.has(test.file)) {
|
|
750
|
+
if (rootFinalizationPendingCountByFile.has(test.file)) {
|
|
751
|
+
rootFallbackPendingFiles.add(test.file)
|
|
752
|
+
continue
|
|
562
753
|
}
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
const coverageFiles = getCoveredFilenamesFromCoverage(global.__coverage__)
|
|
566
|
-
testSuiteCodeCoverageCh.publish({ coverageFiles, suiteFile: file })
|
|
567
|
-
mergeCoverage(global.__coverage__, originalCoverageMap)
|
|
568
|
-
resetCoverage(global.__coverage__)
|
|
569
|
-
}
|
|
570
|
-
const ctx = testFileToSuiteCtx.get(file)
|
|
571
|
-
if (ctx) {
|
|
572
|
-
testSuiteFinishCh.publish({ status, ...ctx.currentStore }, () => {})
|
|
573
|
-
} else {
|
|
574
|
-
log.warn('No ctx found for suite', file)
|
|
754
|
+
|
|
755
|
+
finishRootSuiteFallbackForFile(test.file)
|
|
575
756
|
}
|
|
576
757
|
}
|
|
577
758
|
return
|
|
@@ -503,13 +503,38 @@ function getTestFinishInfo (test, status, config, error) {
|
|
|
503
503
|
}
|
|
504
504
|
}
|
|
505
505
|
|
|
506
|
-
function getOnTestEndHandler (config) {
|
|
506
|
+
function getOnTestEndHandler (config, finalAttemptHandlers) {
|
|
507
507
|
return async function (test) {
|
|
508
508
|
if (test._ddShouldSkipEfdRetry) {
|
|
509
509
|
return
|
|
510
510
|
}
|
|
511
511
|
const ctx = getTestContext(test)
|
|
512
512
|
const status = getTestStatus(test)
|
|
513
|
+
const shouldFinishTest = ctx && (!getAfterEachHooks(test).length || (test._ddIsDisabled && !test._ddIsAttemptToFix))
|
|
514
|
+
let testFinishInfo
|
|
515
|
+
let isFinalAttempt = false
|
|
516
|
+
|
|
517
|
+
// If there are afterEach to be run, we don't finish the test yet.
|
|
518
|
+
// Disabled tests (marked pending by us) are finished immediately without waiting for afterEach hooks.
|
|
519
|
+
// In older mocha versions, pending tests don't run afterEach hooks, so we can't rely on
|
|
520
|
+
// getOnHookEndHandler to finish the test. This mirrors Jest's approach where the skip handler
|
|
521
|
+
// directly sets finalStatus without waiting for hooks
|
|
522
|
+
if (!ctx && test.isPending()) {
|
|
523
|
+
test._ddIsFinalAttempt = true
|
|
524
|
+
isFinalAttempt = true
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
if (shouldFinishTest) {
|
|
528
|
+
testFinishInfo = getTestFinishInfo(test, status, config, ctx.err || test.err)
|
|
529
|
+
if (testFinishInfo.finalStatus !== undefined) {
|
|
530
|
+
test._ddIsFinalAttempt = true
|
|
531
|
+
isFinalAttempt = true
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
if (isFinalAttempt) {
|
|
536
|
+
finalAttemptHandlers?.onStart?.(test)
|
|
537
|
+
}
|
|
513
538
|
|
|
514
539
|
// After finishing it might take a bit for the snapshot to be handled.
|
|
515
540
|
// This means that tests retried with DI are BREAKPOINT_HIT_GRACE_PERIOD_MS slower at least.
|
|
@@ -521,13 +546,7 @@ function getOnTestEndHandler (config) {
|
|
|
521
546
|
})
|
|
522
547
|
}
|
|
523
548
|
|
|
524
|
-
|
|
525
|
-
// Disabled tests (marked pending by us) are finished immediately without waiting for afterEach hooks.
|
|
526
|
-
// In older mocha versions, pending tests don't run afterEach hooks, so we can't rely on
|
|
527
|
-
// getOnHookEndHandler to finish the test. This mirrors Jest's approach where the skip handler
|
|
528
|
-
// directly sets finalStatus without waiting for hooks
|
|
529
|
-
if (ctx && (!getAfterEachHooks(test).length || (test._ddIsDisabled && !test._ddIsAttemptToFix))) {
|
|
530
|
-
const testFinishInfo = getTestFinishInfo(test, status, config, ctx.err || test.err)
|
|
549
|
+
if (shouldFinishTest) {
|
|
531
550
|
testFinishCh.publish({
|
|
532
551
|
status,
|
|
533
552
|
hasBeenRetried: isMochaRetry(test),
|
|
@@ -536,6 +555,10 @@ function getOnTestEndHandler (config) {
|
|
|
536
555
|
...ctx.currentStore,
|
|
537
556
|
})
|
|
538
557
|
}
|
|
558
|
+
|
|
559
|
+
if (isFinalAttempt) {
|
|
560
|
+
finalAttemptHandlers?.onFinish?.(test)
|
|
561
|
+
}
|
|
539
562
|
}
|
|
540
563
|
}
|
|
541
564
|
|
|
@@ -552,6 +575,9 @@ function getOnHookEndHandler (config) {
|
|
|
552
575
|
// skip to avoid double-publishing
|
|
553
576
|
if (ctx && (!test._ddIsDisabled || test._ddIsAttemptToFix)) {
|
|
554
577
|
const testFinishInfo = getTestFinishInfo(test, status, config, ctx.err || test.err)
|
|
578
|
+
if (testFinishInfo.finalStatus !== undefined) {
|
|
579
|
+
test._ddIsFinalAttempt = true
|
|
580
|
+
}
|
|
555
581
|
testFinishCh.publish({
|
|
556
582
|
status,
|
|
557
583
|
hasBeenRetried: isMochaRetry(test),
|
|
@@ -583,6 +609,20 @@ function getOnFailHandler (isMain, config) {
|
|
|
583
609
|
testContext.err = err
|
|
584
610
|
errorCh.runStores(testContext, () => {})
|
|
585
611
|
const testFinishInfo = getTestFinishInfo(test, 'fail', config, err)
|
|
612
|
+
// ATR never retries hook failures: this.retries(N) is set in runnableWrapper
|
|
613
|
+
// which only runs when the test function executes — hooks bypass that path,
|
|
614
|
+
// so _retries stays at -1 and getIsLastRetry returns false, leaving finalStatus
|
|
615
|
+
// undefined. We must also mark the attempt final when no clone-based retry
|
|
616
|
+
// mechanism (EFD original, EFD clone, ATF) has queued further attempts.
|
|
617
|
+
const noCloneRetries = !test._ddIsEfdRetry &&
|
|
618
|
+
!((test._ddIsNew || test._ddIsModified) && config.isEarlyFlakeDetectionEnabled) &&
|
|
619
|
+
!test._ddIsAttemptToFix
|
|
620
|
+
if (testFinishInfo.finalStatus !== undefined || noCloneRetries) {
|
|
621
|
+
test._ddIsFinalAttempt = true
|
|
622
|
+
}
|
|
623
|
+
// test.state is never set to 'failed' for hook failures (Mocha marks the hook,
|
|
624
|
+
// not the test). Flag it so finishRootSuiteForFile can compute the correct status.
|
|
625
|
+
test._ddHookFailed = true
|
|
586
626
|
testFinishCh.publish({
|
|
587
627
|
status: 'fail',
|
|
588
628
|
hasBeenRetried: isMochaRetry(test),
|
|
@@ -174,7 +174,7 @@ function instrument (operation, command, instance, args, server, ns, ops, option
|
|
|
174
174
|
name,
|
|
175
175
|
}
|
|
176
176
|
return startCh.runStores(ctx, () => {
|
|
177
|
-
args[index] = shimmer.
|
|
177
|
+
args[index] = shimmer.wrapCallback(callback, callback => function (err, res) {
|
|
178
178
|
if (err) {
|
|
179
179
|
ctx.error = err
|
|
180
180
|
errorCh.publish(ctx)
|
|
@@ -125,21 +125,19 @@ addHook({
|
|
|
125
125
|
const resolve = args[0]
|
|
126
126
|
const reject = args[1]
|
|
127
127
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
if (resolve) {
|
|
128
|
+
if (typeof resolve === 'function') {
|
|
129
|
+
args[0] = shimmer.wrapCallback(resolve, resolve => function wrappedResolve (...args) {
|
|
130
|
+
finishCh.publish(ctx)
|
|
132
131
|
return resolve.apply(this, args)
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
args[1] = shimmer.wrapFunction(reject, reject => function wrappedReject (...args) {
|
|
137
|
-
finishCh.publish(ctx)
|
|
132
|
+
})
|
|
133
|
+
}
|
|
138
134
|
|
|
139
|
-
|
|
135
|
+
if (typeof reject === 'function') {
|
|
136
|
+
args[1] = shimmer.wrapCallback(reject, reject => function wrappedReject (...args) {
|
|
137
|
+
finishCh.publish(ctx)
|
|
140
138
|
return reject.apply(this, args)
|
|
141
|
-
}
|
|
142
|
-
}
|
|
139
|
+
})
|
|
140
|
+
}
|
|
143
141
|
|
|
144
142
|
return originalThen.apply(this, args)
|
|
145
143
|
}
|
|
@@ -29,7 +29,7 @@ addHook({ name: 'mysql', file: 'lib/Connection.js', versions: ['>=2'] }, Connect
|
|
|
29
29
|
|
|
30
30
|
if (res._callback) {
|
|
31
31
|
const cb = res._callback
|
|
32
|
-
res._callback = shimmer.
|
|
32
|
+
res._callback = shimmer.wrapCallback(cb, cb => function (error, result) {
|
|
33
33
|
if (error) {
|
|
34
34
|
ctx.error = error
|
|
35
35
|
errorCh.publish(ctx)
|
|
@@ -86,7 +86,7 @@ addHook({ name: 'mysql', file: 'lib/Pool.js', versions: ['>=2'] }, Pool => {
|
|
|
86
86
|
return startPoolQueryCh.runStores(ctx, () => {
|
|
87
87
|
const cb = args[args.length - 1]
|
|
88
88
|
if (typeof cb === 'function') {
|
|
89
|
-
args[args.length - 1] = shimmer.
|
|
89
|
+
args[args.length - 1] = shimmer.wrapCallback(cb, cb => function (...args) {
|
|
90
90
|
return finishPoolQueryCh.runStores(ctx, cb, this, ...args)
|
|
91
91
|
})
|
|
92
92
|
}
|
|
@@ -176,7 +176,7 @@ function wrapConnection (Connection, version) {
|
|
|
176
176
|
if (typeof this.onResult === 'function') {
|
|
177
177
|
const onResult = this.onResult
|
|
178
178
|
|
|
179
|
-
this.onResult = shimmer.
|
|
179
|
+
this.onResult = shimmer.wrapCallback(onResult, onResult => function (error) {
|
|
180
180
|
if (error) {
|
|
181
181
|
ctx.error = error
|
|
182
182
|
errorCh.publish(ctx)
|
|
@@ -193,7 +193,7 @@ function wrapPoolQuery (query) {
|
|
|
193
193
|
}
|
|
194
194
|
|
|
195
195
|
if (typeof cb === 'function') {
|
|
196
|
-
args[args.length - 1] = shimmer.
|
|
196
|
+
args[args.length - 1] = shimmer.wrapCallback(cb, cb => function (...args) {
|
|
197
197
|
finish(ctx)
|
|
198
198
|
return cb.apply(this, args)
|
|
199
199
|
})
|