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
|
@@ -13,6 +13,25 @@ const REQUIRED_SETTINGS_KEYS = [
|
|
|
13
13
|
'impacted_tests_enabled',
|
|
14
14
|
'coverage_report_upload_enabled',
|
|
15
15
|
]
|
|
16
|
+
const SETTINGS_BOOLEAN_KEYS = [
|
|
17
|
+
'code_coverage',
|
|
18
|
+
'tests_skipping',
|
|
19
|
+
'itr_enabled',
|
|
20
|
+
'require_git',
|
|
21
|
+
'flaky_test_retries_enabled',
|
|
22
|
+
'di_enabled',
|
|
23
|
+
'known_tests_enabled',
|
|
24
|
+
'impacted_tests_enabled',
|
|
25
|
+
'coverage_report_upload_enabled',
|
|
26
|
+
]
|
|
27
|
+
const EARLY_FLAKE_DETECTION_KEYS = ['enabled', 'faulty_session_threshold', 'slow_test_retries']
|
|
28
|
+
const TEST_MANAGEMENT_KEYS = ['attempt_to_fix_retries', 'enabled']
|
|
29
|
+
const RETRY_THRESHOLD_PATTERN = /^\d+(?:ms|s|m|h)$/
|
|
30
|
+
const MAX_VALIDATION_MODULES = 1000
|
|
31
|
+
const MAX_VALIDATION_SUITES = 10_000
|
|
32
|
+
const MAX_VALIDATION_TESTS = 100_000
|
|
33
|
+
const MAX_VALIDATION_RETRIES = 100
|
|
34
|
+
const MAX_VALIDATION_STRING_BYTES = 4096
|
|
16
35
|
|
|
17
36
|
function isObject (value) {
|
|
18
37
|
return value !== null && typeof value === 'object' && !Array.isArray(value)
|
|
@@ -38,7 +57,7 @@ function getAttributes (response, endpoint) {
|
|
|
38
57
|
return attributes
|
|
39
58
|
}
|
|
40
59
|
|
|
41
|
-
function validateSettingsResponse (response) {
|
|
60
|
+
function validateSettingsResponse (response, options = {}) {
|
|
42
61
|
const attributes = response?.data?.attributes ?? response
|
|
43
62
|
assertObject(attributes, 'Invalid settings response: attributes must be an object')
|
|
44
63
|
assertHasKeys(attributes, REQUIRED_SETTINGS_KEYS, 'settings')
|
|
@@ -46,9 +65,27 @@ function validateSettingsResponse (response) {
|
|
|
46
65
|
assertObject(attributes.test_management, 'Invalid settings response: test_management must be an object')
|
|
47
66
|
assertHasKeys(attributes.early_flake_detection, ['enabled'], 'settings early_flake_detection')
|
|
48
67
|
assertHasKeys(attributes.test_management, ['enabled'], 'settings test_management')
|
|
68
|
+
if (!options.validationMode) return
|
|
69
|
+
|
|
70
|
+
assertOnlyKeys(attributes, REQUIRED_SETTINGS_KEYS, 'settings')
|
|
71
|
+
assertOnlyKeys(attributes.early_flake_detection, EARLY_FLAKE_DETECTION_KEYS, 'settings early_flake_detection')
|
|
72
|
+
assertOnlyKeys(attributes.test_management, TEST_MANAGEMENT_KEYS, 'settings test_management')
|
|
73
|
+
assertBooleanFields(attributes, SETTINGS_BOOLEAN_KEYS, 'settings')
|
|
74
|
+
assertBooleanFields(attributes.early_flake_detection, ['enabled'], 'settings early_flake_detection')
|
|
75
|
+
assertBooleanFields(attributes.test_management, ['enabled'], 'settings test_management')
|
|
76
|
+
assertOptionalBoundedNumber(
|
|
77
|
+
attributes.early_flake_detection.faulty_session_threshold,
|
|
78
|
+
'settings early_flake_detection faulty_session_threshold'
|
|
79
|
+
)
|
|
80
|
+
assertRetryMap(attributes.early_flake_detection.slow_test_retries)
|
|
81
|
+
assertOptionalBoundedNumber(
|
|
82
|
+
attributes.test_management.attempt_to_fix_retries,
|
|
83
|
+
'settings test_management attempt_to_fix_retries',
|
|
84
|
+
{ integer: true }
|
|
85
|
+
)
|
|
49
86
|
}
|
|
50
87
|
|
|
51
|
-
function validateKnownTestsResponse (response) {
|
|
88
|
+
function validateKnownTestsResponse (response, options = {}) {
|
|
52
89
|
const attributes = getAttributes(response, 'known tests')
|
|
53
90
|
assertHasKeys(attributes, ['tests'], 'known tests')
|
|
54
91
|
|
|
@@ -56,26 +93,46 @@ function validateKnownTestsResponse (response) {
|
|
|
56
93
|
if (tests === null) return
|
|
57
94
|
assertObject(tests, 'Invalid known tests response: tests must be an object or null')
|
|
58
95
|
|
|
59
|
-
|
|
96
|
+
let suiteCount = 0
|
|
97
|
+
let testCount = 0
|
|
98
|
+
const modules = Object.entries(tests)
|
|
99
|
+
if (options.validationMode && modules.length > MAX_VALIDATION_MODULES) {
|
|
100
|
+
throw new Error('Invalid known tests response: too many modules')
|
|
101
|
+
}
|
|
102
|
+
for (const [moduleName, suites] of modules) {
|
|
103
|
+
if (options.validationMode) assertValidationString(moduleName, 'known tests module')
|
|
60
104
|
assertObject(suites, 'Invalid known tests response: module suites must be objects')
|
|
61
|
-
for (const testNames of Object.
|
|
105
|
+
for (const [suiteName, testNames] of Object.entries(suites)) {
|
|
106
|
+
suiteCount++
|
|
107
|
+
if (options.validationMode) {
|
|
108
|
+
if (suiteCount > MAX_VALIDATION_SUITES) throw new Error('Invalid known tests response: too many suites')
|
|
109
|
+
assertValidationString(suiteName, 'known tests suite')
|
|
110
|
+
}
|
|
62
111
|
if (!Array.isArray(testNames)) {
|
|
63
112
|
throw new TypeError('Invalid known tests response: suite tests must be arrays')
|
|
64
113
|
}
|
|
65
114
|
for (const testName of testNames) {
|
|
115
|
+
testCount++
|
|
66
116
|
if (typeof testName !== 'string') {
|
|
67
117
|
throw new TypeError('Invalid known tests response: test names must be strings')
|
|
68
118
|
}
|
|
119
|
+
if (options.validationMode) {
|
|
120
|
+
if (testCount > MAX_VALIDATION_TESTS) throw new Error('Invalid known tests response: too many tests')
|
|
121
|
+
assertValidationString(testName, 'known test name')
|
|
122
|
+
}
|
|
69
123
|
}
|
|
70
124
|
}
|
|
71
125
|
}
|
|
72
126
|
}
|
|
73
127
|
|
|
74
|
-
function validateSkippableTestsResponse (response) {
|
|
128
|
+
function validateSkippableTestsResponse (response, options = {}) {
|
|
75
129
|
assertObject(response, 'Invalid skippable tests response: response must be an object')
|
|
76
130
|
if (!Array.isArray(response.data)) {
|
|
77
131
|
throw new TypeError('Invalid skippable tests response: data must be an array')
|
|
78
132
|
}
|
|
133
|
+
if (options.validationMode && response.data.length > MAX_VALIDATION_TESTS) {
|
|
134
|
+
throw new Error('Invalid skippable tests response: too many items')
|
|
135
|
+
}
|
|
79
136
|
if (response.meta !== undefined) {
|
|
80
137
|
assertObject(response.meta, 'Invalid skippable tests response: meta must be an object')
|
|
81
138
|
}
|
|
@@ -91,28 +148,56 @@ function validateSkippableTestsResponse (response) {
|
|
|
91
148
|
if (typeof item.type !== 'string') {
|
|
92
149
|
throw new TypeError('Invalid skippable tests response: data entry type must be a string')
|
|
93
150
|
}
|
|
151
|
+
if (options.validationMode) assertValidationString(item.type, 'skippable test type')
|
|
94
152
|
assertObject(item.attributes, 'Invalid skippable tests response: data entry attributes must be an object')
|
|
95
153
|
if ((item.type === 'suite' || item.type === 'test') && typeof item.attributes.suite !== 'string') {
|
|
96
154
|
throw new Error('Invalid skippable tests response: data entry suite must be a string')
|
|
97
155
|
}
|
|
156
|
+
if (options.validationMode && item.attributes.suite !== undefined) {
|
|
157
|
+
assertValidationString(item.attributes.suite, 'skippable test suite')
|
|
158
|
+
}
|
|
98
159
|
if (item.type === 'test' && typeof item.attributes.name !== 'string') {
|
|
99
160
|
throw new Error('Invalid skippable tests response: data entry name must be a string')
|
|
100
161
|
}
|
|
162
|
+
if (options.validationMode && item.attributes.name !== undefined) {
|
|
163
|
+
assertValidationString(item.attributes.name, 'skippable test name')
|
|
164
|
+
}
|
|
101
165
|
}
|
|
102
166
|
}
|
|
103
167
|
|
|
104
|
-
function validateTestManagementTestsResponse (response) {
|
|
168
|
+
function validateTestManagementTestsResponse (response, options = {}) {
|
|
105
169
|
const attributes = getAttributes(response, 'test management tests')
|
|
106
170
|
assertHasKeys(attributes, ['modules'], 'test management tests')
|
|
107
171
|
assertObject(attributes.modules, 'Invalid test management tests response: modules must be an object')
|
|
108
172
|
|
|
109
|
-
|
|
173
|
+
let suiteCount = 0
|
|
174
|
+
let testCount = 0
|
|
175
|
+
const modules = Object.entries(attributes.modules)
|
|
176
|
+
if (options.validationMode && modules.length > MAX_VALIDATION_MODULES) {
|
|
177
|
+
throw new Error('Invalid test management tests response: too many modules')
|
|
178
|
+
}
|
|
179
|
+
for (const [moduleName, testModule] of modules) {
|
|
180
|
+
if (options.validationMode) assertValidationString(moduleName, 'test management module')
|
|
110
181
|
assertObject(testModule, 'Invalid test management tests response: modules entries must be objects')
|
|
111
182
|
assertObject(testModule.suites, 'Invalid test management tests response: module suites must be objects')
|
|
112
|
-
for (const suite of Object.
|
|
183
|
+
for (const [suiteName, suite] of Object.entries(testModule.suites)) {
|
|
184
|
+
suiteCount++
|
|
185
|
+
if (options.validationMode) {
|
|
186
|
+
if (suiteCount > MAX_VALIDATION_SUITES) {
|
|
187
|
+
throw new Error('Invalid test management tests response: too many suites')
|
|
188
|
+
}
|
|
189
|
+
assertValidationString(suiteName, 'test management suite')
|
|
190
|
+
}
|
|
113
191
|
assertObject(suite, 'Invalid test management tests response: suites entries must be objects')
|
|
114
192
|
assertObject(suite.tests, 'Invalid test management tests response: suite tests must be objects')
|
|
115
|
-
for (const test of Object.
|
|
193
|
+
for (const [testName, test] of Object.entries(suite.tests)) {
|
|
194
|
+
testCount++
|
|
195
|
+
if (options.validationMode) {
|
|
196
|
+
if (testCount > MAX_VALIDATION_TESTS) {
|
|
197
|
+
throw new Error('Invalid test management tests response: too many tests')
|
|
198
|
+
}
|
|
199
|
+
assertValidationString(testName, 'test management test')
|
|
200
|
+
}
|
|
116
201
|
assertObject(test, 'Invalid test management tests response: tests entries must be objects')
|
|
117
202
|
assertObject(test.properties, 'Invalid test management tests response: test properties must be objects')
|
|
118
203
|
}
|
|
@@ -120,6 +205,49 @@ function validateTestManagementTestsResponse (response) {
|
|
|
120
205
|
}
|
|
121
206
|
}
|
|
122
207
|
|
|
208
|
+
function assertBooleanFields (object, keys, endpoint) {
|
|
209
|
+
for (const key of keys) {
|
|
210
|
+
if (typeof object[key] !== 'boolean') {
|
|
211
|
+
throw new TypeError(`Invalid ${endpoint} response: ${key} must be a boolean`)
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function assertOptionalBoundedNumber (value, endpoint, { integer = false } = {}) {
|
|
217
|
+
if (value === undefined) return
|
|
218
|
+
if (!Number.isFinite(value) || (integer && !Number.isInteger(value)) ||
|
|
219
|
+
value < 0 || value > MAX_VALIDATION_RETRIES) {
|
|
220
|
+
throw new TypeError(`Invalid ${endpoint}: value must be between 0 and ${MAX_VALIDATION_RETRIES}`)
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function assertRetryMap (retries) {
|
|
225
|
+
if (retries === undefined) return
|
|
226
|
+
assertObject(retries, 'Invalid settings response: early_flake_detection slow_test_retries must be an object')
|
|
227
|
+
for (const [threshold, count] of Object.entries(retries)) {
|
|
228
|
+
assertValidationString(threshold, 'early flake detection retry threshold')
|
|
229
|
+
if (!RETRY_THRESHOLD_PATTERN.test(threshold)) {
|
|
230
|
+
throw new TypeError('Invalid early flake detection retry threshold: expected a duration such as 5s')
|
|
231
|
+
}
|
|
232
|
+
assertOptionalBoundedNumber(count, `early flake detection retry count ${threshold}`, { integer: true })
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function assertOnlyKeys (object, keys, endpoint) {
|
|
237
|
+
const allowed = new Set(keys)
|
|
238
|
+
for (const key of Object.keys(object)) {
|
|
239
|
+
if (!allowed.has(key)) {
|
|
240
|
+
throw new Error(`Invalid ${endpoint} response: unexpected ${key}`)
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function assertValidationString (value, field) {
|
|
246
|
+
if (typeof value !== 'string' || Buffer.byteLength(value) > MAX_VALIDATION_STRING_BYTES) {
|
|
247
|
+
throw new TypeError(`Invalid ${field}: value must be a bounded string`)
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
123
251
|
module.exports = {
|
|
124
252
|
validateKnownTestsResponse,
|
|
125
253
|
validateSettingsResponse,
|
|
@@ -48,6 +48,10 @@ const SKIPPABLE_TESTS_FILE_NAME = 'skippable_tests.json'
|
|
|
48
48
|
const TEST_MANAGEMENT_FILE_NAME = 'test_management.json'
|
|
49
49
|
|
|
50
50
|
const RUNFILES_MANIFEST_SEPARATOR = ' '
|
|
51
|
+
const DEFAULT_VALIDATION_MAX_FILE_BYTES = 1024 * 1024
|
|
52
|
+
const DEFAULT_VALIDATION_MAX_ENTRIES = 100_000
|
|
53
|
+
const DEFAULT_VALIDATION_MAX_NESTING_DEPTH = 32
|
|
54
|
+
const DEFAULT_VALIDATION_MAX_STRING_BYTES = 4096
|
|
51
55
|
|
|
52
56
|
function parseManifestVersion (content) {
|
|
53
57
|
// Supported just the number version or 'version=x'
|
|
@@ -57,12 +61,22 @@ function parseManifestVersion (content) {
|
|
|
57
61
|
}
|
|
58
62
|
|
|
59
63
|
class TestOptimizationHttpCache {
|
|
60
|
-
constructor ({
|
|
64
|
+
constructor ({
|
|
65
|
+
cwd = process.cwd(),
|
|
66
|
+
env,
|
|
67
|
+
validationManifestPath,
|
|
68
|
+
maxFileBytes = DEFAULT_VALIDATION_MAX_FILE_BYTES,
|
|
69
|
+
} = {}) {
|
|
61
70
|
this._cwd = cwd
|
|
62
71
|
// This cache intentionally consumes env vars that are not tracer config keys. (??)
|
|
63
72
|
// eslint-disable-next-line eslint-rules/eslint-process-env
|
|
64
73
|
this._env = env ?? process.env
|
|
65
|
-
this.
|
|
74
|
+
this._validationManifestPath = validationManifestPath
|
|
75
|
+
this._maxFileBytes = maxFileBytes
|
|
76
|
+
this._lastError = undefined
|
|
77
|
+
this._manifestPath = validationManifestPath
|
|
78
|
+
? this._resolveValidationManifestPath(validationManifestPath)
|
|
79
|
+
: this._resolveManifestPath()
|
|
66
80
|
this._testOptimizationPath = undefined
|
|
67
81
|
this._httpCachePath = undefined
|
|
68
82
|
this._available = false
|
|
@@ -74,6 +88,10 @@ class TestOptimizationHttpCache {
|
|
|
74
88
|
return this._available
|
|
75
89
|
}
|
|
76
90
|
|
|
91
|
+
getLastError () {
|
|
92
|
+
return this._lastError
|
|
93
|
+
}
|
|
94
|
+
|
|
77
95
|
readSettings () {
|
|
78
96
|
const payload = this._readFile(SETTINGS_FILE_NAME)
|
|
79
97
|
if (payload === CACHE_MISS) {
|
|
@@ -82,7 +100,11 @@ class TestOptimizationHttpCache {
|
|
|
82
100
|
}
|
|
83
101
|
|
|
84
102
|
try {
|
|
85
|
-
const settings = parseLibraryConfigurationResponse(
|
|
103
|
+
const settings = parseLibraryConfigurationResponse(
|
|
104
|
+
this._parsePayload(payload, SETTINGS_FILE_NAME),
|
|
105
|
+
undefined,
|
|
106
|
+
{ validateRequiredFields: true, validationMode: Boolean(this._validationManifestPath) }
|
|
107
|
+
)
|
|
86
108
|
incrementCountMetric(TELEMETRY_GIT_REQUESTS_SETTINGS_RESPONSE, settings)
|
|
87
109
|
return settings
|
|
88
110
|
} catch (err) {
|
|
@@ -97,7 +119,10 @@ class TestOptimizationHttpCache {
|
|
|
97
119
|
if (payload === CACHE_MISS) return CACHE_MISS
|
|
98
120
|
|
|
99
121
|
try {
|
|
100
|
-
const knownTests = parseKnownTestsResponse(
|
|
122
|
+
const knownTests = parseKnownTestsResponse(
|
|
123
|
+
this._parsePayload(payload, KNOWN_TESTS_FILE_NAME),
|
|
124
|
+
{ validateRequiredFields: true, validationMode: Boolean(this._validationManifestPath) }
|
|
125
|
+
)
|
|
101
126
|
distributionMetric(TELEMETRY_KNOWN_TESTS_RESPONSE_TESTS, {}, getNumFromKnownTests(knownTests))
|
|
102
127
|
distributionMetric(TELEMETRY_KNOWN_TESTS_RESPONSE_BYTES, {}, payload.length)
|
|
103
128
|
return knownTests
|
|
@@ -112,8 +137,13 @@ class TestOptimizationHttpCache {
|
|
|
112
137
|
if (payload === CACHE_MISS) return CACHE_MISS
|
|
113
138
|
|
|
114
139
|
try {
|
|
115
|
-
const
|
|
116
|
-
const
|
|
140
|
+
const boundedPayload = this._parsePayload(payload, SKIPPABLE_TESTS_FILE_NAME)
|
|
141
|
+
const parsedResponse = typeof boundedPayload === 'string' ? JSON.parse(boundedPayload) : boundedPayload
|
|
142
|
+
const result = parseSkippableSuitesResponse(parsedResponse, {
|
|
143
|
+
...options,
|
|
144
|
+
validateRequiredFields: true,
|
|
145
|
+
validationMode: Boolean(this._validationManifestPath),
|
|
146
|
+
})
|
|
117
147
|
const testLevel = options.testLevel || 'suite'
|
|
118
148
|
logSkippableSuitesResponse(result, testLevel)
|
|
119
149
|
const skippableItems = parsedResponse.data.filter(({ type }) => type === testLevel)
|
|
@@ -137,7 +167,10 @@ class TestOptimizationHttpCache {
|
|
|
137
167
|
if (payload === CACHE_MISS) return false
|
|
138
168
|
|
|
139
169
|
try {
|
|
140
|
-
parseSkippableSuitesResponse(
|
|
170
|
+
parseSkippableSuitesResponse(
|
|
171
|
+
this._parsePayload(payload, SKIPPABLE_TESTS_FILE_NAME),
|
|
172
|
+
{ ...options, validateRequiredFields: true, validationMode: Boolean(this._validationManifestPath) }
|
|
173
|
+
)
|
|
141
174
|
return true
|
|
142
175
|
} catch {
|
|
143
176
|
return false
|
|
@@ -149,7 +182,10 @@ class TestOptimizationHttpCache {
|
|
|
149
182
|
if (payload === CACHE_MISS) return CACHE_MISS
|
|
150
183
|
|
|
151
184
|
try {
|
|
152
|
-
const testManagementTests = parseTestManagementTestsResponse(
|
|
185
|
+
const testManagementTests = parseTestManagementTestsResponse(
|
|
186
|
+
this._parsePayload(payload, TEST_MANAGEMENT_FILE_NAME),
|
|
187
|
+
{ validateRequiredFields: true, validationMode: Boolean(this._validationManifestPath) }
|
|
188
|
+
)
|
|
153
189
|
distributionMetric(
|
|
154
190
|
TELEMETRY_TEST_MANAGEMENT_TESTS_RESPONSE_TESTS,
|
|
155
191
|
{},
|
|
@@ -165,12 +201,18 @@ class TestOptimizationHttpCache {
|
|
|
165
201
|
|
|
166
202
|
_buildReader () {
|
|
167
203
|
if (!this._manifestPath) {
|
|
204
|
+
if (this._validationManifestPath && !this._lastError) {
|
|
205
|
+
this._setError('Offline Test Optimization validation manifest was not found.')
|
|
206
|
+
}
|
|
168
207
|
log.debug('Test Optimization HTTP cache manifest not found')
|
|
169
208
|
return
|
|
170
209
|
}
|
|
171
210
|
|
|
172
211
|
const version = this._readManifestVersion(this._manifestPath)
|
|
173
212
|
if (version !== SUPPORTED_MANIFEST_VERSION) {
|
|
213
|
+
if (this._validationManifestPath) {
|
|
214
|
+
this._setError(`Unsupported offline Test Optimization validation manifest version: ${version || 'missing'}.`)
|
|
215
|
+
}
|
|
174
216
|
log.debug('Unsupported Test Optimization HTTP cache manifest version %j at %s', version, this._manifestPath)
|
|
175
217
|
return
|
|
176
218
|
}
|
|
@@ -184,6 +226,9 @@ class TestOptimizationHttpCache {
|
|
|
184
226
|
|
|
185
227
|
const settingsPath = path.join(this._httpCachePath, SETTINGS_FILE_NAME)
|
|
186
228
|
if (!fs.existsSync(settingsPath)) {
|
|
229
|
+
if (this._validationManifestPath) {
|
|
230
|
+
this._setError('Offline Test Optimization validation settings fixture is missing.')
|
|
231
|
+
}
|
|
187
232
|
log.debug('Test Optimization HTTP cache settings file not found at %s', settingsPath)
|
|
188
233
|
return
|
|
189
234
|
}
|
|
@@ -207,6 +252,30 @@ class TestOptimizationHttpCache {
|
|
|
207
252
|
}
|
|
208
253
|
}
|
|
209
254
|
|
|
255
|
+
_resolveValidationManifestPath (manifestPath) {
|
|
256
|
+
if (!path.isAbsolute(manifestPath)) {
|
|
257
|
+
this._setError('Offline Test Optimization validation manifest path must be absolute.')
|
|
258
|
+
return
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
const resolvedManifestPath = path.resolve(manifestPath)
|
|
262
|
+
const testOptimizationPath = path.dirname(resolvedManifestPath)
|
|
263
|
+
const fixtureRoot = path.dirname(testOptimizationPath)
|
|
264
|
+
if (path.basename(resolvedManifestPath) !== MANIFEST_FILE_NAME ||
|
|
265
|
+
path.basename(testOptimizationPath) !== PLAN_FOLDER) {
|
|
266
|
+
this._setError('Offline Test Optimization validation manifest must use the fixed .testoptimization layout.')
|
|
267
|
+
return
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
try {
|
|
271
|
+
assertPathComponentsAreNotSymlinks(fixtureRoot, resolvedManifestPath)
|
|
272
|
+
assertRegularFixtureFile(resolvedManifestPath)
|
|
273
|
+
return resolvedManifestPath
|
|
274
|
+
} catch (err) {
|
|
275
|
+
this._setError(err.message)
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
210
279
|
_resolveRunfilePath (manifestFile) {
|
|
211
280
|
if (fs.existsSync(manifestFile)) {
|
|
212
281
|
return manifestFile
|
|
@@ -257,8 +326,10 @@ class TestOptimizationHttpCache {
|
|
|
257
326
|
|
|
258
327
|
_readManifestVersion (manifestPath) {
|
|
259
328
|
try {
|
|
329
|
+
this._assertValidationFixtureFile(manifestPath)
|
|
260
330
|
return parseManifestVersion(fs.readFileSync(manifestPath, 'utf8'))
|
|
261
331
|
} catch (err) {
|
|
332
|
+
if (this._validationManifestPath) this._setError(err.message)
|
|
262
333
|
log.debug('Failed to read Test Optimization HTTP cache manifest %s: %s', manifestPath, err.message)
|
|
263
334
|
}
|
|
264
335
|
}
|
|
@@ -268,24 +339,113 @@ class TestOptimizationHttpCache {
|
|
|
268
339
|
|
|
269
340
|
const filePath = path.join(this._httpCachePath, fileName)
|
|
270
341
|
try {
|
|
342
|
+
this._assertValidationFixtureFile(filePath)
|
|
271
343
|
log.debug('Reading Test Optimization HTTP cache file %s', filePath)
|
|
272
344
|
return fs.readFileSync(filePath, 'utf8')
|
|
273
345
|
} catch (err) {
|
|
346
|
+
if (this._validationManifestPath) this._setError(err.message)
|
|
274
347
|
log.debug('Test Optimization HTTP cache file %s could not be read: %s', filePath, err.message)
|
|
275
348
|
return CACHE_MISS
|
|
276
349
|
}
|
|
277
350
|
}
|
|
278
351
|
|
|
279
352
|
_logInvalidCacheFile (fileName, err) {
|
|
353
|
+
if (this._validationManifestPath) {
|
|
354
|
+
this._setError(`Invalid offline Test Optimization ${fileName} fixture: ${err.message}`)
|
|
355
|
+
}
|
|
280
356
|
log.debug('Test Optimization HTTP cache file %s could not be parsed: %s', fileName, err.message)
|
|
281
357
|
}
|
|
282
358
|
|
|
359
|
+
_assertValidationFixtureFile (filePath) {
|
|
360
|
+
if (!this._validationManifestPath) return
|
|
361
|
+
const fixtureRoot = path.dirname(path.dirname(this._manifestPath))
|
|
362
|
+
assertPathComponentsAreNotSymlinks(fixtureRoot, filePath)
|
|
363
|
+
const stat = assertRegularFixtureFile(filePath)
|
|
364
|
+
if (stat.size > this._maxFileBytes) {
|
|
365
|
+
throw new Error(
|
|
366
|
+
`Offline Test Optimization fixture ${path.basename(filePath)} exceeds ${this._maxFileBytes} bytes.`
|
|
367
|
+
)
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
_parsePayload (payload, fileName) {
|
|
372
|
+
if (!this._validationManifestPath) return payload
|
|
373
|
+
|
|
374
|
+
const parsed = JSON.parse(payload)
|
|
375
|
+
assertBoundedValidationValue(parsed, fileName)
|
|
376
|
+
return parsed
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
_setError (message) {
|
|
380
|
+
this._lastError = new Error(message)
|
|
381
|
+
}
|
|
382
|
+
|
|
283
383
|
_disable () {
|
|
284
384
|
this._available = false
|
|
285
385
|
}
|
|
286
386
|
}
|
|
287
387
|
|
|
388
|
+
function assertPathComponentsAreNotSymlinks (root, filename) {
|
|
389
|
+
const resolvedRoot = path.resolve(root)
|
|
390
|
+
const resolvedFilename = path.resolve(filename)
|
|
391
|
+
const rootStat = fs.lstatSync(resolvedRoot)
|
|
392
|
+
if (!rootStat.isDirectory() || rootStat.isSymbolicLink()) {
|
|
393
|
+
throw new Error(`Offline Test Optimization fixture root must be a regular directory: ${resolvedRoot}`)
|
|
394
|
+
}
|
|
395
|
+
const relative = path.relative(resolvedRoot, resolvedFilename)
|
|
396
|
+
if (relative.startsWith('..') || path.isAbsolute(relative)) {
|
|
397
|
+
throw new Error('Offline Test Optimization fixture path escapes its validation root.')
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
let current = resolvedRoot
|
|
401
|
+
for (const segment of relative ? relative.split(path.sep) : []) {
|
|
402
|
+
current = path.join(current, segment)
|
|
403
|
+
const stat = fs.lstatSync(current)
|
|
404
|
+
if (stat.isSymbolicLink()) {
|
|
405
|
+
throw new Error(`Offline Test Optimization fixture path contains a symbolic link: ${current}`)
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
function assertRegularFixtureFile (filename) {
|
|
411
|
+
const stat = fs.lstatSync(filename)
|
|
412
|
+
if (!stat.isFile()) {
|
|
413
|
+
throw new Error(`Offline Test Optimization fixture is not a regular file: ${filename}`)
|
|
414
|
+
}
|
|
415
|
+
if (stat.nlink > 1) {
|
|
416
|
+
throw new Error(`Offline Test Optimization fixture must not be hard-linked: ${filename}`)
|
|
417
|
+
}
|
|
418
|
+
return stat
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
function assertBoundedValidationValue (value, fileName) {
|
|
422
|
+
const pending = [{ depth: 0, value }]
|
|
423
|
+
let entries = 0
|
|
424
|
+
|
|
425
|
+
while (pending.length > 0) {
|
|
426
|
+
const current = pending.pop()
|
|
427
|
+
if (current.depth > DEFAULT_VALIDATION_MAX_NESTING_DEPTH) {
|
|
428
|
+
throw new Error(`${fileName} exceeds the validation nesting limit.`)
|
|
429
|
+
}
|
|
430
|
+
if (typeof current.value === 'string' &&
|
|
431
|
+
Buffer.byteLength(current.value) > DEFAULT_VALIDATION_MAX_STRING_BYTES) {
|
|
432
|
+
throw new Error(`${fileName} contains a string that exceeds the validation limit.`)
|
|
433
|
+
}
|
|
434
|
+
if (!current.value || typeof current.value !== 'object') continue
|
|
435
|
+
|
|
436
|
+
const values = Array.isArray(current.value) ? current.value : Object.values(current.value)
|
|
437
|
+
entries += values.length
|
|
438
|
+
if (entries > DEFAULT_VALIDATION_MAX_ENTRIES) {
|
|
439
|
+
throw new Error(`${fileName} exceeds the validation collection-entry limit.`)
|
|
440
|
+
}
|
|
441
|
+
for (const nestedValue of values) {
|
|
442
|
+
pending.push({ depth: current.depth + 1, value: nestedValue })
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
|
|
288
447
|
module.exports = {
|
|
289
448
|
CACHE_MISS,
|
|
449
|
+
DEFAULT_VALIDATION_MAX_FILE_BYTES,
|
|
290
450
|
TestOptimizationHttpCache,
|
|
291
451
|
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { statSync } = require('node:fs')
|
|
4
|
+
|
|
5
|
+
const {
|
|
6
|
+
TEST_FAILURE_SCREENSHOT_UPLOADED,
|
|
7
|
+
TEST_FAILURE_SCREENSHOT_UPLOAD_ERROR,
|
|
8
|
+
} = require('../plugins/util/test')
|
|
9
|
+
|
|
10
|
+
const dateNow = Date.now
|
|
11
|
+
|
|
12
|
+
const SCREENSHOT_UPLOAD_RESULT_UPLOADED = 'uploaded'
|
|
13
|
+
const SCREENSHOT_UPLOAD_RESULT_ERROR = 'error'
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Resolves a screenshot's capture time in epoch milliseconds.
|
|
17
|
+
*
|
|
18
|
+
* @param {object|string} screenshot - Framework screenshot metadata or its file path
|
|
19
|
+
* @param {string} filePath - Resolved screenshot file path
|
|
20
|
+
* @returns {number} Capture time in epoch milliseconds
|
|
21
|
+
*/
|
|
22
|
+
function getScreenshotCapturedAtMs (screenshot, filePath) {
|
|
23
|
+
const takenAt = screenshot !== null && typeof screenshot === 'object' ? screenshot.takenAt : undefined
|
|
24
|
+
if (takenAt) {
|
|
25
|
+
const parsedMs = new Date(takenAt).getTime()
|
|
26
|
+
if (Number.isInteger(parsedMs) && parsedMs > 0) {
|
|
27
|
+
return parsedMs
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
try {
|
|
31
|
+
return Math.floor(statSync(filePath).mtimeMs)
|
|
32
|
+
} catch {
|
|
33
|
+
return dateNow()
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Combines screenshot upload results, giving errors precedence over successes.
|
|
39
|
+
*
|
|
40
|
+
* @param {Array<string|undefined>} uploadResults - Per-screenshot upload results
|
|
41
|
+
* @returns {string|undefined} Combined upload result
|
|
42
|
+
*/
|
|
43
|
+
function getScreenshotUploadResult (uploadResults) {
|
|
44
|
+
let hasUploaded = false
|
|
45
|
+
for (const uploadResult of uploadResults) {
|
|
46
|
+
if (uploadResult === SCREENSHOT_UPLOAD_RESULT_ERROR) {
|
|
47
|
+
return SCREENSHOT_UPLOAD_RESULT_ERROR
|
|
48
|
+
}
|
|
49
|
+
if (uploadResult === SCREENSHOT_UPLOAD_RESULT_UPLOADED) {
|
|
50
|
+
hasUploaded = true
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return hasUploaded ? SCREENSHOT_UPLOAD_RESULT_UPLOADED : undefined
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Returns the test tag that represents an aggregate screenshot upload outcome.
|
|
58
|
+
*
|
|
59
|
+
* @param {string|undefined} uploadResult - Aggregate screenshot upload result
|
|
60
|
+
* @returns {string|undefined} Screenshot upload result tag
|
|
61
|
+
*/
|
|
62
|
+
function getScreenshotUploadTag (uploadResult) {
|
|
63
|
+
if (uploadResult === SCREENSHOT_UPLOAD_RESULT_ERROR) {
|
|
64
|
+
return TEST_FAILURE_SCREENSHOT_UPLOAD_ERROR
|
|
65
|
+
}
|
|
66
|
+
if (uploadResult === SCREENSHOT_UPLOAD_RESULT_UPLOADED) {
|
|
67
|
+
return TEST_FAILURE_SCREENSHOT_UPLOADED
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Tags a test span with the aggregate screenshot upload outcome.
|
|
73
|
+
*
|
|
74
|
+
* @param {object} testSpan - Test span to tag
|
|
75
|
+
* @param {string|undefined} uploadResult - Aggregate screenshot upload result
|
|
76
|
+
* @returns {void}
|
|
77
|
+
*/
|
|
78
|
+
function setScreenshotUploadTags (testSpan, uploadResult) {
|
|
79
|
+
const uploadTag = getScreenshotUploadTag(uploadResult)
|
|
80
|
+
if (uploadTag) testSpan.setTag(uploadTag, 'true')
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
module.exports = {
|
|
84
|
+
SCREENSHOT_UPLOAD_RESULT_ERROR,
|
|
85
|
+
SCREENSHOT_UPLOAD_RESULT_UPLOADED,
|
|
86
|
+
getScreenshotCapturedAtMs,
|
|
87
|
+
getScreenshotUploadResult,
|
|
88
|
+
getScreenshotUploadTag,
|
|
89
|
+
setScreenshotUploadTags,
|
|
90
|
+
}
|
|
@@ -16,6 +16,7 @@ export interface GeneratedConfig {
|
|
|
16
16
|
DD_API_SECURITY_MAX_DOWNSTREAM_BODY_BYTES: number;
|
|
17
17
|
DD_API_SECURITY_MAX_DOWNSTREAM_REQUEST_BODY_ANALYSIS: number;
|
|
18
18
|
DD_API_SECURITY_SAMPLE_DELAY: number;
|
|
19
|
+
DD_APPSEC_AGENTIC_ONBOARDING: string;
|
|
19
20
|
DD_APPSEC_SCA_ENABLED: boolean | undefined;
|
|
20
21
|
enabled: boolean | undefined;
|
|
21
22
|
eventTracking: {
|
|
@@ -431,6 +432,13 @@ export interface GeneratedConfig {
|
|
|
431
432
|
};
|
|
432
433
|
};
|
|
433
434
|
};
|
|
435
|
+
featureFlags: {
|
|
436
|
+
DD_FEATURE_FLAGS_CONFIGURATION_SOURCE: "agentless" | "remote_config";
|
|
437
|
+
DD_FEATURE_FLAGS_CONFIGURATION_SOURCE_AGENTLESS_BASE_URL: string | undefined;
|
|
438
|
+
DD_FEATURE_FLAGS_CONFIGURATION_SOURCE_AGENTLESS_POLL_INTERVAL_SECONDS: number;
|
|
439
|
+
DD_FEATURE_FLAGS_CONFIGURATION_SOURCE_AGENTLESS_REQUEST_TIMEOUT_SECONDS: number;
|
|
440
|
+
DD_FEATURE_FLAGS_ENABLED: boolean;
|
|
441
|
+
};
|
|
434
442
|
flushInterval: number;
|
|
435
443
|
flushMinSpans: number;
|
|
436
444
|
headerTags: string[];
|
|
@@ -562,6 +570,7 @@ export interface GeneratedConfig {
|
|
|
562
570
|
DD_CIVISIBILITY_TEST_COMMAND: string | undefined;
|
|
563
571
|
DD_CIVISIBILITY_TEST_MODULE_ID: string | undefined;
|
|
564
572
|
DD_CIVISIBILITY_TEST_SESSION_ID: string | undefined;
|
|
573
|
+
DD_CODE_COVERAGE_FLAGS: string | undefined;
|
|
565
574
|
DD_TEST_EARLY_FLAKE_DETECTION_RETRY_COUNT: number | undefined;
|
|
566
575
|
DD_TEST_FAILED_TEST_REPLAY_ENABLED: boolean;
|
|
567
576
|
DD_TEST_FAILURE_SCREENSHOTS_ENABLED: boolean | undefined;
|
|
@@ -612,6 +621,7 @@ export interface GeneratedEnvVarConfig {
|
|
|
612
621
|
DD_APM_FLUSH_DEADLINE_MILLISECONDS: number;
|
|
613
622
|
DD_APM_TRACING_ENABLED: boolean;
|
|
614
623
|
DD_APP_KEY: string | undefined;
|
|
624
|
+
DD_APPSEC_AGENTIC_ONBOARDING: string;
|
|
615
625
|
DD_APPSEC_AUTO_USER_INSTRUMENTATION_MODE: string;
|
|
616
626
|
DD_APPSEC_AUTOMATED_USER_EVENTS_TRACKING: string;
|
|
617
627
|
DD_APPSEC_COLLECT_ALL_HEADERS: boolean;
|
|
@@ -654,6 +664,7 @@ export interface GeneratedEnvVarConfig {
|
|
|
654
664
|
DD_CIVISIBILITY_TEST_COMMAND: string | undefined;
|
|
655
665
|
DD_CIVISIBILITY_TEST_MODULE_ID: string | undefined;
|
|
656
666
|
DD_CIVISIBILITY_TEST_SESSION_ID: string | undefined;
|
|
667
|
+
DD_CODE_COVERAGE_FLAGS: string | undefined;
|
|
657
668
|
DD_CODE_ORIGIN_FOR_SPANS_ENABLED: boolean;
|
|
658
669
|
DD_CODE_ORIGIN_FOR_SPANS_EXPERIMENTAL_EXIT_SPANS_ENABLED: boolean;
|
|
659
670
|
DD_CRASHTRACKING_ENABLED: boolean;
|
|
@@ -688,6 +699,11 @@ export interface GeneratedEnvVarConfig {
|
|
|
688
699
|
DD_EXPERIMENTAL_TEST_OPT_VITEST_NO_WORKER_INIT: boolean | undefined;
|
|
689
700
|
DD_EXPERIMENTAL_TEST_REQUESTS_FS_CACHE: boolean;
|
|
690
701
|
DD_EXTERNAL_ENV: string | undefined;
|
|
702
|
+
DD_FEATURE_FLAGS_CONFIGURATION_SOURCE: "agentless" | "remote_config";
|
|
703
|
+
DD_FEATURE_FLAGS_CONFIGURATION_SOURCE_AGENTLESS_BASE_URL: string | undefined;
|
|
704
|
+
DD_FEATURE_FLAGS_CONFIGURATION_SOURCE_AGENTLESS_POLL_INTERVAL_SECONDS: number;
|
|
705
|
+
DD_FEATURE_FLAGS_CONFIGURATION_SOURCE_AGENTLESS_REQUEST_TIMEOUT_SECONDS: number;
|
|
706
|
+
DD_FEATURE_FLAGS_ENABLED: boolean;
|
|
691
707
|
DD_GIT_BRANCH: string | undefined;
|
|
692
708
|
DD_GIT_COMMIT_AUTHOR_DATE: string | undefined;
|
|
693
709
|
DD_GIT_COMMIT_AUTHOR_EMAIL: string | undefined;
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
* @property {string} [namespace] Nests the canonical env name under this property path (e.g. `telemetry`).
|
|
14
14
|
* @property {string} [transform]
|
|
15
15
|
* @property {string} [allowed]
|
|
16
|
+
* @property {string} [description]
|
|
16
17
|
* @property {string|boolean} [deprecated]
|
|
17
18
|
* @property {boolean} [sensitive] Excludes the configuration value from configuration telemetry.
|
|
18
19
|
*/
|
|
@@ -333,6 +333,16 @@ class Config extends ConfigBase {
|
|
|
333
333
|
#applyCalculated () {
|
|
334
334
|
undo(this, 'calculated')
|
|
335
335
|
|
|
336
|
+
if (this.featureFlags.DD_FEATURE_FLAGS_ENABLED &&
|
|
337
|
+
!trackedConfigOrigins.has('featureFlags.DD_FEATURE_FLAGS_CONFIGURATION_SOURCE') &&
|
|
338
|
+
trackedConfigOrigins.has('experimental.flaggingProvider.enabled')) {
|
|
339
|
+
if (this.experimental.flaggingProvider.enabled) {
|
|
340
|
+
setAndTrack(this, 'featureFlags.DD_FEATURE_FLAGS_CONFIGURATION_SOURCE', 'remote_config')
|
|
341
|
+
} else {
|
|
342
|
+
setAndTrack(this, 'featureFlags.DD_FEATURE_FLAGS_ENABLED', false)
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
336
346
|
if (this.url ||
|
|
337
347
|
os.type() !== 'Windows_NT' &&
|
|
338
348
|
!trackedConfigOrigins.has('hostname') &&
|