dd-trace 5.110.0 → 5.112.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/LICENSE-3rdparty.csv +1 -2
- package/README.md +13 -8
- package/ci/init.js +21 -2
- package/ci/vitest-no-worker-init-setup.mjs +430 -0
- package/ext/tags.js +2 -0
- package/index.d.ts +34 -1
- package/initialize.mjs +5 -6
- package/loader-hook.mjs +76 -55
- package/package.json +37 -34
- package/packages/datadog-instrumentations/src/ai.js +45 -0
- package/packages/datadog-instrumentations/src/apollo-server.js +5 -13
- package/packages/datadog-instrumentations/src/child_process.js +3 -3
- package/packages/datadog-instrumentations/src/claude-agent-sdk.js +587 -0
- package/packages/datadog-instrumentations/src/cucumber.js +102 -43
- package/packages/datadog-instrumentations/src/cypress-config.js +11 -3
- package/packages/datadog-instrumentations/src/fastify.js +27 -8
- package/packages/datadog-instrumentations/src/fs.js +8 -6
- package/packages/datadog-instrumentations/src/graphql.js +26 -495
- package/packages/datadog-instrumentations/src/helpers/hooks.js +1 -0
- package/packages/datadog-instrumentations/src/helpers/instrument.js +7 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/ai.js +6 -6
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/claude-agent-sdk.js +17 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/graphql.js +201 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/index.js +2 -0
- package/packages/datadog-instrumentations/src/jest.js +713 -63
- package/packages/datadog-instrumentations/src/mocha/main.js +24 -3
- package/packages/datadog-instrumentations/src/mocha/utils.js +128 -22
- package/packages/datadog-instrumentations/src/mocha/worker.js +13 -0
- package/packages/datadog-instrumentations/src/mongodb.js +74 -0
- package/packages/datadog-instrumentations/src/mongoose.js +13 -2
- package/packages/datadog-instrumentations/src/playwright.js +13 -9
- package/packages/datadog-instrumentations/src/vitest-main-no-worker-init.js +1297 -0
- package/packages/datadog-instrumentations/src/vitest-main.js +1594 -0
- package/packages/datadog-instrumentations/src/vitest-util.js +254 -0
- package/packages/datadog-instrumentations/src/vitest-worker.js +823 -0
- package/packages/datadog-instrumentations/src/vitest.js +11 -1855
- package/packages/datadog-plugin-ai/src/index.js +1 -1
- package/packages/datadog-plugin-ai/src/tracing.js +66 -3
- package/packages/datadog-plugin-ai/src/utils.js +17 -4
- package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/client.js +2 -2
- package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/context.js +19 -10
- package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/handler.js +1 -0
- package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/util.js +47 -11
- package/packages/datadog-plugin-child_process/src/index.js +13 -2
- package/packages/datadog-plugin-claude-agent-sdk/src/index.js +31 -0
- package/packages/datadog-plugin-claude-agent-sdk/src/tracing.js +107 -0
- package/packages/datadog-plugin-claude-agent-sdk/src/util.js +15 -0
- package/packages/datadog-plugin-cucumber/src/index.js +15 -24
- package/packages/datadog-plugin-cypress/src/cypress-plugin.js +360 -14
- package/packages/datadog-plugin-cypress/src/index.js +47 -2
- package/packages/datadog-plugin-cypress/src/plugin.js +1 -0
- package/packages/datadog-plugin-cypress/src/support.js +45 -1
- package/packages/datadog-plugin-express/src/code_origin.js +1 -1
- package/packages/datadog-plugin-fastify/src/code_origin.js +1 -1
- package/packages/datadog-plugin-graphql/src/execute.js +639 -12
- package/packages/datadog-plugin-graphql/src/index.js +37 -9
- package/packages/datadog-plugin-graphql/src/parse.js +24 -4
- package/packages/datadog-plugin-graphql/src/utils.js +9 -2
- package/packages/datadog-plugin-graphql/src/validate.js +17 -6
- package/packages/datadog-plugin-http/src/index.js +6 -8
- package/packages/datadog-plugin-jest/src/index.js +31 -15
- package/packages/datadog-plugin-mocha/src/index.js +40 -15
- package/packages/datadog-plugin-mongodb-core/src/bulk-write.js +43 -0
- package/packages/datadog-plugin-mongodb-core/src/index.js +8 -443
- package/packages/datadog-plugin-mongodb-core/src/query.js +452 -0
- package/packages/datadog-plugin-openai/src/services.js +2 -2
- package/packages/datadog-plugin-playwright/src/index.js +4 -3
- package/packages/datadog-plugin-vitest/src/index.js +120 -72
- package/packages/datadog-shimmer/src/shimmer.js +37 -16
- package/packages/dd-trace/src/aiguard/index.js +9 -14
- package/packages/dd-trace/src/aiguard/integrations/index.js +34 -0
- package/packages/dd-trace/src/aiguard/integrations/openai.js +47 -33
- package/packages/dd-trace/src/aiguard/integrations/vercel-ai.js +42 -28
- package/packages/dd-trace/src/aiguard/sdk.js +2 -2
- package/packages/dd-trace/src/appsec/api_security/sampler.js +3 -3
- package/packages/dd-trace/src/appsec/channels.js +3 -1
- package/packages/dd-trace/src/appsec/downstream_requests.js +4 -4
- package/packages/dd-trace/src/appsec/graphql.js +14 -11
- package/packages/dd-trace/src/appsec/iast/analyzers/nosql-injection-mongodb-analyzer.js +23 -23
- package/packages/dd-trace/src/appsec/iast/security-controls/index.js +2 -2
- package/packages/dd-trace/src/appsec/iast/telemetry/index.js +2 -1
- package/packages/dd-trace/src/appsec/index.js +10 -1
- package/packages/dd-trace/src/appsec/lambda.js +135 -0
- package/packages/dd-trace/src/appsec/reporter.js +49 -10
- package/packages/dd-trace/src/appsec/telemetry/index.js +1 -1
- package/packages/dd-trace/src/appsec/waf/index.js +16 -4
- package/packages/dd-trace/src/appsec/waf/waf_context_wrapper.js +4 -4
- package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/index.js +26 -1
- package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/worker/index.js +115 -24
- package/packages/dd-trace/src/ci-visibility/early-flake-detection/get-known-tests.js +21 -6
- package/packages/dd-trace/src/ci-visibility/exporters/agent-proxy/index.js +5 -2
- package/packages/dd-trace/src/ci-visibility/exporters/agentless/coverage-writer.js +1 -1
- package/packages/dd-trace/src/ci-visibility/exporters/agentless/di-logs-writer.js +1 -1
- package/packages/dd-trace/src/ci-visibility/exporters/agentless/index.js +6 -2
- package/packages/dd-trace/src/ci-visibility/exporters/agentless/writer.js +1 -1
- package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +130 -20
- package/packages/dd-trace/src/ci-visibility/exporters/git/git_metadata.js +2 -2
- package/packages/dd-trace/src/ci-visibility/exporters/test-worker/writer.js +17 -0
- package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +47 -26
- package/packages/dd-trace/src/ci-visibility/log-submission/log-submission-plugin.js +2 -2
- package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +64 -53
- package/packages/dd-trace/src/ci-visibility/requests/upload-coverage-report.js +3 -3
- package/packages/dd-trace/src/ci-visibility/requests/upload-test-screenshot.js +156 -0
- package/packages/dd-trace/src/ci-visibility/test-management/get-test-management-tests.js +23 -5
- package/packages/dd-trace/src/ci-visibility/test-optimization-http-cache-schema.js +128 -0
- package/packages/dd-trace/src/ci-visibility/test-optimization-http-cache.js +287 -0
- package/packages/dd-trace/src/config/defaults.js +3 -2
- package/packages/dd-trace/src/config/generated-config-types.d.ts +71 -57
- package/packages/dd-trace/src/config/helper.js +1 -0
- package/packages/dd-trace/src/config/index.js +35 -22
- package/packages/dd-trace/src/config/major-overrides.js +4 -2
- package/packages/dd-trace/src/config/remote_config.js +1 -1
- package/packages/dd-trace/src/config/supported-configurations.json +127 -56
- package/packages/dd-trace/src/constants.js +7 -0
- package/packages/dd-trace/src/crashtracking/crashtracker.js +2 -2
- package/packages/dd-trace/src/datastreams/processor.js +11 -3
- package/packages/dd-trace/src/exporters/agent/index.js +1 -1
- package/packages/dd-trace/src/exporters/agentless/writer.js +4 -4
- package/packages/dd-trace/src/llmobs/index.js +1 -1
- package/packages/dd-trace/src/llmobs/plugins/ai/ddTelemetry.js +403 -0
- package/packages/dd-trace/src/llmobs/plugins/ai/index.js +8 -395
- package/packages/dd-trace/src/llmobs/plugins/ai/util.js +27 -0
- package/packages/dd-trace/src/llmobs/plugins/ai/vercelTelemetry.js +363 -0
- package/packages/dd-trace/src/llmobs/plugins/base.js +4 -4
- package/packages/dd-trace/src/llmobs/plugins/claude-agent-sdk/index.js +270 -0
- package/packages/dd-trace/src/llmobs/plugins/claude-agent-sdk/utils.js +10 -0
- package/packages/dd-trace/src/llmobs/plugins/langgraph/index.js +1 -1
- package/packages/dd-trace/src/llmobs/sdk.js +3 -3
- package/packages/dd-trace/src/llmobs/span_processor.js +1 -1
- package/packages/dd-trace/src/llmobs/tagger.js +3 -3
- package/packages/dd-trace/src/llmobs/writers/base.js +1 -1
- package/packages/dd-trace/src/opentelemetry/metrics/index.js +54 -5
- package/packages/dd-trace/src/opentelemetry/metrics/otlp_span_stats_exporter.js +40 -0
- package/packages/dd-trace/src/opentelemetry/metrics/otlp_span_stats_transformer.js +169 -0
- package/packages/dd-trace/src/opentelemetry/otlp/otlp_http_exporter_base.js +38 -39
- package/packages/dd-trace/src/opentelemetry/span.js +5 -0
- package/packages/dd-trace/src/opentelemetry/trace/index.js +4 -0
- package/packages/dd-trace/src/opentelemetry/tracer_provider.js +1 -1
- package/packages/dd-trace/src/opentracing/tracer.js +6 -1
- package/packages/dd-trace/src/plugin_manager.js +23 -7
- package/packages/dd-trace/src/plugins/ci_plugin.js +159 -10
- package/packages/dd-trace/src/plugins/index.js +2 -0
- package/packages/dd-trace/src/plugins/util/llm.js +6 -1
- package/packages/dd-trace/src/plugins/util/test.js +15 -9
- package/packages/dd-trace/src/profiling/exporter_cli.js +2 -2
- package/packages/dd-trace/src/profiling/exporters/agent.js +28 -3
- package/packages/dd-trace/src/profiling/exporters/event_serializer.js +3 -3
- package/packages/dd-trace/src/profiling/profilers/wall.js +1 -1
- package/packages/dd-trace/src/proxy.js +8 -8
- package/packages/dd-trace/src/span_processor.js +5 -6
- package/packages/dd-trace/src/span_stats.js +96 -78
- package/packages/dd-trace/src/startup-log.js +2 -1
- package/packages/dd-trace/src/telemetry/dependencies.js +1 -1
- package/packages/dd-trace/src/telemetry/endpoints.js +6 -4
- package/packages/dd-trace/src/telemetry/index.js +2 -2
- package/packages/dd-trace/src/telemetry/logs/index.js +1 -1
- package/packages/dd-trace/src/telemetry/send-data.js +8 -8
- package/packages/dd-trace/src/telemetry/session-propagation.js +1 -1
- package/packages/dd-trace/src/telemetry/telemetry.js +9 -9
- 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-plugin-graphql/src/resolve.js +0 -170
|
@@ -50,7 +50,7 @@ function getCommonRequestOptions (url) {
|
|
|
50
50
|
return {
|
|
51
51
|
method: 'POST',
|
|
52
52
|
headers: {
|
|
53
|
-
'dd-api-key': getConfig().
|
|
53
|
+
'dd-api-key': getConfig().DD_API_KEY,
|
|
54
54
|
},
|
|
55
55
|
timeout: 15_000,
|
|
56
56
|
url,
|
|
@@ -287,7 +287,7 @@ function sendGitMetadata (url, { isEvpProxy, evpProxyPrefix }, configRepositoryU
|
|
|
287
287
|
}
|
|
288
288
|
// Otherwise we unshallow and get commits to upload again
|
|
289
289
|
log.debug('It is shallow clone, unshallowing...')
|
|
290
|
-
if (getConfig().DD_CIVISIBILITY_GIT_UNSHALLOW_ENABLED) {
|
|
290
|
+
if (getConfig().testOptimization.DD_CIVISIBILITY_GIT_UNSHALLOW_ENABLED) {
|
|
291
291
|
unshallowRepository(false)
|
|
292
292
|
}
|
|
293
293
|
|
|
@@ -3,6 +3,11 @@ const { JSONEncoder } = require('../../encode/json-encoder')
|
|
|
3
3
|
const { getEnvironmentVariable } = require('../../../config/helper')
|
|
4
4
|
const log = require('../../../log')
|
|
5
5
|
|
|
6
|
+
function getVitestWorkerPort () {
|
|
7
|
+
const port = globalThis.__vitest_worker__?.ctx?.port
|
|
8
|
+
return typeof port?.postMessage === 'function' ? port : undefined
|
|
9
|
+
}
|
|
10
|
+
|
|
6
11
|
class Writer {
|
|
7
12
|
constructor (interprocessCode) {
|
|
8
13
|
this._encoder = new JSONEncoder()
|
|
@@ -37,6 +42,18 @@ class Writer {
|
|
|
37
42
|
? { __tinypool_worker_message__: true, interprocessCode: this._interprocessCode, data }
|
|
38
43
|
: [this._interprocessCode, data]
|
|
39
44
|
|
|
45
|
+
const vitestWorkerPort = getVitestWorkerPort()
|
|
46
|
+
if (vitestWorkerPort) {
|
|
47
|
+
try {
|
|
48
|
+
vitestWorkerPort.postMessage(payload)
|
|
49
|
+
} catch (error) {
|
|
50
|
+
log.error('Error posting message to vitest worker port', error)
|
|
51
|
+
} finally {
|
|
52
|
+
onDone()
|
|
53
|
+
}
|
|
54
|
+
return
|
|
55
|
+
}
|
|
56
|
+
|
|
40
57
|
// child_process workers (jest default, cucumber)
|
|
41
58
|
if (process.send) {
|
|
42
59
|
process.send(payload, () => {
|
|
@@ -14,6 +14,42 @@ const {
|
|
|
14
14
|
TELEMETRY_ITR_SKIPPABLE_TESTS_RESPONSE_BYTES,
|
|
15
15
|
} = require('../../ci-visibility/telemetry')
|
|
16
16
|
const { buildCacheKey, writeToCache, withCache } = require('../requests/fs-cache')
|
|
17
|
+
const { validateSkippableTestsResponse } = require('../test-optimization-http-cache-schema')
|
|
18
|
+
|
|
19
|
+
function parseJsonResponse (rawJson) {
|
|
20
|
+
return typeof rawJson === 'string' ? JSON.parse(rawJson) : rawJson
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function parseSkippableSuitesResponse (
|
|
24
|
+
rawJson,
|
|
25
|
+
{ testLevel = 'suite', isCoverageReportUploadEnabled = false, validateRequiredFields = false } = {}
|
|
26
|
+
) {
|
|
27
|
+
const parsedResponse = parseJsonResponse(rawJson)
|
|
28
|
+
if (validateRequiredFields) {
|
|
29
|
+
validateSkippableTestsResponse(parsedResponse)
|
|
30
|
+
}
|
|
31
|
+
const coverage = parsedResponse.meta?.coverage || {}
|
|
32
|
+
|
|
33
|
+
const skippableItems = parsedResponse
|
|
34
|
+
.data
|
|
35
|
+
.filter(({ type }) => type === testLevel)
|
|
36
|
+
const skippableSuites = []
|
|
37
|
+
for (const {
|
|
38
|
+
attributes: {
|
|
39
|
+
suite,
|
|
40
|
+
name,
|
|
41
|
+
_is_missing_line_code_coverage: isMissingLineCodeCoverage,
|
|
42
|
+
},
|
|
43
|
+
} of skippableItems) {
|
|
44
|
+
// Only reject candidates without backend line coverage when we need that coverage to backfill reports.
|
|
45
|
+
if (isCoverageReportUploadEnabled && isMissingLineCodeCoverage) continue
|
|
46
|
+
|
|
47
|
+
skippableSuites.push(testLevel === 'suite' ? suite : { suite, name })
|
|
48
|
+
}
|
|
49
|
+
const correlationId = parsedResponse.meta?.correlation_id
|
|
50
|
+
|
|
51
|
+
return { skippableSuites, correlationId, coverage }
|
|
52
|
+
}
|
|
17
53
|
|
|
18
54
|
function getSkippableSuites ({
|
|
19
55
|
url,
|
|
@@ -124,12 +160,12 @@ function fetchFromApi ({
|
|
|
124
160
|
options.path = `${evpProxyPrefix}/api/v2/ci/tests/skippable`
|
|
125
161
|
options.headers['X-Datadog-EVP-Subdomain'] = 'api'
|
|
126
162
|
} else {
|
|
127
|
-
const {
|
|
128
|
-
if (!
|
|
163
|
+
const { DD_API_KEY } = getConfig()
|
|
164
|
+
if (!DD_API_KEY) {
|
|
129
165
|
return done(new Error('Skippable suites were not fetched because Datadog API key is not defined.'))
|
|
130
166
|
}
|
|
131
167
|
|
|
132
|
-
options.headers['dd-api-key'] =
|
|
168
|
+
options.headers['dd-api-key'] = DD_API_KEY
|
|
133
169
|
}
|
|
134
170
|
|
|
135
171
|
const data = JSON.stringify({
|
|
@@ -163,26 +199,12 @@ function fetchFromApi ({
|
|
|
163
199
|
done(err)
|
|
164
200
|
} else {
|
|
165
201
|
try {
|
|
166
|
-
const parsedResponse =
|
|
167
|
-
const
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
const skippableSuites = []
|
|
173
|
-
for (const {
|
|
174
|
-
attributes: {
|
|
175
|
-
suite,
|
|
176
|
-
name,
|
|
177
|
-
_is_missing_line_code_coverage: isMissingLineCodeCoverage,
|
|
178
|
-
},
|
|
179
|
-
} of skippableItems) {
|
|
180
|
-
// Only reject candidates without backend line coverage when we need that coverage to backfill reports.
|
|
181
|
-
if (isCoverageReportUploadEnabled && isMissingLineCodeCoverage) continue
|
|
182
|
-
|
|
183
|
-
skippableSuites.push(testLevel === 'suite' ? suite : { suite, name })
|
|
184
|
-
}
|
|
185
|
-
const correlationId = parsedResponse.meta?.correlation_id
|
|
202
|
+
const parsedResponse = parseJsonResponse(res)
|
|
203
|
+
const result = parseSkippableSuitesResponse(parsedResponse, {
|
|
204
|
+
testLevel,
|
|
205
|
+
isCoverageReportUploadEnabled,
|
|
206
|
+
})
|
|
207
|
+
const skippableItems = parsedResponse.data.filter(({ type }) => type === testLevel)
|
|
186
208
|
incrementCountMetric(
|
|
187
209
|
testLevel === 'test'
|
|
188
210
|
? TELEMETRY_ITR_SKIPPABLE_TESTS_RESPONSE_TESTS
|
|
@@ -191,9 +213,8 @@ function fetchFromApi ({
|
|
|
191
213
|
skippableItems.length
|
|
192
214
|
)
|
|
193
215
|
distributionMetric(TELEMETRY_ITR_SKIPPABLE_TESTS_RESPONSE_BYTES, {}, res.length)
|
|
194
|
-
log.debug('Number of received skippable %ss:', testLevel, skippableSuites.length)
|
|
216
|
+
log.debug('Number of received skippable %ss:', testLevel, result.skippableSuites.length)
|
|
195
217
|
|
|
196
|
-
const result = { skippableSuites, correlationId, coverage }
|
|
197
218
|
writeToCache(cacheKey, result)
|
|
198
219
|
|
|
199
220
|
done(null, result)
|
|
@@ -204,4 +225,4 @@ function fetchFromApi ({
|
|
|
204
225
|
})
|
|
205
226
|
}
|
|
206
227
|
|
|
207
|
-
module.exports = { getSkippableSuites }
|
|
228
|
+
module.exports = { getSkippableSuites, parseSkippableSuitesResponse }
|
|
@@ -4,14 +4,14 @@ const Plugin = require('../../plugins/plugin')
|
|
|
4
4
|
const log = require('../../log')
|
|
5
5
|
|
|
6
6
|
function getWinstonLogSubmissionParameters (config) {
|
|
7
|
-
const { site, service,
|
|
7
|
+
const { site, service, DD_API_KEY, DD_AGENTLESS_LOG_SUBMISSION_URL } = config
|
|
8
8
|
|
|
9
9
|
const defaultParameters = {
|
|
10
10
|
host: `http-intake.logs.${site}`,
|
|
11
11
|
path: `/api/v2/logs?ddsource=winston&service=${service}`,
|
|
12
12
|
ssl: true,
|
|
13
13
|
headers: {
|
|
14
|
-
'DD-API-KEY':
|
|
14
|
+
'DD-API-KEY': DD_API_KEY,
|
|
15
15
|
},
|
|
16
16
|
}
|
|
17
17
|
|
|
@@ -12,12 +12,72 @@ const {
|
|
|
12
12
|
TELEMETRY_GIT_REQUESTS_SETTINGS_RESPONSE,
|
|
13
13
|
} = require('../telemetry')
|
|
14
14
|
const { writeSettingsToCache } = require('../test-optimization-cache')
|
|
15
|
+
const { validateSettingsResponse } = require('../test-optimization-http-cache-schema')
|
|
15
16
|
const request = require('./request')
|
|
16
17
|
|
|
17
18
|
const DEFAULT_EARLY_FLAKE_DETECTION_NUM_RETRIES = 2
|
|
18
19
|
const DEFAULT_EARLY_FLAKE_DETECTION_SLOW_TEST_RETRIES = { '5s': 10, '10s': 5, '30s': 3, '5m': 2 }
|
|
19
20
|
const DEFAULT_EARLY_FLAKE_DETECTION_ERROR_THRESHOLD = 30
|
|
20
21
|
|
|
22
|
+
function parseJsonResponse (rawJson) {
|
|
23
|
+
return typeof rawJson === 'string' ? JSON.parse(rawJson) : rawJson
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function parseLibraryConfigurationResponse (rawJson, config = getConfig(), options = {}) {
|
|
27
|
+
const parsedResponse = parseJsonResponse(rawJson)
|
|
28
|
+
if (options.validateRequiredFields) {
|
|
29
|
+
validateSettingsResponse(parsedResponse)
|
|
30
|
+
}
|
|
31
|
+
const {
|
|
32
|
+
code_coverage: isCodeCoverageEnabled,
|
|
33
|
+
tests_skipping: isSuitesSkippingEnabled,
|
|
34
|
+
itr_enabled: isItrEnabled,
|
|
35
|
+
require_git: requireGit,
|
|
36
|
+
early_flake_detection: earlyFlakeDetectionConfig,
|
|
37
|
+
flaky_test_retries_enabled: isFlakyTestRetriesEnabled,
|
|
38
|
+
di_enabled: isDiEnabled,
|
|
39
|
+
known_tests_enabled: isKnownTestsEnabled,
|
|
40
|
+
test_management: testManagementConfig,
|
|
41
|
+
impacted_tests_enabled: isImpactedTestsEnabled,
|
|
42
|
+
coverage_report_upload_enabled: isCoverageReportUploadEnabled,
|
|
43
|
+
} = parsedResponse?.data?.attributes ?? parsedResponse
|
|
44
|
+
|
|
45
|
+
const settings = {
|
|
46
|
+
isCodeCoverageEnabled,
|
|
47
|
+
isSuitesSkippingEnabled,
|
|
48
|
+
isItrEnabled,
|
|
49
|
+
requireGit,
|
|
50
|
+
isEarlyFlakeDetectionEnabled: isKnownTestsEnabled && (earlyFlakeDetectionConfig?.enabled ?? false),
|
|
51
|
+
earlyFlakeDetectionNumRetries:
|
|
52
|
+
earlyFlakeDetectionConfig?.slow_test_retries?.['5s'] || DEFAULT_EARLY_FLAKE_DETECTION_NUM_RETRIES,
|
|
53
|
+
earlyFlakeDetectionSlowTestRetries:
|
|
54
|
+
earlyFlakeDetectionConfig?.slow_test_retries ?? DEFAULT_EARLY_FLAKE_DETECTION_SLOW_TEST_RETRIES,
|
|
55
|
+
earlyFlakeDetectionFaultyThreshold:
|
|
56
|
+
earlyFlakeDetectionConfig?.faulty_session_threshold ?? DEFAULT_EARLY_FLAKE_DETECTION_ERROR_THRESHOLD,
|
|
57
|
+
isFlakyTestRetriesEnabled,
|
|
58
|
+
isDiEnabled: isDiEnabled && isFlakyTestRetriesEnabled,
|
|
59
|
+
isKnownTestsEnabled,
|
|
60
|
+
isTestManagementEnabled: (testManagementConfig?.enabled ?? false),
|
|
61
|
+
testManagementAttemptToFixRetries:
|
|
62
|
+
testManagementConfig?.attempt_to_fix_retries,
|
|
63
|
+
isImpactedTestsEnabled,
|
|
64
|
+
isCoverageReportUploadEnabled: isCoverageReportUploadEnabled ?? false,
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
log.debug('Remote settings: %j', settings)
|
|
68
|
+
|
|
69
|
+
if (config.testOptimization.DD_CIVISIBILITY_DANGEROUSLY_FORCE_COVERAGE) {
|
|
70
|
+
settings.isCodeCoverageEnabled = true
|
|
71
|
+
log.debug('Dangerously set code coverage to true')
|
|
72
|
+
}
|
|
73
|
+
if (config.testOptimization.DD_CIVISIBILITY_DANGEROUSLY_FORCE_TEST_SKIPPING) {
|
|
74
|
+
settings.isSuitesSkippingEnabled = true
|
|
75
|
+
log.debug('Dangerously set test skipping to true')
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return settings
|
|
79
|
+
}
|
|
80
|
+
|
|
21
81
|
function getLibraryConfiguration ({
|
|
22
82
|
url,
|
|
23
83
|
isEvpProxy,
|
|
@@ -51,10 +111,10 @@ function getLibraryConfiguration ({
|
|
|
51
111
|
options.path = `${evpProxyPrefix}/api/v2/libraries/tests/services/setting`
|
|
52
112
|
options.headers['X-Datadog-EVP-Subdomain'] = 'api'
|
|
53
113
|
} else {
|
|
54
|
-
if (!config.
|
|
114
|
+
if (!config.DD_API_KEY) {
|
|
55
115
|
return done(new Error('Request to settings endpoint was not done because Datadog API key is not defined.'))
|
|
56
116
|
}
|
|
57
|
-
options.headers['dd-api-key'] = config.
|
|
117
|
+
options.headers['dd-api-key'] = config.DD_API_KEY
|
|
58
118
|
}
|
|
59
119
|
|
|
60
120
|
const data = JSON.stringify({
|
|
@@ -90,56 +150,7 @@ function getLibraryConfiguration ({
|
|
|
90
150
|
done(err)
|
|
91
151
|
} else {
|
|
92
152
|
try {
|
|
93
|
-
const
|
|
94
|
-
data: {
|
|
95
|
-
attributes: {
|
|
96
|
-
code_coverage: isCodeCoverageEnabled,
|
|
97
|
-
tests_skipping: isSuitesSkippingEnabled,
|
|
98
|
-
itr_enabled: isItrEnabled,
|
|
99
|
-
require_git: requireGit,
|
|
100
|
-
early_flake_detection: earlyFlakeDetectionConfig,
|
|
101
|
-
flaky_test_retries_enabled: isFlakyTestRetriesEnabled,
|
|
102
|
-
di_enabled: isDiEnabled,
|
|
103
|
-
known_tests_enabled: isKnownTestsEnabled,
|
|
104
|
-
test_management: testManagementConfig,
|
|
105
|
-
impacted_tests_enabled: isImpactedTestsEnabled,
|
|
106
|
-
coverage_report_upload_enabled: isCoverageReportUploadEnabled,
|
|
107
|
-
},
|
|
108
|
-
},
|
|
109
|
-
} = JSON.parse(res)
|
|
110
|
-
|
|
111
|
-
const settings = {
|
|
112
|
-
isCodeCoverageEnabled,
|
|
113
|
-
isSuitesSkippingEnabled,
|
|
114
|
-
isItrEnabled,
|
|
115
|
-
requireGit,
|
|
116
|
-
isEarlyFlakeDetectionEnabled: isKnownTestsEnabled && (earlyFlakeDetectionConfig?.enabled ?? false),
|
|
117
|
-
earlyFlakeDetectionNumRetries:
|
|
118
|
-
earlyFlakeDetectionConfig?.slow_test_retries?.['5s'] || DEFAULT_EARLY_FLAKE_DETECTION_NUM_RETRIES,
|
|
119
|
-
earlyFlakeDetectionSlowTestRetries:
|
|
120
|
-
earlyFlakeDetectionConfig?.slow_test_retries ?? DEFAULT_EARLY_FLAKE_DETECTION_SLOW_TEST_RETRIES,
|
|
121
|
-
earlyFlakeDetectionFaultyThreshold:
|
|
122
|
-
earlyFlakeDetectionConfig?.faulty_session_threshold ?? DEFAULT_EARLY_FLAKE_DETECTION_ERROR_THRESHOLD,
|
|
123
|
-
isFlakyTestRetriesEnabled,
|
|
124
|
-
isDiEnabled: isDiEnabled && isFlakyTestRetriesEnabled,
|
|
125
|
-
isKnownTestsEnabled,
|
|
126
|
-
isTestManagementEnabled: (testManagementConfig?.enabled ?? false),
|
|
127
|
-
testManagementAttemptToFixRetries:
|
|
128
|
-
testManagementConfig?.attempt_to_fix_retries,
|
|
129
|
-
isImpactedTestsEnabled,
|
|
130
|
-
isCoverageReportUploadEnabled: isCoverageReportUploadEnabled ?? false,
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
log.debug('Remote settings: %j', settings)
|
|
134
|
-
|
|
135
|
-
if (config.DD_CIVISIBILITY_DANGEROUSLY_FORCE_COVERAGE) {
|
|
136
|
-
settings.isCodeCoverageEnabled = true
|
|
137
|
-
log.debug('Dangerously set code coverage to true')
|
|
138
|
-
}
|
|
139
|
-
if (config.DD_CIVISIBILITY_DANGEROUSLY_FORCE_TEST_SKIPPING) {
|
|
140
|
-
settings.isSuitesSkippingEnabled = true
|
|
141
|
-
log.debug('Dangerously set test skipping to true')
|
|
142
|
-
}
|
|
153
|
+
const settings = parseLibraryConfigurationResponse(res, config)
|
|
143
154
|
|
|
144
155
|
incrementCountMetric(TELEMETRY_GIT_REQUESTS_SETTINGS_RESPONSE, settings)
|
|
145
156
|
|
|
@@ -153,4 +164,4 @@ function getLibraryConfiguration ({
|
|
|
153
164
|
})
|
|
154
165
|
}
|
|
155
166
|
|
|
156
|
-
module.exports = { getLibraryConfiguration }
|
|
167
|
+
module.exports = { getLibraryConfiguration, parseLibraryConfigurationResponse }
|
|
@@ -34,9 +34,9 @@ function uploadCoverageReport (
|
|
|
34
34
|
{ filePath, format, testEnvironmentMetadata, url, isEvpProxy, evpProxyPrefix },
|
|
35
35
|
callback
|
|
36
36
|
) {
|
|
37
|
-
const
|
|
37
|
+
const { DD_API_KEY } = getConfig()
|
|
38
38
|
|
|
39
|
-
if (!
|
|
39
|
+
if (!DD_API_KEY && !isEvpProxy) {
|
|
40
40
|
return callback(new Error('DD_API_KEY is required for coverage report upload'))
|
|
41
41
|
}
|
|
42
42
|
|
|
@@ -82,7 +82,7 @@ function uploadCoverageReport (
|
|
|
82
82
|
options.headers['X-Datadog-EVP-Subdomain'] = 'ci-intake'
|
|
83
83
|
} else {
|
|
84
84
|
options.path = '/api/v2/cicovreprt'
|
|
85
|
-
options.headers['dd-api-key'] =
|
|
85
|
+
options.headers['dd-api-key'] = DD_API_KEY
|
|
86
86
|
}
|
|
87
87
|
|
|
88
88
|
log.debug('Uploading coverage report %s to %s%s', filePath, url, options.path)
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { readFileSync } = require('node:fs')
|
|
4
|
+
const { extname } = require('node:path')
|
|
5
|
+
|
|
6
|
+
const getConfig = require('../../config')
|
|
7
|
+
const request = require('../../exporters/common/request')
|
|
8
|
+
const log = require('../../log')
|
|
9
|
+
|
|
10
|
+
const UPLOAD_TIMEOUT_MS = 30_000
|
|
11
|
+
const TEST_SCREENSHOT_ENDPOINT_PREFIX = '/api/v2/ci/test-runs/'
|
|
12
|
+
const TEST_SCREENSHOT_ENDPOINT_SUFFIX = '/media'
|
|
13
|
+
const UINT64_MAX = 18_446_744_073_709_551_615n
|
|
14
|
+
|
|
15
|
+
function getContentType (filePath) {
|
|
16
|
+
const extension = extname(filePath).toLowerCase()
|
|
17
|
+
if (extension === '.gif') {
|
|
18
|
+
return 'image/gif'
|
|
19
|
+
}
|
|
20
|
+
if (extension === '.jpg' || extension === '.jpeg') {
|
|
21
|
+
return 'image/jpeg'
|
|
22
|
+
}
|
|
23
|
+
if (extension === '.webp') {
|
|
24
|
+
return 'image/webp'
|
|
25
|
+
}
|
|
26
|
+
return 'image/png'
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function isValidTraceId (traceId) {
|
|
30
|
+
if (!/^[1-9]\d*$/.test(traceId)) {
|
|
31
|
+
return false
|
|
32
|
+
}
|
|
33
|
+
return BigInt(traceId) <= UINT64_MAX
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Renders the idempotency key (`${traceId}:${basename(filePath)}`) into a value safe to carry in
|
|
38
|
+
* the upload's query string. The Agent's evp_proxy validates the forwarded query against a
|
|
39
|
+
* restrictive charset and rejects a raw Cypress filename (spaces, parens, non-ASCII), so the
|
|
40
|
+
* filename part is hex-encoded to [0-9a-f]; the decimal trace id and ':' separator are already in
|
|
41
|
+
* the allowed set and stay readable. Deterministic, so a retried upload reproduces the same key
|
|
42
|
+
* and the backend's UUIDv5 overwrite-on-retry holds.
|
|
43
|
+
*
|
|
44
|
+
* @param {string} idempotencyKey - The raw idempotency key (`<traceId>:<filename>`)
|
|
45
|
+
* @returns {string} A query-safe, deterministic representation of the key
|
|
46
|
+
*/
|
|
47
|
+
function toIdempotencyQueryValue (idempotencyKey) {
|
|
48
|
+
const separatorIndex = idempotencyKey.indexOf(':')
|
|
49
|
+
if (separatorIndex === -1) {
|
|
50
|
+
return Buffer.from(idempotencyKey, 'utf8').toString('hex')
|
|
51
|
+
}
|
|
52
|
+
const traceIdPart = idempotencyKey.slice(0, separatorIndex)
|
|
53
|
+
const filenamePart = idempotencyKey.slice(separatorIndex + 1)
|
|
54
|
+
return `${traceIdPart}:${Buffer.from(filenamePart, 'utf8').toString('hex')}`
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Uploads a single test screenshot to the Test Optimization media intake.
|
|
59
|
+
* The trace id is included in the request path and the body is the raw image bytes.
|
|
60
|
+
*
|
|
61
|
+
* The media service requires two values from the tracer, sent as query params so they
|
|
62
|
+
* survive the Agent's evp_proxy (which forwards only an allow-listed header set and would
|
|
63
|
+
* otherwise strip the metadata, leaving the backend with an empty idempotency key):
|
|
64
|
+
* - `idempotencyKey`: stable per artifact and reused on retry, so a retried
|
|
65
|
+
* upload overwrites the same stored object instead of creating a duplicate.
|
|
66
|
+
* - `capturedAtMs`: the capture time in epoch milliseconds, stamped once at
|
|
67
|
+
* capture and resent unchanged on retry (it is part of the stored object key).
|
|
68
|
+
*
|
|
69
|
+
* @param {object} options - Upload options
|
|
70
|
+
* @param {string} options.filePath - Path to the screenshot file
|
|
71
|
+
* @param {string} options.traceId - Test trace id used as the screenshot key
|
|
72
|
+
* @param {string} options.idempotencyKey - Stable per-artifact key, reused on retry
|
|
73
|
+
* @param {number} options.capturedAtMs - Capture time in epoch milliseconds
|
|
74
|
+
* @param {URL} options.url - The base URL for the screenshot upload
|
|
75
|
+
* @param {boolean} [options.isEvpProxy] - Whether to upload through the Agent's evp_proxy
|
|
76
|
+
* @param {string} [options.evpProxyPrefix] - The evp_proxy path prefix (e.g. '/evp_proxy/v4')
|
|
77
|
+
* @param {Function} callback - Callback function (err)
|
|
78
|
+
*/
|
|
79
|
+
function uploadTestScreenshot (
|
|
80
|
+
{ filePath, traceId, idempotencyKey, capturedAtMs, url, isEvpProxy, evpProxyPrefix },
|
|
81
|
+
callback
|
|
82
|
+
) {
|
|
83
|
+
const { DD_API_KEY } = getConfig()
|
|
84
|
+
|
|
85
|
+
if (!isValidTraceId(traceId)) {
|
|
86
|
+
return callback(new Error('A non-zero decimal uint64 trace_id is required for test screenshot upload'))
|
|
87
|
+
}
|
|
88
|
+
if (!DD_API_KEY && !isEvpProxy) {
|
|
89
|
+
return callback(new Error('DD_API_KEY is required for test screenshot upload'))
|
|
90
|
+
}
|
|
91
|
+
if (!idempotencyKey) {
|
|
92
|
+
return callback(new Error('An idempotency key is required for test screenshot upload'))
|
|
93
|
+
}
|
|
94
|
+
if (!Number.isInteger(capturedAtMs) || capturedAtMs <= 0) {
|
|
95
|
+
return callback(new Error('A positive captured-at timestamp (epoch ms) is required for test screenshot upload'))
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
let screenshotContent
|
|
99
|
+
try {
|
|
100
|
+
screenshotContent = readFileSync(filePath)
|
|
101
|
+
} catch (err) {
|
|
102
|
+
return callback(new Error(`Failed to read screenshot at ${filePath}: ${err.message}`))
|
|
103
|
+
}
|
|
104
|
+
if (screenshotContent.length === 0) {
|
|
105
|
+
return callback(new Error(`Screenshot at ${filePath} is empty`))
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Metadata rides the query string, not X-Dd-* headers: the Agent's evp_proxy strips
|
|
109
|
+
// non-allow-listed headers, so header-borne metadata reached the backend empty. The key is
|
|
110
|
+
// rendered proxy-safe (see toIdempotencyQueryValue) because evp_proxy also validates the
|
|
111
|
+
// forwarded query against a restrictive charset. capturedAtMs is a plain integer and part of the
|
|
112
|
+
// stored object key, so it (and the key) must stay stable across retries.
|
|
113
|
+
const query = new URLSearchParams({
|
|
114
|
+
idempotency_key: toIdempotencyQueryValue(idempotencyKey),
|
|
115
|
+
captured_at_ms: String(capturedAtMs),
|
|
116
|
+
}).toString()
|
|
117
|
+
const basePath = `${TEST_SCREENSHOT_ENDPOINT_PREFIX}${traceId}${TEST_SCREENSHOT_ENDPOINT_SUFFIX}`
|
|
118
|
+
|
|
119
|
+
const contentType = getContentType(filePath)
|
|
120
|
+
const options = {
|
|
121
|
+
method: 'POST',
|
|
122
|
+
headers: {
|
|
123
|
+
'Content-Type': contentType,
|
|
124
|
+
},
|
|
125
|
+
path: `${basePath}?${query}`,
|
|
126
|
+
timeout: UPLOAD_TIMEOUT_MS,
|
|
127
|
+
url,
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (isEvpProxy) {
|
|
131
|
+
// Agent mode: prefix the evp_proxy path, tell the proxy which subdomain to forward to, and
|
|
132
|
+
// drop the API key — the Agent injects it. The query params survive the proxy.
|
|
133
|
+
options.path = `${evpProxyPrefix}${basePath}?${query}`
|
|
134
|
+
options.headers['X-Datadog-EVP-Subdomain'] = 'api'
|
|
135
|
+
} else {
|
|
136
|
+
options.headers['DD-API-KEY'] = DD_API_KEY
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
log.debug('Uploading test screenshot %s to %s', filePath, new URL(options.path, url).href)
|
|
140
|
+
|
|
141
|
+
request(screenshotContent, options, (err, res, statusCode) => {
|
|
142
|
+
if (err) {
|
|
143
|
+
log.error('Error uploading test screenshot: %s', err.message)
|
|
144
|
+
return callback(err)
|
|
145
|
+
}
|
|
146
|
+
if (statusCode === undefined) {
|
|
147
|
+
const uploadError = new Error('Test screenshot upload request was dropped before it was sent')
|
|
148
|
+
log.error('Error uploading test screenshot: %s', uploadError.message)
|
|
149
|
+
return callback(uploadError)
|
|
150
|
+
}
|
|
151
|
+
log.debug('Test screenshot uploaded successfully (status: %d)', statusCode)
|
|
152
|
+
callback(null)
|
|
153
|
+
})
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
module.exports = { TEST_SCREENSHOT_ENDPOINT_PREFIX, TEST_SCREENSHOT_ENDPOINT_SUFFIX, uploadTestScreenshot }
|
|
@@ -16,6 +16,7 @@ const {
|
|
|
16
16
|
} = require('../telemetry')
|
|
17
17
|
|
|
18
18
|
const { buildCacheKey, writeToCache, withCache } = require('../requests/fs-cache')
|
|
19
|
+
const { validateTestManagementTestsResponse } = require('../test-optimization-http-cache-schema')
|
|
19
20
|
|
|
20
21
|
// Calculate the number of tests from the test management tests response, which has a shape like:
|
|
21
22
|
// { module: { suites: { suite: { tests: { testName: { properties: {...} } } } } } }
|
|
@@ -39,6 +40,19 @@ function getNumFromTestManagementTests (testManagementTests) {
|
|
|
39
40
|
return totalNumTests
|
|
40
41
|
}
|
|
41
42
|
|
|
43
|
+
function parseJsonResponse (rawJson) {
|
|
44
|
+
return typeof rawJson === 'string' ? JSON.parse(rawJson) : rawJson
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function parseTestManagementTestsResponse (rawJson, options = {}) {
|
|
48
|
+
const parsedResponse = parseJsonResponse(rawJson)
|
|
49
|
+
if (options.validateRequiredFields) {
|
|
50
|
+
validateTestManagementTestsResponse(parsedResponse)
|
|
51
|
+
}
|
|
52
|
+
const { data: { attributes: { modules: testManagementTests } } } = parsedResponse
|
|
53
|
+
return testManagementTests
|
|
54
|
+
}
|
|
55
|
+
|
|
42
56
|
function getTestManagementTests ({
|
|
43
57
|
url,
|
|
44
58
|
isEvpProxy,
|
|
@@ -121,12 +135,12 @@ function fetchFromApi ({
|
|
|
121
135
|
options.path = `${evpProxyPrefix}/api/v2/test/libraries/test-management/tests`
|
|
122
136
|
options.headers['X-Datadog-EVP-Subdomain'] = 'api'
|
|
123
137
|
} else {
|
|
124
|
-
const {
|
|
125
|
-
if (!
|
|
138
|
+
const { DD_API_KEY } = getConfig()
|
|
139
|
+
if (!DD_API_KEY) {
|
|
126
140
|
return done(new Error('Test management tests were not fetched because Datadog API key is not defined.'))
|
|
127
141
|
}
|
|
128
142
|
|
|
129
|
-
options.headers['dd-api-key'] =
|
|
143
|
+
options.headers['dd-api-key'] = DD_API_KEY
|
|
130
144
|
}
|
|
131
145
|
|
|
132
146
|
const data = JSON.stringify({
|
|
@@ -155,7 +169,7 @@ function fetchFromApi ({
|
|
|
155
169
|
done(err)
|
|
156
170
|
} else {
|
|
157
171
|
try {
|
|
158
|
-
const
|
|
172
|
+
const testManagementTests = parseTestManagementTestsResponse(res)
|
|
159
173
|
|
|
160
174
|
const numTests = getNumFromTestManagementTests(testManagementTests)
|
|
161
175
|
|
|
@@ -174,4 +188,8 @@ function fetchFromApi ({
|
|
|
174
188
|
})
|
|
175
189
|
}
|
|
176
190
|
|
|
177
|
-
module.exports = {
|
|
191
|
+
module.exports = {
|
|
192
|
+
getNumFromTestManagementTests, // Exported for later use in the cache
|
|
193
|
+
getTestManagementTests,
|
|
194
|
+
parseTestManagementTestsResponse,
|
|
195
|
+
}
|