dd-trace 6.13.0 → 6.14.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 +3 -1
- package/ci/init.js +8 -3
- package/ci/test-optimization-validation/ci-package-scripts.js +2 -2
- package/ci/test-optimization-validation/ci-remediation.js +6 -6
- package/ci/test-optimization-validation/command-blocker.js +7 -5
- package/ci/test-optimization-validation/framework-adapters/cucumber.js +1 -1
- package/ci/test-optimization-validation/framework-adapters/vitest.js +3 -3
- package/ci/test-optimization-validation/package-check.js +1 -1
- package/ci/test-optimization-validation/report-writer.js +7 -5
- package/ci/vitest-no-worker-init-runner-setup.mjs +8 -0
- package/ci/vitest-no-worker-init-setup.mjs +13 -13
- package/ci/vitest-no-worker-init-v5-setup.mjs +4 -0
- package/package.json +13 -10
- package/packages/datadog-esbuild/index.js +3 -1
- package/packages/datadog-instrumentations/src/cypress-config.js +19 -17
- package/packages/datadog-instrumentations/src/fs.js +24 -20
- package/packages/datadog-instrumentations/src/google-cloud-vertexai.js +2 -2
- package/packages/datadog-instrumentations/src/graphql.js +16 -16
- package/packages/datadog-instrumentations/src/helpers/graphql-jit-runtime.js +504 -0
- package/packages/datadog-instrumentations/src/helpers/hooks.js +2 -0
- package/packages/datadog-instrumentations/src/helpers/pool-acquire.js +265 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/compiler.js +1 -2
- package/packages/datadog-instrumentations/src/helpers/rewriter/index.js +95 -30
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/ai.js +0 -25
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/graphql-jit.js +83 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/graphql.js +85 -1
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/index.js +1 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/mercurius.js +30 -12
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/playwright.js +13 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/webdriverio.js +251 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/targets.json +11 -1
- package/packages/datadog-instrumentations/src/helpers/rewriter/transforms.js +562 -27
- package/packages/datadog-instrumentations/src/jest.js +25 -20
- package/packages/datadog-instrumentations/src/knex.js +76 -0
- package/packages/datadog-instrumentations/src/mariadb.js +6 -0
- package/packages/datadog-instrumentations/src/mocha/common.js +59 -2
- package/packages/datadog-instrumentations/src/mocha/main.js +25 -15
- package/packages/datadog-instrumentations/src/mocha/utils.js +2 -1
- package/packages/datadog-instrumentations/src/mocha/webdriverio-protocol.js +98 -0
- package/packages/datadog-instrumentations/src/mocha/worker.js +12 -8
- package/packages/datadog-instrumentations/src/net.js +14 -17
- package/packages/datadog-instrumentations/src/next.js +212 -62
- package/packages/datadog-instrumentations/src/openai.js +42 -40
- package/packages/datadog-instrumentations/src/pg.js +15 -0
- package/packages/datadog-instrumentations/src/playwright.js +109 -13
- package/packages/datadog-instrumentations/src/rum-browser-scripts.js +40 -0
- package/packages/datadog-instrumentations/src/sequelize.js +94 -2
- package/packages/datadog-instrumentations/src/undici.js +53 -4
- package/packages/datadog-instrumentations/src/vitest-main-no-worker-init.js +37 -3
- package/packages/datadog-instrumentations/src/vitest-main.js +92 -9
- package/packages/datadog-instrumentations/src/vitest-worker.js +71 -14
- package/packages/datadog-instrumentations/src/webdriverio.js +965 -6
- package/packages/datadog-plugin-amqp10/src/consumer.js +3 -1
- package/packages/datadog-plugin-amqp10/src/producer.js +3 -1
- package/packages/datadog-plugin-amqp10/src/util.js +2 -1
- package/packages/datadog-plugin-avsc/src/schema_iterator.js +6 -1
- package/packages/datadog-plugin-aws-sdk/src/services/bedrockruntime/utils.js +16 -5
- package/packages/datadog-plugin-cassandra-driver/src/index.js +8 -3
- package/packages/datadog-plugin-cucumber/src/index.js +0 -2
- package/packages/datadog-plugin-cypress/src/cypress-plugin.js +262 -48
- package/packages/datadog-plugin-google-cloud-pubsub/src/client.js +4 -1
- package/packages/datadog-plugin-graphql/src/execute.js +998 -196
- package/packages/datadog-plugin-graphql/src/index.js +27 -7
- package/packages/datadog-plugin-graphql/src/jit.js +136 -0
- package/packages/datadog-plugin-graphql/src/request.js +18 -27
- package/packages/datadog-plugin-graphql/src/resolve-error.js +108 -0
- package/packages/datadog-plugin-graphql/src/tools/signature.js +1 -2
- package/packages/datadog-plugin-graphql/src/tools/transforms.js +45 -3
- package/packages/datadog-plugin-graphql/src/utils.js +208 -120
- package/packages/datadog-plugin-graphql/src/validate.js +10 -23
- package/packages/datadog-plugin-jest/src/index.js +0 -6
- package/packages/datadog-plugin-langchain/src/tracing.js +7 -0
- package/packages/datadog-plugin-mocha/src/index.js +403 -75
- package/packages/datadog-plugin-mongodb-core/src/query.js +6 -0
- package/packages/datadog-plugin-mysql/src/index.js +41 -15
- package/packages/datadog-plugin-net/src/tcp.js +3 -1
- package/packages/datadog-plugin-next/src/index.js +63 -29
- package/packages/datadog-plugin-playwright/src/index.js +167 -27
- package/packages/datadog-plugin-undici/src/index.js +27 -41
- package/packages/datadog-plugin-vitest/src/index.js +6 -12
- package/packages/dd-trace/src/aiguard/evaluation.js +2 -1
- package/packages/dd-trace/src/aiguard/messages/anthropic.js +8 -1
- package/packages/dd-trace/src/aiguard/messages/openai.js +8 -1
- package/packages/dd-trace/src/aiguard/messages/vercel-ai.js +20 -6
- package/packages/dd-trace/src/appsec/api_security/index.js +23 -6
- package/packages/dd-trace/src/appsec/api_security/sampler.js +99 -40
- package/packages/dd-trace/src/appsec/graphql.js +6 -2
- package/packages/dd-trace/src/appsec/iast/taint-tracking/plugin.js +3 -3
- package/packages/dd-trace/src/appsec/iast/taint-tracking/taint-tracking-impl.js +2 -1
- package/packages/dd-trace/src/appsec/iast/telemetry/span-tags.js +8 -2
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/sql-sensitive-analyzer.js +11 -36
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/url-sensitive-analyzer.js +1 -1
- package/packages/dd-trace/src/appsec/lambda.js +44 -26
- package/packages/dd-trace/src/ci-visibility/encode/json-encoder.js +32 -2
- package/packages/dd-trace/src/ci-visibility/exporters/agent-proxy/index.js +8 -8
- package/packages/dd-trace/src/ci-visibility/exporters/agentless/coverage-writer.js +6 -4
- package/packages/dd-trace/src/ci-visibility/exporters/agentless/di-logs-writer.js +20 -4
- package/packages/dd-trace/src/ci-visibility/exporters/agentless/request-tracker.js +5 -173
- package/packages/dd-trace/src/ci-visibility/exporters/agentless/writer.js +7 -6
- package/packages/dd-trace/src/ci-visibility/exporters/agents.js +54 -0
- package/packages/dd-trace/src/ci-visibility/exporters/ci-validation/writer.js +1 -2
- package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +394 -225
- package/packages/dd-trace/src/ci-visibility/exporters/limits.js +9 -0
- package/packages/dd-trace/src/ci-visibility/exporters/request.js +323 -31
- package/packages/dd-trace/src/ci-visibility/exporters/settings-cache-key.js +42 -0
- package/packages/dd-trace/src/ci-visibility/final-flush.js +2 -1
- package/packages/dd-trace/src/ci-visibility/requests/fs-cache.js +57 -18
- package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +0 -3
- package/packages/dd-trace/src/ci-visibility/requests/upload-test-screenshot.js +164 -71
- package/packages/dd-trace/src/ci-visibility/requests/video-request.js +176 -0
- package/packages/dd-trace/src/ci-visibility/telemetry.js +10 -7
- package/packages/dd-trace/src/ci-visibility/test-video.js +61 -0
- package/packages/dd-trace/src/config/generated-config-types.d.ts +4 -0
- package/packages/dd-trace/src/config/index.js +73 -13
- package/packages/dd-trace/src/config/parsers.js +11 -1
- package/packages/dd-trace/src/config/stable.js +3 -3
- package/packages/dd-trace/src/config/supported-configurations.json +20 -2
- package/packages/dd-trace/src/crashtracking/crashtracker.js +82 -18
- package/packages/dd-trace/src/debugger/config.js +12 -1
- package/packages/dd-trace/src/debugger/constants.js +1 -0
- package/packages/dd-trace/src/debugger/devtools_client/breakpoints.js +2 -1
- package/packages/dd-trace/src/debugger/devtools_client/request-options.js +33 -0
- package/packages/dd-trace/src/debugger/devtools_client/send.js +7 -40
- package/packages/dd-trace/src/debugger/devtools_client/status.js +11 -6
- package/packages/dd-trace/src/debugger/devtools_client/tags.js +41 -0
- package/packages/dd-trace/src/debugger/index.js +23 -3
- package/packages/dd-trace/src/dogstatsd.js +510 -76
- package/packages/dd-trace/src/encode/0.4-cross-payload.js +170 -0
- package/packages/dd-trace/src/encode/0.4.js +5 -5
- package/packages/dd-trace/src/encode/agentless-ci-visibility.js +9 -8
- package/packages/dd-trace/src/evp_proxy/direct.js +27 -23
- package/packages/dd-trace/src/exporters/agent/index.js +2 -1
- package/packages/dd-trace/src/exporters/agent/writer.js +24 -7
- package/packages/dd-trace/src/exporters/agentless/index.js +7 -9
- package/packages/dd-trace/src/exporters/agentless/writer.js +100 -118
- package/packages/dd-trace/src/exporters/common/agents.js +28 -6
- package/packages/dd-trace/src/exporters/common/buffering-exporter.js +2 -2
- package/packages/dd-trace/src/exporters/common/final-flush-request-tracker.js +191 -0
- package/packages/dd-trace/src/exporters/common/request.js +31 -11
- package/packages/dd-trace/src/exporters/common/url.js +40 -1
- package/packages/dd-trace/src/exporters/common/writer.js +9 -7
- package/packages/dd-trace/src/flush.js +6 -3
- package/packages/dd-trace/src/id.js +14 -0
- package/packages/dd-trace/src/index.js +5 -2
- package/packages/dd-trace/src/llmobs/experiments/dataset.js +2 -1
- package/packages/dd-trace/src/llmobs/index.js +32 -32
- package/packages/dd-trace/src/llmobs/plugins/ai/ddTelemetry.js +22 -71
- package/packages/dd-trace/src/llmobs/plugins/ai/vercelTelemetry.js +23 -13
- package/packages/dd-trace/src/llmobs/plugins/langchain/index.js +7 -0
- package/packages/dd-trace/src/llmobs/util.js +0 -33
- package/packages/dd-trace/src/llmobs/writers/base.js +49 -32
- package/packages/dd-trace/src/log/index.js +7 -1
- package/packages/dd-trace/src/{ci-visibility/log-submission → log-submission}/log-submission-plugin.js +48 -19
- package/packages/dd-trace/src/openfeature/writers/base.js +14 -12
- package/packages/dd-trace/src/openfeature/writers/util.js +1 -1
- package/packages/dd-trace/src/opentelemetry/logs/index.js +5 -24
- package/packages/dd-trace/src/opentelemetry/logs/otlp_http_log_exporter.js +14 -4
- package/packages/dd-trace/src/opentelemetry/metrics/index.js +15 -22
- package/packages/dd-trace/src/opentelemetry/metrics/otlp_http_metric_exporter.js +14 -4
- package/packages/dd-trace/src/opentelemetry/metrics/otlp_span_stats_exporter.js +10 -0
- package/packages/dd-trace/src/opentelemetry/otlp/otlp_transformer_base.js +19 -4
- package/packages/dd-trace/src/opentelemetry/resource-attributes.js +55 -0
- package/packages/dd-trace/src/opentelemetry/trace/index.js +6 -1
- package/packages/dd-trace/src/opentelemetry/trace/otlp_http_trace_exporter.js +10 -0
- package/packages/dd-trace/src/opentracing/propagation/text_map.js +93 -18
- package/packages/dd-trace/src/payload-tagging/index.js +2 -1
- package/packages/dd-trace/src/plugins/ci_plugin.js +1 -6
- package/packages/dd-trace/src/plugins/tracing.js +12 -10
- package/packages/dd-trace/src/plugins/util/ci.js +3 -1
- package/packages/dd-trace/src/plugins/util/inferred_proxy.js +16 -15
- package/packages/dd-trace/src/plugins/util/test.js +83 -20
- package/packages/dd-trace/src/plugins/util/url.js +2 -2
- package/packages/dd-trace/src/plugins/util/web.js +4 -9
- package/packages/dd-trace/src/profiling/config.js +1 -0
- package/packages/dd-trace/src/profiling/oom.js +7 -2
- package/packages/dd-trace/src/profiling/profiler.js +23 -13
- package/packages/dd-trace/src/profiling/profilers/space.js +38 -11
- package/packages/dd-trace/src/profiling/webspan-utils.js +7 -4
- package/packages/dd-trace/src/proxy.js +75 -5
- package/packages/dd-trace/src/remote_config/index.js +319 -101
- package/packages/dd-trace/src/remote_config/scheduler.js +37 -12
- package/packages/dd-trace/src/runtime_metrics/client.js +37 -8
- package/packages/dd-trace/src/runtime_metrics/otlp_runtime_metrics.js +5 -1
- package/packages/dd-trace/src/runtime_metrics/runtime_metrics.js +5 -1
- package/packages/dd-trace/src/sampling_rule.js +36 -9
- package/packages/dd-trace/src/serverless/telemetry-delivery-tracker.js +4 -8
- package/packages/dd-trace/src/serverless/vercel.js +53 -20
- package/packages/dd-trace/src/serverless.js +10 -3
- package/packages/dd-trace/src/service-naming/schemas/util.js +1 -1
- package/packages/dd-trace/src/span_stats.js +24 -28
- package/packages/dd-trace/src/startup-log.js +3 -2
- package/packages/dd-trace/src/telemetry/agentless-url.js +28 -0
- package/packages/dd-trace/src/telemetry/send-data.js +18 -16
- package/packages/dd-trace/src/telemetry/session-propagation.js +3 -2
- package/packages/dd-trace/src/tracer.js +4 -5
- package/packages/dd-trace/src/tracer_metadata.js +11 -3
- 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/packages/datadog-instrumentations/src/playwright-browser-scripts.js +0 -27
- package/packages/dd-trace/src/encode/agentless-json.js +0 -210
|
@@ -1,36 +1,56 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
+
const fs = require('node:fs')
|
|
3
4
|
const { hostname: getHostname } = require('node:os')
|
|
4
5
|
const URL = require('url').URL
|
|
5
6
|
|
|
6
7
|
const { version: tracerVersion } = require('../../../../../package.json')
|
|
7
8
|
const { EMPTY_EFD_RETRY_POLICY, createEfdRetryPolicy } = require('../efd-retry-policy')
|
|
8
9
|
const { getLibraryConfiguration: getLibraryConfigurationRequest } = require('../requests/get-library-configuration')
|
|
10
|
+
const { getCachePath, withCache, writeToCache } = require('../requests/fs-cache')
|
|
9
11
|
const { getSkippableSuites: getSkippableSuitesRequest } = require('../intelligent-test-runner/get-skippable-suites')
|
|
10
12
|
const { getKnownTests: getKnownTestsRequest } = require('../early-flake-detection/get-known-tests')
|
|
11
13
|
const { getTestManagementTests: getTestManagementTestsRequest } =
|
|
12
14
|
require('../test-management/get-test-management-tests')
|
|
13
15
|
const { writeSettingsToCache } = require('../test-optimization-cache')
|
|
14
16
|
const { CACHE_MISS, TestOptimizationHttpCache } = require('../test-optimization-http-cache')
|
|
15
|
-
const {
|
|
17
|
+
const { MAX_RETRIES } = require('../test-optimization-http-cache-schema')
|
|
16
18
|
const { uploadCoverageReport: uploadCoverageReportRequest } = require('../requests/upload-coverage-report')
|
|
17
|
-
const {
|
|
19
|
+
const {
|
|
20
|
+
uploadTestScreenshot: uploadTestScreenshotRequest,
|
|
21
|
+
uploadTestSuiteVideo: uploadTestSuiteVideoRequest,
|
|
22
|
+
uploadTestVideo: uploadTestVideoRequest,
|
|
23
|
+
} = require('../requests/upload-test-screenshot')
|
|
18
24
|
const { parsers } = require('../../config/parsers')
|
|
19
25
|
const log = require('../../log')
|
|
20
|
-
const spanFormat = require('../../span_format')
|
|
21
26
|
const { getSegment } = require('../../util')
|
|
22
27
|
const BufferingExporter = require('../../exporters/common/buffering-exporter')
|
|
23
28
|
const { GIT_REPOSITORY_URL, GIT_COMMIT_SHA } = require('../../plugins/util/tags')
|
|
24
|
-
const { TEST_STATUS } = require('../../plugins/util/test')
|
|
25
29
|
const {
|
|
26
30
|
createFinalFlushTimeoutError,
|
|
27
31
|
FINAL_FLUSH_FALLBACK_DELAY,
|
|
28
32
|
FINAL_FLUSH_TIMEOUT,
|
|
29
33
|
} = require('../final-flush')
|
|
34
|
+
const { incrementCountMetric, TELEMETRY_ENDPOINT_PAYLOAD_DROPPED } = require('../telemetry')
|
|
30
35
|
const { sendGitMetadata: sendGitMetadataRequest } = require('./git/git_metadata')
|
|
36
|
+
const buildSettingsCacheKey = require('./settings-cache-key')
|
|
31
37
|
|
|
32
38
|
const hostname = getHostname()
|
|
33
39
|
const EMPTY_SETTINGS = Object.freeze({})
|
|
40
|
+
const SETTINGS_CACHE_TTL_MS = 5 * 60 * 1000 // 5 minutes
|
|
41
|
+
const SETTINGS_BOOLEAN_FIELDS = Object.freeze([
|
|
42
|
+
'isCodeCoverageEnabled',
|
|
43
|
+
'isSuitesSkippingEnabled',
|
|
44
|
+
'isItrEnabled',
|
|
45
|
+
'requireGit',
|
|
46
|
+
'isEarlyFlakeDetectionEnabled',
|
|
47
|
+
'isFlakyTestRetriesEnabled',
|
|
48
|
+
'isDiEnabled',
|
|
49
|
+
'isKnownTestsEnabled',
|
|
50
|
+
'isTestManagementEnabled',
|
|
51
|
+
'isImpactedTestsEnabled',
|
|
52
|
+
'isCoverageReportUploadEnabled',
|
|
53
|
+
])
|
|
34
54
|
|
|
35
55
|
/**
|
|
36
56
|
* Test session identity sent with every request. Fields are optional because the CI provider,
|
|
@@ -56,50 +76,131 @@ function getTestConfigurationTags (tags) {
|
|
|
56
76
|
}, {})
|
|
57
77
|
}
|
|
58
78
|
|
|
79
|
+
function isTestSessionEvent (span) {
|
|
80
|
+
return span.type === 'test_session_end' || span.type === 'test_suite_end' || span.type === 'test_module_end'
|
|
81
|
+
}
|
|
82
|
+
|
|
59
83
|
function getIsTestSessionTrace (trace) {
|
|
60
|
-
return trace.some(
|
|
61
|
-
|
|
62
|
-
|
|
84
|
+
return trace.some(isTestSessionEvent)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Checks whether a value is a non-negative safe integer.
|
|
89
|
+
*
|
|
90
|
+
* @param {unknown} value - Candidate integer.
|
|
91
|
+
* @returns {value is number}
|
|
92
|
+
*/
|
|
93
|
+
function isNonNegativeSafeInteger (value) {
|
|
94
|
+
return Number.isSafeInteger(value) && value >= 0
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Checks whether a value is a retry count accepted by the settings parser.
|
|
99
|
+
*
|
|
100
|
+
* @param {unknown} value - Candidate retry count.
|
|
101
|
+
* @returns {value is number}
|
|
102
|
+
*/
|
|
103
|
+
function isValidRetryCount (value) {
|
|
104
|
+
return isNonNegativeSafeInteger(value) && value <= MAX_RETRIES
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Checks whether a cached EFD retry policy has the complete parsed shape.
|
|
109
|
+
*
|
|
110
|
+
* @param {unknown} retryPolicy - Candidate retry policy.
|
|
111
|
+
* @returns {boolean}
|
|
112
|
+
*/
|
|
113
|
+
function isValidCachedEfdRetryPolicy (retryPolicy) {
|
|
114
|
+
if (retryPolicy === null || typeof retryPolicy !== 'object' || Array.isArray(retryPolicy)) return false
|
|
115
|
+
if (!isValidRetryCount(retryPolicy.schedulingRetryCount)) return false
|
|
116
|
+
if (!Array.isArray(retryPolicy.durationRetryCounts) ||
|
|
117
|
+
retryPolicy.durationRetryCounts.length !== EMPTY_EFD_RETRY_POLICY.durationRetryCounts.length) {
|
|
118
|
+
return false
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
let schedulingRetryCount = 0
|
|
122
|
+
for (let index = 0; index < EMPTY_EFD_RETRY_POLICY.durationRetryCounts.length; index++) {
|
|
123
|
+
const durationRetryCount = retryPolicy.durationRetryCounts[index]
|
|
124
|
+
if (durationRetryCount === null || typeof durationRetryCount !== 'object' ||
|
|
125
|
+
Array.isArray(durationRetryCount) ||
|
|
126
|
+
durationRetryCount.durationLimitMs !==
|
|
127
|
+
EMPTY_EFD_RETRY_POLICY.durationRetryCounts[index].durationLimitMs ||
|
|
128
|
+
!isValidRetryCount(durationRetryCount.retryCount)) {
|
|
129
|
+
return false
|
|
130
|
+
}
|
|
131
|
+
if (durationRetryCount.retryCount > schedulingRetryCount) {
|
|
132
|
+
schedulingRetryCount = durationRetryCount.retryCount
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return retryPolicy.schedulingRetryCount === schedulingRetryCount
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Checks whether a filesystem cache value has the complete shape produced by
|
|
140
|
+
* parseLibraryConfigurationResponse.
|
|
141
|
+
*
|
|
142
|
+
* @param {unknown} settings - Cached settings value.
|
|
143
|
+
* @returns {settings is Record<string, unknown>}
|
|
144
|
+
*/
|
|
145
|
+
function isValidCachedSettings (settings) {
|
|
146
|
+
if (settings === null || typeof settings !== 'object' || Array.isArray(settings)) return false
|
|
147
|
+
for (const field of SETTINGS_BOOLEAN_FIELDS) {
|
|
148
|
+
if (typeof settings[field] !== 'boolean') return false
|
|
149
|
+
}
|
|
150
|
+
if (settings.isDiEnabled && !settings.isFlakyTestRetriesEnabled) return false
|
|
151
|
+
if (settings.isEarlyFlakeDetectionEnabled && !settings.isKnownTestsEnabled) return false
|
|
152
|
+
if (!isValidCachedEfdRetryPolicy(settings.earlyFlakeDetectionRetryPolicy)) return false
|
|
153
|
+
if (!isNonNegativeSafeInteger(settings.earlyFlakeDetectionFaultyThreshold) ||
|
|
154
|
+
settings.earlyFlakeDetectionFaultyThreshold > 100) {
|
|
155
|
+
return false
|
|
156
|
+
}
|
|
157
|
+
return settings.testManagementAttemptToFixRetries === undefined ||
|
|
158
|
+
isValidRetryCount(settings.testManagementAttemptToFixRetries)
|
|
63
159
|
}
|
|
64
160
|
|
|
65
161
|
const GIT_UPLOAD_TIMEOUT = 60_000 // 60 seconds
|
|
66
162
|
const CAN_USE_CI_VIS_PROTOCOL_TIMEOUT = GIT_UPLOAD_TIMEOUT
|
|
67
163
|
const MAX_COVERAGE_REPORT_FLAGS = 32
|
|
164
|
+
// Give a 200 MiB background video five minutes; final flush still caps shutdown at 60 seconds.
|
|
165
|
+
const BACKGROUND_VIDEO_UPLOAD_TIMEOUT = 5 * FINAL_FLUSH_TIMEOUT
|
|
68
166
|
|
|
167
|
+
/**
|
|
168
|
+
* @param {string} tags
|
|
169
|
+
* @param {string} key
|
|
170
|
+
* @param {string | undefined} value
|
|
171
|
+
*/
|
|
69
172
|
function appendLogTag (tags, key, value) {
|
|
70
|
-
if (value
|
|
71
|
-
|
|
72
|
-
}
|
|
173
|
+
if (value === undefined) return tags
|
|
174
|
+
|
|
175
|
+
const tag = `${key}:${value}`
|
|
176
|
+
return tags ? `${tags},${tag}` : tag
|
|
73
177
|
}
|
|
74
178
|
|
|
75
179
|
function getLogTags (logMessage, { env, version }, gitRepositoryUrl, gitCommitSha) {
|
|
76
|
-
|
|
180
|
+
let tags = ''
|
|
77
181
|
if (Array.isArray(logMessage.ddtags)) {
|
|
78
|
-
|
|
79
|
-
tags.push(tag)
|
|
80
|
-
}
|
|
182
|
+
tags = logMessage.ddtags.join(',')
|
|
81
183
|
} else if (logMessage.ddtags) {
|
|
82
|
-
|
|
83
|
-
tags.push(tag)
|
|
84
|
-
}
|
|
184
|
+
tags = logMessage.ddtags
|
|
85
185
|
}
|
|
86
186
|
|
|
87
|
-
appendLogTag(tags, 'env', env)
|
|
88
|
-
appendLogTag(tags, 'version', version)
|
|
89
|
-
appendLogTag(tags, 'debugger_version', tracerVersion)
|
|
90
|
-
appendLogTag(tags, 'host_name', hostname)
|
|
91
|
-
appendLogTag(tags, GIT_COMMIT_SHA, gitCommitSha)
|
|
92
|
-
appendLogTag(tags, GIT_REPOSITORY_URL, gitRepositoryUrl)
|
|
187
|
+
tags = appendLogTag(tags, 'env', env)
|
|
188
|
+
tags = appendLogTag(tags, 'version', version)
|
|
189
|
+
tags = appendLogTag(tags, 'debugger_version', tracerVersion)
|
|
190
|
+
tags = appendLogTag(tags, 'host_name', hostname)
|
|
191
|
+
tags = appendLogTag(tags, GIT_COMMIT_SHA, gitCommitSha)
|
|
192
|
+
tags = appendLogTag(tags, GIT_REPOSITORY_URL, gitRepositoryUrl)
|
|
93
193
|
|
|
94
|
-
return tags
|
|
194
|
+
return tags
|
|
95
195
|
}
|
|
96
196
|
|
|
97
197
|
class CiVisibilityExporter extends BufferingExporter {
|
|
98
198
|
#finalFlush
|
|
99
199
|
#deferredTestSessionTraces = []
|
|
100
|
-
#
|
|
101
|
-
#
|
|
200
|
+
#pendingMediaUploads = new Set()
|
|
201
|
+
#mediaFlushWaiters = new Set()
|
|
102
202
|
#deferredTestSuiteSpans = new Map()
|
|
203
|
+
#gitUploadTimeoutId
|
|
103
204
|
|
|
104
205
|
constructor (config, options = {}) {
|
|
105
206
|
super(config)
|
|
@@ -125,11 +226,8 @@ class CiVisibilityExporter extends BufferingExporter {
|
|
|
125
226
|
|
|
126
227
|
this._isTestFailureScreenshotsEnabled =
|
|
127
228
|
Boolean(config?.testOptimization?.DD_TEST_FAILURE_SCREENSHOTS_ENABLED)
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
this._resolveGit(new Error('Timeout while uploading git metadata'))
|
|
131
|
-
}, GIT_UPLOAD_TIMEOUT)
|
|
132
|
-
gitUploadTimeoutId.unref?.()
|
|
229
|
+
this._isTestFailureVideosEnabled =
|
|
230
|
+
Boolean(config?.testOptimization?.DD_TEST_FAILURE_VIDEOS_ENABLED)
|
|
133
231
|
|
|
134
232
|
const canUseCiVisProtocolTimeoutId = setTimeout(() => {
|
|
135
233
|
this._resolveCanUseCiVisProtocol(false)
|
|
@@ -138,7 +236,8 @@ class CiVisibilityExporter extends BufferingExporter {
|
|
|
138
236
|
|
|
139
237
|
this._gitUploadPromise = new Promise(resolve => {
|
|
140
238
|
this._resolveGit = (err) => {
|
|
141
|
-
clearTimeout(gitUploadTimeoutId)
|
|
239
|
+
clearTimeout(this.#gitUploadTimeoutId)
|
|
240
|
+
this.#gitUploadTimeoutId = null
|
|
142
241
|
resolve(err)
|
|
143
242
|
}
|
|
144
243
|
})
|
|
@@ -270,51 +369,142 @@ class CiVisibilityExporter extends BufferingExporter {
|
|
|
270
369
|
const cachedLibraryConfig = this._testOptimizationHttpCache.readSettings()
|
|
271
370
|
if (cachedLibraryConfig !== CACHE_MISS) {
|
|
272
371
|
log.debug('Test Optimization HTTP cache settings found, skipping settings request')
|
|
273
|
-
|
|
274
|
-
this._libraryConfig = this.filterConfiguration(cachedLibraryConfig)
|
|
275
|
-
const canUseCachedSkippableSuites = !this.shouldRequestSkippableSuites() ||
|
|
276
|
-
this._testOptimizationHttpCache.hasValidSkippableSuites({
|
|
277
|
-
testLevel: configuration.testLevel,
|
|
278
|
-
isCoverageReportUploadEnabled: configuration.isCoverageReportUploadEnabled,
|
|
279
|
-
})
|
|
280
|
-
if (this._libraryConfig.requireGit && !canUseCachedSkippableSuites) {
|
|
281
|
-
this.sendGitMetadata(repositoryUrl)
|
|
282
|
-
} else {
|
|
283
|
-
this._resolveGit()
|
|
284
|
-
}
|
|
285
|
-
return callback(null, this._libraryConfig)
|
|
372
|
+
return this._applyCachedSettings(cachedLibraryConfig, configuration, repositoryUrl, false, callback)
|
|
286
373
|
}
|
|
287
374
|
|
|
288
375
|
if (this._isTestOptimizationCacheOnly) {
|
|
289
376
|
return callback(this._getCacheOnlyError('settings'), {})
|
|
290
377
|
}
|
|
291
378
|
|
|
292
|
-
|
|
293
|
-
|
|
379
|
+
// The settings request has a two-phase shape: when the backend returns
|
|
380
|
+
// `require_git`, we upload git metadata and re-request settings. Only the
|
|
381
|
+
// final (post-git-upload) configuration is cached, so the cross-process
|
|
382
|
+
// filesystem cache never serves a pre-git-upload response that would skip
|
|
383
|
+
// the git upload in other processes.
|
|
384
|
+
//
|
|
385
|
+
// The live-fetch and cache-hit paths resolve the git upload promise
|
|
386
|
+
// differently, so they must not share a single apply path:
|
|
387
|
+
// - Live fetch: `sendGitMetadata` is started in `_fetchLibraryConfigurationFromBackend`
|
|
388
|
+
// and resolves `_gitUploadPromise` itself (with the upload result). We must NOT call
|
|
389
|
+
// `_resolveGit()` here, or we would race the in-flight upload and mask its error.
|
|
390
|
+
// - Filesystem cache hit: no upload was started in this process, so `_applyCachedSettings`
|
|
391
|
+
// resolves the git promise locally (starting an upload only if the cached config still
|
|
392
|
+
// requires git and we lack valid cached skippable suites).
|
|
393
|
+
let liveFetchStarted = false
|
|
394
|
+
const fsCacheKey = buildSettingsCacheKey(configuration)
|
|
395
|
+
const fetchSettings = (activeCacheKey, done) => {
|
|
396
|
+
liveFetchStarted = true
|
|
397
|
+
this._fetchLibraryConfigurationFromBackend(configuration, repositoryUrl, activeCacheKey, done)
|
|
398
|
+
}
|
|
399
|
+
const applySettings = (err, libraryConfig) => {
|
|
294
400
|
/**
|
|
295
401
|
* **Important**: this._libraryConfig remains empty in testing frameworks
|
|
296
402
|
* where the tests run in a subprocess, like Jest,
|
|
297
403
|
* because `getLibraryConfiguration` is called only once in the main process.
|
|
298
404
|
*/
|
|
299
|
-
this._libraryConfig = this.filterConfiguration(libraryConfig)
|
|
300
|
-
|
|
301
405
|
if (err) {
|
|
302
|
-
callback(err, {})
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
406
|
+
return callback(err, {})
|
|
407
|
+
}
|
|
408
|
+
if (liveFetchStarted) {
|
|
409
|
+
// Live fetch: the git upload was already started and resolves
|
|
410
|
+
// `_gitUploadPromise` itself. Do not call `_resolveGit()`.
|
|
411
|
+
writeSettingsToCache(libraryConfig)
|
|
412
|
+
this._libraryConfig = this.filterConfiguration(libraryConfig)
|
|
413
|
+
return callback(null, this._libraryConfig)
|
|
414
|
+
}
|
|
415
|
+
// Filesystem cache hit: no git upload was started in this process.
|
|
416
|
+
if (!isValidCachedSettings(libraryConfig)) {
|
|
417
|
+
// A syntactically valid cache file with an invalid settings payload is corrupt.
|
|
418
|
+
// Settings never writes such a value, so this is external corruption. Remove the
|
|
419
|
+
// file and fall back to the backend so we never serve a garbage config that would
|
|
420
|
+
// crash filterConfiguration or silently disable every feature.
|
|
421
|
+
try {
|
|
422
|
+
fs.unlinkSync(getCachePath(fsCacheKey))
|
|
423
|
+
} catch (err) {
|
|
424
|
+
if (err.code !== 'ENOENT') {
|
|
425
|
+
return fetchSettings(null, applySettings)
|
|
308
426
|
}
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
})
|
|
313
|
-
})
|
|
314
|
-
} else {
|
|
315
|
-
callback(null, this._libraryConfig)
|
|
427
|
+
}
|
|
428
|
+
liveFetchStarted = false
|
|
429
|
+
return withCache(fsCacheKey, fetchSettings, applySettings, SETTINGS_CACHE_TTL_MS)
|
|
316
430
|
}
|
|
431
|
+
this._applyCachedSettings(libraryConfig, configuration, repositoryUrl, true, callback)
|
|
432
|
+
}
|
|
433
|
+
withCache(fsCacheKey, fetchSettings, applySettings, SETTINGS_CACHE_TTL_MS)
|
|
434
|
+
})
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
/**
|
|
438
|
+
* Applies a resolved (cached or freshly fetched) library configuration: writes it to the
|
|
439
|
+
* shared settings cache, filters it through local kill switches, and resolves the git
|
|
440
|
+
* upload promise according to whether git metadata still needs to be uploaded.
|
|
441
|
+
*
|
|
442
|
+
* @param {Record<string, unknown>} settings - Resolved library configuration.
|
|
443
|
+
* @param {object} configuration - Request configuration used to evaluate cached skippable suites.
|
|
444
|
+
* @param {string} repositoryUrl - Repository URL for git metadata upload.
|
|
445
|
+
* @param {boolean} isFilesystemCache - Whether settings came from the cross-process cache.
|
|
446
|
+
* @param {Function} callback - Completion callback.
|
|
447
|
+
* @returns {void}
|
|
448
|
+
*/
|
|
449
|
+
_applyCachedSettings (settings, configuration, repositoryUrl, isFilesystemCache, callback) {
|
|
450
|
+
writeSettingsToCache(settings)
|
|
451
|
+
this._libraryConfig = this.filterConfiguration(settings)
|
|
452
|
+
const canUseCachedSkippableSuites = !this.shouldRequestSkippableSuites() ||
|
|
453
|
+
this._testOptimizationHttpCache.hasValidSkippableSuites({
|
|
454
|
+
testLevel: configuration.testLevel,
|
|
455
|
+
isCoverageReportUploadEnabled: configuration.isCoverageReportUploadEnabled,
|
|
317
456
|
})
|
|
457
|
+
if (!canUseCachedSkippableSuites && (this._libraryConfig.requireGit || isFilesystemCache)) {
|
|
458
|
+
this.sendGitMetadata(repositoryUrl)
|
|
459
|
+
} else {
|
|
460
|
+
this._resolveGit()
|
|
461
|
+
}
|
|
462
|
+
callback(null, this._libraryConfig)
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
/**
|
|
466
|
+
* Fetches library configuration from the backend, performing the two-phase
|
|
467
|
+
* `require_git` re-request, and writes only the final configuration to the
|
|
468
|
+
* filesystem cache when this process owns the cache lock.
|
|
469
|
+
*
|
|
470
|
+
* @param {object} configuration - Request configuration for the settings endpoint.
|
|
471
|
+
* @param {string} repositoryUrl - Repository URL for git metadata upload.
|
|
472
|
+
* @param {string|null} cacheKey - Filesystem cache key when this process owns the lock, null otherwise.
|
|
473
|
+
* @param {Function} done - Completion callback.
|
|
474
|
+
* @returns {void}
|
|
475
|
+
*/
|
|
476
|
+
_fetchLibraryConfigurationFromBackend (configuration, repositoryUrl, cacheKey, done) {
|
|
477
|
+
this.sendGitMetadata(repositoryUrl)
|
|
478
|
+
getLibraryConfigurationRequest(configuration, (err, libraryConfig) => {
|
|
479
|
+
// Mirror the original live path: keep the phase-1 config on `_libraryConfig` even
|
|
480
|
+
// before the git upload resolves, so `shouldRequestSkippableSuites()` and the
|
|
481
|
+
// skippable path's `_gitUploadPromise` await behave identically to the uncached flow.
|
|
482
|
+
this._libraryConfig = this.filterConfiguration(libraryConfig)
|
|
483
|
+
if (err) {
|
|
484
|
+
return done(err, libraryConfig)
|
|
485
|
+
}
|
|
486
|
+
if (libraryConfig?.requireGit) {
|
|
487
|
+
// If the backend requires git, wait for the upload to finish and request settings again
|
|
488
|
+
this._gitUploadPromise.then(gitUploadError => {
|
|
489
|
+
if (gitUploadError) {
|
|
490
|
+
return done(gitUploadError, libraryConfig)
|
|
491
|
+
}
|
|
492
|
+
getLibraryConfigurationRequest(configuration, (finalErr, finalLibraryConfig) => {
|
|
493
|
+
if (finalErr) {
|
|
494
|
+
// Match the original live path: reset `_libraryConfig` from the final
|
|
495
|
+
// (failed) response so stale phase-1 feature flags don't stay installed.
|
|
496
|
+
// On error `finalLibraryConfig` is undefined, so this resolves to empty settings.
|
|
497
|
+
this._libraryConfig = this.filterConfiguration(finalLibraryConfig)
|
|
498
|
+
return done(finalErr, finalLibraryConfig)
|
|
499
|
+
}
|
|
500
|
+
writeToCache(cacheKey, finalLibraryConfig)
|
|
501
|
+
done(null, finalLibraryConfig)
|
|
502
|
+
})
|
|
503
|
+
})
|
|
504
|
+
} else {
|
|
505
|
+
writeToCache(cacheKey, libraryConfig)
|
|
506
|
+
done(null, libraryConfig)
|
|
507
|
+
}
|
|
318
508
|
})
|
|
319
509
|
}
|
|
320
510
|
|
|
@@ -381,6 +571,13 @@ class CiVisibilityExporter extends BufferingExporter {
|
|
|
381
571
|
this._resolveGit()
|
|
382
572
|
return
|
|
383
573
|
}
|
|
574
|
+
if (this.#gitUploadTimeoutId === null) return
|
|
575
|
+
if (this.#gitUploadTimeoutId === undefined) {
|
|
576
|
+
this.#gitUploadTimeoutId = setTimeout(() => {
|
|
577
|
+
this._resolveGit(new Error('Timeout while uploading git metadata'))
|
|
578
|
+
}, GIT_UPLOAD_TIMEOUT)
|
|
579
|
+
this.#gitUploadTimeoutId.unref?.()
|
|
580
|
+
}
|
|
384
581
|
this._canUseCiVisProtocolPromise.then((canUseCiVisProtocol) => {
|
|
385
582
|
if (!canUseCiVisProtocol) {
|
|
386
583
|
return
|
|
@@ -403,34 +600,7 @@ class CiVisibilityExporter extends BufferingExporter {
|
|
|
403
600
|
|
|
404
601
|
export (trace) {
|
|
405
602
|
this.#resetFinalFlush()
|
|
406
|
-
|
|
407
|
-
if (this.#deferredTestSuiteSpans.size === 0) {
|
|
408
|
-
this.#exportTrace(trace)
|
|
409
|
-
return
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
let hasDeferredTestSuiteSpan = false
|
|
413
|
-
let immediateTrace
|
|
414
|
-
for (let index = 0; index < trace.length; index++) {
|
|
415
|
-
const formattedSpan = trace[index]
|
|
416
|
-
const spanId = formattedSpan.span_id?.toString()
|
|
417
|
-
const deferredTestSuiteSpan = this.#deferredTestSuiteSpans.get(spanId)
|
|
418
|
-
if (deferredTestSuiteSpan) {
|
|
419
|
-
if (!hasDeferredTestSuiteSpan && index > 0) immediateTrace = trace.slice(0, index)
|
|
420
|
-
hasDeferredTestSuiteSpan = true
|
|
421
|
-
if (deferredTestSuiteSpan.emitted || (this._isInitialized && !this.canReportSessionTraces())) {
|
|
422
|
-
this.#deferredTestSuiteSpans.delete(spanId)
|
|
423
|
-
} else {
|
|
424
|
-
deferredTestSuiteSpan.formattedSpan = formattedSpan
|
|
425
|
-
}
|
|
426
|
-
} else if (hasDeferredTestSuiteSpan) {
|
|
427
|
-
immediateTrace ??= []
|
|
428
|
-
immediateTrace.push(formattedSpan)
|
|
429
|
-
}
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
if (!hasDeferredTestSuiteSpan) this.#exportTrace(trace)
|
|
433
|
-
else if (immediateTrace) this.#exportTrace(immediateTrace)
|
|
603
|
+
this.#exportTrace(trace)
|
|
434
604
|
}
|
|
435
605
|
|
|
436
606
|
/**
|
|
@@ -447,6 +617,11 @@ class CiVisibilityExporter extends BufferingExporter {
|
|
|
447
617
|
}
|
|
448
618
|
const isTestSessionTrace = getIsTestSessionTrace(trace)
|
|
449
619
|
if (!this.canReportSessionTraces() && isTestSessionTrace) {
|
|
620
|
+
const testTrace = []
|
|
621
|
+
for (const span of trace) {
|
|
622
|
+
if (!isTestSessionEvent(span)) testTrace.push(span)
|
|
623
|
+
}
|
|
624
|
+
if (testTrace.length > 0) this._export(testTrace)
|
|
450
625
|
return
|
|
451
626
|
}
|
|
452
627
|
if (this._export(trace, undefined, undefined, isTestSessionTrace) === false && isTestSessionTrace) {
|
|
@@ -454,115 +629,6 @@ class CiVisibilityExporter extends BufferingExporter {
|
|
|
454
629
|
}
|
|
455
630
|
}
|
|
456
631
|
|
|
457
|
-
/**
|
|
458
|
-
* Retains a completed suite until finalization so a later framework error can still update it.
|
|
459
|
-
*
|
|
460
|
-
* @param {import('../../opentracing/span')} testSuiteSpan
|
|
461
|
-
* @returns {void}
|
|
462
|
-
*/
|
|
463
|
-
deferTestSuiteSpan (testSuiteSpan) {
|
|
464
|
-
this.#resetFinalFlush()
|
|
465
|
-
this.#deferredTestSuiteSpans.set(testSuiteSpan.context()._spanId.toString(), {
|
|
466
|
-
span: testSuiteSpan,
|
|
467
|
-
formattedSpan: undefined,
|
|
468
|
-
emitted: false,
|
|
469
|
-
})
|
|
470
|
-
}
|
|
471
|
-
|
|
472
|
-
/**
|
|
473
|
-
* Retains formatted test suite events received from test framework workers until finalization.
|
|
474
|
-
*
|
|
475
|
-
* @param {Array<object>} trace
|
|
476
|
-
* @returns {void}
|
|
477
|
-
*/
|
|
478
|
-
exportTraceWithDeferredTestSuite (trace) {
|
|
479
|
-
for (const formattedSpan of trace) {
|
|
480
|
-
if (formattedSpan.type !== 'test_suite_end') continue
|
|
481
|
-
|
|
482
|
-
this.#deferredTestSuiteSpans.set(formattedSpan.span_id.toString(), {
|
|
483
|
-
span: undefined,
|
|
484
|
-
formattedSpan,
|
|
485
|
-
emitted: false,
|
|
486
|
-
})
|
|
487
|
-
}
|
|
488
|
-
this.export(trace)
|
|
489
|
-
}
|
|
490
|
-
|
|
491
|
-
/**
|
|
492
|
-
* Applies a late framework error to every completed suite retained by the current finalization boundary.
|
|
493
|
-
*
|
|
494
|
-
* @param {Error} error
|
|
495
|
-
* @returns {void}
|
|
496
|
-
*/
|
|
497
|
-
setDeferredTestSuiteError (error) {
|
|
498
|
-
for (const { span, formattedSpan, emitted } of this.#deferredTestSuiteSpans.values()) {
|
|
499
|
-
if (emitted) continue
|
|
500
|
-
if (span) {
|
|
501
|
-
span.setTag(TEST_STATUS, 'fail')
|
|
502
|
-
span.setTag('error', error)
|
|
503
|
-
} else if (formattedSpan) {
|
|
504
|
-
formattedSpan.meta[TEST_STATUS] = 'fail'
|
|
505
|
-
spanFormat.addError(formattedSpan, error)
|
|
506
|
-
}
|
|
507
|
-
}
|
|
508
|
-
}
|
|
509
|
-
|
|
510
|
-
/**
|
|
511
|
-
* Appends a completed suite while preserving the ordinary writer call shape outside finalization.
|
|
512
|
-
*
|
|
513
|
-
* @param {object} formattedSpan
|
|
514
|
-
* @param {{ deadline?: number }} [options]
|
|
515
|
-
* @returns {boolean|undefined}
|
|
516
|
-
*/
|
|
517
|
-
#appendDeferredTestSuiteSpan (formattedSpan, options) {
|
|
518
|
-
const appended = options === undefined
|
|
519
|
-
? this._writer.append([formattedSpan])
|
|
520
|
-
: this._writer.append([formattedSpan], options)
|
|
521
|
-
|
|
522
|
-
if (appended !== false && this._config.isCiVisibility) {
|
|
523
|
-
incrementCountMetric(TELEMETRY_EVENTS_ENQUEUED_FOR_SERIALIZATION)
|
|
524
|
-
}
|
|
525
|
-
return appended
|
|
526
|
-
}
|
|
527
|
-
|
|
528
|
-
/**
|
|
529
|
-
* Appends completed suites before their module and session parents are exported.
|
|
530
|
-
*
|
|
531
|
-
* @param {{ deadline?: number }} [options]
|
|
532
|
-
* @returns {void}
|
|
533
|
-
*/
|
|
534
|
-
exportDeferredTestSuiteSpans (options) {
|
|
535
|
-
if (!this._writer || !this.canReportSessionTraces()) return
|
|
536
|
-
|
|
537
|
-
for (const [spanId, deferredTestSuiteSpan] of this.#deferredTestSuiteSpans) {
|
|
538
|
-
const { span, formattedSpan, emitted } = deferredTestSuiteSpan
|
|
539
|
-
if (emitted) continue
|
|
540
|
-
const updatedSpan = span && spanFormat(span)
|
|
541
|
-
if (span && formattedSpan) {
|
|
542
|
-
formattedSpan.error = updatedSpan.error
|
|
543
|
-
Object.assign(formattedSpan.meta, updatedSpan.meta)
|
|
544
|
-
Object.assign(formattedSpan.metrics, updatedSpan.metrics)
|
|
545
|
-
if (this.#appendDeferredTestSuiteSpan(formattedSpan, options) === false) continue
|
|
546
|
-
this.#deferredTestSuiteSpans.delete(spanId)
|
|
547
|
-
} else if (formattedSpan) {
|
|
548
|
-
if (this.#appendDeferredTestSuiteSpan(formattedSpan, options) === false) continue
|
|
549
|
-
this.#deferredTestSuiteSpans.delete(spanId)
|
|
550
|
-
} else {
|
|
551
|
-
if (this.#appendDeferredTestSuiteSpan(updatedSpan, options) === false) continue
|
|
552
|
-
deferredTestSuiteSpan.emitted = true
|
|
553
|
-
}
|
|
554
|
-
}
|
|
555
|
-
}
|
|
556
|
-
|
|
557
|
-
/**
|
|
558
|
-
* Discards completed suites that the selected transport cannot deliver.
|
|
559
|
-
*
|
|
560
|
-
* @returns {void}
|
|
561
|
-
*/
|
|
562
|
-
resetDeferredTestSuiteSpans () {
|
|
563
|
-
this.#deferredTestSuiteSpans.clear()
|
|
564
|
-
}
|
|
565
|
-
|
|
566
632
|
/**
|
|
567
633
|
* Retries session, module, and suite traces rejected by writer backpressure within the final deadline.
|
|
568
634
|
*
|
|
@@ -630,11 +696,17 @@ class CiVisibilityExporter extends BufferingExporter {
|
|
|
630
696
|
}
|
|
631
697
|
|
|
632
698
|
this.#resetFinalFlush()
|
|
633
|
-
this._export(
|
|
699
|
+
const appended = this._export(
|
|
634
700
|
this.formatLogMessage(testEnvironmentMetadata, logMessage),
|
|
635
701
|
this._logsWriter,
|
|
636
702
|
'_logsTimer'
|
|
637
703
|
)
|
|
704
|
+
if (appended === false) {
|
|
705
|
+
incrementCountMetric(
|
|
706
|
+
TELEMETRY_ENDPOINT_PAYLOAD_DROPPED,
|
|
707
|
+
{ endpoint: 'di_logs', statusCode: undefined, errorType: 'encoder_limit' }
|
|
708
|
+
)
|
|
709
|
+
}
|
|
638
710
|
}
|
|
639
711
|
|
|
640
712
|
flush (done) {
|
|
@@ -651,7 +723,8 @@ class CiVisibilityExporter extends BufferingExporter {
|
|
|
651
723
|
if (isFinalFlush && !this._isInitialized &&
|
|
652
724
|
this._traceBuffer.length === 0 && this._coverageBuffer.length === 0 &&
|
|
653
725
|
this.#deferredTestSuiteSpans.size === 0 && this.#deferredTestSessionTraces.length === 0 &&
|
|
654
|
-
this.#
|
|
726
|
+
this.#pendingMediaUploads.size === 0) {
|
|
727
|
+
this._initializationRequest?.controller.abort()
|
|
655
728
|
onDone()
|
|
656
729
|
return
|
|
657
730
|
}
|
|
@@ -668,6 +741,9 @@ class CiVisibilityExporter extends BufferingExporter {
|
|
|
668
741
|
const deadline = isFinalFlush ? Date.now() + FINAL_FLUSH_TIMEOUT : undefined
|
|
669
742
|
let hasCompleted = false
|
|
670
743
|
let initializationTimeoutId
|
|
744
|
+
let mediaTimeoutId
|
|
745
|
+
let writersFlushed = false
|
|
746
|
+
let writerFlushError
|
|
671
747
|
|
|
672
748
|
const fallbackTimeoutId = isFinalFlush
|
|
673
749
|
? setTimeout(() => {
|
|
@@ -680,7 +756,8 @@ class CiVisibilityExporter extends BufferingExporter {
|
|
|
680
756
|
hasCompleted = true
|
|
681
757
|
clearTimeout(fallbackTimeoutId)
|
|
682
758
|
clearTimeout(initializationTimeoutId)
|
|
683
|
-
|
|
759
|
+
clearTimeout(mediaTimeoutId)
|
|
760
|
+
this.#mediaFlushWaiters.delete(completeAfterMedia)
|
|
684
761
|
if (error) log.error('Error flushing Test Optimization data', error)
|
|
685
762
|
if (!isFinalFlush) {
|
|
686
763
|
onDone(error)
|
|
@@ -700,15 +777,23 @@ class CiVisibilityExporter extends BufferingExporter {
|
|
|
700
777
|
}
|
|
701
778
|
}
|
|
702
779
|
|
|
703
|
-
const
|
|
704
|
-
if (
|
|
705
|
-
|
|
780
|
+
const completeAfterMedia = () => {
|
|
781
|
+
if (writersFlushed) complete(writerFlushError)
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
const onWritersFlushed = (error) => {
|
|
785
|
+
writersFlushed = true
|
|
786
|
+
writerFlushError = error
|
|
787
|
+
if (isFinalFlush && this.#pendingMediaUploads.size !== 0) {
|
|
788
|
+
this.#mediaFlushWaiters.add(completeAfterMedia)
|
|
706
789
|
return
|
|
707
790
|
}
|
|
791
|
+
complete(error)
|
|
792
|
+
}
|
|
708
793
|
|
|
794
|
+
const flushWriters = () => {
|
|
709
795
|
const options = deadline === undefined ? undefined : { deadline }
|
|
710
796
|
if (isFinalFlush) {
|
|
711
|
-
this.exportDeferredTestSuiteSpans(options)
|
|
712
797
|
this.#exportDeferredTestSessionTraces(options)
|
|
713
798
|
}
|
|
714
799
|
|
|
@@ -722,24 +807,31 @@ class CiVisibilityExporter extends BufferingExporter {
|
|
|
722
807
|
let flushError
|
|
723
808
|
|
|
724
809
|
if (remaining === 0) {
|
|
725
|
-
|
|
810
|
+
onWritersFlushed()
|
|
726
811
|
return
|
|
727
812
|
}
|
|
728
813
|
|
|
729
814
|
const onFlushComplete = (error) => {
|
|
730
815
|
flushError ||= error
|
|
731
816
|
remaining -= 1
|
|
732
|
-
if (remaining === 0)
|
|
817
|
+
if (remaining === 0) onWritersFlushed(flushError)
|
|
733
818
|
}
|
|
734
819
|
|
|
735
820
|
for (const writer of writers) writer.flush(onFlushComplete, options)
|
|
736
821
|
}
|
|
737
822
|
|
|
823
|
+
if (isFinalFlush && this.#pendingMediaUploads.size !== 0) {
|
|
824
|
+
mediaTimeoutId = setTimeout(() => {
|
|
825
|
+
const error = createFinalFlushTimeoutError()
|
|
826
|
+
for (const controller of this.#pendingMediaUploads) controller.abort(error)
|
|
827
|
+
}, Math.max(0, deadline - Date.now()))
|
|
828
|
+
mediaTimeoutId.unref?.()
|
|
829
|
+
}
|
|
830
|
+
|
|
738
831
|
if (isFinalFlush && this._initializationRequest) {
|
|
739
832
|
const initializationRequest = this._initializationRequest
|
|
740
|
-
const { controller
|
|
833
|
+
const { controller } = initializationRequest
|
|
741
834
|
initializationRequest.finalFlush = finalFlush
|
|
742
|
-
options.deadline = deadline
|
|
743
835
|
initializationTimeoutId = setTimeout(() => {
|
|
744
836
|
const error = createFinalFlushTimeoutError()
|
|
745
837
|
if (initializationRequest.finalFlush === finalFlush) controller.abort(error)
|
|
@@ -847,25 +939,104 @@ class CiVisibilityExporter extends BufferingExporter {
|
|
|
847
939
|
return Boolean(this._testScreenshotUploadUrl) && this._isTestFailureScreenshotsEnabled
|
|
848
940
|
}
|
|
849
941
|
|
|
942
|
+
/**
|
|
943
|
+
* Returns whether the exporter can upload test failure videos.
|
|
944
|
+
*
|
|
945
|
+
* @returns {boolean}
|
|
946
|
+
*/
|
|
947
|
+
canUploadTestVideos () {
|
|
948
|
+
return Boolean(this._testScreenshotUploadUrl) && this._isTestFailureVideosEnabled
|
|
949
|
+
}
|
|
950
|
+
|
|
850
951
|
/**
|
|
851
952
|
* Uploads a single test screenshot to the Test Optimization media intake.
|
|
852
953
|
*
|
|
853
954
|
* @param {object} options - Upload options
|
|
854
|
-
* @param {string} options.filePath - Path to the screenshot file
|
|
955
|
+
* @param {string} [options.filePath] - Path to the screenshot file
|
|
956
|
+
* @param {Buffer} [options.content] - In-memory screenshot content
|
|
855
957
|
* @param {string} options.traceId - Test trace id used as the screenshot key
|
|
856
958
|
* @param {string} options.idempotencyKey - Stable per-artifact key, reused on retry
|
|
857
959
|
* @param {number} options.capturedAtMs - Capture time in epoch milliseconds
|
|
858
960
|
* @param {AbortSignal} [options.signal] - Additional signal used to cancel the upload
|
|
859
961
|
* @param {Function} callback - Callback function (err)
|
|
860
962
|
*/
|
|
861
|
-
uploadTestScreenshot ({ filePath, traceId, idempotencyKey, capturedAtMs, signal }, callback) {
|
|
963
|
+
uploadTestScreenshot ({ filePath, content, traceId, idempotencyKey, capturedAtMs, signal }, callback) {
|
|
862
964
|
if (!this._testScreenshotUploadUrl) {
|
|
863
965
|
return callback(new Error('Test screenshot upload URL not configured'))
|
|
864
966
|
}
|
|
865
967
|
|
|
968
|
+
this.#uploadTestMedia(uploadTestScreenshotRequest, {
|
|
969
|
+
filePath,
|
|
970
|
+
content,
|
|
971
|
+
traceId,
|
|
972
|
+
idempotencyKey,
|
|
973
|
+
capturedAtMs,
|
|
974
|
+
signal,
|
|
975
|
+
}, callback)
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
/**
|
|
979
|
+
* Uploads a test-scoped failure video to the Test Optimization media intake.
|
|
980
|
+
*
|
|
981
|
+
* @param {object} options - Upload options
|
|
982
|
+
* @param {string} options.filePath - Path to the video file
|
|
983
|
+
* @param {string} options.traceId - Test trace id used as the media key
|
|
984
|
+
* @param {string} options.idempotencyKey - Stable per-artifact key, reused on retry
|
|
985
|
+
* @param {number} options.capturedAtMs - Capture time in epoch milliseconds
|
|
986
|
+
* @param {AbortSignal} [options.signal] - Additional signal used to cancel the upload
|
|
987
|
+
* @param {Function} callback - Callback function (err)
|
|
988
|
+
* @returns {void}
|
|
989
|
+
*/
|
|
990
|
+
uploadTestVideo (options, callback) {
|
|
991
|
+
if (!this._testScreenshotUploadUrl) {
|
|
992
|
+
return callback(new Error('Test video upload URL not configured'))
|
|
993
|
+
}
|
|
994
|
+
|
|
995
|
+
this.#uploadTestMedia(uploadTestVideoRequest, options, callback, BACKGROUND_VIDEO_UPLOAD_TIMEOUT)
|
|
996
|
+
}
|
|
997
|
+
|
|
998
|
+
/**
|
|
999
|
+
* Uploads a suite-scoped failure video to the Test Optimization media intake.
|
|
1000
|
+
*
|
|
1001
|
+
* @param {object} options - Upload options
|
|
1002
|
+
* @param {string} options.filePath - Path to the video file
|
|
1003
|
+
* @param {string} options.testSessionId - Test session id used as the media key
|
|
1004
|
+
* @param {string} options.testSuiteId - Test suite id used as the media key
|
|
1005
|
+
* @param {string} options.idempotencyKey - Stable per-artifact key, reused on retry
|
|
1006
|
+
* @param {number} options.capturedAtMs - Capture time in epoch milliseconds
|
|
1007
|
+
* @param {AbortSignal} [options.signal] - Additional signal used to cancel the upload
|
|
1008
|
+
* @param {Function} callback - Callback function (err)
|
|
1009
|
+
* @returns {void}
|
|
1010
|
+
*/
|
|
1011
|
+
uploadTestSuiteVideo (options, callback) {
|
|
1012
|
+
if (!this._testScreenshotUploadUrl) {
|
|
1013
|
+
return callback(new Error('Test suite video upload URL not configured'))
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
this.#uploadTestMedia(uploadTestSuiteVideoRequest, options, callback, BACKGROUND_VIDEO_UPLOAD_TIMEOUT)
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
/**
|
|
1020
|
+
* Starts and tracks one media upload so final flush waits for it.
|
|
1021
|
+
*
|
|
1022
|
+
* @param {Function} uploadRequest - Media request function
|
|
1023
|
+
* @param {object} uploadOptions - Media request options
|
|
1024
|
+
* @param {AbortSignal} [uploadOptions.signal] - Additional signal used to cancel the upload
|
|
1025
|
+
* @param {Function} callback - Callback function (err)
|
|
1026
|
+
* @param {number} [timeoutMs] - Maximum background upload duration
|
|
1027
|
+
* @returns {void}
|
|
1028
|
+
*/
|
|
1029
|
+
#uploadTestMedia (uploadRequest, uploadOptions, callback, timeoutMs = FINAL_FLUSH_TIMEOUT) {
|
|
1030
|
+
const { signal } = uploadOptions
|
|
1031
|
+
|
|
1032
|
+
if (signal?.aborted) {
|
|
1033
|
+
const error = signal.reason || Object.assign(new Error('Test screenshot upload aborted'), { code: 'ABORT_ERR' })
|
|
1034
|
+
callback(error)
|
|
1035
|
+
return
|
|
1036
|
+
}
|
|
866
1037
|
this.#resetFinalFlush()
|
|
1038
|
+
const deadline = Date.now() + timeoutMs
|
|
867
1039
|
const controller = new AbortController()
|
|
868
|
-
const deadline = Date.now() + FINAL_FLUSH_TIMEOUT
|
|
869
1040
|
let settled = false
|
|
870
1041
|
const complete = (error) => {
|
|
871
1042
|
if (settled) return
|
|
@@ -876,10 +1047,10 @@ class CiVisibilityExporter extends BufferingExporter {
|
|
|
876
1047
|
try {
|
|
877
1048
|
callback(error)
|
|
878
1049
|
} finally {
|
|
879
|
-
this.#
|
|
880
|
-
if (this.#
|
|
881
|
-
const waiters = [...this.#
|
|
882
|
-
this.#
|
|
1050
|
+
this.#pendingMediaUploads.delete(controller)
|
|
1051
|
+
if (this.#pendingMediaUploads.size === 0) {
|
|
1052
|
+
const waiters = [...this.#mediaFlushWaiters]
|
|
1053
|
+
this.#mediaFlushWaiters.clear()
|
|
883
1054
|
for (const waiter of waiters) queueMicrotask(waiter)
|
|
884
1055
|
}
|
|
885
1056
|
}
|
|
@@ -890,25 +1061,23 @@ class CiVisibilityExporter extends BufferingExporter {
|
|
|
890
1061
|
complete(error)
|
|
891
1062
|
}
|
|
892
1063
|
|
|
893
|
-
this.#pendingScreenshotUploads.add(controller)
|
|
894
|
-
signal?.addEventListener('abort', onAbort, { once: true })
|
|
895
1064
|
const timeoutId = setTimeout(() => {
|
|
896
1065
|
const error = createFinalFlushTimeoutError()
|
|
897
1066
|
controller.abort(error)
|
|
898
1067
|
complete(error)
|
|
899
|
-
},
|
|
1068
|
+
}, Math.max(0, deadline - Date.now()))
|
|
900
1069
|
timeoutId.unref?.()
|
|
901
1070
|
|
|
1071
|
+
this.#pendingMediaUploads.add(controller)
|
|
1072
|
+
signal?.addEventListener('abort', onAbort, { once: true })
|
|
1073
|
+
|
|
902
1074
|
if (signal?.aborted) {
|
|
903
1075
|
onAbort()
|
|
904
1076
|
return
|
|
905
1077
|
}
|
|
906
1078
|
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
traceId,
|
|
910
|
-
idempotencyKey,
|
|
911
|
-
capturedAtMs,
|
|
1079
|
+
uploadRequest({
|
|
1080
|
+
...uploadOptions,
|
|
912
1081
|
url: this._testScreenshotUploadUrl,
|
|
913
1082
|
isEvpProxy: !!this._isUsingEvpProxy,
|
|
914
1083
|
evpProxyPrefix: this.evpProxyPrefix,
|