dd-trace 5.86.0 → 5.87.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 +18 -3
- package/package.json +1 -1
- package/packages/datadog-instrumentations/src/cucumber.js +14 -0
- package/packages/datadog-instrumentations/src/helpers/hooks.js +1 -0
- package/packages/datadog-instrumentations/src/http/client.js +119 -1
- package/packages/datadog-instrumentations/src/jest.js +104 -4
- package/packages/datadog-instrumentations/src/mocha/utils.js +6 -0
- package/packages/datadog-instrumentations/src/mysql2.js +131 -64
- package/packages/datadog-instrumentations/src/playwright.js +8 -0
- package/packages/datadog-instrumentations/src/stripe.js +92 -0
- package/packages/datadog-instrumentations/src/vitest.js +11 -0
- package/packages/datadog-plugin-azure-functions/src/index.js +53 -37
- package/packages/datadog-plugin-cypress/src/cypress-plugin.js +7 -0
- package/packages/dd-trace/src/appsec/addresses.js +11 -0
- package/packages/dd-trace/src/appsec/channels.js +5 -1
- package/packages/dd-trace/src/appsec/downstream_requests.js +302 -0
- package/packages/dd-trace/src/appsec/index.js +103 -0
- package/packages/dd-trace/src/appsec/rasp/ssrf.js +66 -4
- package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/worker/index.js +14 -1
- package/packages/dd-trace/src/config/defaults.js +2 -0
- package/packages/dd-trace/src/config/index.js +6 -0
- package/packages/dd-trace/src/config/supported-configurations.json +2 -0
- package/packages/dd-trace/src/debugger/devtools_client/breakpoints.js +47 -2
- package/packages/dd-trace/src/debugger/devtools_client/index.js +75 -23
- package/packages/dd-trace/src/debugger/devtools_client/remote_config.js +23 -1
- package/packages/dd-trace/src/debugger/devtools_client/snapshot/collector.js +3 -3
- package/packages/dd-trace/src/debugger/devtools_client/snapshot/index.js +168 -36
- package/packages/dd-trace/src/debugger/devtools_client/snapshot/processor.js +18 -0
- package/packages/dd-trace/src/exporters/common/agents.js +1 -1
- package/packages/dd-trace/src/llmobs/constants/writers.js +1 -1
- package/packages/dd-trace/src/llmobs/sdk.js +34 -5
- package/packages/dd-trace/src/plugins/database.js +42 -43
- package/packages/dd-trace/src/plugins/outbound.js +27 -2
- package/packages/dd-trace/src/plugins/tracing.js +39 -4
- package/packages/dd-trace/src/plugins/util/inferred_proxy.js +7 -0
- package/packages/dd-trace/src/plugins/util/web.js +8 -7
- package/packages/dd-trace/src/startup-log.js +2 -2
- package/packages/dd-trace/src/plugins/util/serverless.js +0 -8
package/index.d.ts
CHANGED
|
@@ -3243,13 +3243,13 @@ declare namespace tracer {
|
|
|
3243
3243
|
/**
|
|
3244
3244
|
* The type of evaluation metric, one of 'categorical', 'score', or 'boolean'
|
|
3245
3245
|
*/
|
|
3246
|
-
metricType: 'categorical' | 'score' | 'boolean',
|
|
3246
|
+
metricType: 'categorical' | 'score' | 'boolean' | 'json',
|
|
3247
3247
|
|
|
3248
3248
|
/**
|
|
3249
3249
|
* The value of the evaluation metric.
|
|
3250
|
-
* Must be string for 'categorical' metrics, number for 'score' metrics,
|
|
3250
|
+
* Must be string for 'categorical' metrics, number for 'score' metrics, boolean for 'boolean' metrics and a JSON object for 'json' metrics.
|
|
3251
3251
|
*/
|
|
3252
|
-
value: string | number | boolean,
|
|
3252
|
+
value: string | number | boolean | { [key: string]: any },
|
|
3253
3253
|
|
|
3254
3254
|
/**
|
|
3255
3255
|
* An object of string key-value pairs to tag the evaluation metric with.
|
|
@@ -3265,6 +3265,21 @@ declare namespace tracer {
|
|
|
3265
3265
|
* The timestamp in milliseconds when the evaluation metric result was generated.
|
|
3266
3266
|
*/
|
|
3267
3267
|
timestampMs?: number
|
|
3268
|
+
|
|
3269
|
+
/**
|
|
3270
|
+
* Reasoning for the evaluation result.
|
|
3271
|
+
*/
|
|
3272
|
+
reasoning?: string,
|
|
3273
|
+
|
|
3274
|
+
/**
|
|
3275
|
+
* Whether the evaluation passed or failed. Valid values are pass and fail.
|
|
3276
|
+
*/
|
|
3277
|
+
assessment?: 'pass' | 'fail',
|
|
3278
|
+
|
|
3279
|
+
/**
|
|
3280
|
+
* Arbitrary JSON data associated with the evaluation.
|
|
3281
|
+
*/
|
|
3282
|
+
metadata?: { [key: string]: any }
|
|
3268
3283
|
}
|
|
3269
3284
|
|
|
3270
3285
|
interface Document {
|
package/package.json
CHANGED
|
@@ -353,6 +353,20 @@ function wrapRun (pl, isLatestVersion, version) {
|
|
|
353
353
|
isEfdRetry = numRetries > 0
|
|
354
354
|
}
|
|
355
355
|
|
|
356
|
+
// Check if all EFD retries failed
|
|
357
|
+
if (isEfdRetry && (isNew || isModified)) {
|
|
358
|
+
const statuses = lastStatusByPickleId.get(this.pickle.id)
|
|
359
|
+
if (statuses.length === earlyFlakeDetectionNumRetries + 1) {
|
|
360
|
+
const { fail } = statuses.reduce((acc, status) => {
|
|
361
|
+
acc[status]++
|
|
362
|
+
return acc
|
|
363
|
+
}, { pass: 0, fail: 0 })
|
|
364
|
+
if (fail === earlyFlakeDetectionNumRetries + 1) {
|
|
365
|
+
hasFailedAllRetries = true
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
|
|
356
370
|
const attemptCtx = numAttemptToCtx.get(numAttempt)
|
|
357
371
|
|
|
358
372
|
const error = getErrorFromCucumberResult(result)
|
|
@@ -136,6 +136,7 @@ module.exports = {
|
|
|
136
136
|
'selenium-webdriver': () => require('../selenium'),
|
|
137
137
|
sequelize: () => require('../sequelize'),
|
|
138
138
|
sharedb: () => require('../sharedb'),
|
|
139
|
+
stripe: () => require('../stripe'),
|
|
139
140
|
tedious: () => require('../tedious'),
|
|
140
141
|
tinypool: { esmFirst: true, fn: () => require('../vitest') },
|
|
141
142
|
undici: () => require('../undici'),
|
|
@@ -14,6 +14,7 @@ const finishChannel = channel('apm:http:client:request:finish')
|
|
|
14
14
|
const endChannel = channel('apm:http:client:request:end')
|
|
15
15
|
const asyncStartChannel = channel('apm:http:client:request:asyncStart')
|
|
16
16
|
const errorChannel = channel('apm:http:client:request:error')
|
|
17
|
+
const responseFinishChannel = channel('apm:http:client:response:finish')
|
|
17
18
|
|
|
18
19
|
const names = ['http', 'https', 'node:http', 'node:https']
|
|
19
20
|
|
|
@@ -39,6 +40,112 @@ function normalizeCallback (inputOptions, callback, inputURL) {
|
|
|
39
40
|
return typeof inputOptions === 'function' ? [inputOptions, inputURL || {}] : [callback, inputOptions]
|
|
40
41
|
}
|
|
41
42
|
|
|
43
|
+
/**
|
|
44
|
+
* Wires the downstream response so we can observe when the customer consumes
|
|
45
|
+
* the body and when the stream finishes
|
|
46
|
+
*
|
|
47
|
+
* @param {object} ctx - Instrumentation context
|
|
48
|
+
* @param {import('http').IncomingMessage} res - The downstream response object.
|
|
49
|
+
* @returns {{ finalizeIfNeeded: () => void }|null} Cleanup helper used for drain.
|
|
50
|
+
*/
|
|
51
|
+
function setupResponseInstrumentation (ctx, res) {
|
|
52
|
+
const shouldInstrumentFinish = responseFinishChannel.hasSubscribers
|
|
53
|
+
|
|
54
|
+
if (!shouldInstrumentFinish) {
|
|
55
|
+
return null
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
let bodyConsumed = false
|
|
59
|
+
let finishCalled = false
|
|
60
|
+
let originalRead = null
|
|
61
|
+
let dataListenerAdded = false
|
|
62
|
+
let dataReadStarted = false
|
|
63
|
+
|
|
64
|
+
const { shouldCollectBody } = ctx
|
|
65
|
+
const bodyChunks = shouldCollectBody ? [] : null
|
|
66
|
+
|
|
67
|
+
const collectChunk = chunk => {
|
|
68
|
+
if (!shouldCollectBody || !chunk) return
|
|
69
|
+
|
|
70
|
+
if (typeof chunk === 'string') {
|
|
71
|
+
bodyChunks.push(chunk)
|
|
72
|
+
} else if (Buffer.isBuffer(chunk)) {
|
|
73
|
+
bodyChunks.push(chunk)
|
|
74
|
+
} else {
|
|
75
|
+
// Handle Uint8Array or other array-like types
|
|
76
|
+
bodyChunks.push(Buffer.from(chunk))
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Listen for body consumption
|
|
81
|
+
const onNewListener = (eventName) => {
|
|
82
|
+
if (eventName === 'data' || eventName === 'readable') {
|
|
83
|
+
bodyConsumed = true
|
|
84
|
+
|
|
85
|
+
// For 'data' events, add our own listener to collect chunks
|
|
86
|
+
if (eventName === 'data' && !dataListenerAdded && !dataReadStarted) {
|
|
87
|
+
dataListenerAdded = true
|
|
88
|
+
res.on('data', collectChunk)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// For 'readable' events, wrap the read() method
|
|
92
|
+
if (eventName === 'readable' && !originalRead && !dataListenerAdded && typeof res.read === 'function') {
|
|
93
|
+
originalRead = res.read
|
|
94
|
+
res.read = function () {
|
|
95
|
+
const chunk = originalRead.apply(this, arguments)
|
|
96
|
+
if (!dataListenerAdded) {
|
|
97
|
+
dataReadStarted = true
|
|
98
|
+
collectChunk(chunk)
|
|
99
|
+
}
|
|
100
|
+
return chunk
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
res.on('newListener', onNewListener)
|
|
107
|
+
|
|
108
|
+
// Cleanup function to restore original behavior
|
|
109
|
+
const cleanup = () => {
|
|
110
|
+
res.off('newListener', onNewListener)
|
|
111
|
+
res.off('data', collectChunk)
|
|
112
|
+
|
|
113
|
+
if (originalRead) {
|
|
114
|
+
res.read = originalRead
|
|
115
|
+
originalRead = null
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const notifyFinish = () => {
|
|
120
|
+
if (finishCalled) return
|
|
121
|
+
finishCalled = true
|
|
122
|
+
|
|
123
|
+
// Combine collected chunks into a single body
|
|
124
|
+
let body = null
|
|
125
|
+
if (bodyChunks?.length) {
|
|
126
|
+
const firstChunk = bodyChunks[0]
|
|
127
|
+
body = typeof firstChunk === 'string'
|
|
128
|
+
? bodyChunks.join('')
|
|
129
|
+
: Buffer.concat(bodyChunks)
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
responseFinishChannel.publish({ ctx, res, body })
|
|
133
|
+
cleanup()
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
res.once('end', notifyFinish)
|
|
137
|
+
res.once('close', notifyFinish)
|
|
138
|
+
|
|
139
|
+
return {
|
|
140
|
+
finalizeIfNeeded () {
|
|
141
|
+
if (!bodyConsumed) {
|
|
142
|
+
// Body not consumed, resume to complete the response
|
|
143
|
+
notifyFinish()
|
|
144
|
+
}
|
|
145
|
+
},
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
42
149
|
function patch (http, methodName) {
|
|
43
150
|
shimmer.wrap(http, methodName, instrumentRequest)
|
|
44
151
|
|
|
@@ -103,7 +210,18 @@ function patch (http, methodName) {
|
|
|
103
210
|
ctx.res = res
|
|
104
211
|
res.once('end', finish)
|
|
105
212
|
res.once(errorMonitor, finish)
|
|
106
|
-
|
|
213
|
+
|
|
214
|
+
const instrumentation = setupResponseInstrumentation(ctx, res)
|
|
215
|
+
|
|
216
|
+
if (!instrumentation) {
|
|
217
|
+
break
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
const result = emit.apply(this, arguments)
|
|
221
|
+
|
|
222
|
+
instrumentation.finalizeIfNeeded()
|
|
223
|
+
|
|
224
|
+
return result
|
|
107
225
|
}
|
|
108
226
|
case 'connect':
|
|
109
227
|
case 'upgrade':
|
|
@@ -129,6 +129,8 @@ function getTestEnvironmentOptions (config) {
|
|
|
129
129
|
return {}
|
|
130
130
|
}
|
|
131
131
|
|
|
132
|
+
const MAX_IGNORED_TEST_NAMES = 10
|
|
133
|
+
|
|
132
134
|
function getTestStats (testStatuses) {
|
|
133
135
|
return testStatuses.reduce((acc, testStatus) => {
|
|
134
136
|
acc[testStatus]++
|
|
@@ -136,6 +138,32 @@ function getTestStats (testStatuses) {
|
|
|
136
138
|
}, { pass: 0, fail: 0 })
|
|
137
139
|
}
|
|
138
140
|
|
|
141
|
+
/**
|
|
142
|
+
* @param {string[]} efdNames
|
|
143
|
+
* @param {string[]} quarantineNames
|
|
144
|
+
* @param {number} totalCount
|
|
145
|
+
*/
|
|
146
|
+
function logIgnoredFailuresSummary (efdNames, quarantineNames, totalCount) {
|
|
147
|
+
const names = []
|
|
148
|
+
for (const n of efdNames) {
|
|
149
|
+
names.push({ name: n, reason: 'Early Flake Detection' })
|
|
150
|
+
}
|
|
151
|
+
for (const n of quarantineNames) {
|
|
152
|
+
names.push({ name: n, reason: 'Quarantine' })
|
|
153
|
+
}
|
|
154
|
+
const shown = names.slice(0, MAX_IGNORED_TEST_NAMES)
|
|
155
|
+
const more = names.length - shown.length
|
|
156
|
+
const moreSuffix = more > 0 ? `\n ... and ${more} more` : ''
|
|
157
|
+
const list = shown.map(({ name, reason }) => ` • ${name} (${reason})`).join('\n')
|
|
158
|
+
const line = '-'.repeat(50)
|
|
159
|
+
// eslint-disable-next-line no-console -- Intentional user-facing message when exit code is flipped
|
|
160
|
+
console.warn(
|
|
161
|
+
`\n${line}\nDatadog Test Optimization\n${line}\n` +
|
|
162
|
+
`${totalCount} test failure(s) were ignored. Exit code set to 0.\n\n` +
|
|
163
|
+
`${list}${moreSuffix}\n`
|
|
164
|
+
)
|
|
165
|
+
}
|
|
166
|
+
|
|
139
167
|
function getWrappedEnvironment (BaseEnvironment, jestVersion) {
|
|
140
168
|
return class DatadogEnvironment extends BaseEnvironment {
|
|
141
169
|
constructor (config, context) {
|
|
@@ -576,6 +604,13 @@ function getWrappedEnvironment (BaseEnvironment, jestVersion) {
|
|
|
576
604
|
} else {
|
|
577
605
|
newTestsTestStatuses.set(testName, [status])
|
|
578
606
|
}
|
|
607
|
+
const testStatuses = newTestsTestStatuses.get(testName)
|
|
608
|
+
// Check if this is the last EFD retry.
|
|
609
|
+
// If it is, we'll set the failedAllTests flag to true if all the tests failed
|
|
610
|
+
if (testStatuses.length === earlyFlakeDetectionNumRetries + 1 &&
|
|
611
|
+
testStatuses.every(status => status === 'fail')) {
|
|
612
|
+
failedAllTests = true
|
|
613
|
+
}
|
|
579
614
|
}
|
|
580
615
|
}
|
|
581
616
|
|
|
@@ -994,11 +1029,18 @@ function getCliWrapper (isNewJestVersion) {
|
|
|
994
1029
|
coverageMap,
|
|
995
1030
|
numFailedTestSuites,
|
|
996
1031
|
numFailedTests,
|
|
1032
|
+
numRuntimeErrorTestSuites = 0,
|
|
997
1033
|
numTotalTests,
|
|
998
1034
|
numTotalTestSuites,
|
|
1035
|
+
runExecError,
|
|
1036
|
+
wasInterrupted,
|
|
999
1037
|
},
|
|
1000
1038
|
} = result
|
|
1001
1039
|
|
|
1040
|
+
const hasSuiteLevelFailures = numRuntimeErrorTestSuites > 0
|
|
1041
|
+
const hasRunLevelFailure = runExecError != null || wasInterrupted === true
|
|
1042
|
+
const mustNotFlipSuccess = hasSuiteLevelFailures || hasRunLevelFailure
|
|
1043
|
+
|
|
1002
1044
|
let testCodeCoverageLinesTotal
|
|
1003
1045
|
|
|
1004
1046
|
if (isUserCodeCoverageEnabled) {
|
|
@@ -1018,16 +1060,44 @@ function getCliWrapper (isNewJestVersion) {
|
|
|
1018
1060
|
* on flakiness (the test will be considered flaky), but you may choose to unblock the pipeline too.
|
|
1019
1061
|
*/
|
|
1020
1062
|
let numEfdFailedTestsToIgnore = 0
|
|
1063
|
+
const efdIgnoredNames = []
|
|
1064
|
+
const quarantineIgnoredNames = []
|
|
1065
|
+
|
|
1066
|
+
// Build fullName -> suite map from results (for EFD display)
|
|
1067
|
+
const fullNameToSuite = new Map()
|
|
1068
|
+
for (const { testResults, testFilePath } of result.results.testResults) {
|
|
1069
|
+
const suite = getTestSuitePath(testFilePath, result.globalConfig.rootDir)
|
|
1070
|
+
for (const { fullName } of testResults) {
|
|
1071
|
+
const name = testSuiteAbsolutePathsWithFastCheck.has(testFilePath)
|
|
1072
|
+
? fullName.replace(SEED_SUFFIX_RE, '')
|
|
1073
|
+
: fullName
|
|
1074
|
+
fullNameToSuite.set(name, suite)
|
|
1075
|
+
}
|
|
1076
|
+
}
|
|
1077
|
+
|
|
1078
|
+
/** @type {{ efdNames: string[], quarantineNames: string[], totalCount: number } | undefined} */
|
|
1079
|
+
let ignoredFailuresSummary
|
|
1021
1080
|
if (isEarlyFlakeDetectionEnabled) {
|
|
1022
|
-
for (const testStatuses of newTestsTestStatuses
|
|
1081
|
+
for (const [testName, testStatuses] of newTestsTestStatuses) {
|
|
1023
1082
|
const { pass, fail } = getTestStats(testStatuses)
|
|
1024
1083
|
if (pass > 0) { // as long as one passes, we'll consider the test passed
|
|
1025
1084
|
numEfdFailedTestsToIgnore += fail
|
|
1085
|
+
const suite = fullNameToSuite.get(testName)
|
|
1086
|
+
efdIgnoredNames.push(suite ? `${suite} › ${testName}` : testName)
|
|
1026
1087
|
}
|
|
1027
1088
|
}
|
|
1028
1089
|
// If every test that failed was an EFD retry, we'll consider the suite passed
|
|
1029
|
-
if (
|
|
1090
|
+
if (
|
|
1091
|
+
!mustNotFlipSuccess &&
|
|
1092
|
+
numEfdFailedTestsToIgnore !== 0 &&
|
|
1093
|
+
result.results.numFailedTests === numEfdFailedTestsToIgnore
|
|
1094
|
+
) {
|
|
1030
1095
|
result.results.success = true
|
|
1096
|
+
ignoredFailuresSummary = {
|
|
1097
|
+
efdNames: efdIgnoredNames,
|
|
1098
|
+
quarantineNames: [],
|
|
1099
|
+
totalCount: numEfdFailedTestsToIgnore,
|
|
1100
|
+
}
|
|
1031
1101
|
}
|
|
1032
1102
|
}
|
|
1033
1103
|
|
|
@@ -1062,8 +1132,10 @@ function getCliWrapper (isNewJestVersion) {
|
|
|
1062
1132
|
// This uses `attempt_to_fix` because this is always the main process and it's not formatted in camelCase
|
|
1063
1133
|
if (testManagementTest?.attempt_to_fix && (testManagementTest?.quarantined || testManagementTest?.disabled)) {
|
|
1064
1134
|
numFailedQuarantinedOrDisabledAttemptedToFixTests++
|
|
1135
|
+
quarantineIgnoredNames.push(`${testSuite} › ${testName}`)
|
|
1065
1136
|
} else if (testManagementTest?.quarantined) {
|
|
1066
1137
|
numFailedQuarantinedTests++
|
|
1138
|
+
quarantineIgnoredNames.push(`${testSuite} › ${testName}`)
|
|
1067
1139
|
}
|
|
1068
1140
|
}
|
|
1069
1141
|
|
|
@@ -1071,22 +1143,42 @@ function getCliWrapper (isNewJestVersion) {
|
|
|
1071
1143
|
// Note that if a test is attempted to fix,
|
|
1072
1144
|
// it's considered quarantined both if it's disabled and if it's quarantined
|
|
1073
1145
|
// (it'll run but its status is ignored)
|
|
1146
|
+
// Skip if EFD block already flipped (to avoid logging twice)
|
|
1074
1147
|
if (
|
|
1148
|
+
!result.results.success &&
|
|
1149
|
+
!mustNotFlipSuccess &&
|
|
1075
1150
|
(numFailedQuarantinedOrDisabledAttemptedToFixTests !== 0 || numFailedQuarantinedTests !== 0) &&
|
|
1076
1151
|
result.results.numFailedTests ===
|
|
1077
1152
|
numFailedQuarantinedTests + numFailedQuarantinedOrDisabledAttemptedToFixTests
|
|
1078
1153
|
) {
|
|
1079
1154
|
result.results.success = true
|
|
1155
|
+
ignoredFailuresSummary = {
|
|
1156
|
+
efdNames: [],
|
|
1157
|
+
quarantineNames: quarantineIgnoredNames,
|
|
1158
|
+
totalCount: numFailedQuarantinedTests + numFailedQuarantinedOrDisabledAttemptedToFixTests,
|
|
1159
|
+
}
|
|
1080
1160
|
}
|
|
1081
1161
|
}
|
|
1082
1162
|
|
|
1083
1163
|
// Combined check: if all failed tests are accounted for by EFD (flaky retries) and/or quarantine,
|
|
1084
1164
|
// we should consider the suite passed even when neither check alone covers all failures.
|
|
1085
|
-
if (
|
|
1165
|
+
if (
|
|
1166
|
+
!result.results.success &&
|
|
1167
|
+
!mustNotFlipSuccess &&
|
|
1168
|
+
(isEarlyFlakeDetectionEnabled || isTestManagementTestsEnabled)
|
|
1169
|
+
) {
|
|
1086
1170
|
const totalIgnoredFailures =
|
|
1087
1171
|
numEfdFailedTestsToIgnore + numFailedQuarantinedTests + numFailedQuarantinedOrDisabledAttemptedToFixTests
|
|
1088
|
-
if (
|
|
1172
|
+
if (
|
|
1173
|
+
totalIgnoredFailures !== 0 &&
|
|
1174
|
+
result.results.numFailedTests === totalIgnoredFailures
|
|
1175
|
+
) {
|
|
1089
1176
|
result.results.success = true
|
|
1177
|
+
ignoredFailuresSummary = {
|
|
1178
|
+
efdNames: efdIgnoredNames,
|
|
1179
|
+
quarantineNames: quarantineIgnoredNames,
|
|
1180
|
+
totalCount: totalIgnoredFailures,
|
|
1181
|
+
}
|
|
1090
1182
|
}
|
|
1091
1183
|
}
|
|
1092
1184
|
|
|
@@ -1144,6 +1236,14 @@ function getCliWrapper (isNewJestVersion) {
|
|
|
1144
1236
|
})
|
|
1145
1237
|
}
|
|
1146
1238
|
|
|
1239
|
+
if (ignoredFailuresSummary) {
|
|
1240
|
+
logIgnoredFailuresSummary(
|
|
1241
|
+
ignoredFailuresSummary.efdNames,
|
|
1242
|
+
ignoredFailuresSummary.quarantineNames,
|
|
1243
|
+
ignoredFailuresSummary.totalCount
|
|
1244
|
+
)
|
|
1245
|
+
}
|
|
1246
|
+
|
|
1147
1247
|
numSkippedSuites = 0
|
|
1148
1248
|
|
|
1149
1249
|
return result
|
|
@@ -271,6 +271,7 @@ function getOnTestEndHandler (config) {
|
|
|
271
271
|
const testStatuses = testsStatuses.get(testName)
|
|
272
272
|
|
|
273
273
|
const isLastAttempt = testStatuses.length === config.testManagementAttemptToFixRetries + 1
|
|
274
|
+
const isLastEfdRetry = testStatuses.length === config.earlyFlakeDetectionNumRetries + 1
|
|
274
275
|
|
|
275
276
|
if (test._ddIsAttemptToFix && isLastAttempt) {
|
|
276
277
|
if (testStatuses.includes('fail')) {
|
|
@@ -283,6 +284,11 @@ function getOnTestEndHandler (config) {
|
|
|
283
284
|
}
|
|
284
285
|
}
|
|
285
286
|
|
|
287
|
+
if (test._ddIsEfdRetry && isLastEfdRetry &&
|
|
288
|
+
testStatuses.every(status => status === 'fail')) {
|
|
289
|
+
hasFailedAllRetries = true
|
|
290
|
+
}
|
|
291
|
+
|
|
286
292
|
const isAttemptToFixRetry = test._ddIsAttemptToFix && testStatuses.length > 1
|
|
287
293
|
const isAtrRetry = config.isFlakyTestRetriesEnabled &&
|
|
288
294
|
!test._ddIsAttemptToFix &&
|