dd-trace 6.4.0 → 6.5.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/README.electron.md +7 -0
- package/README.md +17 -0
- package/ci/diagnose.js +2100 -0
- package/ci/init.js +23 -2
- package/ci/runbook.md +198 -0
- package/ci/test-optimization-validation/approval-artifacts.js +143 -0
- package/ci/test-optimization-validation/approval.js +299 -0
- package/ci/test-optimization-validation/artifact-id.js +20 -0
- package/ci/test-optimization-validation/bounded-json.js +121 -0
- package/ci/test-optimization-validation/ci-command-candidate.js +83 -0
- package/ci/test-optimization-validation/ci-discovery.js +181 -0
- package/ci/test-optimization-validation/ci-remediation.js +210 -0
- package/ci/test-optimization-validation/cli.js +903 -0
- package/ci/test-optimization-validation/command-blocker.js +81 -0
- package/ci/test-optimization-validation/command-output-policy.js +206 -0
- package/ci/test-optimization-validation/command-runner.js +707 -0
- package/ci/test-optimization-validation/command-suitability.js +471 -0
- package/ci/test-optimization-validation/executable-approval.js +48 -0
- package/ci/test-optimization-validation/executable.js +480 -0
- package/ci/test-optimization-validation/generated-file-policy.js +63 -0
- package/ci/test-optimization-validation/generated-files.js +351 -0
- package/ci/test-optimization-validation/generated-verifier.js +196 -0
- package/ci/test-optimization-validation/init-probe-preload.js +163 -0
- package/ci/test-optimization-validation/init-probe.js +246 -0
- package/ci/test-optimization-validation/late-initialization.js +63 -0
- package/ci/test-optimization-validation/local-command.js +163 -0
- package/ci/test-optimization-validation/manifest-loader.js +133 -0
- package/ci/test-optimization-validation/manifest-scaffold.js +738 -0
- package/ci/test-optimization-validation/manifest-schema.js +862 -0
- package/ci/test-optimization-validation/offline-fixtures.js +327 -0
- package/ci/test-optimization-validation/offline-output.js +406 -0
- package/ci/test-optimization-validation/payload-normalizer.js +72 -0
- package/ci/test-optimization-validation/plan-writer.js +1120 -0
- package/ci/test-optimization-validation/preflight-runner.js +114 -0
- package/ci/test-optimization-validation/redaction.js +331 -0
- package/ci/test-optimization-validation/report-writer.js +1508 -0
- package/ci/test-optimization-validation/safe-files.js +203 -0
- package/ci/test-optimization-validation/scenarios/auto-test-retries.js +159 -0
- package/ci/test-optimization-validation/scenarios/basic-reporting.js +657 -0
- package/ci/test-optimization-validation/scenarios/ci-wiring.js +780 -0
- package/ci/test-optimization-validation/scenarios/early-flake-detection.js +141 -0
- package/ci/test-optimization-validation/scenarios/helpers.js +539 -0
- package/ci/test-optimization-validation/scenarios/test-management.js +218 -0
- package/ci/test-optimization-validation/setup-runner.js +97 -0
- package/ci/test-optimization-validation/static-diagnosis.js +159 -0
- package/ci/test-optimization-validation/test-output.js +129 -0
- package/ci/test-optimization-validation-manifest.schema.json +1 -0
- package/ci/validate-test-optimization.js +3 -0
- package/ext/exporters.js +1 -0
- package/index.d.ts +121 -8
- package/init.js +18 -0
- package/initialize.mjs +1 -1
- package/loader-hook.mjs +28 -18
- package/openfeature.d.ts +1 -0
- package/openfeature.js +4 -0
- package/package.json +10 -7
- package/packages/datadog-instrumentations/src/ai.js +8 -2
- package/packages/datadog-instrumentations/src/child_process.js +1 -1
- package/packages/datadog-instrumentations/src/claude-agent-sdk.js +1 -1
- package/packages/datadog-instrumentations/src/cucumber-worker-threads.js +9 -3
- package/packages/datadog-instrumentations/src/cucumber.js +20 -7
- package/packages/datadog-instrumentations/src/cypress-config.js +398 -52
- package/packages/datadog-instrumentations/src/fastify.js +7 -11
- package/packages/datadog-instrumentations/src/helpers/hook.js +27 -6
- package/packages/datadog-instrumentations/src/helpers/optional-peer-bundler.js +8 -8
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/playwright.js +28 -2
- package/packages/datadog-instrumentations/src/jest.js +26 -0
- package/packages/datadog-instrumentations/src/mariadb.js +1 -1
- package/packages/datadog-instrumentations/src/mocha/main.js +0 -2
- package/packages/datadog-instrumentations/src/mocha/utils.js +0 -4
- package/packages/datadog-instrumentations/src/mongodb.js +3 -3
- package/packages/datadog-instrumentations/src/mongoose.js +3 -3
- package/packages/datadog-instrumentations/src/mquery.js +2 -2
- package/packages/datadog-instrumentations/src/mysql.js +1 -1
- package/packages/datadog-instrumentations/src/next.js +24 -0
- package/packages/datadog-instrumentations/src/nyc.js +0 -2
- package/packages/datadog-instrumentations/src/oracledb.js +2 -2
- package/packages/datadog-instrumentations/src/pg.js +2 -2
- package/packages/datadog-instrumentations/src/playwright.js +159 -22
- package/packages/datadog-instrumentations/src/process.js +3 -0
- package/packages/datadog-instrumentations/src/router.js +18 -8
- package/packages/datadog-instrumentations/src/selenium.js +52 -26
- package/packages/datadog-instrumentations/src/vitest-main-no-worker-init.js +2 -2
- package/packages/datadog-instrumentations/src/vitest-main.js +0 -2
- package/packages/datadog-instrumentations/src/ws.js +2 -1
- package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/context.js +2 -2
- package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/util.js +2 -1
- package/packages/datadog-plugin-aws-sdk/src/util.js +2 -2
- package/packages/datadog-plugin-cucumber/src/index.js +1 -1
- package/packages/datadog-plugin-cypress/src/cypress-plugin.js +19 -57
- package/packages/datadog-plugin-cypress/src/index.js +6 -1
- package/packages/datadog-plugin-cypress/src/support.js +92 -26
- package/packages/datadog-plugin-electron/src/ipc.js +1 -1
- package/packages/datadog-plugin-graphql/src/execute.js +87 -45
- package/packages/datadog-plugin-graphql/src/validate.js +2 -2
- package/packages/datadog-plugin-http2/src/client.js +1 -1
- package/packages/datadog-plugin-langchain/src/handlers/embedding.js +2 -1
- package/packages/datadog-plugin-langchain/src/handlers/language_models.js +2 -1
- package/packages/datadog-plugin-langchain/src/tokens.js +2 -2
- package/packages/datadog-plugin-playwright/src/index.js +177 -64
- package/packages/datadog-plugin-router/src/index.js +11 -2
- package/packages/datadog-plugin-selenium/src/index.js +5 -36
- package/packages/datadog-plugin-ws/src/close.js +2 -1
- package/packages/datadog-plugin-ws/src/producer.js +2 -1
- package/packages/datadog-plugin-ws/src/receiver.js +2 -1
- package/packages/datadog-plugin-ws/src/server.js +2 -1
- package/packages/dd-trace/src/aiguard/sdk.js +1 -1
- package/packages/dd-trace/src/appsec/iast/taint-tracking/plugin.js +2 -2
- package/packages/dd-trace/src/appsec/iast/telemetry/span-tags.js +3 -1
- package/packages/dd-trace/src/azure_metadata.js +2 -1
- package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/index.js +93 -28
- package/packages/dd-trace/src/ci-visibility/early-flake-detection/get-known-tests.js +1 -1
- package/packages/dd-trace/src/ci-visibility/exporters/ci-validation/index.js +160 -0
- package/packages/dd-trace/src/ci-visibility/exporters/ci-validation/msgpack-to-json.js +288 -0
- package/packages/dd-trace/src/ci-visibility/exporters/ci-validation/payload-projection.js +84 -0
- package/packages/dd-trace/src/ci-visibility/exporters/ci-validation/sink.js +369 -0
- package/packages/dd-trace/src/ci-visibility/exporters/ci-validation/writer.js +60 -0
- package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +41 -3
- package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +7 -2
- package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +1 -1
- package/packages/dd-trace/src/ci-visibility/requests/upload-coverage-report.js +6 -2
- package/packages/dd-trace/src/ci-visibility/rum.js +7 -0
- package/packages/dd-trace/src/ci-visibility/test-api-manual/test-api-manual-plugin.js +4 -0
- package/packages/dd-trace/src/ci-visibility/test-management/get-test-management-tests.js +1 -1
- package/packages/dd-trace/src/ci-visibility/test-optimization-http-cache-schema.js +137 -9
- package/packages/dd-trace/src/ci-visibility/test-optimization-http-cache.js +168 -8
- package/packages/dd-trace/src/ci-visibility/test-screenshot.js +90 -0
- package/packages/dd-trace/src/config/generated-config-types.d.ts +16 -0
- package/packages/dd-trace/src/config/helper.js +1 -0
- package/packages/dd-trace/src/config/index.js +10 -0
- package/packages/dd-trace/src/config/supported-configurations.json +66 -0
- package/packages/dd-trace/src/exporter.js +2 -0
- package/packages/dd-trace/src/exporters/common/client-library-headers.js +21 -0
- package/packages/dd-trace/src/exporters/common/request.js +59 -30
- package/packages/dd-trace/src/exporters/common/url.js +15 -1
- package/packages/dd-trace/src/feature-registry.js +10 -3
- package/packages/dd-trace/src/llmobs/experiments/client.js +113 -0
- package/packages/dd-trace/src/llmobs/experiments/dataset.js +154 -0
- package/packages/dd-trace/src/llmobs/experiments/experiment.js +283 -0
- package/packages/dd-trace/src/llmobs/experiments/index.js +152 -0
- package/packages/dd-trace/src/llmobs/experiments/noop.js +30 -0
- package/packages/dd-trace/src/llmobs/experiments/result.js +34 -0
- package/packages/dd-trace/src/llmobs/noop.js +6 -0
- package/packages/dd-trace/src/llmobs/plugins/ai/vercelTelemetry.js +20 -3
- package/packages/dd-trace/src/llmobs/plugins/anthropic/index.js +2 -2
- package/packages/dd-trace/src/llmobs/plugins/langgraph/index.js +1 -1
- package/packages/dd-trace/src/llmobs/plugins/openai/constants.js +8 -0
- package/packages/dd-trace/src/llmobs/plugins/openai/index.js +48 -14
- package/packages/dd-trace/src/llmobs/plugins/openai/utils.js +43 -0
- package/packages/dd-trace/src/llmobs/sdk.js +17 -0
- package/packages/dd-trace/src/llmobs/tagger.js +68 -0
- package/packages/dd-trace/src/llmobs/telemetry.js +2 -1
- package/packages/dd-trace/src/llmobs/util.js +32 -0
- package/packages/dd-trace/src/openfeature/agentless_configuration_source.js +322 -0
- package/packages/dd-trace/src/openfeature/configuration_source.js +90 -0
- package/packages/dd-trace/src/openfeature/flagging_provider.js +14 -23
- package/packages/dd-trace/src/openfeature/index.js +0 -2
- package/packages/dd-trace/src/openfeature/noop.js +1 -28
- package/packages/dd-trace/src/openfeature/register.js +16 -27
- package/packages/dd-trace/src/openfeature/remote_config.js +15 -18
- package/packages/dd-trace/src/openfeature/require-provider.js +18 -0
- package/packages/dd-trace/src/opentelemetry/span-ending-hook.js +10 -0
- package/packages/dd-trace/src/opentelemetry/span.js +5 -0
- package/packages/dd-trace/src/plugins/ci_plugin.js +20 -6
- package/packages/dd-trace/src/plugins/util/git.js +3 -2
- package/packages/dd-trace/src/plugins/util/inferred_proxy.js +29 -5
- package/packages/dd-trace/src/plugins/util/test.js +56 -4
- package/packages/dd-trace/src/proxy.js +84 -7
- package/packages/dd-trace/src/span_processor.js +5 -0
- package/packages/dd-trace/src/standalone/index.js +0 -14
- package/packages/dd-trace/src/telemetry/send-data.js +2 -2
- package/packages/dd-trace/src/util.js +26 -0
- package/vendor/dist/@apm-js-collab/code-transformer/index.js +1 -1
- package/vendor/dist/@datadog/sketches-js/index.js +1 -1
- package/vendor/dist/protobufjs/index.js +1 -1
- package/vendor/dist/protobufjs/minimal/index.js +1 -1
- package/vendor/dist/shell-quote/index.js +1 -1
- package/packages/datadog-instrumentations/src/helpers/require-optional-peer.js +0 -17
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const crypto = require('node:crypto')
|
|
4
|
+
const fs = require('node:fs')
|
|
5
|
+
const path = require('node:path')
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Creates a directory while refusing symlink components and paths outside the allowed root.
|
|
9
|
+
*
|
|
10
|
+
* @param {string} root allowed root
|
|
11
|
+
* @param {string} directory directory to create or validate
|
|
12
|
+
* @param {string} label customer-facing path label
|
|
13
|
+
* @param {{allowRootSymlink?: boolean}} [options] validation options
|
|
14
|
+
*/
|
|
15
|
+
function ensureSafeDirectory (root, directory, label, options = {}) {
|
|
16
|
+
const lexicalRoot = path.resolve(root)
|
|
17
|
+
const resolvedDirectory = path.resolve(directory)
|
|
18
|
+
const rootStat = fs.lstatSync(lexicalRoot)
|
|
19
|
+
if (rootStat.isSymbolicLink() && !options.allowRootSymlink) {
|
|
20
|
+
throw new Error(`Refusing ${label} because its allowed root is a symbolic link: ${lexicalRoot}`)
|
|
21
|
+
}
|
|
22
|
+
if (!rootStat.isDirectory() && !rootStat.isSymbolicLink()) {
|
|
23
|
+
throw new Error(`Refusing ${label} because its allowed root is not a directory: ${lexicalRoot}`)
|
|
24
|
+
}
|
|
25
|
+
if (!isPathInside(lexicalRoot, resolvedDirectory)) {
|
|
26
|
+
throw new Error(`Refusing ${label} outside allowed root: ${resolvedDirectory}`)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
let current = lexicalRoot
|
|
30
|
+
const relative = path.relative(lexicalRoot, resolvedDirectory)
|
|
31
|
+
for (const segment of relative ? relative.split(path.sep) : []) {
|
|
32
|
+
current = path.join(current, segment)
|
|
33
|
+
let stat
|
|
34
|
+
try {
|
|
35
|
+
stat = fs.lstatSync(current)
|
|
36
|
+
} catch (error) {
|
|
37
|
+
if (error.code !== 'ENOENT') throw error
|
|
38
|
+
fs.mkdirSync(current)
|
|
39
|
+
stat = fs.lstatSync(current)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (stat.isSymbolicLink()) {
|
|
43
|
+
throw new Error(`Refusing ${label} through symbolic link: ${current}`)
|
|
44
|
+
}
|
|
45
|
+
if (!stat.isDirectory()) {
|
|
46
|
+
throw new Error(`Refusing ${label} through non-directory path: ${current}`)
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const physicalRoot = fs.realpathSync(lexicalRoot)
|
|
51
|
+
const physicalDirectory = fs.realpathSync(resolvedDirectory)
|
|
52
|
+
if (!isPathInside(physicalRoot, physicalDirectory)) {
|
|
53
|
+
throw new Error(`Refusing ${label} outside physical allowed root: ${resolvedDirectory}`)
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Writes a regular file without following a symbolic-link target.
|
|
59
|
+
*
|
|
60
|
+
* @param {string} root allowed root
|
|
61
|
+
* @param {string} filename output filename
|
|
62
|
+
* @param {string|Buffer} data output data
|
|
63
|
+
* @param {string} label customer-facing path label
|
|
64
|
+
*/
|
|
65
|
+
function writeFileSafely (root, filename, data, label) {
|
|
66
|
+
const resolvedFilename = path.resolve(filename)
|
|
67
|
+
const parent = path.dirname(resolvedFilename)
|
|
68
|
+
ensureSafeDirectory(root, parent, label)
|
|
69
|
+
const parentIdentity = getDirectoryIdentity(parent, label)
|
|
70
|
+
const temporaryFilename = path.join(
|
|
71
|
+
parent,
|
|
72
|
+
`.${path.basename(filename)}.${crypto.randomBytes(12).toString('hex')}.tmp`
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
try {
|
|
76
|
+
openAndWrite(root, temporaryFilename, data, label, fs.constants.O_EXCL)
|
|
77
|
+
assertDirectoryIdentity(parent, parentIdentity, label)
|
|
78
|
+
fs.renameSync(temporaryFilename, resolvedFilename)
|
|
79
|
+
} catch (error) {
|
|
80
|
+
removeTemporaryFile(temporaryFilename, parent, parentIdentity)
|
|
81
|
+
throw error
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Creates a new regular file without following or replacing an existing path.
|
|
87
|
+
*
|
|
88
|
+
* @param {string} root allowed root
|
|
89
|
+
* @param {string} filename output filename
|
|
90
|
+
* @param {string|Buffer} data output data
|
|
91
|
+
* @param {string} label customer-facing path label
|
|
92
|
+
*/
|
|
93
|
+
function createFileSafely (root, filename, data, label) {
|
|
94
|
+
openAndWrite(root, filename, data, label, fs.constants.O_EXCL)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Opens and writes a file with no-follow semantics.
|
|
99
|
+
*
|
|
100
|
+
* @param {string} root allowed root
|
|
101
|
+
* @param {string} filename output filename
|
|
102
|
+
* @param {string|Buffer} data output data
|
|
103
|
+
* @param {string} label customer-facing path label
|
|
104
|
+
* @param {number} creationFlag file creation mode
|
|
105
|
+
*/
|
|
106
|
+
function openAndWrite (root, filename, data, label, creationFlag) {
|
|
107
|
+
const resolvedFilename = path.resolve(filename)
|
|
108
|
+
ensureSafeDirectory(root, path.dirname(resolvedFilename), label)
|
|
109
|
+
refuseSymbolicLink(resolvedFilename, label)
|
|
110
|
+
|
|
111
|
+
const flags = fs.constants.O_WRONLY |
|
|
112
|
+
fs.constants.O_CREAT |
|
|
113
|
+
creationFlag |
|
|
114
|
+
(fs.constants.O_NOFOLLOW || 0)
|
|
115
|
+
const file = fs.openSync(resolvedFilename, flags, 0o600)
|
|
116
|
+
try {
|
|
117
|
+
const stat = fs.fstatSync(file)
|
|
118
|
+
if (!stat.isFile() || stat.nlink !== 1) {
|
|
119
|
+
throw new Error(`Refusing ${label} because its output is not a private regular file: ${resolvedFilename}`)
|
|
120
|
+
}
|
|
121
|
+
fs.writeFileSync(file, data)
|
|
122
|
+
} finally {
|
|
123
|
+
fs.closeSync(file)
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Captures one directory identity without accepting a symbolic link.
|
|
129
|
+
*
|
|
130
|
+
* @param {string} directory directory path
|
|
131
|
+
* @param {string} label customer-facing path label
|
|
132
|
+
* @returns {{dev: number, ino: number}} directory identity
|
|
133
|
+
*/
|
|
134
|
+
function getDirectoryIdentity (directory, label) {
|
|
135
|
+
const stat = fs.lstatSync(directory)
|
|
136
|
+
if (!stat.isDirectory() || stat.isSymbolicLink()) {
|
|
137
|
+
throw new Error(`Refusing ${label} because its parent is not a regular directory: ${directory}`)
|
|
138
|
+
}
|
|
139
|
+
return { dev: stat.dev, ino: stat.ino }
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Refuses replacement of a parent directory between safe creation and publication.
|
|
144
|
+
*
|
|
145
|
+
* @param {string} directory directory path
|
|
146
|
+
* @param {{dev: number, ino: number}} expected expected identity
|
|
147
|
+
* @param {string} label customer-facing path label
|
|
148
|
+
*/
|
|
149
|
+
function assertDirectoryIdentity (directory, expected, label) {
|
|
150
|
+
const current = getDirectoryIdentity(directory, label)
|
|
151
|
+
if (current.dev !== expected.dev || current.ino !== expected.ino) {
|
|
152
|
+
throw new Error(`Refusing ${label} because its parent directory changed during the write: ${directory}`)
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Removes only the validator-created temporary regular file or symbolic link.
|
|
158
|
+
*
|
|
159
|
+
* @param {string} filename temporary filename
|
|
160
|
+
* @param {string} parent expected parent directory
|
|
161
|
+
* @param {{dev: number, ino: number}} parentIdentity expected parent identity
|
|
162
|
+
*/
|
|
163
|
+
function removeTemporaryFile (filename, parent, parentIdentity) {
|
|
164
|
+
try {
|
|
165
|
+
assertDirectoryIdentity(parent, parentIdentity, 'temporary file cleanup')
|
|
166
|
+
const stat = fs.lstatSync(filename)
|
|
167
|
+
if (stat.isFile() || stat.isSymbolicLink()) fs.unlinkSync(filename)
|
|
168
|
+
} catch {}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Refuses a final path component that is a symbolic link.
|
|
173
|
+
*
|
|
174
|
+
* @param {string} filename candidate filename
|
|
175
|
+
* @param {string} label customer-facing path label
|
|
176
|
+
*/
|
|
177
|
+
function refuseSymbolicLink (filename, label) {
|
|
178
|
+
try {
|
|
179
|
+
if (fs.lstatSync(filename).isSymbolicLink()) {
|
|
180
|
+
throw new Error(`Refusing ${label} symbolic-link target: ${filename}`)
|
|
181
|
+
}
|
|
182
|
+
} catch (error) {
|
|
183
|
+
if (error.code !== 'ENOENT') throw error
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Checks lexical path containment.
|
|
189
|
+
*
|
|
190
|
+
* @param {string} root allowed root
|
|
191
|
+
* @param {string} filename candidate path
|
|
192
|
+
* @returns {boolean} true when the candidate is inside the root
|
|
193
|
+
*/
|
|
194
|
+
function isPathInside (root, filename) {
|
|
195
|
+
const relative = path.relative(root, filename)
|
|
196
|
+
return relative === '' || (relative && !relative.startsWith('..') && !path.isAbsolute(relative))
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
module.exports = {
|
|
200
|
+
createFileSafely,
|
|
201
|
+
ensureSafeDirectory,
|
|
202
|
+
writeFileSafely,
|
|
203
|
+
}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const {
|
|
4
|
+
discoverScenarioTests,
|
|
5
|
+
discoveryEvidence,
|
|
6
|
+
error,
|
|
7
|
+
failWithDebugRerun,
|
|
8
|
+
pass,
|
|
9
|
+
prepareGeneratedScenario,
|
|
10
|
+
requireGeneratedScenario,
|
|
11
|
+
runInstrumentedCommand,
|
|
12
|
+
testEventSamples,
|
|
13
|
+
testsForDiscoveredScenario,
|
|
14
|
+
} = require('./helpers')
|
|
15
|
+
|
|
16
|
+
async function runAutoTestRetries ({ framework, out, options }) {
|
|
17
|
+
const scenarioName = 'atr'
|
|
18
|
+
const skipResult = requireGeneratedScenario(framework, 'atr-fail-once', scenarioName)
|
|
19
|
+
if (skipResult) return skipResult
|
|
20
|
+
|
|
21
|
+
let outDir
|
|
22
|
+
try {
|
|
23
|
+
const { scenario } = await prepareGeneratedScenario(framework, 'atr-fail-once')
|
|
24
|
+
const discovery = await discoverScenarioTests({ framework, out, scenarioName, scenario, options })
|
|
25
|
+
if (discovery.tests.length === 0) {
|
|
26
|
+
return failWithDebugRerun({
|
|
27
|
+
command: scenario.runCommand,
|
|
28
|
+
diagnosis: 'The fail-once generated test was not reported during baseline identity discovery.',
|
|
29
|
+
evidence: discoveryEvidence(discovery),
|
|
30
|
+
framework,
|
|
31
|
+
options,
|
|
32
|
+
out,
|
|
33
|
+
outDir: discovery.outDir,
|
|
34
|
+
scenarioName,
|
|
35
|
+
})
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const fixtureConfig = {
|
|
39
|
+
settings: {
|
|
40
|
+
flaky_test_retries_enabled: true,
|
|
41
|
+
},
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const run = await runInstrumentedCommand({
|
|
45
|
+
framework,
|
|
46
|
+
out,
|
|
47
|
+
scenarioName,
|
|
48
|
+
command: scenario.runCommand,
|
|
49
|
+
options,
|
|
50
|
+
fixtureConfig,
|
|
51
|
+
})
|
|
52
|
+
outDir = run.outDir
|
|
53
|
+
|
|
54
|
+
const tests = testsForDiscoveredScenario(run.events, scenario, discovery)
|
|
55
|
+
const autoTestRetryEvents = tests.filter(test => test.retryReason === 'auto_test_retry')
|
|
56
|
+
const externalRetryEvents = tests.filter(test => test.isRetry && test.retryReason !== 'auto_test_retry')
|
|
57
|
+
const evidence = {
|
|
58
|
+
...discoveryEvidence(discovery),
|
|
59
|
+
commandExitCode: run.result.exitCode,
|
|
60
|
+
settingsLoadedFromCache: run.offline.inputs.settings?.status === 'loaded',
|
|
61
|
+
matchingTestEvents: tests.length,
|
|
62
|
+
autoTestRetryEvents: autoTestRetryEvents.length,
|
|
63
|
+
externalRetryEvents: externalRetryEvents.length,
|
|
64
|
+
failedAttempts: tests.filter(test => test.testStatus === 'fail' || test.error === 1).length,
|
|
65
|
+
passedAttempts: tests.filter(test => test.testStatus === 'pass').length,
|
|
66
|
+
samples: testEventSamples(tests),
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (!evidence.settingsLoadedFromCache) {
|
|
70
|
+
return failWithDebugRerun({
|
|
71
|
+
command: scenario.runCommand,
|
|
72
|
+
fixtureConfig,
|
|
73
|
+
diagnosis: 'Auto Test Retries settings were not loaded from the offline cache fixture.',
|
|
74
|
+
evidence,
|
|
75
|
+
framework,
|
|
76
|
+
options,
|
|
77
|
+
out,
|
|
78
|
+
outDir,
|
|
79
|
+
scenarioName,
|
|
80
|
+
})
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (run.result.exitCode !== 0) {
|
|
84
|
+
return failWithDebugRerun({
|
|
85
|
+
command: scenario.runCommand,
|
|
86
|
+
fixtureConfig,
|
|
87
|
+
diagnosis: getAutoTestRetriesFailureDiagnosis(framework, evidence),
|
|
88
|
+
evidence,
|
|
89
|
+
framework,
|
|
90
|
+
options,
|
|
91
|
+
out,
|
|
92
|
+
outDir,
|
|
93
|
+
scenarioName,
|
|
94
|
+
})
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (tests.length < 2 || autoTestRetryEvents.length === 0) {
|
|
98
|
+
return failWithDebugRerun({
|
|
99
|
+
command: scenario.runCommand,
|
|
100
|
+
fixtureConfig,
|
|
101
|
+
diagnosis: getAutoTestRetriesFailureDiagnosis(framework, evidence),
|
|
102
|
+
evidence,
|
|
103
|
+
framework,
|
|
104
|
+
options,
|
|
105
|
+
out,
|
|
106
|
+
outDir,
|
|
107
|
+
scenarioName,
|
|
108
|
+
})
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return pass(
|
|
112
|
+
framework,
|
|
113
|
+
scenarioName,
|
|
114
|
+
'The fail-once generated test was retried and the command passed.',
|
|
115
|
+
evidence,
|
|
116
|
+
outDir
|
|
117
|
+
)
|
|
118
|
+
} catch (err) {
|
|
119
|
+
return error(framework, scenarioName, err, outDir)
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function getAutoTestRetriesFailureDiagnosis (framework, evidence) {
|
|
124
|
+
const frameworkName = getFrameworkName(framework)
|
|
125
|
+
const retryTagSummary = getRetryTagSummary(evidence.autoTestRetryEvents)
|
|
126
|
+
if (evidence.autoTestRetryEvents > 0 || evidence.failedAttempts > 1) {
|
|
127
|
+
return 'Auto Test Retries executed for the generated test, but every attempt failed. Observed ' +
|
|
128
|
+
`${formatAttemptCount(evidence.failedAttempts, 'failed')}, ` +
|
|
129
|
+
`${formatAttemptCount(evidence.passedAttempts, 'passed retry')}, and ${retryTagSummary}. ` +
|
|
130
|
+
'Review the generated test failure because retry execution itself was observed.'
|
|
131
|
+
}
|
|
132
|
+
return 'Auto Test Retries was enabled, and the generated failing test was reported, but ' +
|
|
133
|
+
`${frameworkName} ` +
|
|
134
|
+
`did not execute a retry attempt. Observed ${formatAttemptCount(evidence.failedAttempts, 'failed')}, ` +
|
|
135
|
+
`${formatAttemptCount(evidence.passedAttempts, 'passed retry')}, and ${retryTagSummary}.`
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function formatAttemptCount (count, label) {
|
|
139
|
+
return `${count} ${label} attempt${count === 1 ? '' : 's'}`
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function getRetryTagSummary (count) {
|
|
143
|
+
if (count === 0) return 'no test.retry_reason=auto_test_retry tag'
|
|
144
|
+
if (count === 1) return '1 event tagged with test.retry_reason=auto_test_retry'
|
|
145
|
+
return `${count} events tagged with test.retry_reason=auto_test_retry`
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function getFrameworkName (framework) {
|
|
149
|
+
return {
|
|
150
|
+
cucumber: 'Cucumber',
|
|
151
|
+
cypress: 'Cypress',
|
|
152
|
+
jest: 'Jest',
|
|
153
|
+
mocha: 'Mocha',
|
|
154
|
+
playwright: 'Playwright',
|
|
155
|
+
vitest: 'Vitest',
|
|
156
|
+
}[framework.framework] || 'the test runner'
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
module.exports = { getAutoTestRetriesFailureDiagnosis, runAutoTestRetries }
|