dd-trace 6.3.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/index.electron.js +3 -0
- 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 +15 -8
- package/packages/datadog-instrumentations/src/ai.js +37 -26
- 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 +24 -10
- package/packages/datadog-instrumentations/src/cypress-config.js +398 -52
- package/packages/datadog-instrumentations/src/express.js +20 -2
- package/packages/datadog-instrumentations/src/fastify.js +7 -11
- package/packages/datadog-instrumentations/src/grpc/server.js +18 -0
- 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/helpers/router-helper.js +8 -8
- package/packages/datadog-instrumentations/src/http2/server.js +143 -17
- package/packages/datadog-instrumentations/src/jest.js +36 -4
- 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 +191 -106
- 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/services/kinesis.js +41 -16
- package/packages/datadog-plugin-aws-sdk/src/services/sns.js +21 -21
- package/packages/datadog-plugin-aws-sdk/src/services/sqs.js +14 -8
- package/packages/datadog-plugin-aws-sdk/src/util.js +2 -24
- package/packages/datadog-plugin-cucumber/src/index.js +1 -1
- package/packages/datadog-plugin-cypress/src/cypress-plugin.js +24 -62
- 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 +177 -59
- package/packages/datadog-plugin-graphql/src/parse.js +22 -1
- package/packages/datadog-plugin-graphql/src/request.js +32 -1
- package/packages/datadog-plugin-graphql/src/utils.js +42 -0
- package/packages/datadog-plugin-graphql/src/validate.js +15 -3
- package/packages/datadog-plugin-http2/src/client.js +1 -1
- package/packages/datadog-plugin-http2/src/server.js +38 -6
- package/packages/datadog-plugin-jest/src/util.js +21 -6
- 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-vitest/src/index.js +4 -2
- 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/index.electron.js +3 -0
- package/packages/dd-trace/index.js +2 -37
- 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/bootstrap.js +39 -0
- 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 +55 -5
- package/packages/dd-trace/src/ci-visibility/exporters/test-worker/index.js +22 -6
- package/packages/dd-trace/src/ci-visibility/exporters/test-worker/writer.js +5 -0
- package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +62 -7
- package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +9 -2
- 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 +173 -9
- package/packages/dd-trace/src/ci-visibility/test-screenshot.js +90 -0
- package/packages/dd-trace/src/config/generated-config-types.d.ts +20 -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 +83 -0
- package/packages/dd-trace/src/constants.js +7 -0
- package/packages/dd-trace/src/datastreams/pathway.js +6 -4
- 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 +29 -0
- package/packages/dd-trace/src/llmobs/constants/tags.js +1 -0
- 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/index.js +10 -2
- 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 +79 -7
- 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/noop/proxy.js +5 -4
- 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 +35 -0
- 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/opentracing/propagation/log.js +11 -2
- package/packages/dd-trace/src/opentracing/propagation/text_map.js +80 -14
- package/packages/dd-trace/src/opentracing/propagation/text_map_dsm.js +10 -1
- package/packages/dd-trace/src/opentracing/tracer.js +7 -1
- 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 +65 -8
- package/packages/dd-trace/src/plugins/util/web.js +12 -0
- package/packages/dd-trace/src/proxy.js +99 -10
- package/packages/dd-trace/src/span_processor.js +5 -0
- package/packages/dd-trace/src/standalone/index.js +0 -36
- 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
|
@@ -22,18 +22,27 @@ function parseJsonResponse (rawJson) {
|
|
|
22
22
|
|
|
23
23
|
function parseSkippableSuitesResponse (
|
|
24
24
|
rawJson,
|
|
25
|
-
{
|
|
25
|
+
{
|
|
26
|
+
testLevel = 'suite',
|
|
27
|
+
isCoverageReportUploadEnabled = false,
|
|
28
|
+
validateRequiredFields = false,
|
|
29
|
+
validationMode = false,
|
|
30
|
+
} = {}
|
|
26
31
|
) {
|
|
27
32
|
const parsedResponse = parseJsonResponse(rawJson)
|
|
28
33
|
if (validateRequiredFields) {
|
|
29
|
-
validateSkippableTestsResponse(parsedResponse)
|
|
34
|
+
validateSkippableTestsResponse(parsedResponse, { validationMode })
|
|
35
|
+
}
|
|
36
|
+
const coverage = {}
|
|
37
|
+
for (const [filename, bitmap] of Object.entries(parsedResponse.meta?.coverage || {})) {
|
|
38
|
+
coverage[filename.replaceAll('\\', '/')] = bitmap
|
|
30
39
|
}
|
|
31
|
-
const coverage = parsedResponse.meta?.coverage || {}
|
|
32
40
|
|
|
33
41
|
const skippableItems = parsedResponse
|
|
34
42
|
.data
|
|
35
43
|
.filter(({ type }) => type === testLevel)
|
|
36
44
|
const skippableSuites = []
|
|
45
|
+
let numExcludedByMissingLineCoverage = 0
|
|
37
46
|
for (const {
|
|
38
47
|
attributes: {
|
|
39
48
|
suite,
|
|
@@ -42,13 +51,58 @@ function parseSkippableSuitesResponse (
|
|
|
42
51
|
},
|
|
43
52
|
} of skippableItems) {
|
|
44
53
|
// Only reject candidates without backend line coverage when we need that coverage to backfill reports.
|
|
45
|
-
if (isCoverageReportUploadEnabled && isMissingLineCodeCoverage)
|
|
54
|
+
if (isCoverageReportUploadEnabled && isMissingLineCodeCoverage) {
|
|
55
|
+
numExcludedByMissingLineCoverage++
|
|
56
|
+
continue
|
|
57
|
+
}
|
|
46
58
|
|
|
47
59
|
skippableSuites.push(testLevel === 'suite' ? suite : { suite, name })
|
|
48
60
|
}
|
|
49
61
|
const correlationId = parsedResponse.meta?.correlation_id
|
|
50
62
|
|
|
51
|
-
return {
|
|
63
|
+
return {
|
|
64
|
+
skippableSuites,
|
|
65
|
+
correlationId,
|
|
66
|
+
coverage,
|
|
67
|
+
numReceivedSkippableItems: skippableItems.length,
|
|
68
|
+
numExcludedByMissingLineCoverage,
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Logs how missing line coverage affected the skippable candidates without logging their contents.
|
|
74
|
+
*
|
|
75
|
+
* @param {{
|
|
76
|
+
* skippableSuites: Array<string|{suite: string, name: string}>,
|
|
77
|
+
* numReceivedSkippableItems?: number,
|
|
78
|
+
* numExcludedByMissingLineCoverage?: number
|
|
79
|
+
* }} result - Parsed skippable response.
|
|
80
|
+
* @param {'suite'|'test'} testLevel - Test optimization skipping level.
|
|
81
|
+
* @returns {void}
|
|
82
|
+
*/
|
|
83
|
+
function logSkippableSuitesResponse (result, testLevel) {
|
|
84
|
+
if (result.numReceivedSkippableItems === undefined) {
|
|
85
|
+
log.debug('Number of received skippable %ss: %d', testLevel, result.skippableSuites.length)
|
|
86
|
+
return
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
log.debug(
|
|
90
|
+
'Received %d skippable %s candidates; excluded %d because line coverage is missing; %d remain.',
|
|
91
|
+
result.numReceivedSkippableItems,
|
|
92
|
+
testLevel,
|
|
93
|
+
result.numExcludedByMissingLineCoverage,
|
|
94
|
+
result.skippableSuites.length
|
|
95
|
+
)
|
|
96
|
+
if (
|
|
97
|
+
result.numReceivedSkippableItems > 0 &&
|
|
98
|
+
result.numExcludedByMissingLineCoverage === result.numReceivedSkippableItems
|
|
99
|
+
) {
|
|
100
|
+
log.warn(
|
|
101
|
+
'All %d skippable %s candidates were excluded: coverage upload is enabled but line coverage is missing.',
|
|
102
|
+
result.numReceivedSkippableItems,
|
|
103
|
+
testLevel
|
|
104
|
+
)
|
|
105
|
+
}
|
|
52
106
|
}
|
|
53
107
|
|
|
54
108
|
function getSkippableSuites ({
|
|
@@ -96,6 +150,7 @@ function getSkippableSuites ({
|
|
|
96
150
|
}, cb)
|
|
97
151
|
}, (err, data) => {
|
|
98
152
|
if (err) return done(err)
|
|
153
|
+
logSkippableSuitesResponse(data, testLevel)
|
|
99
154
|
done(null, data.skippableSuites, data.correlationId, data.coverage)
|
|
100
155
|
})
|
|
101
156
|
}
|
|
@@ -203,6 +258,7 @@ function fetchFromApi ({
|
|
|
203
258
|
const result = parseSkippableSuitesResponse(parsedResponse, {
|
|
204
259
|
testLevel,
|
|
205
260
|
isCoverageReportUploadEnabled,
|
|
261
|
+
validateRequiredFields: true,
|
|
206
262
|
})
|
|
207
263
|
const skippableItems = parsedResponse.data.filter(({ type }) => type === testLevel)
|
|
208
264
|
incrementCountMetric(
|
|
@@ -213,7 +269,6 @@ function fetchFromApi ({
|
|
|
213
269
|
skippableItems.length
|
|
214
270
|
)
|
|
215
271
|
distributionMetric(TELEMETRY_ITR_SKIPPABLE_TESTS_RESPONSE_BYTES, {}, res.length)
|
|
216
|
-
log.debug('Number of received skippable %ss:', testLevel, result.skippableSuites.length)
|
|
217
272
|
|
|
218
273
|
writeToCache(cacheKey, result)
|
|
219
274
|
|
|
@@ -225,4 +280,4 @@ function fetchFromApi ({
|
|
|
225
280
|
})
|
|
226
281
|
}
|
|
227
282
|
|
|
228
|
-
module.exports = { getSkippableSuites, parseSkippableSuitesResponse }
|
|
283
|
+
module.exports = { getSkippableSuites, logSkippableSuitesResponse, parseSkippableSuitesResponse }
|
|
@@ -26,7 +26,7 @@ function parseJsonResponse (rawJson) {
|
|
|
26
26
|
function parseLibraryConfigurationResponse (rawJson, config = getConfig(), options = {}) {
|
|
27
27
|
const parsedResponse = parseJsonResponse(rawJson)
|
|
28
28
|
if (options.validateRequiredFields) {
|
|
29
|
-
validateSettingsResponse(parsedResponse)
|
|
29
|
+
validateSettingsResponse(parsedResponse, options)
|
|
30
30
|
}
|
|
31
31
|
const {
|
|
32
32
|
code_coverage: isCodeCoverageEnabled,
|
|
@@ -49,7 +49,7 @@ function parseLibraryConfigurationResponse (rawJson, config = getConfig(), optio
|
|
|
49
49
|
requireGit,
|
|
50
50
|
isEarlyFlakeDetectionEnabled: isKnownTestsEnabled && (earlyFlakeDetectionConfig?.enabled ?? false),
|
|
51
51
|
earlyFlakeDetectionNumRetries:
|
|
52
|
-
earlyFlakeDetectionConfig?.slow_test_retries?.['5s']
|
|
52
|
+
earlyFlakeDetectionConfig?.slow_test_retries?.['5s'] ?? DEFAULT_EARLY_FLAKE_DETECTION_NUM_RETRIES,
|
|
53
53
|
earlyFlakeDetectionSlowTestRetries:
|
|
54
54
|
earlyFlakeDetectionConfig?.slow_test_retries ?? DEFAULT_EARLY_FLAKE_DETECTION_SLOW_TEST_RETRIES,
|
|
55
55
|
earlyFlakeDetectionFaultyThreshold:
|
|
@@ -74,6 +74,13 @@ function parseLibraryConfigurationResponse (rawJson, config = getConfig(), optio
|
|
|
74
74
|
settings.isSuitesSkippingEnabled = true
|
|
75
75
|
log.debug('Dangerously set test skipping to true')
|
|
76
76
|
}
|
|
77
|
+
if (
|
|
78
|
+
settings.isCoverageReportUploadEnabled &&
|
|
79
|
+
!config.testOptimization.DD_CIVISIBILITY_CODE_COVERAGE_REPORT_UPLOAD_ENABLED
|
|
80
|
+
) {
|
|
81
|
+
settings.isCoverageReportUploadEnabled = false
|
|
82
|
+
log.debug('Code coverage report upload was disabled by the environment variable')
|
|
83
|
+
}
|
|
77
84
|
|
|
78
85
|
return settings
|
|
79
86
|
}
|
|
@@ -24,14 +24,15 @@ const UPLOAD_TIMEOUT_MS = 30_000
|
|
|
24
24
|
* @param {object} options - Upload options
|
|
25
25
|
* @param {string} options.filePath - Path to the coverage report file
|
|
26
26
|
* @param {string} options.format - Format of the coverage report (e.g., 'lcov', 'cobertura')
|
|
27
|
+
* @param {string[]} [options.flags] - Optional coverage report grouping flags
|
|
27
28
|
* @param {object} options.testEnvironmentMetadata - Test environment metadata containing git/CI tags
|
|
28
29
|
* @param {URL} options.url - The base URL for the coverage report upload
|
|
29
30
|
* @param {boolean} [options.isEvpProxy] - Whether to use EVP proxy for the upload
|
|
30
31
|
* @param {string} [options.evpProxyPrefix] - The EVP proxy prefix (e.g., '/evp_proxy/v4')
|
|
31
|
-
* @param {
|
|
32
|
+
* @param {(error: Error|null) => void} callback - Callback function
|
|
32
33
|
*/
|
|
33
34
|
function uploadCoverageReport (
|
|
34
|
-
{ filePath, format, testEnvironmentMetadata, url, isEvpProxy, evpProxyPrefix },
|
|
35
|
+
{ filePath, format, flags, testEnvironmentMetadata, url, isEvpProxy, evpProxyPrefix },
|
|
35
36
|
callback
|
|
36
37
|
) {
|
|
37
38
|
const { DD_API_KEY } = getConfig()
|
|
@@ -54,6 +55,9 @@ function uploadCoverageReport (
|
|
|
54
55
|
format,
|
|
55
56
|
...testEnvironmentMetadata,
|
|
56
57
|
}
|
|
58
|
+
if (flags?.length) {
|
|
59
|
+
eventPayload['report.flags'] = flags
|
|
60
|
+
}
|
|
57
61
|
|
|
58
62
|
// Create multipart form
|
|
59
63
|
const form = new FormData()
|
|
@@ -28,12 +28,16 @@ class TestApiManualPlugin extends CiPlugin {
|
|
|
28
28
|
const store = legacyStorage.getStore()
|
|
29
29
|
const testSpan = store && store.span
|
|
30
30
|
if (testSpan) {
|
|
31
|
+
const previousStore = testSpan._store === undefined
|
|
32
|
+
? undefined
|
|
33
|
+
: legacyStorage.getStore(testSpan._store)
|
|
31
34
|
testSpan.setTag(TEST_STATUS, status)
|
|
32
35
|
if (error) {
|
|
33
36
|
testSpan.setTag('error', error)
|
|
34
37
|
}
|
|
35
38
|
testSpan.finish()
|
|
36
39
|
finishAllTraceSpans(testSpan)
|
|
40
|
+
legacyStorage.enterWith(previousStore)
|
|
37
41
|
}
|
|
38
42
|
})
|
|
39
43
|
this.unconfiguredAddSub('dd-trace:ci:manual:test:addTags', (tags) => {
|
|
@@ -47,7 +47,7 @@ function parseJsonResponse (rawJson) {
|
|
|
47
47
|
function parseTestManagementTestsResponse (rawJson, options = {}) {
|
|
48
48
|
const parsedResponse = parseJsonResponse(rawJson)
|
|
49
49
|
if (options.validateRequiredFields) {
|
|
50
|
-
validateTestManagementTestsResponse(parsedResponse)
|
|
50
|
+
validateTestManagementTestsResponse(parsedResponse, options)
|
|
51
51
|
}
|
|
52
52
|
const { data: { attributes: { modules: testManagementTests } } } = parsedResponse
|
|
53
53
|
return testManagementTests
|
|
@@ -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,
|