dd-trace 6.12.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/cypress.js +9 -11
- 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/test-optimization-validation/scenarios/ci-wiring.js +5 -4
- 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/index.d.ts +127 -26
- package/package.json +22 -19
- package/packages/datadog-esbuild/index.js +3 -1
- package/packages/datadog-instrumentations/src/bunyan.js +4 -2
- 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/bunyan.js +12 -3
- 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 +266 -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 +279 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/targets.json +12 -1
- package/packages/datadog-instrumentations/src/helpers/rewriter/transforms.js +562 -27
- package/packages/datadog-instrumentations/src/jest.js +165 -38
- package/packages/datadog-instrumentations/src/knex.js +76 -0
- package/packages/datadog-instrumentations/src/mariadb-bundle.js +1597 -0
- package/packages/datadog-instrumentations/src/mariadb.js +13 -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/pino.js +15 -4
- package/packages/datadog-instrumentations/src/playwright.js +137 -27
- 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 +44 -7
- package/packages/datadog-instrumentations/src/vitest-main.js +92 -9
- package/packages/datadog-instrumentations/src/vitest-worker.js +82 -14
- package/packages/datadog-instrumentations/src/webdriverio.js +986 -1
- package/packages/datadog-instrumentations/src/winston.js +6 -3
- 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-cypress/src/support.js +1 -0
- 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-http/src/client.js +2 -15
- package/packages/datadog-plugin-http2/src/client.js +2 -15
- 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-openai-agents/src/integration.js +15 -11
- package/packages/datadog-plugin-playwright/src/index.js +176 -28
- package/packages/datadog-plugin-undici/src/index.js +29 -56
- package/packages/datadog-plugin-vitest/src/index.js +6 -12
- package/packages/dd-trace/src/aiguard/evaluation.js +96 -21
- package/packages/dd-trace/src/aiguard/integrations/vercel-ai.js +2 -3
- package/packages/dd-trace/src/aiguard/messages/anthropic.js +29 -10
- 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/aiguard/noop.js +9 -1
- package/packages/dd-trace/src/aiguard/redaction.js +183 -0
- package/packages/dd-trace/src/aiguard/sdk.js +7 -2
- package/packages/dd-trace/src/aiguard/tags.js +2 -0
- 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/iast/vulnerabilities-formatter/utils.js +10 -9
- package/packages/dd-trace/src/appsec/lambda.js +44 -26
- package/packages/dd-trace/src/appsec/rule_manager.js +2 -0
- 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 +7 -150
- 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 +397 -234
- 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 +21 -0
- 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 +10 -0
- package/packages/dd-trace/src/config/index.js +75 -14
- 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 +47 -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 -39
- 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 +532 -71
- 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 +24 -3
- package/packages/dd-trace/src/exporters/agent/writer.js +28 -11
- 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 +31 -7
- package/packages/dd-trace/src/exporters/span-stats/index.js +23 -4
- package/packages/dd-trace/src/external-logger/src/index.js +4 -3
- package/packages/dd-trace/src/flush.js +105 -0
- 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/client.js +4 -0
- package/packages/dd-trace/src/llmobs/experiments/dataset.js +52 -2
- package/packages/dd-trace/src/llmobs/experiments/experiment.js +420 -105
- package/packages/dd-trace/src/llmobs/experiments/index.js +54 -42
- package/packages/dd-trace/src/llmobs/experiments/noop.js +39 -8
- package/packages/dd-trace/src/llmobs/experiments/result.js +1 -0
- package/packages/dd-trace/src/llmobs/experiments/util.js +21 -0
- package/packages/dd-trace/src/llmobs/index.js +111 -48
- package/packages/dd-trace/src/llmobs/noop.js +2 -1
- 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/anthropic/util.js +8 -4
- package/packages/dd-trace/src/llmobs/plugins/claude-agent-sdk/index.js +6 -3
- package/packages/dd-trace/src/llmobs/plugins/langchain/index.js +7 -0
- package/packages/dd-trace/src/llmobs/plugins/openai/utils.js +13 -6
- package/packages/dd-trace/src/llmobs/plugins/openai-agents/utils.js +5 -7
- package/packages/dd-trace/src/llmobs/util.js +0 -33
- package/packages/dd-trace/src/llmobs/writers/base.js +106 -53
- package/packages/dd-trace/src/log/index.js +7 -1
- package/packages/dd-trace/src/log-submission/log-submission-plugin.js +249 -0
- package/packages/dd-trace/src/noop/span.js +5 -0
- package/packages/dd-trace/src/openfeature/writers/base.js +14 -12
- package/packages/dd-trace/src/openfeature/writers/exposures.js +8 -1
- package/packages/dd-trace/src/openfeature/writers/util.js +1 -1
- package/packages/dd-trace/src/opentelemetry/logs/batch_log_processor.js +45 -3
- package/packages/dd-trace/src/opentelemetry/logs/index.js +9 -25
- package/packages/dd-trace/src/opentelemetry/logs/logger_provider.js +7 -4
- package/packages/dd-trace/src/opentelemetry/logs/otlp_http_log_exporter.js +14 -4
- package/packages/dd-trace/src/opentelemetry/metrics/index.js +18 -23
- package/packages/dd-trace/src/opentelemetry/metrics/meter_provider.js +8 -0
- package/packages/dd-trace/src/opentelemetry/metrics/otlp_http_metric_exporter.js +18 -6
- package/packages/dd-trace/src/opentelemetry/metrics/otlp_span_stats_exporter.js +14 -2
- package/packages/dd-trace/src/opentelemetry/metrics/periodic_metric_reader.js +25 -5
- package/packages/dd-trace/src/opentelemetry/otlp/otlp_http_exporter_base.js +61 -25
- 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/opentelemetry/tracer.js +26 -2
- package/packages/dd-trace/src/opentracing/propagation/text_map.js +494 -351
- package/packages/dd-trace/src/opentracing/span.js +28 -0
- package/packages/dd-trace/src/otel-thread-ctx.js +23 -5
- package/packages/dd-trace/src/payload-tagging/index.js +2 -1
- package/packages/dd-trace/src/plugin_manager.js +2 -0
- 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/status-validator.js +98 -0
- package/packages/dd-trace/src/plugins/util/test.js +157 -41
- package/packages/dd-trace/src/plugins/util/url.js +2 -2
- package/packages/dd-trace/src/plugins/util/web.js +6 -74
- package/packages/dd-trace/src/profiling/config.js +1 -0
- package/packages/dd-trace/src/profiling/oom.js +11 -5
- package/packages/dd-trace/src/profiling/profiler.js +30 -13
- package/packages/dd-trace/src/profiling/profilers/events.js +3 -3
- package/packages/dd-trace/src/profiling/profilers/space.js +38 -11
- package/packages/dd-trace/src/profiling/profilers/wall.js +14 -14
- package/packages/dd-trace/src/profiling/webspan-utils.js +7 -4
- package/packages/dd-trace/src/proxy.js +108 -10
- 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/index.js +5 -0
- package/packages/dd-trace/src/runtime_metrics/otlp_runtime_metrics.js +10 -1
- package/packages/dd-trace/src/runtime_metrics/runtime_metrics.js +22 -7
- package/packages/dd-trace/src/sampling_rule.js +36 -9
- package/packages/dd-trace/src/serverless/telemetry-delivery-tracker.js +51 -0
- package/packages/dd-trace/src/serverless/vercel.js +155 -0
- package/packages/dd-trace/src/serverless.js +45 -21
- package/packages/dd-trace/src/service-naming/schemas/util.js +1 -1
- package/packages/dd-trace/src/span_stats.js +64 -31
- 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 +26 -5
- package/packages/dd-trace/src/tracer_metadata.js +11 -3
- package/packages/dd-trace/src/web-tags-cache.js +67 -13
- package/vendor/dist/@datadog/openfeature-node-server/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/packages/datadog-instrumentations/src/playwright-browser-scripts.js +0 -27
- package/packages/dd-trace/src/ci-visibility/log-submission/log-submission-plugin.js +0 -53
- package/packages/dd-trace/src/encode/agentless-json.js +0 -209
|
@@ -1,31 +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 {
|
|
29
|
+
const {
|
|
30
|
+
createFinalFlushTimeoutError,
|
|
31
|
+
FINAL_FLUSH_FALLBACK_DELAY,
|
|
32
|
+
FINAL_FLUSH_TIMEOUT,
|
|
33
|
+
} = require('../final-flush')
|
|
34
|
+
const { incrementCountMetric, TELEMETRY_ENDPOINT_PAYLOAD_DROPPED } = require('../telemetry')
|
|
25
35
|
const { sendGitMetadata: sendGitMetadataRequest } = require('./git/git_metadata')
|
|
36
|
+
const buildSettingsCacheKey = require('./settings-cache-key')
|
|
26
37
|
|
|
27
38
|
const hostname = getHostname()
|
|
28
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
|
+
])
|
|
29
54
|
|
|
30
55
|
/**
|
|
31
56
|
* Test session identity sent with every request. Fields are optional because the CI provider,
|
|
@@ -51,61 +76,131 @@ function getTestConfigurationTags (tags) {
|
|
|
51
76
|
}, {})
|
|
52
77
|
}
|
|
53
78
|
|
|
79
|
+
function isTestSessionEvent (span) {
|
|
80
|
+
return span.type === 'test_session_end' || span.type === 'test_suite_end' || span.type === 'test_module_end'
|
|
81
|
+
}
|
|
82
|
+
|
|
54
83
|
function getIsTestSessionTrace (trace) {
|
|
55
|
-
return trace.some(
|
|
56
|
-
|
|
57
|
-
|
|
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)
|
|
58
159
|
}
|
|
59
160
|
|
|
60
161
|
const GIT_UPLOAD_TIMEOUT = 60_000 // 60 seconds
|
|
61
162
|
const CAN_USE_CI_VIS_PROTOCOL_TIMEOUT = GIT_UPLOAD_TIMEOUT
|
|
62
163
|
const MAX_COVERAGE_REPORT_FLAGS = 32
|
|
63
|
-
|
|
64
|
-
const
|
|
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
|
|
65
166
|
|
|
66
167
|
/**
|
|
67
|
-
* @
|
|
168
|
+
* @param {string} tags
|
|
169
|
+
* @param {string} key
|
|
170
|
+
* @param {string | undefined} value
|
|
68
171
|
*/
|
|
69
|
-
function createFinalFlushTimeoutError () {
|
|
70
|
-
const error = new Error('Timed out waiting for Test Optimization to flush')
|
|
71
|
-
error.code = 'ERR_DD_TEST_OPTIMIZATION_FLUSH_TIMEOUT'
|
|
72
|
-
return error
|
|
73
|
-
}
|
|
74
|
-
|
|
75
172
|
function appendLogTag (tags, key, value) {
|
|
76
|
-
if (value
|
|
77
|
-
|
|
78
|
-
}
|
|
173
|
+
if (value === undefined) return tags
|
|
174
|
+
|
|
175
|
+
const tag = `${key}:${value}`
|
|
176
|
+
return tags ? `${tags},${tag}` : tag
|
|
79
177
|
}
|
|
80
178
|
|
|
81
179
|
function getLogTags (logMessage, { env, version }, gitRepositoryUrl, gitCommitSha) {
|
|
82
|
-
|
|
180
|
+
let tags = ''
|
|
83
181
|
if (Array.isArray(logMessage.ddtags)) {
|
|
84
|
-
|
|
85
|
-
tags.push(tag)
|
|
86
|
-
}
|
|
182
|
+
tags = logMessage.ddtags.join(',')
|
|
87
183
|
} else if (logMessage.ddtags) {
|
|
88
|
-
|
|
89
|
-
tags.push(tag)
|
|
90
|
-
}
|
|
184
|
+
tags = logMessage.ddtags
|
|
91
185
|
}
|
|
92
186
|
|
|
93
|
-
appendLogTag(tags, 'env', env)
|
|
94
|
-
appendLogTag(tags, 'version', version)
|
|
95
|
-
appendLogTag(tags, 'debugger_version', tracerVersion)
|
|
96
|
-
appendLogTag(tags, 'host_name', hostname)
|
|
97
|
-
appendLogTag(tags, GIT_COMMIT_SHA, gitCommitSha)
|
|
98
|
-
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)
|
|
99
193
|
|
|
100
|
-
return tags
|
|
194
|
+
return tags
|
|
101
195
|
}
|
|
102
196
|
|
|
103
197
|
class CiVisibilityExporter extends BufferingExporter {
|
|
104
198
|
#finalFlush
|
|
105
199
|
#deferredTestSessionTraces = []
|
|
106
|
-
#
|
|
107
|
-
#
|
|
200
|
+
#pendingMediaUploads = new Set()
|
|
201
|
+
#mediaFlushWaiters = new Set()
|
|
108
202
|
#deferredTestSuiteSpans = new Map()
|
|
203
|
+
#gitUploadTimeoutId
|
|
109
204
|
|
|
110
205
|
constructor (config, options = {}) {
|
|
111
206
|
super(config)
|
|
@@ -131,11 +226,8 @@ class CiVisibilityExporter extends BufferingExporter {
|
|
|
131
226
|
|
|
132
227
|
this._isTestFailureScreenshotsEnabled =
|
|
133
228
|
Boolean(config?.testOptimization?.DD_TEST_FAILURE_SCREENSHOTS_ENABLED)
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
this._resolveGit(new Error('Timeout while uploading git metadata'))
|
|
137
|
-
}, GIT_UPLOAD_TIMEOUT)
|
|
138
|
-
gitUploadTimeoutId.unref?.()
|
|
229
|
+
this._isTestFailureVideosEnabled =
|
|
230
|
+
Boolean(config?.testOptimization?.DD_TEST_FAILURE_VIDEOS_ENABLED)
|
|
139
231
|
|
|
140
232
|
const canUseCiVisProtocolTimeoutId = setTimeout(() => {
|
|
141
233
|
this._resolveCanUseCiVisProtocol(false)
|
|
@@ -144,7 +236,8 @@ class CiVisibilityExporter extends BufferingExporter {
|
|
|
144
236
|
|
|
145
237
|
this._gitUploadPromise = new Promise(resolve => {
|
|
146
238
|
this._resolveGit = (err) => {
|
|
147
|
-
clearTimeout(gitUploadTimeoutId)
|
|
239
|
+
clearTimeout(this.#gitUploadTimeoutId)
|
|
240
|
+
this.#gitUploadTimeoutId = null
|
|
148
241
|
resolve(err)
|
|
149
242
|
}
|
|
150
243
|
})
|
|
@@ -276,51 +369,142 @@ class CiVisibilityExporter extends BufferingExporter {
|
|
|
276
369
|
const cachedLibraryConfig = this._testOptimizationHttpCache.readSettings()
|
|
277
370
|
if (cachedLibraryConfig !== CACHE_MISS) {
|
|
278
371
|
log.debug('Test Optimization HTTP cache settings found, skipping settings request')
|
|
279
|
-
|
|
280
|
-
this._libraryConfig = this.filterConfiguration(cachedLibraryConfig)
|
|
281
|
-
const canUseCachedSkippableSuites = !this.shouldRequestSkippableSuites() ||
|
|
282
|
-
this._testOptimizationHttpCache.hasValidSkippableSuites({
|
|
283
|
-
testLevel: configuration.testLevel,
|
|
284
|
-
isCoverageReportUploadEnabled: configuration.isCoverageReportUploadEnabled,
|
|
285
|
-
})
|
|
286
|
-
if (this._libraryConfig.requireGit && !canUseCachedSkippableSuites) {
|
|
287
|
-
this.sendGitMetadata(repositoryUrl)
|
|
288
|
-
} else {
|
|
289
|
-
this._resolveGit()
|
|
290
|
-
}
|
|
291
|
-
return callback(null, this._libraryConfig)
|
|
372
|
+
return this._applyCachedSettings(cachedLibraryConfig, configuration, repositoryUrl, false, callback)
|
|
292
373
|
}
|
|
293
374
|
|
|
294
375
|
if (this._isTestOptimizationCacheOnly) {
|
|
295
376
|
return callback(this._getCacheOnlyError('settings'), {})
|
|
296
377
|
}
|
|
297
378
|
|
|
298
|
-
|
|
299
|
-
|
|
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) => {
|
|
300
400
|
/**
|
|
301
401
|
* **Important**: this._libraryConfig remains empty in testing frameworks
|
|
302
402
|
* where the tests run in a subprocess, like Jest,
|
|
303
403
|
* because `getLibraryConfiguration` is called only once in the main process.
|
|
304
404
|
*/
|
|
305
|
-
this._libraryConfig = this.filterConfiguration(libraryConfig)
|
|
306
|
-
|
|
307
405
|
if (err) {
|
|
308
|
-
callback(err, {})
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
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)
|
|
314
426
|
}
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
})
|
|
319
|
-
})
|
|
320
|
-
} else {
|
|
321
|
-
callback(null, this._libraryConfig)
|
|
427
|
+
}
|
|
428
|
+
liveFetchStarted = false
|
|
429
|
+
return withCache(fsCacheKey, fetchSettings, applySettings, SETTINGS_CACHE_TTL_MS)
|
|
322
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,
|
|
323
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
|
+
}
|
|
324
508
|
})
|
|
325
509
|
}
|
|
326
510
|
|
|
@@ -387,6 +571,13 @@ class CiVisibilityExporter extends BufferingExporter {
|
|
|
387
571
|
this._resolveGit()
|
|
388
572
|
return
|
|
389
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
|
+
}
|
|
390
581
|
this._canUseCiVisProtocolPromise.then((canUseCiVisProtocol) => {
|
|
391
582
|
if (!canUseCiVisProtocol) {
|
|
392
583
|
return
|
|
@@ -409,34 +600,7 @@ class CiVisibilityExporter extends BufferingExporter {
|
|
|
409
600
|
|
|
410
601
|
export (trace) {
|
|
411
602
|
this.#resetFinalFlush()
|
|
412
|
-
|
|
413
|
-
if (this.#deferredTestSuiteSpans.size === 0) {
|
|
414
|
-
this.#exportTrace(trace)
|
|
415
|
-
return
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
let hasDeferredTestSuiteSpan = false
|
|
419
|
-
let immediateTrace
|
|
420
|
-
for (let index = 0; index < trace.length; index++) {
|
|
421
|
-
const formattedSpan = trace[index]
|
|
422
|
-
const spanId = formattedSpan.span_id?.toString()
|
|
423
|
-
const deferredTestSuiteSpan = this.#deferredTestSuiteSpans.get(spanId)
|
|
424
|
-
if (deferredTestSuiteSpan) {
|
|
425
|
-
if (!hasDeferredTestSuiteSpan && index > 0) immediateTrace = trace.slice(0, index)
|
|
426
|
-
hasDeferredTestSuiteSpan = true
|
|
427
|
-
if (deferredTestSuiteSpan.emitted || (this._isInitialized && !this.canReportSessionTraces())) {
|
|
428
|
-
this.#deferredTestSuiteSpans.delete(spanId)
|
|
429
|
-
} else {
|
|
430
|
-
deferredTestSuiteSpan.formattedSpan = formattedSpan
|
|
431
|
-
}
|
|
432
|
-
} else if (hasDeferredTestSuiteSpan) {
|
|
433
|
-
immediateTrace ??= []
|
|
434
|
-
immediateTrace.push(formattedSpan)
|
|
435
|
-
}
|
|
436
|
-
}
|
|
437
|
-
|
|
438
|
-
if (!hasDeferredTestSuiteSpan) this.#exportTrace(trace)
|
|
439
|
-
else if (immediateTrace) this.#exportTrace(immediateTrace)
|
|
603
|
+
this.#exportTrace(trace)
|
|
440
604
|
}
|
|
441
605
|
|
|
442
606
|
/**
|
|
@@ -453,6 +617,11 @@ class CiVisibilityExporter extends BufferingExporter {
|
|
|
453
617
|
}
|
|
454
618
|
const isTestSessionTrace = getIsTestSessionTrace(trace)
|
|
455
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)
|
|
456
625
|
return
|
|
457
626
|
}
|
|
458
627
|
if (this._export(trace, undefined, undefined, isTestSessionTrace) === false && isTestSessionTrace) {
|
|
@@ -460,115 +629,6 @@ class CiVisibilityExporter extends BufferingExporter {
|
|
|
460
629
|
}
|
|
461
630
|
}
|
|
462
631
|
|
|
463
|
-
/**
|
|
464
|
-
* Retains a completed suite until finalization so a later framework error can still update it.
|
|
465
|
-
*
|
|
466
|
-
* @param {import('../../opentracing/span')} testSuiteSpan
|
|
467
|
-
* @returns {void}
|
|
468
|
-
*/
|
|
469
|
-
deferTestSuiteSpan (testSuiteSpan) {
|
|
470
|
-
this.#resetFinalFlush()
|
|
471
|
-
this.#deferredTestSuiteSpans.set(testSuiteSpan.context()._spanId.toString(), {
|
|
472
|
-
span: testSuiteSpan,
|
|
473
|
-
formattedSpan: undefined,
|
|
474
|
-
emitted: false,
|
|
475
|
-
})
|
|
476
|
-
}
|
|
477
|
-
|
|
478
|
-
/**
|
|
479
|
-
* Retains formatted test suite events received from test framework workers until finalization.
|
|
480
|
-
*
|
|
481
|
-
* @param {Array<object>} trace
|
|
482
|
-
* @returns {void}
|
|
483
|
-
*/
|
|
484
|
-
exportTraceWithDeferredTestSuite (trace) {
|
|
485
|
-
for (const formattedSpan of trace) {
|
|
486
|
-
if (formattedSpan.type !== 'test_suite_end') continue
|
|
487
|
-
|
|
488
|
-
this.#deferredTestSuiteSpans.set(formattedSpan.span_id.toString(), {
|
|
489
|
-
span: undefined,
|
|
490
|
-
formattedSpan,
|
|
491
|
-
emitted: false,
|
|
492
|
-
})
|
|
493
|
-
}
|
|
494
|
-
this.export(trace)
|
|
495
|
-
}
|
|
496
|
-
|
|
497
|
-
/**
|
|
498
|
-
* Applies a late framework error to every completed suite retained by the current finalization boundary.
|
|
499
|
-
*
|
|
500
|
-
* @param {Error} error
|
|
501
|
-
* @returns {void}
|
|
502
|
-
*/
|
|
503
|
-
setDeferredTestSuiteError (error) {
|
|
504
|
-
for (const { span, formattedSpan, emitted } of this.#deferredTestSuiteSpans.values()) {
|
|
505
|
-
if (emitted) continue
|
|
506
|
-
if (span) {
|
|
507
|
-
span.setTag(TEST_STATUS, 'fail')
|
|
508
|
-
span.setTag('error', error)
|
|
509
|
-
} else if (formattedSpan) {
|
|
510
|
-
formattedSpan.meta[TEST_STATUS] = 'fail'
|
|
511
|
-
spanFormat.addError(formattedSpan, error)
|
|
512
|
-
}
|
|
513
|
-
}
|
|
514
|
-
}
|
|
515
|
-
|
|
516
|
-
/**
|
|
517
|
-
* Appends a completed suite while preserving the ordinary writer call shape outside finalization.
|
|
518
|
-
*
|
|
519
|
-
* @param {object} formattedSpan
|
|
520
|
-
* @param {{ deadline?: number }} [options]
|
|
521
|
-
* @returns {boolean|undefined}
|
|
522
|
-
*/
|
|
523
|
-
#appendDeferredTestSuiteSpan (formattedSpan, options) {
|
|
524
|
-
const appended = options === undefined
|
|
525
|
-
? this._writer.append([formattedSpan])
|
|
526
|
-
: this._writer.append([formattedSpan], options)
|
|
527
|
-
|
|
528
|
-
if (appended !== false && this._config.isCiVisibility) {
|
|
529
|
-
incrementCountMetric(TELEMETRY_EVENTS_ENQUEUED_FOR_SERIALIZATION)
|
|
530
|
-
}
|
|
531
|
-
return appended
|
|
532
|
-
}
|
|
533
|
-
|
|
534
|
-
/**
|
|
535
|
-
* Appends completed suites before their module and session parents are exported.
|
|
536
|
-
*
|
|
537
|
-
* @param {{ deadline?: number }} [options]
|
|
538
|
-
* @returns {void}
|
|
539
|
-
*/
|
|
540
|
-
exportDeferredTestSuiteSpans (options) {
|
|
541
|
-
if (!this._writer || !this.canReportSessionTraces()) return
|
|
542
|
-
|
|
543
|
-
for (const [spanId, deferredTestSuiteSpan] of this.#deferredTestSuiteSpans) {
|
|
544
|
-
const { span, formattedSpan, emitted } = deferredTestSuiteSpan
|
|
545
|
-
if (emitted) continue
|
|
546
|
-
const updatedSpan = span && spanFormat(span)
|
|
547
|
-
if (span && formattedSpan) {
|
|
548
|
-
formattedSpan.error = updatedSpan.error
|
|
549
|
-
Object.assign(formattedSpan.meta, updatedSpan.meta)
|
|
550
|
-
Object.assign(formattedSpan.metrics, updatedSpan.metrics)
|
|
551
|
-
if (this.#appendDeferredTestSuiteSpan(formattedSpan, options) === false) continue
|
|
552
|
-
this.#deferredTestSuiteSpans.delete(spanId)
|
|
553
|
-
} else if (formattedSpan) {
|
|
554
|
-
if (this.#appendDeferredTestSuiteSpan(formattedSpan, options) === false) continue
|
|
555
|
-
this.#deferredTestSuiteSpans.delete(spanId)
|
|
556
|
-
} else {
|
|
557
|
-
if (this.#appendDeferredTestSuiteSpan(updatedSpan, options) === false) continue
|
|
558
|
-
deferredTestSuiteSpan.emitted = true
|
|
559
|
-
}
|
|
560
|
-
}
|
|
561
|
-
}
|
|
562
|
-
|
|
563
|
-
/**
|
|
564
|
-
* Discards completed suites that the selected transport cannot deliver.
|
|
565
|
-
*
|
|
566
|
-
* @returns {void}
|
|
567
|
-
*/
|
|
568
|
-
resetDeferredTestSuiteSpans () {
|
|
569
|
-
this.#deferredTestSuiteSpans.clear()
|
|
570
|
-
}
|
|
571
|
-
|
|
572
632
|
/**
|
|
573
633
|
* Retries session, module, and suite traces rejected by writer backpressure within the final deadline.
|
|
574
634
|
*
|
|
@@ -636,11 +696,17 @@ class CiVisibilityExporter extends BufferingExporter {
|
|
|
636
696
|
}
|
|
637
697
|
|
|
638
698
|
this.#resetFinalFlush()
|
|
639
|
-
this._export(
|
|
699
|
+
const appended = this._export(
|
|
640
700
|
this.formatLogMessage(testEnvironmentMetadata, logMessage),
|
|
641
701
|
this._logsWriter,
|
|
642
702
|
'_logsTimer'
|
|
643
703
|
)
|
|
704
|
+
if (appended === false) {
|
|
705
|
+
incrementCountMetric(
|
|
706
|
+
TELEMETRY_ENDPOINT_PAYLOAD_DROPPED,
|
|
707
|
+
{ endpoint: 'di_logs', statusCode: undefined, errorType: 'encoder_limit' }
|
|
708
|
+
)
|
|
709
|
+
}
|
|
644
710
|
}
|
|
645
711
|
|
|
646
712
|
flush (done) {
|
|
@@ -657,7 +723,8 @@ class CiVisibilityExporter extends BufferingExporter {
|
|
|
657
723
|
if (isFinalFlush && !this._isInitialized &&
|
|
658
724
|
this._traceBuffer.length === 0 && this._coverageBuffer.length === 0 &&
|
|
659
725
|
this.#deferredTestSuiteSpans.size === 0 && this.#deferredTestSessionTraces.length === 0 &&
|
|
660
|
-
this.#
|
|
726
|
+
this.#pendingMediaUploads.size === 0) {
|
|
727
|
+
this._initializationRequest?.controller.abort()
|
|
661
728
|
onDone()
|
|
662
729
|
return
|
|
663
730
|
}
|
|
@@ -674,6 +741,9 @@ class CiVisibilityExporter extends BufferingExporter {
|
|
|
674
741
|
const deadline = isFinalFlush ? Date.now() + FINAL_FLUSH_TIMEOUT : undefined
|
|
675
742
|
let hasCompleted = false
|
|
676
743
|
let initializationTimeoutId
|
|
744
|
+
let mediaTimeoutId
|
|
745
|
+
let writersFlushed = false
|
|
746
|
+
let writerFlushError
|
|
677
747
|
|
|
678
748
|
const fallbackTimeoutId = isFinalFlush
|
|
679
749
|
? setTimeout(() => {
|
|
@@ -686,7 +756,8 @@ class CiVisibilityExporter extends BufferingExporter {
|
|
|
686
756
|
hasCompleted = true
|
|
687
757
|
clearTimeout(fallbackTimeoutId)
|
|
688
758
|
clearTimeout(initializationTimeoutId)
|
|
689
|
-
|
|
759
|
+
clearTimeout(mediaTimeoutId)
|
|
760
|
+
this.#mediaFlushWaiters.delete(completeAfterMedia)
|
|
690
761
|
if (error) log.error('Error flushing Test Optimization data', error)
|
|
691
762
|
if (!isFinalFlush) {
|
|
692
763
|
onDone(error)
|
|
@@ -706,15 +777,23 @@ class CiVisibilityExporter extends BufferingExporter {
|
|
|
706
777
|
}
|
|
707
778
|
}
|
|
708
779
|
|
|
709
|
-
const
|
|
710
|
-
if (
|
|
711
|
-
|
|
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)
|
|
712
789
|
return
|
|
713
790
|
}
|
|
791
|
+
complete(error)
|
|
792
|
+
}
|
|
714
793
|
|
|
794
|
+
const flushWriters = () => {
|
|
715
795
|
const options = deadline === undefined ? undefined : { deadline }
|
|
716
796
|
if (isFinalFlush) {
|
|
717
|
-
this.exportDeferredTestSuiteSpans(options)
|
|
718
797
|
this.#exportDeferredTestSessionTraces(options)
|
|
719
798
|
}
|
|
720
799
|
|
|
@@ -728,24 +807,31 @@ class CiVisibilityExporter extends BufferingExporter {
|
|
|
728
807
|
let flushError
|
|
729
808
|
|
|
730
809
|
if (remaining === 0) {
|
|
731
|
-
|
|
810
|
+
onWritersFlushed()
|
|
732
811
|
return
|
|
733
812
|
}
|
|
734
813
|
|
|
735
814
|
const onFlushComplete = (error) => {
|
|
736
815
|
flushError ||= error
|
|
737
816
|
remaining -= 1
|
|
738
|
-
if (remaining === 0)
|
|
817
|
+
if (remaining === 0) onWritersFlushed(flushError)
|
|
739
818
|
}
|
|
740
819
|
|
|
741
820
|
for (const writer of writers) writer.flush(onFlushComplete, options)
|
|
742
821
|
}
|
|
743
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
|
+
|
|
744
831
|
if (isFinalFlush && this._initializationRequest) {
|
|
745
832
|
const initializationRequest = this._initializationRequest
|
|
746
|
-
const { controller
|
|
833
|
+
const { controller } = initializationRequest
|
|
747
834
|
initializationRequest.finalFlush = finalFlush
|
|
748
|
-
options.deadline = deadline
|
|
749
835
|
initializationTimeoutId = setTimeout(() => {
|
|
750
836
|
const error = createFinalFlushTimeoutError()
|
|
751
837
|
if (initializationRequest.finalFlush === finalFlush) controller.abort(error)
|
|
@@ -853,25 +939,104 @@ class CiVisibilityExporter extends BufferingExporter {
|
|
|
853
939
|
return Boolean(this._testScreenshotUploadUrl) && this._isTestFailureScreenshotsEnabled
|
|
854
940
|
}
|
|
855
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
|
+
|
|
856
951
|
/**
|
|
857
952
|
* Uploads a single test screenshot to the Test Optimization media intake.
|
|
858
953
|
*
|
|
859
954
|
* @param {object} options - Upload options
|
|
860
|
-
* @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
|
|
861
957
|
* @param {string} options.traceId - Test trace id used as the screenshot key
|
|
862
958
|
* @param {string} options.idempotencyKey - Stable per-artifact key, reused on retry
|
|
863
959
|
* @param {number} options.capturedAtMs - Capture time in epoch milliseconds
|
|
864
960
|
* @param {AbortSignal} [options.signal] - Additional signal used to cancel the upload
|
|
865
961
|
* @param {Function} callback - Callback function (err)
|
|
866
962
|
*/
|
|
867
|
-
uploadTestScreenshot ({ filePath, traceId, idempotencyKey, capturedAtMs, signal }, callback) {
|
|
963
|
+
uploadTestScreenshot ({ filePath, content, traceId, idempotencyKey, capturedAtMs, signal }, callback) {
|
|
868
964
|
if (!this._testScreenshotUploadUrl) {
|
|
869
965
|
return callback(new Error('Test screenshot upload URL not configured'))
|
|
870
966
|
}
|
|
871
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
|
+
}
|
|
872
1037
|
this.#resetFinalFlush()
|
|
1038
|
+
const deadline = Date.now() + timeoutMs
|
|
873
1039
|
const controller = new AbortController()
|
|
874
|
-
const deadline = Date.now() + FINAL_FLUSH_TIMEOUT
|
|
875
1040
|
let settled = false
|
|
876
1041
|
const complete = (error) => {
|
|
877
1042
|
if (settled) return
|
|
@@ -882,10 +1047,10 @@ class CiVisibilityExporter extends BufferingExporter {
|
|
|
882
1047
|
try {
|
|
883
1048
|
callback(error)
|
|
884
1049
|
} finally {
|
|
885
|
-
this.#
|
|
886
|
-
if (this.#
|
|
887
|
-
const waiters = [...this.#
|
|
888
|
-
this.#
|
|
1050
|
+
this.#pendingMediaUploads.delete(controller)
|
|
1051
|
+
if (this.#pendingMediaUploads.size === 0) {
|
|
1052
|
+
const waiters = [...this.#mediaFlushWaiters]
|
|
1053
|
+
this.#mediaFlushWaiters.clear()
|
|
889
1054
|
for (const waiter of waiters) queueMicrotask(waiter)
|
|
890
1055
|
}
|
|
891
1056
|
}
|
|
@@ -896,25 +1061,23 @@ class CiVisibilityExporter extends BufferingExporter {
|
|
|
896
1061
|
complete(error)
|
|
897
1062
|
}
|
|
898
1063
|
|
|
899
|
-
this.#pendingScreenshotUploads.add(controller)
|
|
900
|
-
signal?.addEventListener('abort', onAbort, { once: true })
|
|
901
1064
|
const timeoutId = setTimeout(() => {
|
|
902
1065
|
const error = createFinalFlushTimeoutError()
|
|
903
1066
|
controller.abort(error)
|
|
904
1067
|
complete(error)
|
|
905
|
-
},
|
|
1068
|
+
}, Math.max(0, deadline - Date.now()))
|
|
906
1069
|
timeoutId.unref?.()
|
|
907
1070
|
|
|
1071
|
+
this.#pendingMediaUploads.add(controller)
|
|
1072
|
+
signal?.addEventListener('abort', onAbort, { once: true })
|
|
1073
|
+
|
|
908
1074
|
if (signal?.aborted) {
|
|
909
1075
|
onAbort()
|
|
910
1076
|
return
|
|
911
1077
|
}
|
|
912
1078
|
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
traceId,
|
|
916
|
-
idempotencyKey,
|
|
917
|
-
capturedAtMs,
|
|
1079
|
+
uploadRequest({
|
|
1080
|
+
...uploadOptions,
|
|
918
1081
|
url: this._testScreenshotUploadUrl,
|
|
919
1082
|
isEvpProxy: !!this._isUsingEvpProxy,
|
|
920
1083
|
evpProxyPrefix: this.evpProxyPrefix,
|