dd-trace 5.99.1 → 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 +4 -4
- 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 +84 -58
- 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-grpc/src/client.js +1 -1
- package/packages/datadog-plugin-grpc/src/server.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/baggage.js +10 -0
- package/packages/dd-trace/src/config/generated-config-types.d.ts +17 -41
- package/packages/dd-trace/src/config/index.js +6 -5
- package/packages/dd-trace/src/config/normalize-service.js +31 -0
- package/packages/dd-trace/src/config/supported-configurations.json +15 -32
- package/packages/dd-trace/src/debugger/config.js +1 -1
- 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/heap_snapshots.js +4 -4
- 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/span-helpers.js +170 -0
- package/packages/dd-trace/src/opentelemetry/span.js +14 -42
- package/packages/dd-trace/src/opentelemetry/tracer.js +11 -36
- package/packages/dd-trace/src/opentracing/propagation/text_map.js +31 -10
- package/packages/dd-trace/src/opentracing/propagation/tracestate.js +42 -12
- package/packages/dd-trace/src/opentracing/span.js +3 -2
- package/packages/dd-trace/src/plugins/util/ci.js +119 -32
- package/packages/dd-trace/src/plugins/util/test.js +293 -27
- 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 +3 -3
- 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
|
@@ -3,7 +3,12 @@
|
|
|
3
3
|
// Capture real timers at module load time, before any test can install fake timers.
|
|
4
4
|
const realSetTimeout = setTimeout
|
|
5
5
|
|
|
6
|
-
const {
|
|
6
|
+
const {
|
|
7
|
+
getTestSuitePath,
|
|
8
|
+
DYNAMIC_NAME_RE,
|
|
9
|
+
recordAttemptToFixExecution,
|
|
10
|
+
logAttemptToFixTestExecution,
|
|
11
|
+
} = require('../../../dd-trace/src/plugins/util/test')
|
|
7
12
|
const { channel } = require('../helpers/instrument')
|
|
8
13
|
const shimmer = require('../../../datadog-shimmer')
|
|
9
14
|
|
|
@@ -30,6 +35,8 @@ const newTestsWithDynamicNames = new Set()
|
|
|
30
35
|
const testsAttemptToFix = new Set()
|
|
31
36
|
const testsQuarantined = new Set()
|
|
32
37
|
const testsStatuses = new Map()
|
|
38
|
+
const attemptToFixExecutions = new Map()
|
|
39
|
+
const loggedAttemptToFixTests = new Set()
|
|
33
40
|
|
|
34
41
|
function getAfterEachHooks (testOrHook) {
|
|
35
42
|
const hooks = []
|
|
@@ -229,6 +236,13 @@ function getOnTestHandler (isMain) {
|
|
|
229
236
|
if (testInfo.hasDynamicName) {
|
|
230
237
|
newTestsWithDynamicNames.add(`${getTestSuitePath(test.file, process.cwd())} › ${test.fullTitle()}`)
|
|
231
238
|
}
|
|
239
|
+
if (isAttemptToFix) {
|
|
240
|
+
logAttemptToFixTestExecution(
|
|
241
|
+
getTestSuitePath(test.file, process.cwd()),
|
|
242
|
+
test.fullTitle(),
|
|
243
|
+
loggedAttemptToFixTests
|
|
244
|
+
)
|
|
245
|
+
}
|
|
232
246
|
// We want to store the result of the new tests
|
|
233
247
|
if (isNew) {
|
|
234
248
|
const testFullName = getTestFullName(test)
|
|
@@ -272,9 +286,8 @@ function getFinalStatus ({
|
|
|
272
286
|
return
|
|
273
287
|
}
|
|
274
288
|
|
|
275
|
-
// If the test is quarantined or disabled,
|
|
276
|
-
|
|
277
|
-
if (isQuarantined || isDisabled) {
|
|
289
|
+
// If the test is quarantined or disabled, its final status is skip unless attempt-to-fix takes precedence.
|
|
290
|
+
if (!isAttemptToFix && (isQuarantined || isDisabled)) {
|
|
278
291
|
return 'skip'
|
|
279
292
|
}
|
|
280
293
|
|
|
@@ -295,116 +308,126 @@ function getFinalStatus ({
|
|
|
295
308
|
}
|
|
296
309
|
}
|
|
297
310
|
|
|
298
|
-
function
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
311
|
+
function getTestFinishInfo (test, status, config, error) {
|
|
312
|
+
let hasFailedAllRetries = false
|
|
313
|
+
let attemptToFixPassed = false
|
|
314
|
+
let attemptToFixFailed = false
|
|
302
315
|
|
|
303
|
-
|
|
304
|
-
// This means that tests retried with DI are BREAKPOINT_HIT_GRACE_PERIOD_MS slower at least.
|
|
305
|
-
if (test._ddShouldWaitForHitProbe || test._retriedTest?._ddShouldWaitForHitProbe) {
|
|
306
|
-
await new Promise((resolve) => {
|
|
307
|
-
realSetTimeout(() => {
|
|
308
|
-
resolve()
|
|
309
|
-
}, BREAKPOINT_HIT_GRACE_PERIOD_MS)
|
|
310
|
-
})
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
let hasFailedAllRetries = false
|
|
314
|
-
let attemptToFixPassed = false
|
|
315
|
-
let attemptToFixFailed = false
|
|
316
|
-
|
|
317
|
-
const testName = getTestFullName(test)
|
|
316
|
+
const testName = getTestFullName(test)
|
|
318
317
|
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
318
|
+
if (testsStatuses.get(testName)) {
|
|
319
|
+
testsStatuses.get(testName).push(status)
|
|
320
|
+
} else {
|
|
321
|
+
testsStatuses.set(testName, [status])
|
|
322
|
+
}
|
|
323
|
+
const testStatuses = testsStatuses.get(testName)
|
|
325
324
|
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
325
|
+
const isLastAttempt = testStatuses.length === config.testManagementAttemptToFixRetries + 1
|
|
326
|
+
const isLastEfdRetry = testStatuses.length === config.earlyFlakeDetectionNumRetries + 1
|
|
327
|
+
const isLastAtrAttempt = getIsLastRetry(test) || (config.isFlakyTestRetriesEnabled && status === 'pass')
|
|
329
328
|
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
329
|
+
// Needed for the getFinalStatus call. This is because EFD does NOT tag as
|
|
330
|
+
// EFD retry the first run of the test. It only tags as retries the clones
|
|
331
|
+
const isEfdRetry = test._ddIsEfdRetry || (test._ddIsNew && config.isEarlyFlakeDetectionEnabled)
|
|
333
332
|
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
}
|
|
338
|
-
if (testStatuses.every(status => status === 'fail')) {
|
|
339
|
-
hasFailedAllRetries = true
|
|
340
|
-
} else if (testStatuses.every(status => status === 'pass')) {
|
|
341
|
-
attemptToFixPassed = true
|
|
342
|
-
}
|
|
333
|
+
if (test._ddIsAttemptToFix && isLastAttempt) {
|
|
334
|
+
if (testStatuses.includes('fail')) {
|
|
335
|
+
attemptToFixFailed = true
|
|
343
336
|
}
|
|
344
|
-
|
|
345
|
-
if (test._ddIsEfdRetry && isLastEfdRetry &&
|
|
346
|
-
testStatuses.every(status => status === 'fail')) {
|
|
337
|
+
if (testStatuses.every(status => status === 'fail')) {
|
|
347
338
|
hasFailedAllRetries = true
|
|
339
|
+
} else if (testStatuses.every(status => status === 'pass')) {
|
|
340
|
+
attemptToFixPassed = true
|
|
348
341
|
}
|
|
342
|
+
}
|
|
349
343
|
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
}
|
|
344
|
+
if (test._ddIsEfdRetry && isLastEfdRetry &&
|
|
345
|
+
testStatuses.every(status => status === 'fail')) {
|
|
346
|
+
hasFailedAllRetries = true
|
|
347
|
+
}
|
|
355
348
|
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
349
|
+
// ATR: set hasFailedAllRetries when all auto test retries were exhausted and every attempt failed
|
|
350
|
+
if (config.isFlakyTestRetriesEnabled && !test._ddIsAttemptToFix && !test._ddIsEfdRetry &&
|
|
351
|
+
getIsLastRetry(test) && testStatuses.every(status => status === 'fail')) {
|
|
352
|
+
hasFailedAllRetries = true
|
|
353
|
+
}
|
|
360
354
|
|
|
361
|
-
|
|
362
|
-
|
|
355
|
+
const isAttemptToFixRetry = test._ddIsAttemptToFix && testStatuses.length > 1
|
|
356
|
+
const isAtrRetry = config.isFlakyTestRetriesEnabled &&
|
|
357
|
+
!test._ddIsAttemptToFix &&
|
|
358
|
+
!test._ddIsEfdRetry
|
|
359
|
+
|
|
360
|
+
const { isFlakyTestRetriesEnabled } = config
|
|
361
|
+
const { _ddIsAttemptToFix, _ddIsQuarantined, _ddIsDisabled } = test
|
|
362
|
+
|
|
363
|
+
const finalStatus = getFinalStatus({
|
|
364
|
+
status,
|
|
365
|
+
hasFailedAllRetries,
|
|
366
|
+
isFlakyTestRetriesEnabled,
|
|
367
|
+
isLastAtrAttempt,
|
|
368
|
+
isEfdRetry,
|
|
369
|
+
isLastEfdRetry,
|
|
370
|
+
isAttemptToFix: _ddIsAttemptToFix,
|
|
371
|
+
isLastAttemptToFix: isLastAttempt,
|
|
372
|
+
attemptToFixPassed,
|
|
373
|
+
isQuarantined: _ddIsQuarantined,
|
|
374
|
+
isDisabled: _ddIsDisabled,
|
|
375
|
+
})
|
|
363
376
|
|
|
364
|
-
|
|
377
|
+
if (_ddIsAttemptToFix) {
|
|
378
|
+
recordAttemptToFixExecution(attemptToFixExecutions, {
|
|
379
|
+
testSuite: getTestSuitePath(test.file, process.cwd()),
|
|
380
|
+
testName: test.fullTitle(),
|
|
365
381
|
status,
|
|
366
|
-
hasFailedAllRetries,
|
|
367
|
-
isFlakyTestRetriesEnabled,
|
|
368
|
-
isLastAtrAttempt,
|
|
369
|
-
isEfdRetry,
|
|
370
|
-
isLastEfdRetry,
|
|
371
|
-
isAttemptToFix: _ddIsAttemptToFix,
|
|
372
|
-
isLastAttemptToFix: isLastAttempt,
|
|
373
|
-
attemptToFixPassed,
|
|
374
|
-
isQuarantined: _ddIsQuarantined,
|
|
375
382
|
isDisabled: _ddIsDisabled,
|
|
383
|
+
isQuarantined: _ddIsQuarantined,
|
|
376
384
|
})
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
return {
|
|
388
|
+
hasFailedAllRetries,
|
|
389
|
+
attemptToFixPassed,
|
|
390
|
+
attemptToFixFailed,
|
|
391
|
+
isAttemptToFixRetry,
|
|
392
|
+
isAtrRetry,
|
|
393
|
+
finalStatus,
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
function getOnTestEndHandler (config) {
|
|
398
|
+
return async function (test) {
|
|
399
|
+
const ctx = getTestContext(test)
|
|
400
|
+
const status = getTestStatus(test)
|
|
401
|
+
|
|
402
|
+
// After finishing it might take a bit for the snapshot to be handled.
|
|
403
|
+
// This means that tests retried with DI are BREAKPOINT_HIT_GRACE_PERIOD_MS slower at least.
|
|
404
|
+
if (test._ddShouldWaitForHitProbe || test._retriedTest?._ddShouldWaitForHitProbe) {
|
|
405
|
+
await new Promise((resolve) => {
|
|
406
|
+
realSetTimeout(() => {
|
|
407
|
+
resolve()
|
|
408
|
+
}, BREAKPOINT_HIT_GRACE_PERIOD_MS)
|
|
409
|
+
})
|
|
410
|
+
}
|
|
377
411
|
|
|
378
412
|
// If there are afterEach to be run, we don't finish the test yet.
|
|
379
413
|
// Disabled tests (marked pending by us) are finished immediately without waiting for afterEach hooks.
|
|
380
414
|
// In older mocha versions, pending tests don't run afterEach hooks, so we can't rely on
|
|
381
415
|
// getOnHookEndHandler to finish the test. This mirrors Jest's approach where the skip handler
|
|
382
416
|
// directly sets finalStatus without waiting for hooks
|
|
383
|
-
if (ctx && (!getAfterEachHooks(test).length || test._ddIsDisabled)) {
|
|
417
|
+
if (ctx && (!getAfterEachHooks(test).length || (test._ddIsDisabled && !test._ddIsAttemptToFix))) {
|
|
418
|
+
const testFinishInfo = getTestFinishInfo(test, status, config, ctx.err || test.err)
|
|
384
419
|
testFinishCh.publish({
|
|
385
420
|
status,
|
|
386
421
|
hasBeenRetried: isMochaRetry(test),
|
|
387
422
|
isLastRetry: getIsLastRetry(test),
|
|
388
|
-
|
|
389
|
-
attemptToFixPassed,
|
|
390
|
-
attemptToFixFailed,
|
|
391
|
-
isAttemptToFixRetry,
|
|
392
|
-
isAtrRetry,
|
|
423
|
+
...testFinishInfo,
|
|
393
424
|
...ctx.currentStore,
|
|
394
|
-
finalStatus,
|
|
395
425
|
})
|
|
396
|
-
} else if (ctx) { // if there is an afterEach to run, let's store the finalStatus for getOnHookEndHandler
|
|
397
|
-
ctx.finalStatus = finalStatus
|
|
398
|
-
ctx.hasFailedAllRetries = hasFailedAllRetries
|
|
399
|
-
ctx.attemptToFixPassed = attemptToFixPassed
|
|
400
|
-
ctx.attemptToFixFailed = attemptToFixFailed
|
|
401
|
-
ctx.isAttemptToFixRetry = isAttemptToFixRetry
|
|
402
|
-
ctx.isAtrRetry = isAtrRetry
|
|
403
426
|
}
|
|
404
427
|
}
|
|
405
428
|
}
|
|
406
429
|
|
|
407
|
-
function getOnHookEndHandler () {
|
|
430
|
+
function getOnHookEndHandler (config) {
|
|
408
431
|
return function (hook) {
|
|
409
432
|
const test = hook.ctx.currentTest
|
|
410
433
|
const afterEachHooks = getAfterEachHooks(hook)
|
|
@@ -415,18 +438,14 @@ function getOnHookEndHandler () {
|
|
|
415
438
|
const ctx = getTestContext(test)
|
|
416
439
|
// Disabled tests are already finished in getOnTestEndHandler,
|
|
417
440
|
// skip to avoid double-publishing
|
|
418
|
-
if (ctx && !test._ddIsDisabled) {
|
|
441
|
+
if (ctx && (!test._ddIsDisabled || test._ddIsAttemptToFix)) {
|
|
442
|
+
const testFinishInfo = getTestFinishInfo(test, status, config, ctx.err || test.err)
|
|
419
443
|
testFinishCh.publish({
|
|
420
444
|
status,
|
|
421
445
|
hasBeenRetried: isMochaRetry(test),
|
|
422
446
|
isLastRetry: getIsLastRetry(test),
|
|
423
|
-
|
|
424
|
-
attemptToFixPassed: ctx.attemptToFixPassed,
|
|
425
|
-
attemptToFixFailed: ctx.attemptToFixFailed,
|
|
426
|
-
isAttemptToFixRetry: ctx.isAttemptToFixRetry,
|
|
427
|
-
isAtrRetry: ctx.isAtrRetry,
|
|
447
|
+
...testFinishInfo,
|
|
428
448
|
...ctx.currentStore,
|
|
429
|
-
finalStatus: ctx.finalStatus,
|
|
430
449
|
})
|
|
431
450
|
}
|
|
432
451
|
}
|
|
@@ -434,7 +453,7 @@ function getOnHookEndHandler () {
|
|
|
434
453
|
}
|
|
435
454
|
}
|
|
436
455
|
|
|
437
|
-
function getOnFailHandler (isMain) {
|
|
456
|
+
function getOnFailHandler (isMain, config) {
|
|
438
457
|
return function (testOrHook, err) {
|
|
439
458
|
const testFile = testOrHook.file
|
|
440
459
|
let test = testOrHook
|
|
@@ -451,15 +470,12 @@ function getOnFailHandler (isMain) {
|
|
|
451
470
|
err.message = `${testOrHook.fullTitle()}: ${err.message}`
|
|
452
471
|
testContext.err = err
|
|
453
472
|
errorCh.runStores(testContext, () => {})
|
|
454
|
-
|
|
455
|
-
// quarantined and disabled tests always report 'skip'
|
|
456
|
-
// as final status, even when hooks fail
|
|
457
|
-
const isSkippedByManagement = test._ddIsQuarantined || test._ddIsDisabled
|
|
473
|
+
const testFinishInfo = getTestFinishInfo(test, 'fail', config, err)
|
|
458
474
|
testFinishCh.publish({
|
|
459
475
|
status: 'fail',
|
|
460
476
|
hasBeenRetried: isMochaRetry(test),
|
|
477
|
+
...testFinishInfo,
|
|
461
478
|
...testContext.currentStore,
|
|
462
|
-
finalStatus: isSkippedByManagement ? 'skip' : 'fail',
|
|
463
479
|
})
|
|
464
480
|
} else {
|
|
465
481
|
testContext.err = err
|
|
@@ -627,4 +643,6 @@ module.exports = {
|
|
|
627
643
|
testsQuarantined,
|
|
628
644
|
testsAttemptToFix,
|
|
629
645
|
testsStatuses,
|
|
646
|
+
attemptToFixExecutions,
|
|
647
|
+
loggedAttemptToFixTests,
|
|
630
648
|
}
|
|
@@ -84,9 +84,9 @@ addHook({
|
|
|
84
84
|
this.on('retry', getOnTestRetryHandler(config))
|
|
85
85
|
|
|
86
86
|
// If the hook passes, 'hook end' will be emitted. Otherwise, 'fail' will be emitted
|
|
87
|
-
this.on('hook end', getOnHookEndHandler())
|
|
87
|
+
this.on('hook end', getOnHookEndHandler(config))
|
|
88
88
|
|
|
89
|
-
this.on('fail', getOnFailHandler(false))
|
|
89
|
+
this.on('fail', getOnFailHandler(false, config))
|
|
90
90
|
|
|
91
91
|
this.on('pending', getOnPendingHandler())
|
|
92
92
|
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { addHook } = require('./helpers/instrument')
|
|
4
|
+
|
|
5
|
+
/** @type {((pattern: string | RegExp) => RegExp | undefined) | undefined} */
|
|
6
|
+
let compileToRegexp
|
|
7
|
+
|
|
8
|
+
addHook({ name: 'path-to-regexp', versions: ['*'] }, moduleExports => {
|
|
9
|
+
// 0.1.x and 6.x: `module.exports = (path, ...) => RegExp`.
|
|
10
|
+
// 7.x: `module.exports = { pathToRegexp(path, ...) => RegExp }`.
|
|
11
|
+
// 8.x: `module.exports = { pathToRegexp(path, ...) => { regexp, keys } }`.
|
|
12
|
+
const compile = typeof moduleExports === 'function'
|
|
13
|
+
? moduleExports
|
|
14
|
+
: (typeof moduleExports?.pathToRegexp === 'function' ? moduleExports.pathToRegexp : undefined)
|
|
15
|
+
|
|
16
|
+
if (compile !== undefined) {
|
|
17
|
+
compileToRegexp = pattern => {
|
|
18
|
+
let result
|
|
19
|
+
try {
|
|
20
|
+
result = compile(pattern)
|
|
21
|
+
} catch {
|
|
22
|
+
return
|
|
23
|
+
}
|
|
24
|
+
const regex = result?.regexp ?? result
|
|
25
|
+
if (regex instanceof RegExp) return regex
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return moduleExports
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Returns whatever path-to-regexp compile adapter the host most recently
|
|
34
|
+
* loaded. Capture this once at addHook fire time so each express/router
|
|
35
|
+
* instance keeps the dialect that was current when its routes were wrapped;
|
|
36
|
+
* a later host load that swaps the global compile won't retroactively change
|
|
37
|
+
* already-wrapped routers. `undefined` when the host has not loaded
|
|
38
|
+
* path-to-regexp yet, or never if it does not depend on it.
|
|
39
|
+
*/
|
|
40
|
+
function getCompileToRegexp () {
|
|
41
|
+
return compileToRegexp
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
module.exports = { getCompileToRegexp }
|
|
@@ -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
|
|