dd-trace 5.116.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 +3 -3
- package/packages/datadog-instrumentations/src/cucumber.js +11 -12
- package/packages/datadog-instrumentations/src/helpers/channel.js +74 -0
- 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/jest.js +18 -17
- package/packages/datadog-instrumentations/src/mocha/main.js +79 -30
- package/packages/datadog-instrumentations/src/openai-agents.js +159 -0
- package/packages/datadog-instrumentations/src/playwright.js +3 -24
- package/packages/datadog-instrumentations/src/vitest-main-no-worker-init.js +1 -1
- package/packages/datadog-instrumentations/src/vitest-main.js +14 -26
- package/packages/datadog-instrumentations/src/vitest-util.js +0 -7
- package/packages/datadog-instrumentations/src/vitest-worker.js +5 -8
- package/packages/datadog-plugin-cucumber/src/index.js +2 -1
- package/packages/datadog-plugin-mocha/src/index.js +2 -1
- 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/datadog-plugin-playwright/src/index.js +4 -1
- package/packages/datadog-plugin-vitest/src/index.js +5 -5
- 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/profiling/profilers/space.js +1 -1
- package/packages/dd-trace/src/profiling/profilers/wall.js +1 -0
- 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,246 +0,0 @@
|
|
|
1
|
-
'use strict'
|
|
2
|
-
|
|
3
|
-
const fs = require('node:fs')
|
|
4
|
-
const path = require('node:path')
|
|
5
|
-
|
|
6
|
-
const {
|
|
7
|
-
mergeNodeOptions,
|
|
8
|
-
runCommand,
|
|
9
|
-
} = require('./command-runner')
|
|
10
|
-
const { inheritApprovedExecutable } = require('./executable-approval')
|
|
11
|
-
const { sanitizeForReport } = require('./redaction')
|
|
12
|
-
const { createFileSafely, ensureSafeDirectory, writeFileSafely } = require('./safe-files')
|
|
13
|
-
|
|
14
|
-
const PROBE_PRELOAD = path.join(__dirname, 'init-probe-preload.js')
|
|
15
|
-
const PROBE_FILE_ENV = 'DD_TEST_OPTIMIZATION_INIT_PROBE_FILE'
|
|
16
|
-
const MAX_PROBE_RECORD_BYTES = 1024 * 1024
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* Runs a CI-shaped command with a lightweight NODE_OPTIONS preload that records process reachability.
|
|
20
|
-
*
|
|
21
|
-
* @param {object} options probe options
|
|
22
|
-
* @param {object} options.command manifest command to run
|
|
23
|
-
* @param {object} options.framework manifest framework entry
|
|
24
|
-
* @param {string} options.outDir scenario output directory
|
|
25
|
-
* @param {object} options.options CLI options
|
|
26
|
-
* @returns {Promise<{ artifacts: object, summary: object }>} probe artifacts and summary
|
|
27
|
-
*/
|
|
28
|
-
async function runInitializationProbe ({ command, framework, outDir, options }) {
|
|
29
|
-
const probeOutDir = path.join(outDir, 'initialization-probe')
|
|
30
|
-
const recordsPath = path.join(probeOutDir, 'records.ndjson')
|
|
31
|
-
const rawRecordsPath = path.join(probeOutDir, '.records.raw.ndjson')
|
|
32
|
-
const probeCommand = getProbeCommand(command)
|
|
33
|
-
|
|
34
|
-
ensureSafeDirectory(outDir, probeOutDir, 'initialization probe artifact directory')
|
|
35
|
-
createFileSafely(outDir, rawRecordsPath, '', 'raw initialization probe records')
|
|
36
|
-
|
|
37
|
-
let result
|
|
38
|
-
let records
|
|
39
|
-
try {
|
|
40
|
-
result = await runCommand(probeCommand, {
|
|
41
|
-
artifactRoot: outDir,
|
|
42
|
-
env: {
|
|
43
|
-
[PROBE_FILE_ENV]: rawRecordsPath,
|
|
44
|
-
NODE_OPTIONS: mergeNodeOptions(
|
|
45
|
-
`-r ${formatNodeRequire(PROBE_PRELOAD)}`
|
|
46
|
-
),
|
|
47
|
-
},
|
|
48
|
-
envMode: 'clean',
|
|
49
|
-
outDir: probeOutDir,
|
|
50
|
-
repositoryRoot: options.repositoryRoot,
|
|
51
|
-
requireExecutableApproval: options.requireExecutableApproval,
|
|
52
|
-
label: `${framework.id}:ci-wiring:init-probe`,
|
|
53
|
-
stopWhen: () => probeReachedFramework(rawRecordsPath, framework.framework),
|
|
54
|
-
verbose: options.verbose,
|
|
55
|
-
})
|
|
56
|
-
records = readProbeRecords(rawRecordsPath)
|
|
57
|
-
writeFileSafely(
|
|
58
|
-
outDir,
|
|
59
|
-
recordsPath,
|
|
60
|
-
records.map(record => JSON.stringify(sanitizeForReport(record))).join('\n') + '\n',
|
|
61
|
-
'initialization probe records'
|
|
62
|
-
)
|
|
63
|
-
} finally {
|
|
64
|
-
fs.rmSync(rawRecordsPath, { force: true })
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
return {
|
|
68
|
-
artifacts: {
|
|
69
|
-
command: result.artifacts.command,
|
|
70
|
-
records: recordsPath,
|
|
71
|
-
stderr: result.artifacts.stderr,
|
|
72
|
-
stdout: result.artifacts.stdout,
|
|
73
|
-
},
|
|
74
|
-
summary: summarizeProbeResult({ framework, result, records, recordsPath }),
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
function getProbeCommand (command) {
|
|
79
|
-
if (!command.env?.NODE_OPTIONS) return command
|
|
80
|
-
|
|
81
|
-
const env = { ...command.env }
|
|
82
|
-
const nodeOptions = removeDatadogPreloads(env.NODE_OPTIONS)
|
|
83
|
-
if (nodeOptions) {
|
|
84
|
-
env.NODE_OPTIONS = nodeOptions
|
|
85
|
-
} else {
|
|
86
|
-
delete env.NODE_OPTIONS
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
return inheritApprovedExecutable(command, {
|
|
90
|
-
...command,
|
|
91
|
-
env,
|
|
92
|
-
})
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
function removeDatadogPreloads (nodeOptions) {
|
|
96
|
-
const tokens = tokenizeNodeOptions(nodeOptions)
|
|
97
|
-
const kept = []
|
|
98
|
-
|
|
99
|
-
for (let index = 0; index < tokens.length; index++) {
|
|
100
|
-
const token = tokens[index]
|
|
101
|
-
if (isSeparatedPreloadFlag(token) && isDatadogPreload(tokens[index + 1])) {
|
|
102
|
-
index++
|
|
103
|
-
continue
|
|
104
|
-
}
|
|
105
|
-
if (isInlineDatadogPreload(token)) continue
|
|
106
|
-
kept.push(token)
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
return kept.join(' ')
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
function tokenizeNodeOptions (nodeOptions) {
|
|
113
|
-
return String(nodeOptions || '').match(/"[^"]*"|'[^']*'|\S+/g) || []
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
function isSeparatedPreloadFlag (token) {
|
|
117
|
-
return token === '-r' || token === '--require' || token === '--import'
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
function isInlineDatadogPreload (token) {
|
|
121
|
-
return (token.startsWith('--require=') && isDatadogPreload(token.slice('--require='.length))) ||
|
|
122
|
-
(token.startsWith('--import=') && isDatadogPreload(token.slice('--import='.length))) ||
|
|
123
|
-
(token.startsWith('-r') && token.length > 2 && isDatadogPreload(token.slice(2)))
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
function isDatadogPreload (value) {
|
|
127
|
-
const normalized = String(value || '').replaceAll(/^['"]|['"]$/g, '')
|
|
128
|
-
return normalized.includes('dd-trace/ci/init') || normalized.includes('dd-trace/register')
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
function summarizeProbeResult ({ framework, result, records, recordsPath }) {
|
|
132
|
-
const processRecords = records.filter(record => record.type === 'process-start')
|
|
133
|
-
const moduleLoadRecords = records.filter(record => record.type === 'module-load')
|
|
134
|
-
const testRunnerSignals = getToolSignals(records, 'test-runner')
|
|
135
|
-
.filter(signal => signal.name === framework.framework)
|
|
136
|
-
const wrapperSignals = getToolSignals(records, 'wrapper', { processStartsOnly: true })
|
|
137
|
-
const packageManagerSignals = getToolSignals(records, 'package-manager', { processStartsOnly: true })
|
|
138
|
-
|
|
139
|
-
return {
|
|
140
|
-
ran: true,
|
|
141
|
-
commandExitCode: result.exitCode,
|
|
142
|
-
commandTimedOut: result.timedOut,
|
|
143
|
-
processCount: processRecords.length,
|
|
144
|
-
moduleLoadCount: moduleLoadRecords.length,
|
|
145
|
-
reachedAnyNodeProcess: processRecords.length > 0,
|
|
146
|
-
reachedTestRunnerProcess: testRunnerSignals.length > 0,
|
|
147
|
-
stoppedAfterRunnerReached: result.stoppedEarly === true && testRunnerSignals.length > 0,
|
|
148
|
-
testRunnerSignals,
|
|
149
|
-
wrapperSignals,
|
|
150
|
-
packageManagerSignals,
|
|
151
|
-
recordsPath,
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
function probeReachedFramework (recordsPath, frameworkName) {
|
|
156
|
-
return readProbeRecords(recordsPath).some(record => {
|
|
157
|
-
return getRecordTools(record).some(tool => tool.kind === 'test-runner' && tool.name === frameworkName)
|
|
158
|
-
})
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
function getToolSignals (records, kind, { processStartsOnly = false } = {}) {
|
|
162
|
-
const signals = []
|
|
163
|
-
const signalsByLocation = new Map()
|
|
164
|
-
|
|
165
|
-
for (const record of records) {
|
|
166
|
-
if (processStartsOnly && record.type !== 'process-start') continue
|
|
167
|
-
for (const tool of getRecordTools(record)) {
|
|
168
|
-
if (tool.kind !== kind) continue
|
|
169
|
-
|
|
170
|
-
const key = `${tool.name}:${record.cwd}`
|
|
171
|
-
let signal = signalsByLocation.get(key)
|
|
172
|
-
if (!signal) {
|
|
173
|
-
signal = {
|
|
174
|
-
name: tool.name,
|
|
175
|
-
kind: tool.kind,
|
|
176
|
-
pid: record.pid,
|
|
177
|
-
ppid: record.ppid,
|
|
178
|
-
source: record.type,
|
|
179
|
-
argv: Array.isArray(record.argv) ? record.argv : undefined,
|
|
180
|
-
cwd: record.cwd,
|
|
181
|
-
request: record.request,
|
|
182
|
-
processCount: 0,
|
|
183
|
-
processIds: new Set(),
|
|
184
|
-
}
|
|
185
|
-
signalsByLocation.set(key, signal)
|
|
186
|
-
signals.push(signal)
|
|
187
|
-
}
|
|
188
|
-
signal.processIds.add(record.pid)
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
return signals.map(signal => {
|
|
193
|
-
signal.processCount = signal.processIds.size
|
|
194
|
-
delete signal.processIds
|
|
195
|
-
return signal
|
|
196
|
-
})
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
function getRecordTools (record) {
|
|
200
|
-
if (record.tool) return [record.tool]
|
|
201
|
-
if (Array.isArray(record.detectedTools)) return record.detectedTools
|
|
202
|
-
return []
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
function readProbeRecords (recordsPath) {
|
|
206
|
-
let file
|
|
207
|
-
try {
|
|
208
|
-
file = fs.openSync(recordsPath, fs.constants.O_RDONLY | (fs.constants.O_NOFOLLOW || 0))
|
|
209
|
-
const stat = fs.fstatSync(file)
|
|
210
|
-
if (!stat.isFile() || stat.size > MAX_PROBE_RECORD_BYTES) return []
|
|
211
|
-
const content = fs.readFileSync(file, 'utf8')
|
|
212
|
-
return parseProbeRecords(content)
|
|
213
|
-
} catch {
|
|
214
|
-
return []
|
|
215
|
-
} finally {
|
|
216
|
-
if (file !== undefined) fs.closeSync(file)
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
function parseProbeRecords (content) {
|
|
221
|
-
const records = []
|
|
222
|
-
for (const line of content.split(/\r?\n/)) {
|
|
223
|
-
if (!line) continue
|
|
224
|
-
try {
|
|
225
|
-
const record = JSON.parse(line)
|
|
226
|
-
if (isProbeRecord(record)) records.push(sanitizeForReport(record))
|
|
227
|
-
} catch {}
|
|
228
|
-
}
|
|
229
|
-
return records
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
function isProbeRecord (record) {
|
|
233
|
-
return record && typeof record === 'object' && !Array.isArray(record) &&
|
|
234
|
-
(record.type === 'process-start' || record.type === 'module-load') &&
|
|
235
|
-
Number.isInteger(record.pid) && Number.isInteger(record.ppid) &&
|
|
236
|
-
typeof record.cwd === 'string' && Array.isArray(record.argv)
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
function formatNodeRequire (filename) {
|
|
240
|
-
if (!/\s/.test(filename)) return filename
|
|
241
|
-
return JSON.stringify(filename)
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
module.exports = {
|
|
245
|
-
runInitializationProbe,
|
|
246
|
-
}
|
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
'use strict'
|
|
2
|
-
|
|
3
|
-
const fs = require('node:fs')
|
|
4
|
-
const path = require('node:path')
|
|
5
|
-
|
|
6
|
-
const MAX_FILE_BYTES = 512 * 1024
|
|
7
|
-
const SCRIPT_LITERAL_PATTERN = /['"]([^'"]+\.[cm]?[jt]sx?)['"]/g
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* Finds Test Optimization initialization loaded from a Vitest setup file.
|
|
11
|
-
*
|
|
12
|
-
* @param {object} manifest normalized manifest
|
|
13
|
-
* @param {object} framework manifest framework
|
|
14
|
-
* @returns {{configFile: string, setupFile: string}[]} late initialization evidence
|
|
15
|
-
*/
|
|
16
|
-
function findLateInitialization (manifest, framework) {
|
|
17
|
-
if (framework.framework !== 'vitest') return []
|
|
18
|
-
|
|
19
|
-
const findings = []
|
|
20
|
-
const seen = new Set()
|
|
21
|
-
for (const configFile of framework.project?.configFiles || []) {
|
|
22
|
-
const config = readSmallFile(configFile)
|
|
23
|
-
if (!config || !/\bsetupFiles\b/.test(config)) continue
|
|
24
|
-
|
|
25
|
-
if (/setupFiles[\s\S]{0,1000}?dd-trace\/ci\/init/.test(config)) {
|
|
26
|
-
addFinding(findings, seen, configFile, 'dd-trace/ci/init')
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
for (const match of config.matchAll(SCRIPT_LITERAL_PATTERN)) {
|
|
30
|
-
for (const candidate of getSetupFileCandidates(manifest.repository.root, configFile, match[1])) {
|
|
31
|
-
const setup = readSmallFile(candidate)
|
|
32
|
-
if (!setup || !/dd-trace\/ci\/init/.test(setup)) continue
|
|
33
|
-
addFinding(findings, seen, configFile, candidate)
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
return findings
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
function getSetupFileCandidates (repositoryRoot, configFile, filename) {
|
|
41
|
-
if (path.isAbsolute(filename)) return [filename]
|
|
42
|
-
return [
|
|
43
|
-
path.resolve(path.dirname(configFile), filename),
|
|
44
|
-
path.resolve(repositoryRoot, filename),
|
|
45
|
-
]
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
function readSmallFile (filename) {
|
|
49
|
-
try {
|
|
50
|
-
const stat = fs.statSync(filename)
|
|
51
|
-
if (!stat.isFile() || stat.size > MAX_FILE_BYTES) return
|
|
52
|
-
return fs.readFileSync(filename, 'utf8')
|
|
53
|
-
} catch {}
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
function addFinding (findings, seen, configFile, setupFile) {
|
|
57
|
-
const key = `${configFile}:${setupFile}`
|
|
58
|
-
if (seen.has(key)) return
|
|
59
|
-
seen.add(key)
|
|
60
|
-
findings.push({ configFile, setupFile })
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
module.exports = { findLateInitialization }
|
|
@@ -1,163 +0,0 @@
|
|
|
1
|
-
'use strict'
|
|
2
|
-
|
|
3
|
-
const path = require('path')
|
|
4
|
-
|
|
5
|
-
const { inheritApprovedExecutable } = require('./executable-approval')
|
|
6
|
-
|
|
7
|
-
const JEST_NO_WATCHMAN_ADJUSTMENT = 'Disable Watchman for local validation to avoid home-directory writes.'
|
|
8
|
-
const INLINE_DATADOG_ENV_PATTERN = /(?:^|[\s;&|()'"])(?:export\s+|set\s+)?["']?(?:\$env:)?DD_[A-Z0-9_]+\s*\+?=/i
|
|
9
|
-
const INIT_PATH = path.resolve(__dirname, '..', 'init.js').replaceAll('\\', '/')
|
|
10
|
-
const REGISTER_PATH = path.resolve(__dirname, '..', '..', 'register.js').replaceAll('\\', '/')
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Applies semantics-preserving local validation adjustments to a command.
|
|
14
|
-
*
|
|
15
|
-
* @param {object} framework manifest framework entry
|
|
16
|
-
* @param {object} command command to adjust
|
|
17
|
-
* @returns {object} adjusted command
|
|
18
|
-
*/
|
|
19
|
-
function getLocalValidationCommand (framework, command) {
|
|
20
|
-
if (framework.framework !== 'jest' || command.usesShell || command.argv?.includes('--no-watchman') ||
|
|
21
|
-
!isJestCommand(command.argv)) {
|
|
22
|
-
return command
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
const argv = [...command.argv]
|
|
26
|
-
const executable = path.basename(argv[0]).replace(/\.cmd$/i, '')
|
|
27
|
-
if (executable === 'npm' && !argv.includes('--')) argv.push('--')
|
|
28
|
-
argv.push('--no-watchman')
|
|
29
|
-
|
|
30
|
-
return inheritApprovedExecutable(command, {
|
|
31
|
-
...command,
|
|
32
|
-
argv,
|
|
33
|
-
localAdjustments: [
|
|
34
|
-
...(command.localAdjustments || []),
|
|
35
|
-
JEST_NO_WATCHMAN_ADJUSTMENT,
|
|
36
|
-
],
|
|
37
|
-
})
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* Identifies Jest entrypoints and package scripts that can forward Jest options.
|
|
42
|
-
*
|
|
43
|
-
* @param {string[]} argv command arguments
|
|
44
|
-
* @returns {boolean} whether --no-watchman can be appended safely
|
|
45
|
-
*/
|
|
46
|
-
function isJestCommand (argv) {
|
|
47
|
-
if (!Array.isArray(argv) || argv.length === 0) return false
|
|
48
|
-
|
|
49
|
-
const executable = path.basename(argv[0]).replace(/\.cmd$/i, '')
|
|
50
|
-
if (['npm', 'npx', 'pnpm', 'yarn', 'yarnpkg'].includes(executable)) return true
|
|
51
|
-
|
|
52
|
-
return argv.some(argument => /(?:^|[/\\])(?:jest|jest\.js)$/.test(argument))
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
/**
|
|
56
|
-
* Removes Datadog initialization from a command before an uninstrumented preflight.
|
|
57
|
-
*
|
|
58
|
-
* @param {object} command command to sanitize
|
|
59
|
-
* @returns {object} Datadog-clean command
|
|
60
|
-
*/
|
|
61
|
-
function getDatadogCleanCommand (command) {
|
|
62
|
-
const inlineInitialization = getInlineDatadogInitialization(command)
|
|
63
|
-
if (inlineInitialization) {
|
|
64
|
-
throw new Error(
|
|
65
|
-
`Cannot create a Datadog-clean command because it ${inlineInitialization}. ` +
|
|
66
|
-
'Remove inline Datadog initialization from the local validation command.'
|
|
67
|
-
)
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
const env = {}
|
|
71
|
-
for (const [name, value] of Object.entries(command.env || {})) {
|
|
72
|
-
if (name.startsWith('DD_') || (name === 'NODE_OPTIONS' && /dd-trace/.test(value))) continue
|
|
73
|
-
env[name] = value
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
return inheritApprovedExecutable(command, {
|
|
77
|
-
...command,
|
|
78
|
-
env,
|
|
79
|
-
})
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
/**
|
|
83
|
-
* Finds Datadog initialization embedded in executable arguments or shell source.
|
|
84
|
-
*
|
|
85
|
-
* @param {object} command manifest command
|
|
86
|
-
* @returns {string|undefined} customer-facing inline initialization description
|
|
87
|
-
*/
|
|
88
|
-
function getInlineDatadogInitialization (command) {
|
|
89
|
-
const source = command?.usesShell
|
|
90
|
-
? String(command.shellCommand || '')
|
|
91
|
-
: (command?.argv || []).join(' ')
|
|
92
|
-
const normalized = source.replaceAll('\\', '/')
|
|
93
|
-
|
|
94
|
-
if (normalized.includes('dd-trace/ci/init') || normalized.includes('dd-trace/register.js') ||
|
|
95
|
-
normalized.includes(INIT_PATH) || normalized.includes(REGISTER_PATH)) {
|
|
96
|
-
return 'contains an inline dd-trace preload'
|
|
97
|
-
}
|
|
98
|
-
if (INLINE_DATADOG_ENV_PATTERN.test(source)) return 'contains an inline DD_* assignment'
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
/**
|
|
102
|
-
* Returns the CI wiring command with the replay shell recorded by CI discovery when available.
|
|
103
|
-
*
|
|
104
|
-
* @param {object} framework manifest framework entry
|
|
105
|
-
* @returns {object|undefined} command to run
|
|
106
|
-
*/
|
|
107
|
-
function getCiWiringCommand (framework) {
|
|
108
|
-
const command = framework.ciWiringCommand
|
|
109
|
-
if (!command || !command.usesShell || command.shell || !framework.ciWiring?.shell) return command
|
|
110
|
-
|
|
111
|
-
const replayCommand = getShellReplayCommand(command, framework.ciWiring.shell)
|
|
112
|
-
if (replayCommand) return inheritApprovedExecutable(command, replayCommand)
|
|
113
|
-
|
|
114
|
-
const shell = getReplayShell(framework.ciWiring.shell)
|
|
115
|
-
if (!shell) return command
|
|
116
|
-
|
|
117
|
-
return inheritApprovedExecutable(command, {
|
|
118
|
-
...command,
|
|
119
|
-
shell,
|
|
120
|
-
})
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
function getShellReplayCommand (command, shell) {
|
|
124
|
-
const tokens = tokenizeShellTemplate(shell)
|
|
125
|
-
const hasTemplate = tokens.includes('{0}')
|
|
126
|
-
if (tokens.length <= 1 && !hasTemplate) return
|
|
127
|
-
|
|
128
|
-
const argv = hasTemplate ? tokens.filter(token => token !== '{0}') : tokens
|
|
129
|
-
if (!isBourneShell(argv[0])) return
|
|
130
|
-
|
|
131
|
-
return {
|
|
132
|
-
...command,
|
|
133
|
-
argv: [...argv, '-c', command.shellCommand],
|
|
134
|
-
shell: undefined,
|
|
135
|
-
shellCommand: undefined,
|
|
136
|
-
usesShell: false,
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
function getReplayShell (shell) {
|
|
141
|
-
const value = String(shell || '').trim()
|
|
142
|
-
if (!value) return
|
|
143
|
-
|
|
144
|
-
const firstToken = value.split(/\s+/)[0]
|
|
145
|
-
if (firstToken && value.includes('{0}')) return firstToken
|
|
146
|
-
if (!/\s/.test(value)) return value
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
function tokenizeShellTemplate (shell) {
|
|
150
|
-
return String(shell || '').trim().split(/\s+/).filter(Boolean)
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
function isBourneShell (executable) {
|
|
154
|
-
const basename = path.basename(String(executable || ''))
|
|
155
|
-
return basename === 'bash' || basename === 'sh' || basename === 'zsh'
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
module.exports = {
|
|
159
|
-
getCiWiringCommand,
|
|
160
|
-
getDatadogCleanCommand,
|
|
161
|
-
getInlineDatadogInitialization,
|
|
162
|
-
getLocalValidationCommand,
|
|
163
|
-
}
|
|
@@ -1,97 +0,0 @@
|
|
|
1
|
-
'use strict'
|
|
2
|
-
|
|
3
|
-
const path = require('path')
|
|
4
|
-
|
|
5
|
-
const { getArtifactId } = require('./artifact-id')
|
|
6
|
-
const { runCommand } = require('./command-runner')
|
|
7
|
-
|
|
8
|
-
async function runSetupCommands ({ framework, out, options }) {
|
|
9
|
-
const commands = framework.setup?.commands || []
|
|
10
|
-
const results = []
|
|
11
|
-
const artifacts = []
|
|
12
|
-
|
|
13
|
-
for (let index = 0; index < commands.length; index++) {
|
|
14
|
-
const command = commands[index]
|
|
15
|
-
const outDir = path.join(
|
|
16
|
-
out,
|
|
17
|
-
'setup',
|
|
18
|
-
getArtifactId(framework.id),
|
|
19
|
-
`${index + 1}-${getArtifactId(command.id || 'setup')}`
|
|
20
|
-
)
|
|
21
|
-
// eslint-disable-next-line no-await-in-loop
|
|
22
|
-
const result = await runCommand(command, {
|
|
23
|
-
artifactRoot: out,
|
|
24
|
-
envMode: 'clean',
|
|
25
|
-
outDir,
|
|
26
|
-
label: `${framework.id}:setup:${command.id || index + 1}`,
|
|
27
|
-
repositoryRoot: options.repositoryRoot,
|
|
28
|
-
requireExecutableApproval: options.requireExecutableApproval,
|
|
29
|
-
verbose: options.verbose,
|
|
30
|
-
})
|
|
31
|
-
const summary = summarizeSetupCommand(command, result, outDir)
|
|
32
|
-
results.push(summary)
|
|
33
|
-
artifacts.push(...Object.values(result.artifacts))
|
|
34
|
-
|
|
35
|
-
if (command.required !== false && result.exitCode !== 0) {
|
|
36
|
-
const failure = getSetupFailure(framework, command, result, results)
|
|
37
|
-
failure.artifacts.push(...artifacts)
|
|
38
|
-
return {
|
|
39
|
-
ok: false,
|
|
40
|
-
results,
|
|
41
|
-
artifacts,
|
|
42
|
-
failure,
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
return { ok: true, results, artifacts }
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
function getSetupFailure (framework, command, result, setupCommands) {
|
|
51
|
-
const setupName = command.description || command.id || result.command
|
|
52
|
-
|
|
53
|
-
return {
|
|
54
|
-
frameworkId: framework.id,
|
|
55
|
-
scenario: 'all',
|
|
56
|
-
status: 'blocked',
|
|
57
|
-
diagnosis: `Validation is blocked by required project setup: ${setupName}. ` +
|
|
58
|
-
'No Test Optimization conclusion was reached for this framework.',
|
|
59
|
-
evidence: {
|
|
60
|
-
blockedByProjectSetup: true,
|
|
61
|
-
setupFailed: true,
|
|
62
|
-
setupCommand: {
|
|
63
|
-
id: command.id,
|
|
64
|
-
description: command.description,
|
|
65
|
-
command: result.command,
|
|
66
|
-
cwd: result.cwd,
|
|
67
|
-
exitCode: result.exitCode,
|
|
68
|
-
timedOut: result.timedOut,
|
|
69
|
-
stdoutSummary: tail(result.stdout),
|
|
70
|
-
stderrSummary: tail(result.stderr),
|
|
71
|
-
},
|
|
72
|
-
setupCommands,
|
|
73
|
-
recommendation: 'Run or fix the documented project setup command, then rerun validation for this framework.',
|
|
74
|
-
},
|
|
75
|
-
artifacts: [],
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
function summarizeSetupCommand (command, result, outDir) {
|
|
80
|
-
return {
|
|
81
|
-
id: command.id,
|
|
82
|
-
description: command.description,
|
|
83
|
-
required: command.required !== false,
|
|
84
|
-
command: result.command,
|
|
85
|
-
cwd: result.cwd,
|
|
86
|
-
exitCode: result.exitCode,
|
|
87
|
-
timedOut: result.timedOut,
|
|
88
|
-
durationMs: result.durationMs,
|
|
89
|
-
artifactDirectory: outDir,
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
function tail (value) {
|
|
94
|
-
return String(value || '').trim().split(/\r?\n/).slice(-20).join('\n')
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
module.exports = { runSetupCommands }
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"$schema":"https://json-schema.org/draft/2020-12/schema","$id":"https://github.com/DataDog/dd-trace-js/ci/test-optimization-validation-manifest.schema.json","title":"Datadog Test Optimization validation manifest","type":"object","required":["schemaVersion","repository","environment","frameworks"],"properties":{"schemaVersion":{"type":"string"},"generatedAt":{"type":"string"},"repository":{"$ref":"#/$defs/repository"},"environment":{"$ref":"#/$defs/environment"},"ciDiscovery":{"$ref":"#/$defs/ciDiscovery"},"frameworks":{"type":"array","minItems":1,"items":{"$ref":"#/$defs/framework"}},"omitted":{"type":"array","items":{"type":"string"}},"warnings":{"type":"array","items":{"type":"string"}}},"additionalProperties":true,"$defs":{"repository":{"type":"object","required":["root"],"properties":{"root":{"$ref":"#/$defs/absolutePathString"},"gitRemote":{"type":["string","null"]},"gitSha":{"type":["string","null"]},"packageManager":{"enum":["npm","yarn","pnpm","bun","mixed","unknown"]},"workspaceManager":{"enum":["npm","yarn","pnpm","lerna","nx","turbo","rush","none","unknown"]}},"additionalProperties":true},"environment":{"type":"object","properties":{"os":{"enum":["darwin","linux","windows","unknown"]},"shell":{"type":["string","null"],"minLength":1,"pattern":"^[^\\u0000-\\u001F\\u007F-\\u009F\\u00AD\\u0600-\\u0605\\u061C\\u06DD\\u070F\\u0890\\u0891\\u08E2\\u180B-\\u180F\\u200B-\\u200F\\u202A-\\u202E\\u2060-\\u2064\\u2066-\\u206F\\uFE00-\\uFE0F\\uFEFF\\uFFF9-\\uFFFB]*$"},"nodeVersion":{"type":["string","null"]},"requiredEnvVars":{"type":"array","items":{"type":"string"}},"safeEnv":{"type":"object"}},"additionalProperties":true},"ciDiscovery":{"type":"object","properties":{"searched":{"type":"array","items":{"type":"string"}},"found":{"type":"array","items":{"type":"string"}},"staticFound":{"type":"array","items":{"type":"string"}},"method":{"type":"string"},"warnings":{"type":"array","items":{"type":"string"}},"notes":{"type":"array","items":{"type":"string"}},"contradictions":{"type":"array","items":{"type":"string"}}},"additionalProperties":true},"framework":{"type":"object","required":["id","framework","status","project"],"properties":{"id":{"type":"string"},"framework":{"enum":["jest","vitest","mocha","cucumber","cypress","playwright","node:test","ava","tap","jasmine","karma","uvu","testcafe","custom","unknown"]},"frameworkVersion":{"type":["string","null"]},"language":{"enum":["javascript","typescript","mixed","unknown"]},"status":{"enum":["runnable","detected_not_runnable","requires_external_service","requires_manual_setup","unsupported_by_validator","unknown"]},"supportLevel":{"enum":["validator_supported","dd_trace_supported_but_validator_missing_adapter","detected_only","unknown"]},"project":{"$ref":"#/$defs/project"},"setup":{"$ref":"#/$defs/setup"},"existingTestCommand":{"$ref":"#/$defs/command"},"ciWiring":{"$ref":"#/$defs/ciWiring"},"ciWiringCommand":{"$ref":"#/$defs/command"},"forcedLocalCommand":false,"preflight":{"$ref":"#/$defs/preflight"},"generatedTestStrategy":{"$ref":"#/$defs/generatedTestStrategy"},"notes":{"type":"array","items":{"type":"string"}}},"additionalProperties":true,"allOf":[{"if":{"properties":{"status":{"const":"runnable"}}},"then":{"required":["existingTestCommand","preflight","ciWiring"]}},{"if":{"properties":{"status":{"enum":["detected_not_runnable","requires_external_service","requires_manual_setup","unsupported_by_validator","unknown"]}}},"then":{"required":["notes"],"properties":{"notes":{"minItems":1}}}},{"if":{"required":["ciWiringCommand"]},"then":{"properties":{"ciWiring":{"required":["provider","configFile","job","step","workingDirectory","whySelected"]}}}},{"if":{"required":["ciWiring"],"properties":{"ciWiring":{"required":["replayability"],"properties":{"replayability":{"const":"replayable"}}}}},"then":{"required":["ciWiringCommand"]}},{"if":{"required":["ciWiring"],"properties":{"ciWiring":{"required":["replayability"],"properties":{"replayability":{"const":"not_replayable"}}}}},"then":{"not":{"required":["ciWiringCommand"]}}}]},"project":{"type":"object","required":["root"],"properties":{"name":{"type":["string","null"]},"root":{"$ref":"#/$defs/absolutePathString"},"packageJson":{"anyOf":[{"$ref":"#/$defs/absolutePathString"},{"type":"null"}]},"configFiles":{"type":"array","items":{"$ref":"#/$defs/absolutePathString"}},"evidence":{"type":"array","items":{"type":"string"}}},"additionalProperties":true},"setup":{"type":"object","properties":{"commands":{"type":"array","items":{"$ref":"#/$defs/setupCommand"}},"services":{"type":"array","items":{"$ref":"#/$defs/service"}}},"additionalProperties":true},"setupCommand":{"allOf":[{"$ref":"#/$defs/command"},{"type":"object","properties":{"id":{"type":"string"},"description":{"type":"string"},"required":{"type":"boolean"}}}]},"service":{"type":"object","properties":{"name":{"type":"string"},"required":{"type":"boolean"},"description":{"type":"string"}},"additionalProperties":true},"command":{"type":"object","required":["cwd"],"properties":{"description":{"type":"string"},"id":{"type":"string"},"required":{"type":"boolean"},"cwd":{"$ref":"#/$defs/absolutePathString"},"argv":{"type":"array","items":{"$ref":"#/$defs/executableString"}},"env":{"type":"object","propertyNames":{"pattern":"^[A-Za-z_][A-Za-z0-9_]*$"},"additionalProperties":{"$ref":"#/$defs/executableString"}},"requiredEnvVars":{"type":"array","items":{"type":"string"}},"outputPaths":{"type":"array","items":{"$ref":"#/$defs/absolutePathString"}},"timeoutMs":{"type":"number","exclusiveMinimum":0,"maximum":1800000},"usesShell":{"type":"boolean"},"shell":{"$ref":"#/$defs/executableString"},"shellCommand":{"anyOf":[{"$ref":"#/$defs/executableString"},{"type":"null"}]},"shellReason":{"type":["string","null"]}},"additionalProperties":false,"allOf":[{"if":{"required":["usesShell"],"properties":{"usesShell":{"const":true}}},"then":{"required":["shellCommand"]}},{"if":{"required":["shell"]},"then":{"required":["usesShell"],"properties":{"usesShell":{"const":true}}}}],"anyOf":[{"required":["argv"]},{"properties":{"usesShell":{"const":true}},"required":["usesShell","shellCommand"]}]},"executableString":{"type":"string","not":{"pattern":"\\$\\{[^}]+\\}"}},"absolutePathString":{"allOf":[{"$ref":"#/$defs/executableString"},{"pattern":"^(?:/|[A-Za-z]:[\\\\/]|\\\\\\\\)"}]},"preflight":{"type":"object","properties":{"status":{"enum":["pending","verified"]},"ran":{"type":"boolean"},"command":{"type":"string"},"exitCode":{"type":["number","null"]},"durationMs":{"type":["number","null"]},"observedTestCount":{"type":["number","null"]},"stdoutSummary":{"type":"string"},"stderrSummary":{"type":"string"},"maxTestCount":{"type":"integer","minimum":1,"maximum":1000}},"additionalProperties":true,"required":["maxTestCount"]},"ciWiring":{"type":"object","required":["status","replayability"],"properties":{"status":{"enum":["pass","fail","skip","unknown"]},"provider":{"type":"string"},"configFile":{"$ref":"#/$defs/absolutePathString"},"workflow":{"type":["string","null"]},"job":{"type":["string","null"]},"step":{"type":["string","null"]},"matrix":{"type":"object"},"runner":{"type":["string","null"]},"shell":{"type":["string","null"],"minLength":1,"pattern":"^[^\\u0000-\\u001F\\u007F-\\u009F\\u00AD\\u0600-\\u0605\\u061C\\u06DD\\u070F\\u0890\\u0891\\u08E2\\u180B-\\u180F\\u200B-\\u200F\\u202A-\\u202E\\u2060-\\u2064\\u2066-\\u206F\\uFE00-\\uFE0F\\uFEFF\\uFFF9-\\uFFFB]*$"},"workingDirectory":{"anyOf":[{"$ref":"#/$defs/absolutePathString"},{"type":"null"}]},"inheritedEnv":{"type":"object"},"requiredSecretEnvVars":{"type":"array","items":{"type":"string"}},"setupCommandIds":{"type":"array","items":{"type":"string"}},"whySelected":{"type":"string"},"workflowEnv":{"type":"object"},"jobEnv":{"type":"object"},"stepEnv":{"type":"object"},"initialization":{"type":"object","required":["status","evidence"],"properties":{"status":{"enum":["configured","not_configured","unknown"]},"evidence":{"type":"array","items":{"type":"string"}}},"allOf":[{"if":{"properties":{"status":{"enum":["configured","not_configured"]}},"required":["status"]},"then":{"properties":{"evidence":{"minItems":1}}}}],"additionalProperties":false},"packageScriptExpansionChain":{"type":"array","items":{"type":"string"}},"runnerToolChain":{"type":"array","items":{"type":"string"}},"unresolved":{"type":"array","items":{"type":"string"}},"diagnosis":{"type":"string"},"reason":{"type":"string"},"replayability":{"enum":["replayable","not_replayable"]},"replayBlocker":{"type":"string","minLength":1},"ciWiringCommand":false},"additionalProperties":true,"allOf":[{"if":{"properties":{"status":{"enum":["skip","unknown"]}}},"then":{"anyOf":[{"required":["diagnosis"]},{"required":["reason"]}]}},{"if":{"properties":{"replayability":{"const":"not_replayable"}},"required":["replayability"]},"then":{"required":["replayBlocker"],"properties":{"status":{"enum":["skip","unknown"]}}}}]},"generatedTestStrategy":{"type":"object","required":["status"],"properties":{"status":{"enum":["planned","verified","proposed","not_possible"]},"reason":{"type":["string","null"]},"adapter":{"enum":["jest","vitest","mocha","cucumber","cypress","playwright","node:test","generic-js","custom","unknown"]},"testDirectory":{"anyOf":[{"$ref":"#/$defs/absolutePathString"},{"type":"null"}]},"moduleSystem":{"enum":["commonjs","esm","typescript","unknown"]},"fileExtension":{"type":"string"},"supportsFocusedSingleFileRun":{"type":"boolean"},"usesMultipleFiles":{"type":"boolean"},"files":{"type":"array","maxItems":8,"items":{"$ref":"#/$defs/generatedFile"}},"scenarios":{"type":"array","items":{"$ref":"#/$defs/scenario"}},"verification":{"$ref":"#/$defs/verification"},"cleanupPaths":{"type":"array","items":{"$ref":"#/$defs/absolutePathString"}},"limitations":{"type":"array","items":{"type":"string"}}},"additionalProperties":true,"allOf":[{"if":{"properties":{"status":{"enum":["planned","verified"]}}},"then":{"required":["files","scenarios","cleanupPaths"],"properties":{"scenarios":{"allOf":[{"items":{"required":["testIdentities","expectedWithoutDatadog"],"properties":{"testIdentities":{"minItems":1}}}},{"contains":{"properties":{"id":{"const":"basic-pass"}},"required":["id"]}},{"contains":{"properties":{"id":{"const":"atr-fail-once"}},"required":["id"]}},{"contains":{"properties":{"id":{"const":"test-management-target"}},"required":["id"]}}]}}}}]},"generatedFile":{"type":"object","required":["path","contentLines"],"properties":{"path":{"$ref":"#/$defs/absolutePathString"},"role":{"enum":["test","feature","steps","support","state","config"]},"contentLines":{"type":"array","maxItems":256,"items":{"type":"string","maxLength":4096,"pattern":"^[^\\r\\n\\u0000-\\u0008\\u000B\\u000C\\u000E-\\u001F\\u007F-\\u009F\\u061C\\u180B-\\u180F\\u200B-\\u200F\\u202A-\\u202E\\u2060-\\u2064\\u2066-\\u206F\\uFE00-\\uFE0F\\uFEFF]*$"}}},"additionalProperties":true},"scenario":{"type":"object","required":["id","runCommand"],"properties":{"id":{"type":"string"},"purpose":{"type":"string"},"runCommand":{"$ref":"#/$defs/command"},"expectedWithoutDatadog":{"$ref":"#/$defs/expectedWithoutDatadog"},"testIdentities":{"type":"array","items":{"$ref":"#/$defs/testIdentity"}}},"additionalProperties":true,"allOf":[{"if":{"required":["id"],"properties":{"id":{"const":"basic-pass"}}},"then":{"properties":{"expectedWithoutDatadog":{"properties":{"exitCode":{"const":0},"observedTestCount":{"const":1}}}}}},{"if":{"required":["id"],"properties":{"id":{"const":"atr-fail-once"}}},"then":{"properties":{"expectedWithoutDatadog":{"properties":{"exitCode":{"const":1},"observedTestCount":{"const":1}}}}}},{"if":{"required":["id"],"properties":{"id":{"const":"test-management-target"}}},"then":{"properties":{"expectedWithoutDatadog":{"properties":{"exitCode":{"const":0},"observedTestCount":{"const":1}}}}}}]},"expectedWithoutDatadog":{"type":"object","required":["exitCode","observedTestCount"],"properties":{"exitCode":{"type":"number"},"observedTestCount":{"type":"number"}},"additionalProperties":true},"testIdentity":{"type":"object","required":["name"],"properties":{"suite":{"type":["string","null"]},"name":{"type":"string","minLength":1},"file":{"anyOf":[{"$ref":"#/$defs/absolutePathString"},{"type":"null"}]},"parameters":{"type":["object","null"]}},"additionalProperties":true},"verification":{"type":"object","properties":{"createdTemporaryFiles":{"type":"boolean"},"ranScenarioIds":{"type":"array","items":{"type":"string"}},"exitCode":{"type":["number","null"]},"durationMs":{"type":["number","null"]},"observedTestCount":{"type":["number","null"]},"cleanupCompleted":{"type":"boolean"},"stdoutSummary":{"type":"string"},"stderrSummary":{"type":"string"}},"additionalProperties":true}}}
|