dd-trace 5.117.0 → 5.118.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/ci/diagnose.js +162 -21
- package/ci/init.js +0 -1
- package/ci/runbook.md +187 -180
- package/ci/test-optimization-validation/approval-artifacts.js +3 -1
- package/ci/test-optimization-validation/approval.js +52 -15
- package/ci/test-optimization-validation/ci-command-candidate.js +26 -32
- package/ci/test-optimization-validation/ci-discovery.js +1 -1
- package/ci/test-optimization-validation/ci-package-scripts.js +141 -0
- package/ci/test-optimization-validation/ci-remediation.js +112 -39
- package/ci/test-optimization-validation/cli.js +516 -681
- package/ci/test-optimization-validation/command-blocker.js +188 -33
- package/ci/test-optimization-validation/command-output-policy.js +3 -26
- package/ci/test-optimization-validation/command-runner.js +189 -245
- package/ci/test-optimization-validation/environment.js +90 -0
- package/ci/test-optimization-validation/executable.js +159 -388
- package/ci/test-optimization-validation/framework-adapters/cucumber.js +119 -0
- package/ci/test-optimization-validation/framework-adapters/cypress.js +107 -0
- package/ci/test-optimization-validation/framework-adapters/playwright.js +181 -0
- package/ci/test-optimization-validation/generated-files.js +75 -13
- package/ci/test-optimization-validation/generated-test-contract.js +283 -0
- package/ci/test-optimization-validation/generated-verifier.js +49 -16
- package/ci/test-optimization-validation/literal-environment.js +57 -0
- package/ci/test-optimization-validation/manifest-loader.js +2 -22
- package/ci/test-optimization-validation/manifest-scaffold.js +816 -515
- package/ci/test-optimization-validation/manifest-schema.js +494 -702
- package/ci/test-optimization-validation/offline-fixtures.js +4 -1
- package/ci/test-optimization-validation/plan-writer.js +243 -984
- package/ci/test-optimization-validation/preflight-runner.js +49 -55
- package/ci/test-optimization-validation/redaction.js +2 -1
- package/ci/test-optimization-validation/report-writer.js +391 -1357
- package/ci/test-optimization-validation/result-semantics.js +86 -0
- package/ci/test-optimization-validation/runner-command.js +249 -0
- package/ci/test-optimization-validation/runner-contract.js +664 -0
- package/ci/test-optimization-validation/scenarios/auto-test-retries.js +29 -3
- package/ci/test-optimization-validation/scenarios/basic-reporting.js +240 -543
- package/ci/test-optimization-validation/scenarios/ci-wiring.js +450 -670
- package/ci/test-optimization-validation/scenarios/early-flake-detection.js +4 -3
- package/ci/test-optimization-validation/scenarios/helpers.js +67 -9
- package/ci/test-optimization-validation/scenarios/test-management.js +4 -3
- package/ci/test-optimization-validation/source-text.js +87 -0
- package/ci/test-optimization-validation/test-output.js +12 -22
- package/ext/tags.d.ts +1 -0
- package/index.d.ts +7 -0
- package/package.json +2 -2
- package/packages/datadog-instrumentations/src/helpers/hooks.js +2 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/index.js +1 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/openai-agents.js +31 -0
- package/packages/datadog-instrumentations/src/openai-agents.js +159 -0
- package/packages/datadog-plugin-openai-agents/src/index.js +74 -0
- package/packages/datadog-plugin-openai-agents/src/integration.js +503 -0
- package/packages/datadog-plugin-openai-agents/src/processor.js +108 -0
- package/packages/datadog-plugin-openai-agents/src/util.js +60 -0
- package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +48 -49
- package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +161 -33
- package/packages/dd-trace/src/ci-visibility/test-optimization-http-cache-schema.js +6 -4
- package/packages/dd-trace/src/config/generated-config-types.d.ts +2 -0
- package/packages/dd-trace/src/config/supported-configurations.json +7 -0
- package/packages/dd-trace/src/encode/span-stats.js +7 -1
- package/packages/dd-trace/src/exporter.js +16 -9
- package/packages/dd-trace/src/llmobs/index.js +9 -1
- package/packages/dd-trace/src/llmobs/plugins/ai/util.js +104 -22
- package/packages/dd-trace/src/llmobs/plugins/openai/index.js +5 -9
- package/packages/dd-trace/src/llmobs/plugins/openai/utils.js +20 -0
- package/packages/dd-trace/src/llmobs/plugins/openai-agents/utils.js +321 -0
- package/packages/dd-trace/src/llmobs/sdk.js +2 -1
- package/packages/dd-trace/src/llmobs/span_processor.js +7 -2
- package/packages/dd-trace/src/llmobs/tagger.js +23 -3
- package/packages/dd-trace/src/llmobs/util.js +56 -3
- package/packages/dd-trace/src/openfeature/agentless_configuration_source.js +3 -6
- package/packages/dd-trace/src/openfeature/configuration_source.js +5 -8
- package/packages/dd-trace/src/opentelemetry/metrics/otlp_span_stats_transformer.js +5 -1
- package/packages/dd-trace/src/plugins/ci_plugin.js +9 -3
- package/packages/dd-trace/src/plugins/index.js +1 -0
- package/packages/dd-trace/src/plugins/outbound.js +4 -1
- package/packages/dd-trace/src/span_processor.js +1 -1
- package/packages/dd-trace/src/span_stats.js +22 -4
- package/ci/test-optimization-validation/command-suitability.js +0 -471
- package/ci/test-optimization-validation/init-probe-preload.js +0 -163
- package/ci/test-optimization-validation/init-probe.js +0 -246
- package/ci/test-optimization-validation/late-initialization.js +0 -63
- package/ci/test-optimization-validation/local-command.js +0 -163
- package/ci/test-optimization-validation/setup-runner.js +0 -97
- package/ci/test-optimization-validation-manifest.schema.json +0 -1
|
@@ -1,657 +1,354 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
const
|
|
4
|
-
const path = require('path')
|
|
5
|
-
|
|
3
|
+
const { getCommandBlocker } = require('../command-blocker')
|
|
6
4
|
const { runCommand } = require('../command-runner')
|
|
7
|
-
const {
|
|
5
|
+
const { eventsOfType } = require('../payload-normalizer')
|
|
6
|
+
const { getBasicCommand } = require('../runner-command')
|
|
8
7
|
|
|
9
8
|
const {
|
|
10
9
|
basicEventEvidence,
|
|
11
|
-
error,
|
|
10
|
+
error: scenarioError,
|
|
12
11
|
failWithDebugRerun,
|
|
13
|
-
findInterestingLines,
|
|
14
12
|
frameworkOutDir,
|
|
15
13
|
hasAllBasicEventTypes,
|
|
16
14
|
inconclusive,
|
|
17
15
|
pass,
|
|
18
16
|
runInstrumentedCommand,
|
|
19
|
-
tailInterestingLines,
|
|
20
17
|
} = require('./helpers')
|
|
21
18
|
|
|
19
|
+
/**
|
|
20
|
+
* Checks whether controlled initialization reports one real project test.
|
|
21
|
+
*
|
|
22
|
+
* @param {object} input scenario inputs
|
|
23
|
+
* @param {object} input.framework framework manifest entry
|
|
24
|
+
* @param {string} input.out validation output directory
|
|
25
|
+
* @param {object} input.options validator options
|
|
26
|
+
* @returns {Promise<object>} scenario result
|
|
27
|
+
*/
|
|
22
28
|
async function runBasicReporting ({ framework, out, options }) {
|
|
23
29
|
const scenarioName = 'basic-reporting'
|
|
30
|
+
const command = getBasicCommand(framework)
|
|
31
|
+
let outDir
|
|
32
|
+
|
|
24
33
|
try {
|
|
25
|
-
const
|
|
26
|
-
|
|
34
|
+
const run = await runInstrumentedCommand({
|
|
35
|
+
allowMissingInitialization: true,
|
|
36
|
+
command,
|
|
27
37
|
framework,
|
|
38
|
+
options,
|
|
28
39
|
out,
|
|
29
40
|
scenarioName,
|
|
30
|
-
command,
|
|
31
|
-
options,
|
|
32
|
-
allowMissingInitialization: true,
|
|
33
41
|
})
|
|
34
|
-
|
|
42
|
+
outDir = run.outDir
|
|
43
|
+
const complete = hasAllBasicEventTypes(run.events)
|
|
44
|
+
const selector = getSelectorEvidence(framework, run.events)
|
|
35
45
|
const evidence = {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
46
|
+
...basicEventEvidence(run.events),
|
|
47
|
+
commandExitCode: run.result.exitCode,
|
|
48
|
+
commandOutputSummary: summarizeTestOutput(run.result.stdout, run.result.stderr),
|
|
49
|
+
commandTimedOut: run.result.timedOut,
|
|
50
|
+
foundationalReportingEstablished: complete &&
|
|
51
|
+
run.offline.initialized &&
|
|
52
|
+
run.offline.inputs.settings?.status === 'loaded' &&
|
|
53
|
+
run.result.exitCode === 0 &&
|
|
54
|
+
!run.result.timedOut,
|
|
55
|
+
offlineExporterInitialized: run.offline.initialized,
|
|
41
56
|
preflight: summarizePreflight(framework.preflight),
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
...basicEventEvidence(events),
|
|
57
|
+
reportingPath: complete ? 'validator-direct-runner' : 'none',
|
|
58
|
+
selector,
|
|
59
|
+
settingsLoadedFromCache: run.offline.inputs.settings?.status === 'loaded',
|
|
46
60
|
}
|
|
61
|
+
evidence.foundationalReportingEstablished = evidence.foundationalReportingEstablished && selector.verified
|
|
47
62
|
|
|
48
|
-
if (
|
|
49
|
-
return
|
|
63
|
+
if (run.result.timedOut) {
|
|
64
|
+
return inconclusive(
|
|
50
65
|
framework,
|
|
51
66
|
scenarioName,
|
|
52
|
-
|
|
67
|
+
'The initialized direct test exceeded its approved timeout. Basic Reporting remains incomplete.',
|
|
68
|
+
{ ...evidence, commandFailure: classifyFailure(framework, run.result) },
|
|
53
69
|
outDir
|
|
54
70
|
)
|
|
55
71
|
}
|
|
56
72
|
|
|
57
|
-
if (!
|
|
58
|
-
|
|
59
|
-
evidence.commandFailure = summarizeCommandFailure(result, evidence)
|
|
60
|
-
}
|
|
61
|
-
const eventLevelFailure = getMissingEventDiagnosis({ framework, result, evidence })
|
|
62
|
-
evidence.eventLevelFailure = eventLevelFailure
|
|
63
|
-
|
|
64
|
-
return failBasicReportingWithDebugRerun({
|
|
65
|
-
command,
|
|
66
|
-
diagnosis: eventLevelFailure.summary,
|
|
67
|
-
evidence,
|
|
73
|
+
if (!selector.verified) {
|
|
74
|
+
return inconclusive(
|
|
68
75
|
framework,
|
|
69
|
-
options,
|
|
70
|
-
out,
|
|
71
|
-
outDir,
|
|
72
76
|
scenarioName,
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
77
|
+
'The repository test wrapper ran, but captured events did not prove that it honored the approved ' +
|
|
78
|
+
'representative test file. Basic Reporting remains incomplete.',
|
|
79
|
+
{
|
|
80
|
+
...evidence,
|
|
81
|
+
recommendation: 'Use a repository wrapper that forwards the selected test file, or use a direct ' +
|
|
82
|
+
'framework runner whose selector can be bounded by the validator.',
|
|
83
|
+
},
|
|
84
|
+
outDir
|
|
85
|
+
)
|
|
76
86
|
}
|
|
77
87
|
|
|
78
|
-
if (
|
|
88
|
+
if (evidence.foundationalReportingEstablished) {
|
|
79
89
|
return pass(
|
|
80
90
|
framework,
|
|
81
91
|
scenarioName,
|
|
82
|
-
'
|
|
92
|
+
'The direct test emitted session, module, suite, and test events.',
|
|
83
93
|
evidence,
|
|
84
94
|
outDir
|
|
85
95
|
)
|
|
86
96
|
}
|
|
87
97
|
|
|
88
|
-
if (
|
|
89
|
-
|
|
90
|
-
evidence.commandExitMatchesPreflight = true
|
|
91
|
-
return pass(
|
|
98
|
+
if (framework.preflight?.observedTestCount === null && evidence.testEvents === 0) {
|
|
99
|
+
return inconclusive(
|
|
92
100
|
framework,
|
|
93
101
|
scenarioName,
|
|
94
|
-
'
|
|
95
|
-
|
|
96
|
-
|
|
102
|
+
'The clean runner exited successfully without a parseable test count, and the initialized run emitted no ' +
|
|
103
|
+
'test event. Basic Reporting remains incomplete because the validator cannot prove that a test executed.',
|
|
104
|
+
{
|
|
105
|
+
...evidence,
|
|
106
|
+
recommendation: 'Use a representative test command that reports an executed-test count, or inspect the ' +
|
|
107
|
+
'runner output and debug artifact before attributing the missing event to dd-trace.',
|
|
108
|
+
},
|
|
97
109
|
outDir
|
|
98
110
|
)
|
|
99
111
|
}
|
|
100
112
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
113
|
+
if (!run.offline.initialized || !evidence.settingsLoadedFromCache) {
|
|
114
|
+
return failWithDebugRerun({
|
|
115
|
+
command,
|
|
116
|
+
diagnosis: run.offline.initialized
|
|
117
|
+
? 'The clean test passed, but Test Optimization did not load the validator-owned offline settings.'
|
|
118
|
+
: 'The clean test passed, but the offline Test Optimization exporter did not initialize.',
|
|
119
|
+
evidence: {
|
|
120
|
+
...evidence,
|
|
121
|
+
possibleLibraryBug: true,
|
|
122
|
+
recommendation: 'Inspect the debug artifact for dd-trace initialization errors and report it with the ' +
|
|
123
|
+
'framework and dd-trace versions.',
|
|
124
|
+
},
|
|
108
125
|
framework,
|
|
126
|
+
options,
|
|
127
|
+
out,
|
|
128
|
+
outDir,
|
|
109
129
|
scenarioName,
|
|
110
|
-
|
|
111
|
-
|
|
130
|
+
})
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (run.result.exitCode !== 0) {
|
|
134
|
+
evidence.commandFailure = classifyFailure(framework, run.result)
|
|
135
|
+
const confirmation = await runCleanConfirmation({ command, framework, options, out })
|
|
136
|
+
evidence.cleanConfirmation = confirmation.evidence
|
|
137
|
+
if (!confirmation.evidence.matchesPreflight) {
|
|
138
|
+
return inconclusive(
|
|
139
|
+
framework,
|
|
140
|
+
scenarioName,
|
|
141
|
+
'The clean baseline changed between runs, so the initialized failure cannot be attributed to dd-trace.',
|
|
142
|
+
evidence,
|
|
143
|
+
outDir,
|
|
144
|
+
confirmation.artifacts
|
|
145
|
+
)
|
|
146
|
+
}
|
|
147
|
+
const result = await failWithDebugRerun({
|
|
148
|
+
command,
|
|
149
|
+
diagnosis: complete
|
|
150
|
+
? `The test passed twice without Datadog but exited ${run.result.exitCode} when initialized, after ` +
|
|
151
|
+
'emitting ' +
|
|
152
|
+
'the complete event hierarchy. This is a possible dd-trace compatibility bug.'
|
|
153
|
+
: `The test passed twice without Datadog but exited ${run.result.exitCode} when initialized and did not ` +
|
|
154
|
+
'emit the complete event hierarchy. This is a possible dd-trace adapter or compatibility bug.',
|
|
155
|
+
evidence: {
|
|
156
|
+
...evidence,
|
|
157
|
+
possibleLibraryBug: true,
|
|
158
|
+
recommendation: 'Attach the debug run, clean confirmations, framework version, and dd-trace version to ' +
|
|
159
|
+
'the engineering investigation.',
|
|
160
|
+
},
|
|
161
|
+
framework,
|
|
162
|
+
options,
|
|
163
|
+
out,
|
|
112
164
|
outDir,
|
|
113
|
-
|
|
114
|
-
)
|
|
165
|
+
scenarioName,
|
|
166
|
+
})
|
|
167
|
+
result.artifacts.push(...confirmation.artifacts)
|
|
168
|
+
return result
|
|
115
169
|
}
|
|
116
170
|
|
|
117
|
-
const
|
|
171
|
+
const missing = getMissingLevels(evidence)
|
|
172
|
+
return failWithDebugRerun({
|
|
118
173
|
command,
|
|
119
|
-
diagnosis:
|
|
120
|
-
|
|
174
|
+
diagnosis: 'The direct test passed cleanly and while initialized, but no complete Test Optimization event ' +
|
|
175
|
+
`hierarchy was captured. Missing levels: ${missing.join(', ')}. This is a possible dd-trace adapter bug.`,
|
|
176
|
+
evidence: {
|
|
177
|
+
...evidence,
|
|
178
|
+
missingEventLevels: missing,
|
|
179
|
+
possibleLibraryBug: true,
|
|
180
|
+
recommendation: 'Inspect the debug artifact for initialization and adapter errors. Include it with the ' +
|
|
181
|
+
'framework and dd-trace versions in an engineering investigation.',
|
|
182
|
+
},
|
|
121
183
|
framework,
|
|
122
184
|
options,
|
|
123
185
|
out,
|
|
124
186
|
outDir,
|
|
125
187
|
scenarioName,
|
|
126
188
|
})
|
|
127
|
-
|
|
189
|
+
} catch (error) {
|
|
190
|
+
const failure = scenarioError(framework, scenarioName, error, outDir || error?.artifactDirectory)
|
|
191
|
+
failure.diagnosis = `Basic Reporting could not complete: ${error?.message || error}`
|
|
192
|
+
failure.evidence.validationIncomplete = true
|
|
128
193
|
return failure
|
|
129
|
-
} catch (err) {
|
|
130
|
-
return error(framework, scenarioName, err)
|
|
131
194
|
}
|
|
132
195
|
}
|
|
133
196
|
|
|
134
197
|
/**
|
|
135
|
-
* Repeats the
|
|
198
|
+
* Repeats the direct clean command after an initialized-only failure.
|
|
136
199
|
*
|
|
137
200
|
* @param {object} input confirmation inputs
|
|
138
|
-
* @param {object} input.command
|
|
139
|
-
* @param {object} input.framework
|
|
201
|
+
* @param {object} input.command direct command
|
|
202
|
+
* @param {object} input.framework framework entry
|
|
140
203
|
* @param {object} input.options validator options
|
|
141
|
-
* @param {string} input.out
|
|
142
|
-
* @
|
|
143
|
-
* @returns {Promise<{artifacts: string[], evidence: object, result: object}>} clean confirmation outcome
|
|
204
|
+
* @param {string} input.out output root
|
|
205
|
+
* @returns {Promise<{artifacts: string[], evidence: object}>} confirmation evidence
|
|
144
206
|
*/
|
|
145
|
-
async function runCleanConfirmation ({ command, framework, options, out
|
|
146
|
-
const outDir = frameworkOutDir(out, framework,
|
|
147
|
-
const result = await runCommand(
|
|
207
|
+
async function runCleanConfirmation ({ command, framework, options, out }) {
|
|
208
|
+
const outDir = frameworkOutDir(out, framework, 'basic-reporting-clean-confirmation')
|
|
209
|
+
const result = await runCommand(command, {
|
|
148
210
|
artifactRoot: out,
|
|
149
211
|
envMode: 'clean',
|
|
150
|
-
label: `${framework.id}
|
|
212
|
+
label: `${framework.id}:basic-reporting:clean-confirmation`,
|
|
151
213
|
outDir,
|
|
152
214
|
repositoryRoot: options.repositoryRoot,
|
|
153
215
|
requireExecutableApproval: options.requireExecutableApproval,
|
|
154
216
|
verbose: options.verbose,
|
|
155
217
|
})
|
|
156
|
-
const exitMatchesPreflight = !result.timedOut && matchesPreflightExitCode(framework.preflight, result.exitCode)
|
|
157
218
|
return {
|
|
158
219
|
artifacts: Object.values(result.artifacts),
|
|
159
|
-
result,
|
|
160
220
|
evidence: {
|
|
161
|
-
ran: true,
|
|
162
221
|
exitCode: result.exitCode,
|
|
222
|
+
matchesPreflight: !result.timedOut &&
|
|
223
|
+
framework.preflight?.exitCode === result.exitCode,
|
|
163
224
|
timedOut: result.timedOut,
|
|
164
|
-
exitMatchesPreflight,
|
|
165
|
-
commandOutputSummary: summarizeTestOutput(result.stdout, result.stderr),
|
|
166
225
|
},
|
|
167
226
|
}
|
|
168
227
|
}
|
|
169
228
|
|
|
170
229
|
/**
|
|
171
|
-
*
|
|
230
|
+
* Returns a compact preflight summary.
|
|
172
231
|
*
|
|
173
|
-
* @param {object} preflight
|
|
174
|
-
* @
|
|
175
|
-
* @param {object} cleanConfirmation repeated clean command result
|
|
176
|
-
* @returns {string} customer-facing diagnosis
|
|
232
|
+
* @param {object} preflight preflight evidence
|
|
233
|
+
* @returns {object} compact evidence
|
|
177
234
|
*/
|
|
178
|
-
function
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
:
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
235
|
+
function summarizePreflight (preflight = {}) {
|
|
236
|
+
return {
|
|
237
|
+
durationMs: preflight.durationMs,
|
|
238
|
+
exitCode: preflight.exitCode,
|
|
239
|
+
observedTestCount: preflight.observedTestCount,
|
|
240
|
+
ran: preflight.ran === true,
|
|
241
|
+
selectorVerification: preflight.selectorVerification,
|
|
242
|
+
timedOut: preflight.timedOut === true,
|
|
243
|
+
}
|
|
185
244
|
}
|
|
186
245
|
|
|
187
246
|
/**
|
|
188
|
-
*
|
|
247
|
+
* Verifies repository wrapper scope from captured test source files.
|
|
189
248
|
*
|
|
190
|
-
* @param {object}
|
|
191
|
-
* @param {object}
|
|
192
|
-
* @returns {
|
|
249
|
+
* @param {object} framework framework manifest entry
|
|
250
|
+
* @param {object[]} events normalized captured events
|
|
251
|
+
* @returns {object} selector verification evidence
|
|
193
252
|
*/
|
|
194
|
-
function
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
function getBasicReportingCommand (framework) {
|
|
201
|
-
return getLocalValidationCommand(framework, framework.existingTestCommand)
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
async function failBasicReportingWithDebugRerun (options) {
|
|
205
|
-
const failure = await failWithDebugRerun(options)
|
|
206
|
-
return refineBasicReportingFailure(failure)
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
function refineBasicReportingFailure (failure) {
|
|
210
|
-
const evidence = failure.evidence || {}
|
|
211
|
-
if (['dd-trace-preload-failed', 'command-setup-failed'].includes(evidence.eventLevelFailure?.kind)) {
|
|
212
|
-
failure.status = 'error'
|
|
213
|
-
evidence.localDiagnosis = evidence.eventLevelFailure
|
|
214
|
-
return failure
|
|
253
|
+
function getSelectorEvidence (framework, events) {
|
|
254
|
+
const mode = framework.validation?.selectorScope
|
|
255
|
+
const expectedTestFile = framework.validation?.testFile
|
|
256
|
+
if (mode === 'bounded_direct_runner') {
|
|
257
|
+
return { mode, verified: true }
|
|
215
258
|
}
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
if (!diagnosis) return failure
|
|
219
|
-
|
|
220
|
-
failure.diagnosis = diagnosis.summary
|
|
221
|
-
evidence.localDiagnosis = diagnosis
|
|
222
|
-
|
|
223
|
-
if (evidence.eventLevelFailure) {
|
|
224
|
-
evidence.eventLevelFailure = {
|
|
225
|
-
...evidence.eventLevelFailure,
|
|
226
|
-
summary: diagnosis.summary,
|
|
227
|
-
recommendation: diagnosis.recommendation,
|
|
228
|
-
signals: diagnosis.signals,
|
|
229
|
-
}
|
|
259
|
+
if (mode !== 'instrumented_event_identity') {
|
|
260
|
+
return { mode: mode || 'missing', verified: false }
|
|
230
261
|
}
|
|
231
262
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
const testOutputSummary = summarizeTestOutput(
|
|
242
|
-
(evidence.commandOutputSummary || []).join('\n'),
|
|
243
|
-
(debugRerun.stdoutExcerpt || []).join('\n')
|
|
244
|
-
)
|
|
245
|
-
const testsRan = commandOutputShowsTestsRan(testOutputSummary)
|
|
246
|
-
const debugLine = findDebugLine(debugRerun, /dd-trace is not initialized in a package manager/i)
|
|
247
|
-
const noDebugEvents = !hasAnyTestOptimizationEvent(debugRerun)
|
|
248
|
-
|
|
249
|
-
if (testsRan && debugLine && noDebugEvents) {
|
|
250
|
-
return {
|
|
251
|
-
kind: 'tests-ran-tracer-not-initialized',
|
|
252
|
-
summary: 'The selected command ran tests, but no Test Optimization events reached the offline event artifact. ' +
|
|
253
|
-
`The debug rerun printed "${debugLine}", which means the preload executed in the package-manager ` +
|
|
254
|
-
'wrapper without producing Test Optimization events from the test process.',
|
|
255
|
-
recommendation: 'Try a direct test-runner command, or verify NODE_OPTIONS with dd-trace/ci/init reaches the ' +
|
|
256
|
-
'final test process rather than only the package-manager wrapper.',
|
|
257
|
-
signals: getDebugSignals({
|
|
258
|
-
debugLine,
|
|
259
|
-
debugRerun,
|
|
260
|
-
testOutputSummary,
|
|
261
|
-
}),
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
if (testsRan && noDebugEvents) {
|
|
266
|
-
return {
|
|
267
|
-
kind: 'tests-ran-no-test-optimization-events',
|
|
268
|
-
summary: 'The selected command ran tests, but no Test Optimization events reached the offline event artifact. ' +
|
|
269
|
-
'The debug rerun did not emit Test Optimization events either.',
|
|
270
|
-
recommendation: 'Inspect the debug rerun excerpt for tracer initialization or offline exporter errors, then ' +
|
|
271
|
-
'verify NODE_OPTIONS with dd-trace/ci/init reaches the final test process.',
|
|
272
|
-
signals: getDebugSignals({
|
|
273
|
-
debugRerun,
|
|
274
|
-
testOutputSummary,
|
|
275
|
-
}),
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
if (debugLine && noDebugEvents) {
|
|
280
|
-
return {
|
|
281
|
-
kind: 'tracer-not-initialized',
|
|
282
|
-
summary: `${currentDiagnosis} The debug rerun printed "${debugLine}".`,
|
|
283
|
-
recommendation: 'Verify NODE_OPTIONS with dd-trace/ci/init reaches the final test process.',
|
|
284
|
-
signals: getDebugSignals({
|
|
285
|
-
debugLine,
|
|
286
|
-
debugRerun,
|
|
287
|
-
testOutputSummary,
|
|
288
|
-
}),
|
|
289
|
-
}
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
function shouldRunDebugRerun (eventLevelFailure, result) {
|
|
294
|
-
return result.timedOut !== true &&
|
|
295
|
-
eventLevelFailure.kind !== 'vitest-benchmark' &&
|
|
296
|
-
eventLevelFailure.kind !== 'custom-jest-runner'
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
function matchesPreflightExitCode (preflight, exitCode) {
|
|
300
|
-
return preflight?.ran === true &&
|
|
301
|
-
Number.isInteger(preflight.exitCode) &&
|
|
302
|
-
preflight.exitCode === exitCode
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
function summarizePreflight (preflight) {
|
|
306
|
-
if (!preflight || preflight.ran !== true) {
|
|
307
|
-
return {
|
|
308
|
-
ran: false,
|
|
309
|
-
reason: 'No dd-trace-less preflight result was recorded in the manifest.',
|
|
310
|
-
}
|
|
311
|
-
}
|
|
263
|
+
const tests = eventsOfType(events, 'test')
|
|
264
|
+
const sourceFiles = tests.map(test => test.testSourceFile).filter(Boolean)
|
|
265
|
+
const matchingTestEvents = sourceFiles.filter(filename => {
|
|
266
|
+
return sourceFilesMatch(filename, expectedTestFile)
|
|
267
|
+
}).length
|
|
268
|
+
const differentSourceFiles = [...new Set(sourceFiles.filter(filename => {
|
|
269
|
+
return !sourceFilesMatch(filename, expectedTestFile)
|
|
270
|
+
}))].slice(0, 5)
|
|
271
|
+
const testEventsWithoutSourceFile = tests.length - sourceFiles.length
|
|
312
272
|
|
|
313
273
|
return {
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
274
|
+
differentSourceFiles,
|
|
275
|
+
expectedTestFile,
|
|
276
|
+
matchingTestEvents,
|
|
277
|
+
mode,
|
|
278
|
+
testEventsWithoutSourceFile,
|
|
279
|
+
verified: matchingTestEvents > 0 &&
|
|
280
|
+
differentSourceFiles.length === 0 &&
|
|
281
|
+
testEventsWithoutSourceFile === 0,
|
|
319
282
|
}
|
|
320
283
|
}
|
|
321
284
|
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
function commandOutputShowsTestsRan (lines) {
|
|
338
|
-
return lines.some(line => {
|
|
339
|
-
return /\b\d+\s+(?:passing|passed)\b/i.test(line) ||
|
|
340
|
-
/\btests?\b.*\bpassed\b/i.test(line) ||
|
|
341
|
-
/\bSuccessfully ran target\b.*\btest\b/i.test(line) ||
|
|
342
|
-
/\bsuccess:\s*[1-9]\d*\b/i.test(line) ||
|
|
343
|
-
/\bfailed:\s*[1-9]\d*\b/i.test(line) ||
|
|
344
|
-
/\bTasks:\s*[1-9]\d*\s+successful\b/i.test(line)
|
|
345
|
-
})
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
function findDebugLine (debugRerun, pattern) {
|
|
349
|
-
const lines = [
|
|
350
|
-
...(debugRerun.debugLines || []),
|
|
351
|
-
...(debugRerun.stdoutExcerpt || []),
|
|
352
|
-
...(debugRerun.stderrExcerpt || []),
|
|
353
|
-
]
|
|
354
|
-
return lines.find(line => pattern.test(line))
|
|
285
|
+
/**
|
|
286
|
+
* Compares absolute and repository-relative test source paths without filesystem access.
|
|
287
|
+
*
|
|
288
|
+
* @param {string} actual captured test source path
|
|
289
|
+
* @param {string} expected approved representative path
|
|
290
|
+
* @returns {boolean} whether both paths identify the same file
|
|
291
|
+
*/
|
|
292
|
+
function sourceFilesMatch (actual, expected) {
|
|
293
|
+
const normalizedActual = normalizeSourceFile(actual)
|
|
294
|
+
const normalizedExpected = normalizeSourceFile(expected)
|
|
295
|
+
if (!normalizedActual || !normalizedExpected) return false
|
|
296
|
+
if (normalizedActual === normalizedExpected) return true
|
|
297
|
+
if (!normalizedActual.includes('/') || !normalizedExpected.includes('/')) return false
|
|
298
|
+
return normalizedExpected.endsWith(`/${normalizedActual}`) ||
|
|
299
|
+
normalizedActual.endsWith(`/${normalizedExpected}`)
|
|
355
300
|
}
|
|
356
301
|
|
|
357
|
-
function
|
|
358
|
-
return
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
testOutputSummary,
|
|
364
|
-
}
|
|
302
|
+
function normalizeSourceFile (filename) {
|
|
303
|
+
return String(filename || '')
|
|
304
|
+
.replace(/^file:\/\//, '')
|
|
305
|
+
.replaceAll('\\', '/')
|
|
306
|
+
.replace(/^\.\//, '')
|
|
307
|
+
.replaceAll(/\/+/g, '/')
|
|
365
308
|
}
|
|
366
309
|
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
/^\s+\d+\) /,
|
|
379
|
-
])
|
|
380
|
-
const testEventsWereReported = evidence.testSessionEvents > 0 &&
|
|
381
|
-
evidence.testModuleEvents > 0 &&
|
|
382
|
-
evidence.testSuiteEvents > 0 &&
|
|
383
|
-
evidence.testEvents > 0
|
|
384
|
-
const summary = getFailureSummary({
|
|
385
|
-
buildErrors,
|
|
386
|
-
assertionFailures,
|
|
387
|
-
exitCode: result.exitCode,
|
|
388
|
-
testEventsWereReported,
|
|
389
|
-
timedOut: result.timedOut,
|
|
310
|
+
/**
|
|
311
|
+
* Classifies a direct command failure.
|
|
312
|
+
*
|
|
313
|
+
* @param {object} framework framework entry
|
|
314
|
+
* @param {object} result command result
|
|
315
|
+
* @returns {object|undefined} blocker
|
|
316
|
+
*/
|
|
317
|
+
function classifyFailure (framework, result) {
|
|
318
|
+
return getCommandBlocker(result, {
|
|
319
|
+
browserRequired: framework.browserRequired,
|
|
320
|
+
framework: framework.framework,
|
|
390
321
|
})
|
|
391
|
-
|
|
392
|
-
return {
|
|
393
|
-
assertionFailures,
|
|
394
|
-
buildErrors,
|
|
395
|
-
stderrExcerpt: tailInterestingLines(result.stderr),
|
|
396
|
-
stdoutExcerpt: tailInterestingLines(result.stdout),
|
|
397
|
-
summary,
|
|
398
|
-
testEventsWereReported,
|
|
399
|
-
}
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
function getMissingEventDiagnosis ({ framework, result, evidence }) {
|
|
403
|
-
const missingLevels = getMissingLevels(evidence)
|
|
404
|
-
const commandFailure = evidence.commandFailure
|
|
405
|
-
const vitestBenchmark = detectVitestBenchmark(framework, result)
|
|
406
|
-
const frameworkSourceTreeRunner = detectFrameworkSourceTreeRunner(framework, result)
|
|
407
|
-
const customJestRunner = detectCustomJestRunner(framework)
|
|
408
|
-
|
|
409
|
-
if (commandFailure?.buildErrors?.length > 0 && !hasAnyTestOptimizationEvent(evidence)) {
|
|
410
|
-
const preloadFailed = /(?:ci\/init\.js|internal\/preload)/.test(`${result.stdout}\n${result.stderr}`)
|
|
411
|
-
return {
|
|
412
|
-
kind: preloadFailed ? 'dd-trace-preload-failed' : 'command-setup-failed',
|
|
413
|
-
missingLevels,
|
|
414
|
-
signals: commandFailure.buildErrors,
|
|
415
|
-
summary: preloadFailed
|
|
416
|
-
? `The Test Optimization preload failed before tests started: ${commandFailure.buildErrors[0]}. ` +
|
|
417
|
-
'No Test Optimization conclusion was reached.'
|
|
418
|
-
: `${commandFailure.summary} No Test Optimization conclusion was reached.`,
|
|
419
|
-
recommendation: preloadFailed
|
|
420
|
-
? 'Repair or reinstall dd-trace so its declared dependencies resolve, then rerun validation.'
|
|
421
|
-
: 'Fix the selected project command or its setup, then rerun validation.',
|
|
422
|
-
}
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
if (vitestBenchmark) {
|
|
426
|
-
return {
|
|
427
|
-
kind: 'vitest-benchmark',
|
|
428
|
-
missingLevels,
|
|
429
|
-
signals: vitestBenchmark.signals,
|
|
430
|
-
summary: 'The selected Vitest command appears to run benchmark mode, not normal tests. ' +
|
|
431
|
-
'Test Optimization reported session/module/suite events, but Vitest benchmark mode did not emit ' +
|
|
432
|
-
'per-test events. Choose a normal Vitest test command such as "vitest run <test-file>" for validation.',
|
|
433
|
-
recommendation: 'Replace the selected command with a normal Vitest test command; do not use `vitest bench` ' +
|
|
434
|
-
'or benchmark-only `*.bench.*` files for Test Optimization validation.',
|
|
435
|
-
}
|
|
436
|
-
}
|
|
437
|
-
|
|
438
|
-
if (frameworkSourceTreeRunner && !hasAnyTestOptimizationEvent(evidence)) {
|
|
439
|
-
return {
|
|
440
|
-
kind: 'framework-source-tree-runner',
|
|
441
|
-
missingLevels,
|
|
442
|
-
signals: frameworkSourceTreeRunner.signals,
|
|
443
|
-
summary: 'The selected command ran tests from the test framework source tree, but no Test Optimization ' +
|
|
444
|
-
'events reached the offline event artifact. This command is not equivalent to a customer project running an ' +
|
|
445
|
-
'installed test-runner package.',
|
|
446
|
-
recommendation: 'Choose a project test command that uses an installed supported framework package. If this ' +
|
|
447
|
-
'repository is the framework itself, treat this entry as not runnable for Test Optimization validation.',
|
|
448
|
-
}
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
if (!hasAnyTestOptimizationEvent(evidence)) {
|
|
452
|
-
return {
|
|
453
|
-
kind: 'no-test-optimization-events',
|
|
454
|
-
missingLevels,
|
|
455
|
-
summary: 'No Test Optimization test events reached the offline event artifact. The tracer may not have ' +
|
|
456
|
-
'initialized in the test process, or the selected command may not have executed tests.',
|
|
457
|
-
recommendation: 'Check the debug rerun output for tracer initialization or offline exporter errors.',
|
|
458
|
-
}
|
|
459
|
-
}
|
|
460
|
-
|
|
461
|
-
if (evidence.testEvents === 0) {
|
|
462
|
-
if (customJestRunner) {
|
|
463
|
-
return {
|
|
464
|
-
kind: 'custom-jest-runner',
|
|
465
|
-
missingLevels,
|
|
466
|
-
customTestRunner: customJestRunner,
|
|
467
|
-
signals: customJestRunner.signals,
|
|
468
|
-
summary: `We detected a custom Jest-compatible runner: \`${customJestRunner.name}\`. The test command ` +
|
|
469
|
-
'executes tests, but it does not use a currently supported Jest entrypoint. Test Optimization ' +
|
|
470
|
-
'initialized, but this runner did not emit the Jest lifecycle events needed to report individual ' +
|
|
471
|
-
'suites and tests.',
|
|
472
|
-
recommendation: 'Try a standard Jest runner command for validation, or choose a test command that does not ' +
|
|
473
|
-
'use the custom runner. If this project must use the custom runner, dd-trace may need explicit support ' +
|
|
474
|
-
'for that runner before per-test reporting and advanced Test Optimization features can work.',
|
|
475
|
-
}
|
|
476
|
-
}
|
|
477
|
-
|
|
478
|
-
return {
|
|
479
|
-
kind: 'missing-test-events',
|
|
480
|
-
missingLevels,
|
|
481
|
-
summary: 'Test Optimization initialized and emitted higher-level events, but per-test events were missing. ' +
|
|
482
|
-
'This usually points to an unsupported runner mode, unsupported framework configuration, or per-test hooks ' +
|
|
483
|
-
'not firing for the selected command.',
|
|
484
|
-
recommendation: 'Choose a smaller standard test command, then inspect the debug rerun output for hook or ' +
|
|
485
|
-
'exporter errors.',
|
|
486
|
-
}
|
|
487
|
-
}
|
|
488
|
-
|
|
489
|
-
return {
|
|
490
|
-
kind: 'missing-event-levels',
|
|
491
|
-
missingLevels,
|
|
492
|
-
summary: `The command ran, but these required Test Optimization event levels were missing: ${
|
|
493
|
-
missingLevels.join(', ')
|
|
494
|
-
}.`,
|
|
495
|
-
recommendation: 'Inspect the debug rerun output for tracer initialization, hook, or exporter errors.',
|
|
496
|
-
}
|
|
497
322
|
}
|
|
498
323
|
|
|
324
|
+
/**
|
|
325
|
+
* Returns missing Test Optimization hierarchy levels.
|
|
326
|
+
*
|
|
327
|
+
* @param {object} evidence event evidence
|
|
328
|
+
* @returns {string[]} missing levels
|
|
329
|
+
*/
|
|
499
330
|
function getMissingLevels (evidence) {
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
}
|
|
507
|
-
|
|
508
|
-
function hasAnyTestOptimizationEvent (evidence) {
|
|
509
|
-
return evidence.testSessionEvents > 0 ||
|
|
510
|
-
evidence.testModuleEvents > 0 ||
|
|
511
|
-
evidence.testSuiteEvents > 0 ||
|
|
512
|
-
evidence.testEvents > 0
|
|
513
|
-
}
|
|
514
|
-
|
|
515
|
-
function detectVitestBenchmark (framework, result) {
|
|
516
|
-
if (framework.framework !== 'vitest') return null
|
|
517
|
-
|
|
518
|
-
const command = result.command || ''
|
|
519
|
-
const output = `${result.stdout}\n${result.stderr}`
|
|
520
|
-
const signals = []
|
|
521
|
-
|
|
522
|
-
if (/\bvitest\s+bench\b/.test(command)) signals.push('command contains `vitest bench`')
|
|
523
|
-
if (/\.bench\.[cm]?[jt]sx?\b/.test(command)) signals.push('command targets a `*.bench.*` file')
|
|
524
|
-
if (/^\s*BENCH\s+Summary\b/m.test(output)) signals.push('stdout contains a Vitest BENCH summary')
|
|
525
|
-
if (/Benchmarking is an experimental feature/.test(output)) {
|
|
526
|
-
signals.push('stderr says Vitest benchmarking is experimental')
|
|
527
|
-
}
|
|
528
|
-
|
|
529
|
-
return signals.length > 0 ? { signals } : null
|
|
530
|
-
}
|
|
531
|
-
|
|
532
|
-
function detectCustomJestRunner (framework) {
|
|
533
|
-
if (framework.framework !== 'jest') return null
|
|
534
|
-
|
|
535
|
-
const configRunner = findJestRunnerInConfigFiles(framework.project?.configFiles || [])
|
|
536
|
-
if (configRunner && isCustomJestRunner(configRunner.name)) return configRunner
|
|
537
|
-
|
|
538
|
-
const packageRunner = findJestRunnerInPackageJson(framework.project?.packageJson)
|
|
539
|
-
if (packageRunner && isCustomJestRunner(packageRunner.name)) return packageRunner
|
|
331
|
+
return [
|
|
332
|
+
['session', evidence.testSessionEvents],
|
|
333
|
+
['module', evidence.testModuleEvents],
|
|
334
|
+
['suite', evidence.testSuiteEvents],
|
|
335
|
+
['test', evidence.testEvents],
|
|
336
|
+
].filter(([, count]) => count < 1).map(([level]) => level)
|
|
540
337
|
}
|
|
541
338
|
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
`Jest config ${configFile} sets runner: ${match[1]}`,
|
|
556
|
-
],
|
|
557
|
-
}
|
|
558
|
-
}
|
|
559
|
-
}
|
|
560
|
-
|
|
561
|
-
function findJestRunnerInPackageJson (packageJsonPath) {
|
|
562
|
-
if (!packageJsonPath) return
|
|
563
|
-
|
|
564
|
-
try {
|
|
565
|
-
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'))
|
|
566
|
-
const runner = packageJson.jest?.runner
|
|
567
|
-
if (typeof runner !== 'string' || runner.length === 0) return
|
|
568
|
-
|
|
569
|
-
return {
|
|
570
|
-
name: runner,
|
|
571
|
-
source: packageJsonPath,
|
|
572
|
-
sourceType: 'package.json',
|
|
573
|
-
signals: [
|
|
574
|
-
`package.json jest.runner is ${runner}`,
|
|
575
|
-
],
|
|
576
|
-
}
|
|
577
|
-
} catch {}
|
|
578
|
-
}
|
|
579
|
-
|
|
580
|
-
function isCustomJestRunner (runner) {
|
|
581
|
-
return runner !== 'jest-runner'
|
|
582
|
-
}
|
|
583
|
-
|
|
584
|
-
function detectFrameworkSourceTreeRunner (framework, result) {
|
|
585
|
-
if (framework.framework !== 'mocha') return null
|
|
586
|
-
|
|
587
|
-
const projectName = framework.project?.name || ''
|
|
588
|
-
const commandAndOutput = [
|
|
589
|
-
result.command || '',
|
|
590
|
-
result.stdout || '',
|
|
591
|
-
result.stderr || '',
|
|
592
|
-
].join('\n')
|
|
593
|
-
const signals = []
|
|
594
|
-
|
|
595
|
-
if (projectName === 'mocha') {
|
|
596
|
-
signals.push('repository package name is `mocha`')
|
|
597
|
-
}
|
|
598
|
-
if (/\bnode\s+\.\/bin\/mocha\.js\b/.test(commandAndOutput) ||
|
|
599
|
-
/\bnode\s+bin\/mocha\.js\b/.test(commandAndOutput)) {
|
|
600
|
-
signals.push('selected command invokes the repository-local `bin/mocha.js` source-tree runner')
|
|
601
|
-
}
|
|
602
|
-
if (fileExists(framework.project?.root, 'lib/mocha.cjs') && fileExists(framework.project?.root, 'lib/runner.cjs')) {
|
|
603
|
-
signals.push('repository contains Mocha source files `lib/mocha.cjs` and `lib/runner.cjs`')
|
|
604
|
-
}
|
|
605
|
-
|
|
606
|
-
return signals.length >= 2 ? { signals } : null
|
|
607
|
-
}
|
|
608
|
-
|
|
609
|
-
function fileExists (root, filename) {
|
|
610
|
-
if (!root) return false
|
|
611
|
-
|
|
612
|
-
try {
|
|
613
|
-
return fs.existsSync(path.join(root, filename))
|
|
614
|
-
} catch {
|
|
615
|
-
return false
|
|
616
|
-
}
|
|
617
|
-
}
|
|
618
|
-
|
|
619
|
-
function readFile (filename) {
|
|
620
|
-
try {
|
|
621
|
-
return fs.readFileSync(filename, 'utf8')
|
|
622
|
-
} catch {}
|
|
623
|
-
}
|
|
624
|
-
|
|
625
|
-
function getFailureSummary ({ buildErrors, assertionFailures, exitCode, testEventsWereReported, timedOut }) {
|
|
626
|
-
if (timedOut) {
|
|
627
|
-
return 'The selected test command timed out before payload validation could pass.'
|
|
628
|
-
}
|
|
629
|
-
|
|
630
|
-
if (buildErrors.length > 0) {
|
|
631
|
-
if (testEventsWereReported) {
|
|
632
|
-
return 'The selected test command reported Datadog test events, but project setup/build errors made it fail.'
|
|
633
|
-
}
|
|
634
|
-
|
|
635
|
-
return 'The selected test command failed during project setup/build before payload validation could pass.'
|
|
636
|
-
}
|
|
637
|
-
|
|
638
|
-
if (assertionFailures.length > 0 && testEventsWereReported) {
|
|
639
|
-
return 'The selected test command reported Datadog test events, but the tests failed.'
|
|
640
|
-
}
|
|
641
|
-
|
|
642
|
-
if (testEventsWereReported) {
|
|
643
|
-
return `The selected test command reported Datadog test events, but exited ${exitCode}.`
|
|
644
|
-
}
|
|
645
|
-
|
|
646
|
-
return `The selected test command exited ${exitCode} before payload validation could pass.`
|
|
339
|
+
/**
|
|
340
|
+
* Extracts bounded useful test output lines.
|
|
341
|
+
*
|
|
342
|
+
* @param {string} [stdout] command stdout
|
|
343
|
+
* @param {string} [stderr] command stderr
|
|
344
|
+
* @returns {string[]} output summary
|
|
345
|
+
*/
|
|
346
|
+
function summarizeTestOutput (stdout = '', stderr = '') {
|
|
347
|
+
const lines = `${stdout}\n${stderr}`.split(/\r?\n/)
|
|
348
|
+
const interesting = lines.filter(line => {
|
|
349
|
+
return /\b(?:error|fail|pass|scenario|suite|test|timed out|warning)\b/i.test(line)
|
|
350
|
+
})
|
|
351
|
+
return [...new Set(interesting.map(line => line.trim()).filter(Boolean))].slice(-12)
|
|
647
352
|
}
|
|
648
353
|
|
|
649
|
-
module.exports = {
|
|
650
|
-
getDebugAwareDiagnosis,
|
|
651
|
-
getBasicReportingCommand,
|
|
652
|
-
getMissingEventDiagnosis,
|
|
653
|
-
refineBasicReportingFailure,
|
|
654
|
-
runBasicReporting,
|
|
655
|
-
shouldRunDebugRerun,
|
|
656
|
-
summarizeTestOutput,
|
|
657
|
-
}
|
|
354
|
+
module.exports = { runBasicReporting, summarizeTestOutput }
|