dd-trace 6.7.0 → 6.9.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/ci/diagnose.js +27 -25
- package/ci/init.js +6 -6
- package/ci/runbook.md +114 -20
- package/ci/test-optimization-validation/approval-artifacts.js +36 -3
- package/ci/test-optimization-validation/approval.js +78 -22
- package/ci/test-optimization-validation/blocker-category.js +24 -0
- package/ci/test-optimization-validation/ci-discovery.js +23 -17
- package/ci/test-optimization-validation/ci-package-scripts.js +34 -6
- package/ci/test-optimization-validation/ci-remediation.js +1 -1
- package/ci/test-optimization-validation/cli.js +280 -52
- package/ci/test-optimization-validation/command-blocker.js +159 -20
- package/ci/test-optimization-validation/command-output-policy.js +14 -8
- package/ci/test-optimization-validation/command-runner.js +9 -7
- package/ci/test-optimization-validation/environment.js +4 -2
- package/ci/test-optimization-validation/executable.js +15 -13
- package/ci/test-optimization-validation/execution-lock.js +70 -0
- package/ci/test-optimization-validation/framework-adapters/cucumber-validation.json +3 -0
- package/ci/test-optimization-validation/framework-adapters/cucumber.js +657 -5
- package/ci/test-optimization-validation/framework-adapters/playwright.js +5 -5
- package/ci/test-optimization-validation/framework-adapters/vitest.js +517 -0
- package/ci/test-optimization-validation/generated-files.js +47 -37
- package/ci/test-optimization-validation/generated-test-contract.js +9 -7
- package/ci/test-optimization-validation/generated-verifier.js +24 -17
- package/ci/test-optimization-validation/literal-glob.js +52 -0
- package/ci/test-optimization-validation/manifest-loader.js +3 -0
- package/ci/test-optimization-validation/manifest-scaffold.js +301 -52
- package/ci/test-optimization-validation/manifest-schema.js +80 -17
- package/ci/test-optimization-validation/offline-fixtures.js +13 -4
- package/ci/test-optimization-validation/offline-output.js +6 -6
- package/ci/test-optimization-validation/package-check.js +94 -0
- package/ci/test-optimization-validation/plan-writer.js +183 -10
- package/ci/test-optimization-validation/preflight-runner.js +110 -31
- package/ci/test-optimization-validation/project-build-artifact.js +31 -0
- package/ci/test-optimization-validation/redaction.js +18 -27
- package/ci/test-optimization-validation/report-writer.js +182 -48
- package/ci/test-optimization-validation/result-semantics.js +20 -3
- package/ci/test-optimization-validation/runner-command.js +41 -21
- package/ci/test-optimization-validation/runner-contract.js +79 -43
- package/ci/test-optimization-validation/safe-files.js +4 -4
- package/ci/test-optimization-validation/scenarios/basic-reporting.js +60 -6
- package/ci/test-optimization-validation/scenarios/ci-wiring.js +141 -35
- package/ci/test-optimization-validation/scenarios/helpers.js +30 -6
- package/ci/test-optimization-validation/scenarios/test-management.js +2 -2
- package/ci/test-optimization-validation/static-diagnosis.js +10 -3
- package/ci/test-optimization-validation/test-output.js +2 -1
- package/ci/test-optimization-validation/validation-blocker.js +21 -0
- package/ci/vitest-no-worker-init-setup.mjs +377 -84
- package/index.d.ts +175 -14
- package/init.js +1 -17
- package/initialize.mjs +11 -0
- package/loader-hook.mjs +6 -4
- package/package.json +12 -4
- package/packages/datadog-core/src/utils/src/kebabcase.js +8 -2
- package/packages/datadog-core/src/utils/src/parse-tags.js +0 -1
- package/packages/datadog-esbuild/index.js +4 -2
- package/packages/datadog-esbuild/src/utils.js +17 -2
- package/packages/datadog-instrumentations/src/ai.js +61 -34
- package/packages/datadog-instrumentations/src/bluebird.js +6 -6
- package/packages/datadog-instrumentations/src/claude-agent-sdk.js +85 -9
- package/packages/datadog-instrumentations/src/cucumber.js +131 -51
- package/packages/datadog-instrumentations/src/dns.js +21 -2
- package/packages/datadog-instrumentations/src/electron.js +1 -0
- package/packages/datadog-instrumentations/src/fastify.js +3 -1
- package/packages/datadog-instrumentations/src/grpc/client.js +4 -2
- package/packages/datadog-instrumentations/src/helpers/callback-instrumentor.js +3 -1
- package/packages/datadog-instrumentations/src/helpers/extract-package-and-module-path.js +10 -0
- package/packages/datadog-instrumentations/src/helpers/hook.js +19 -2
- package/packages/datadog-instrumentations/src/helpers/hooks.js +4 -0
- package/packages/datadog-instrumentations/src/helpers/register.js +9 -3
- package/packages/datadog-instrumentations/src/helpers/rewriter/index.js +34 -9
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/index.js +1 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/webdriverio.js +175 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/loader.mjs +20 -2
- package/packages/datadog-instrumentations/src/helpers/rewriter/targets.js +32 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/targets.json +61 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/transforms.js +50 -18
- package/packages/datadog-instrumentations/src/helpers/router-helper.js +38 -7
- package/packages/datadog-instrumentations/src/helpers/shared-utils.js +39 -0
- package/packages/datadog-instrumentations/src/http/client.js +1 -3
- package/packages/datadog-instrumentations/src/jest/coverage-backfill.js +1 -1
- package/packages/datadog-instrumentations/src/jest.js +986 -246
- package/packages/datadog-instrumentations/src/mocha/common.js +1 -3
- package/packages/datadog-instrumentations/src/mocha/main.js +109 -51
- package/packages/datadog-instrumentations/src/mocha/utils.js +120 -32
- package/packages/datadog-instrumentations/src/mocha/webdriverio-protocol.js +44 -0
- package/packages/datadog-instrumentations/src/mocha/worker.js +441 -43
- package/packages/datadog-instrumentations/src/moleculer/client.js +1 -1
- package/packages/datadog-instrumentations/src/mysql.js +1 -1
- package/packages/datadog-instrumentations/src/next.js +155 -23
- package/packages/datadog-instrumentations/src/openai.js +14 -0
- package/packages/datadog-instrumentations/src/pg.js +180 -6
- package/packages/datadog-instrumentations/src/playwright.js +176 -81
- package/packages/datadog-instrumentations/src/promise-js.js +4 -4
- package/packages/datadog-instrumentations/src/promise.js +3 -3
- package/packages/datadog-instrumentations/src/rhea.js +1 -1
- package/packages/datadog-instrumentations/src/router.js +195 -19
- package/packages/datadog-instrumentations/src/vitest-main-no-worker-init.js +567 -65
- package/packages/datadog-instrumentations/src/vitest-main.js +438 -54
- package/packages/datadog-instrumentations/src/vitest-util.js +42 -7
- package/packages/datadog-instrumentations/src/vitest-worker.js +234 -17
- package/packages/datadog-instrumentations/src/webdriverio.js +1090 -0
- package/packages/datadog-instrumentations/src/when.js +3 -3
- package/packages/datadog-instrumentations/src/ws.js +5 -1
- package/packages/datadog-plugin-aerospike/src/index.js +1 -3
- package/packages/datadog-plugin-amqplib/src/client.js +1 -1
- package/packages/datadog-plugin-amqplib/src/producer.js +1 -1
- package/packages/datadog-plugin-aws-sdk/src/services/bedrockruntime/utils.js +59 -48
- package/packages/datadog-plugin-aws-sdk/src/services/eventbridge.js +2 -2
- package/packages/datadog-plugin-aws-sdk/src/services/stepfunctions.js +2 -2
- package/packages/datadog-plugin-azure-event-hubs/src/producer.js +1 -3
- package/packages/datadog-plugin-azure-service-bus/src/producer.js +5 -3
- package/packages/datadog-plugin-bullmq/src/producer.js +3 -3
- package/packages/datadog-plugin-child_process/src/index.js +3 -3
- package/packages/datadog-plugin-child_process/src/scrub-cmd-params.js +6 -11
- package/packages/datadog-plugin-cucumber/src/index.js +5 -1
- package/packages/datadog-plugin-cypress/src/cypress-plugin.js +50 -46
- package/packages/datadog-plugin-cypress/src/source-map-utils.js +1 -1
- package/packages/datadog-plugin-cypress/src/support.js +7 -4
- package/packages/datadog-plugin-electron/src/net.js +10 -4
- package/packages/datadog-plugin-fastify/src/tracing.js +1 -1
- package/packages/datadog-plugin-fs/src/index.js +0 -4
- package/packages/datadog-plugin-google-cloud-pubsub/src/consumer.js +14 -7
- package/packages/datadog-plugin-google-cloud-pubsub/src/producer.js +19 -10
- package/packages/datadog-plugin-graphql/src/execute.js +1 -0
- package/packages/datadog-plugin-graphql/src/tools/signature.js +1 -1
- package/packages/datadog-plugin-http/src/client.js +3 -2
- package/packages/datadog-plugin-http2/src/client.js +1 -1
- package/packages/datadog-plugin-jest/src/index.js +3 -21
- package/packages/datadog-plugin-jest/src/util.js +4 -1
- package/packages/datadog-plugin-kafkajs/src/producer.js +2 -1
- package/packages/datadog-plugin-langchain/src/tokens.js +2 -2
- package/packages/datadog-plugin-mocha/src/index.js +342 -6
- package/packages/datadog-plugin-next/src/index.js +57 -9
- package/packages/datadog-plugin-openai/src/tracing.js +4 -0
- package/packages/datadog-plugin-pg/src/index.js +65 -1
- package/packages/datadog-plugin-playwright/src/index.js +1 -1
- package/packages/datadog-plugin-prisma/src/index.js +5 -1
- package/packages/datadog-plugin-rhea/src/consumer.js +16 -13
- package/packages/datadog-plugin-rhea/src/producer.js +1 -1
- package/packages/datadog-plugin-undici/src/index.js +2 -1
- package/packages/datadog-plugin-vitest/src/index.js +179 -18
- package/packages/datadog-shimmer/src/shimmer.js +6 -2
- package/packages/datadog-webpack/index.js +2 -2
- package/packages/dd-trace/src/aiguard/sdk.js +3 -0
- package/packages/dd-trace/src/appsec/{blocking.js → blocking/index.js} +26 -6
- package/packages/dd-trace/src/appsec/downstream_requests.js +1 -1
- package/packages/dd-trace/src/appsec/handlers/auth.js +62 -0
- package/packages/dd-trace/src/appsec/handlers/http-request.js +215 -0
- package/packages/dd-trace/src/appsec/handlers/http-response.js +78 -0
- package/packages/dd-trace/src/appsec/handlers/http-shared.js +24 -0
- package/packages/dd-trace/src/appsec/handlers/payments.js +104 -0
- package/packages/dd-trace/src/appsec/iast/analyzers/hardcoded-base-analyzer.js +1 -1
- package/packages/dd-trace/src/appsec/iast/analyzers/hardcoded-password-analyzer.js +1 -1
- package/packages/dd-trace/src/appsec/iast/analyzers/hsts-header-missing-analyzer.js +1 -1
- package/packages/dd-trace/src/appsec/iast/analyzers/sql-injection-analyzer.js +1 -1
- package/packages/dd-trace/src/appsec/iast/taint-tracking/taint-tracking-impl.js +1 -3
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/utils.js +2 -2
- package/packages/dd-trace/src/appsec/iast/vulnerability-reporter.js +2 -2
- package/packages/dd-trace/src/appsec/index.js +48 -476
- package/packages/dd-trace/src/appsec/sdk/track_event.js +10 -1
- package/packages/dd-trace/src/appsec/waf/waf_manager.js +1 -1
- package/packages/dd-trace/src/ci-visibility/coverage-report-discovery.js +46 -14
- package/packages/dd-trace/src/ci-visibility/efd-retry-policy.js +89 -0
- package/packages/dd-trace/src/ci-visibility/exporters/ci-validation/sink.js +7 -6
- package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +29 -15
- package/packages/dd-trace/src/ci-visibility/exporters/git/git_metadata.js +19 -1
- package/packages/dd-trace/src/ci-visibility/exporters/test-worker/index.js +25 -1
- package/packages/dd-trace/src/ci-visibility/exporters/test-worker/webdriverio.js +26 -0
- package/packages/dd-trace/src/ci-visibility/exporters/test-worker/writer.js +9 -1
- package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +22 -7
- package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +9 -14
- package/packages/dd-trace/src/ci-visibility/requests/request.js +3 -3
- package/packages/dd-trace/src/ci-visibility/requests/upload-coverage-report.js +21 -5
- package/packages/dd-trace/src/ci-visibility/test-optimization-http-cache-schema.js +2 -2
- package/packages/dd-trace/src/ci-visibility/test-optimization-http-cache.js +5 -1
- package/packages/dd-trace/src/config/defaults.js +14 -6
- package/packages/dd-trace/src/config/generated-config-types.d.ts +4 -2
- package/packages/dd-trace/src/config/helper.js +2 -0
- package/packages/dd-trace/src/config/index.js +31 -5
- package/packages/dd-trace/src/config/parsers.js +19 -1
- package/packages/dd-trace/src/config/remote_config.js +1 -1
- package/packages/dd-trace/src/config/supported-configurations.json +13 -6
- package/packages/dd-trace/src/datastreams/encoding.js +3 -4
- package/packages/dd-trace/src/debugger/devtools_client/log.js +2 -1
- package/packages/dd-trace/src/debugger/devtools_client/probe_sampler.js +1 -4
- package/packages/dd-trace/src/debugger/devtools_client/send.js +1 -2
- package/packages/dd-trace/src/debugger/devtools_client/snapshot/index.js +2 -2
- package/packages/dd-trace/src/debugger/devtools_client/snapshot/redaction.js +16 -2
- package/packages/dd-trace/src/debugger/devtools_client/status.js +1 -1
- package/packages/dd-trace/src/encode/coverage-ci-visibility.js +0 -1
- package/packages/dd-trace/src/encode/tags-processors.js +2 -2
- package/packages/dd-trace/src/exporters/common/url.js +3 -5
- package/packages/dd-trace/src/exporters/electron/index.js +4 -1
- package/packages/dd-trace/src/external-logger/src/index.js +1 -2
- package/packages/dd-trace/src/flare/index.js +2 -2
- package/packages/dd-trace/src/guardrails/apply-pm2-env.js +16 -0
- package/packages/dd-trace/src/guardrails/log.js +1 -10
- package/packages/dd-trace/src/id.js +1 -1
- package/packages/dd-trace/src/llmobs/constants/tags.js +1 -1
- package/packages/dd-trace/src/llmobs/eval-metric.js +249 -0
- package/packages/dd-trace/src/llmobs/experiments/client.js +4 -2
- package/packages/dd-trace/src/llmobs/experiments/dataset.js +66 -14
- package/packages/dd-trace/src/llmobs/experiments/experiment.js +289 -95
- package/packages/dd-trace/src/llmobs/experiments/index.js +66 -19
- package/packages/dd-trace/src/llmobs/experiments/noop.js +95 -10
- package/packages/dd-trace/src/llmobs/experiments/result.js +13 -2
- package/packages/dd-trace/src/llmobs/experiments/util.js +220 -0
- package/packages/dd-trace/src/llmobs/noop.js +2 -0
- package/packages/dd-trace/src/llmobs/plugins/ai/ddTelemetry.js +1 -1
- package/packages/dd-trace/src/llmobs/plugins/ai/util.js +1 -1
- package/packages/dd-trace/src/llmobs/plugins/anthropic/index.js +1 -1
- package/packages/dd-trace/src/llmobs/plugins/bedrockruntime.js +5 -5
- package/packages/dd-trace/src/llmobs/plugins/claude-agent-sdk/index.js +1 -1
- package/packages/dd-trace/src/llmobs/plugins/genai/index.js +1 -1
- package/packages/dd-trace/src/llmobs/plugins/langchain/handlers/index.js +1 -1
- package/packages/dd-trace/src/llmobs/plugins/langchain/index.js +1 -1
- package/packages/dd-trace/src/llmobs/sdk.js +152 -78
- package/packages/dd-trace/src/llmobs/span_processor.js +9 -7
- package/packages/dd-trace/src/llmobs/tagger.js +1 -2
- package/packages/dd-trace/src/llmobs/telemetry.js +18 -0
- package/packages/dd-trace/src/llmobs/writers/base.js +3 -0
- package/packages/dd-trace/src/llmobs/writers/spans.js +12 -6
- package/packages/dd-trace/src/llmobs/writers/util.js +2 -0
- package/packages/dd-trace/src/log/channels.js +1 -9
- package/packages/dd-trace/src/log/levels.js +12 -0
- package/packages/dd-trace/src/noop/proxy.js +2 -2
- package/packages/dd-trace/src/openfeature/writers/exposures.js +1 -1
- package/packages/dd-trace/src/openfeature/writers/util.js +1 -1
- package/packages/dd-trace/src/opentelemetry/logs/batch_log_processor.js +1 -1
- package/packages/dd-trace/src/opentelemetry/metrics/meter.js +8 -4
- package/packages/dd-trace/src/opentelemetry/metrics/otlp_transformer.js +1 -1
- package/packages/dd-trace/src/opentelemetry/metrics/periodic_metric_reader.js +3 -2
- package/packages/dd-trace/src/opentelemetry/span.js +4 -0
- package/packages/dd-trace/src/opentracing/span.js +1 -1
- package/packages/dd-trace/src/opentracing/span_context.js +1 -1
- package/packages/dd-trace/src/plugins/ci_plugin.js +124 -23
- package/packages/dd-trace/src/plugins/index.js +2 -0
- package/packages/dd-trace/src/plugins/plugin.js +1 -1
- package/packages/dd-trace/src/plugins/tracing.js +1 -0
- package/packages/dd-trace/src/plugins/util/ci.js +13 -6
- package/packages/dd-trace/src/plugins/util/git.js +7 -5
- package/packages/dd-trace/src/plugins/util/http-otel-semantics.js +3 -3
- package/packages/dd-trace/src/plugins/util/ip_extractor.js +1 -1
- package/packages/dd-trace/src/plugins/util/llm.js +2 -2
- package/packages/dd-trace/src/plugins/util/stacktrace.js +3 -0
- package/packages/dd-trace/src/plugins/util/tags.js +2 -0
- package/packages/dd-trace/src/plugins/util/test.js +356 -86
- package/packages/dd-trace/src/plugins/util/url.js +5 -5
- package/packages/dd-trace/src/plugins/util/user-provided-git.js +1 -1
- package/packages/dd-trace/src/plugins/util/web.js +24 -2
- package/packages/dd-trace/src/priority_sampler.js +6 -3
- package/packages/dd-trace/src/profiler.js +1 -1
- package/packages/dd-trace/src/profiling/exporters/event_serializer.js +1 -1
- package/packages/dd-trace/src/profiling/profilers/events.js +2 -2
- package/packages/dd-trace/src/profiling/profilers/poisson.js +1 -1
- package/packages/dd-trace/src/profiling/profilers/shared.js +2 -2
- package/packages/dd-trace/src/proxy.js +1 -0
- package/packages/dd-trace/src/ritm.js +10 -20
- package/packages/dd-trace/src/runtime_metrics/runtime_metrics.js +4 -2
- package/packages/dd-trace/src/sampling_rule.js +4 -2
- package/packages/dd-trace/src/service-naming/schemas/v0/storage.js +1 -1
- package/packages/dd-trace/src/service-naming/schemas/v1/storage.js +1 -1
- package/packages/dd-trace/src/startup-log.js +3 -0
- package/packages/dd-trace/src/telemetry/logs/index.js +3 -0
- package/packages/dd-trace/src/telemetry/logs/log-collector.js +3 -1
- package/packages/dd-trace/src/telemetry/telemetry.js +1 -1
- package/packages/dd-trace/src/util.js +16 -0
- package/vendor/dist/@apm-js-collab/code-transformer/index.js +12 -12
- package/vendor/dist/pprof-format/index.js +1 -1
- package/vendor/dist/source-map/index.js +1 -1
- package/version.js +10 -8
- /package/packages/dd-trace/src/appsec/{blocked_templates.js → blocking/templates.js} +0 -0
|
@@ -5,6 +5,8 @@ const path = require('node:path')
|
|
|
5
5
|
|
|
6
6
|
const log = require('../log')
|
|
7
7
|
|
|
8
|
+
const BIGINT_LSTAT_OPTIONS = { bigint: true, throwIfNoEntry: false }
|
|
9
|
+
|
|
8
10
|
/**
|
|
9
11
|
* Coverage report file patterns to search for
|
|
10
12
|
* Each entry contains the relative path from root and the format identifier
|
|
@@ -38,7 +40,7 @@ const COVERAGE_REPORT_PATTERNS = [
|
|
|
38
40
|
/**
|
|
39
41
|
* Discovers code coverage report files in the given root directory
|
|
40
42
|
* @param {string} rootDir - The root directory to search for coverage reports
|
|
41
|
-
* @returns {Array<{filePath: string, format: string}>}
|
|
43
|
+
* @returns {Array<{filePath: string, fileDevice: bigint, fileInode: bigint, format: string}>}
|
|
42
44
|
*/
|
|
43
45
|
function discoverCoverageReports (rootDir) {
|
|
44
46
|
if (!rootDir) {
|
|
@@ -46,27 +48,57 @@ function discoverCoverageReports (rootDir) {
|
|
|
46
48
|
return []
|
|
47
49
|
}
|
|
48
50
|
|
|
51
|
+
let resolvedRoot
|
|
52
|
+
try {
|
|
53
|
+
const unresolvedRoot = path.resolve(rootDir)
|
|
54
|
+
const rootStats = fs.lstatSync(unresolvedRoot, BIGINT_LSTAT_OPTIONS)
|
|
55
|
+
if (rootStats?.isSymbolicLink() || !rootStats?.isDirectory()) {
|
|
56
|
+
log.debug('Coverage report root is not a regular directory: %s', unresolvedRoot)
|
|
57
|
+
return []
|
|
58
|
+
}
|
|
59
|
+
resolvedRoot = fs.realpathSync(unresolvedRoot)
|
|
60
|
+
const resolvedRootStats = fs.lstatSync(resolvedRoot, BIGINT_LSTAT_OPTIONS)
|
|
61
|
+
if (!resolvedRootStats || resolvedRootStats.dev !== rootStats.dev || resolvedRootStats.ino !== rootStats.ino) {
|
|
62
|
+
log.debug('Coverage report root changed while resolving it: %s', unresolvedRoot)
|
|
63
|
+
return []
|
|
64
|
+
}
|
|
65
|
+
} catch (error) {
|
|
66
|
+
log.debug('Error checking coverage report root %s: %s', rootDir, error.message)
|
|
67
|
+
return []
|
|
68
|
+
}
|
|
69
|
+
|
|
49
70
|
const discoveredReports = []
|
|
50
71
|
|
|
51
72
|
for (const pattern of COVERAGE_REPORT_PATTERNS) {
|
|
52
|
-
|
|
73
|
+
let currentPath = resolvedRoot
|
|
53
74
|
|
|
54
75
|
try {
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
if (stats.
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
76
|
+
let stats
|
|
77
|
+
for (const pathSegment of pattern.path.split('/')) {
|
|
78
|
+
currentPath = path.join(currentPath, pathSegment)
|
|
79
|
+
stats = fs.lstatSync(currentPath, BIGINT_LSTAT_OPTIONS)
|
|
80
|
+
if (!stats || stats.isSymbolicLink()) break
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// A hard link can name a file outside the root without changing its real path.
|
|
84
|
+
if (stats?.isFile() && stats.nlink === 1n) {
|
|
85
|
+
const resolvedPath = fs.realpathSync(currentPath)
|
|
86
|
+
const pathFromRoot = path.relative(resolvedRoot, resolvedPath)
|
|
87
|
+
if (pathFromRoot === '..' || pathFromRoot.startsWith(`..${path.sep}`) || path.isAbsolute(pathFromRoot)) {
|
|
88
|
+
log.debug('Coverage report resolved outside the root directory: %s', currentPath)
|
|
89
|
+
continue
|
|
65
90
|
}
|
|
91
|
+
discoveredReports.push({
|
|
92
|
+
filePath: resolvedPath,
|
|
93
|
+
fileDevice: stats.dev,
|
|
94
|
+
fileInode: stats.ino,
|
|
95
|
+
format: pattern.format,
|
|
96
|
+
})
|
|
97
|
+
log.debug('Found coverage report: %s (format: %s)', resolvedPath, pattern.format)
|
|
66
98
|
}
|
|
67
|
-
} catch (
|
|
99
|
+
} catch (error) {
|
|
68
100
|
// Log but don't fail if we can't access a file
|
|
69
|
-
log.debug('Error checking coverage report path %s: %s',
|
|
101
|
+
log.debug('Error checking coverage report path %s: %s', currentPath, error.message)
|
|
70
102
|
}
|
|
71
103
|
}
|
|
72
104
|
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const EARLY_FLAKE_DETECTION_RETRY_THRESHOLDS = [
|
|
4
|
+
{ limitMs: 5000, key: '5s' },
|
|
5
|
+
{ limitMs: 10_000, key: '10s' },
|
|
6
|
+
{ limitMs: 30_000, key: '30s' },
|
|
7
|
+
{ limitMs: 300_000, key: '5m' },
|
|
8
|
+
]
|
|
9
|
+
|
|
10
|
+
// A bucket the settings validator accepts but this module ignores silently loses its retries.
|
|
11
|
+
const EARLY_FLAKE_DETECTION_RETRY_BUCKETS =
|
|
12
|
+
Object.freeze(EARLY_FLAKE_DETECTION_RETRY_THRESHOLDS.map(({ key }) => key))
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @typedef {object} EfdDurationRetryCount
|
|
16
|
+
* @property {number} durationLimitMs
|
|
17
|
+
* @property {number} retryCount
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* @typedef {object} EfdRetryPolicy
|
|
22
|
+
* @property {readonly EfdDurationRetryCount[]} durationRetryCounts
|
|
23
|
+
* @property {number} schedulingRetryCount
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* @param {number} durationMs
|
|
28
|
+
* @param {EfdRetryPolicy} retryPolicy
|
|
29
|
+
* @returns {number}
|
|
30
|
+
*/
|
|
31
|
+
function getEfdRetryCountForDuration (durationMs, retryPolicy) {
|
|
32
|
+
for (const { durationLimitMs, retryCount } of retryPolicy.durationRetryCounts) {
|
|
33
|
+
if (durationMs < durationLimitMs) {
|
|
34
|
+
return retryCount
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return 0
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* @param {Record<string, unknown> | undefined} retriesByDuration
|
|
42
|
+
* @returns {EfdRetryPolicy}
|
|
43
|
+
*/
|
|
44
|
+
function createEfdRetryPolicy (retriesByDuration = {}) {
|
|
45
|
+
const durationRetryCounts = []
|
|
46
|
+
let schedulingRetryCount = 0
|
|
47
|
+
for (const { limitMs: durationLimitMs, key } of EARLY_FLAKE_DETECTION_RETRY_THRESHOLDS) {
|
|
48
|
+
const configuredRetryCount = retriesByDuration[key]
|
|
49
|
+
const retryCount = Number.isSafeInteger(configuredRetryCount) && configuredRetryCount >= 0
|
|
50
|
+
? configuredRetryCount
|
|
51
|
+
: 0
|
|
52
|
+
durationRetryCounts.push(Object.freeze({ durationLimitMs, retryCount }))
|
|
53
|
+
if (retryCount > schedulingRetryCount) {
|
|
54
|
+
schedulingRetryCount = retryCount
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return Object.freeze({
|
|
58
|
+
durationRetryCounts: Object.freeze(durationRetryCounts),
|
|
59
|
+
schedulingRetryCount,
|
|
60
|
+
})
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* @param {EfdRetryPolicy | undefined} retryPolicy
|
|
65
|
+
* @returns {boolean}
|
|
66
|
+
*/
|
|
67
|
+
function hasEfdRetries (retryPolicy) {
|
|
68
|
+
return (retryPolicy?.schedulingRetryCount ?? 0) > 0
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* @param {number} retryIndex
|
|
73
|
+
* @param {number | undefined} retryCount
|
|
74
|
+
* @returns {boolean}
|
|
75
|
+
*/
|
|
76
|
+
function shouldSkipEfdRetry (retryIndex, retryCount) {
|
|
77
|
+
return retryCount !== undefined && retryIndex > retryCount
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const EMPTY_EFD_RETRY_POLICY = createEfdRetryPolicy()
|
|
81
|
+
|
|
82
|
+
module.exports = {
|
|
83
|
+
EARLY_FLAKE_DETECTION_RETRY_BUCKETS,
|
|
84
|
+
EMPTY_EFD_RETRY_POLICY,
|
|
85
|
+
createEfdRetryPolicy,
|
|
86
|
+
getEfdRetryCountForDuration,
|
|
87
|
+
hasEfdRetries,
|
|
88
|
+
shouldSkipEfdRetry,
|
|
89
|
+
}
|
|
@@ -303,10 +303,11 @@ function writeNewFile (filename, payload) {
|
|
|
303
303
|
*
|
|
304
304
|
* @param {string} directory directory path
|
|
305
305
|
* @param {string} label directory label
|
|
306
|
-
* @returns {{dev:
|
|
306
|
+
* @returns {{dev: bigint, ino: bigint}} stable directory identity
|
|
307
307
|
*/
|
|
308
308
|
function captureDirectory (directory, label) {
|
|
309
|
-
|
|
309
|
+
// Windows file reference numbers exceed 2^53, where distinct directories round to one number.
|
|
310
|
+
const stat = fs.lstatSync(directory, { bigint: true })
|
|
310
311
|
if (!stat.isDirectory() || stat.isSymbolicLink()) {
|
|
311
312
|
throw new Error(`Offline Test Optimization validation ${label} must be a regular directory.`)
|
|
312
313
|
}
|
|
@@ -317,10 +318,10 @@ function captureDirectory (directory, label) {
|
|
|
317
318
|
* Creates or validates one child directory without accepting symbolic links.
|
|
318
319
|
*
|
|
319
320
|
* @param {string} parent parent directory path
|
|
320
|
-
* @param {{dev:
|
|
321
|
+
* @param {{dev: bigint, ino: bigint}} parentIdentity expected parent identity
|
|
321
322
|
* @param {string} directory child directory path
|
|
322
323
|
* @param {string} label directory label
|
|
323
|
-
* @returns {{dev:
|
|
324
|
+
* @returns {{dev: bigint, ino: bigint}} stable child identity
|
|
324
325
|
*/
|
|
325
326
|
function createDirectory (parent, parentIdentity, directory, label) {
|
|
326
327
|
assertDirectoryUnchanged(parent, parentIdentity, 'parent output')
|
|
@@ -336,7 +337,7 @@ function createDirectory (parent, parentIdentity, directory, label) {
|
|
|
336
337
|
* Rejects a directory that changed after sink construction.
|
|
337
338
|
*
|
|
338
339
|
* @param {string} directory directory path
|
|
339
|
-
* @param {{dev:
|
|
340
|
+
* @param {{dev: bigint, ino: bigint}} identity expected directory identity
|
|
340
341
|
* @param {string} label directory label
|
|
341
342
|
*/
|
|
342
343
|
function assertDirectoryUnchanged (directory, identity, label) {
|
|
@@ -351,7 +352,7 @@ function assertDirectoryUnchanged (directory, identity, label) {
|
|
|
351
352
|
*
|
|
352
353
|
* @param {string} filename partial payload path
|
|
353
354
|
* @param {string} directory expected parent directory
|
|
354
|
-
* @param {{dev:
|
|
355
|
+
* @param {{dev: bigint, ino: bigint}} identity expected parent identity
|
|
355
356
|
*/
|
|
356
357
|
function removePartialFile (filename, directory, identity) {
|
|
357
358
|
try {
|
|
@@ -4,6 +4,7 @@ const { hostname: getHostname } = require('node:os')
|
|
|
4
4
|
const URL = require('url').URL
|
|
5
5
|
|
|
6
6
|
const { version: tracerVersion } = require('../../../../../package.json')
|
|
7
|
+
const { EMPTY_EFD_RETRY_POLICY, createEfdRetryPolicy } = require('../efd-retry-policy')
|
|
7
8
|
const { getLibraryConfiguration: getLibraryConfigurationRequest } = require('../requests/get-library-configuration')
|
|
8
9
|
const { getSkippableSuites: getSkippableSuitesRequest } = require('../intelligent-test-runner/get-skippable-suites')
|
|
9
10
|
const { getKnownTests: getKnownTestsRequest } = require('../early-flake-detection/get-known-tests')
|
|
@@ -15,6 +16,7 @@ const { uploadCoverageReport: uploadCoverageReportRequest } = require('../reques
|
|
|
15
16
|
const { uploadTestScreenshot: uploadTestScreenshotRequest } = require('../requests/upload-test-screenshot')
|
|
16
17
|
const { parsers } = require('../../config/parsers')
|
|
17
18
|
const log = require('../../log')
|
|
19
|
+
const { getSegment } = require('../../util')
|
|
18
20
|
const BufferingExporter = require('../../exporters/common/buffering-exporter')
|
|
19
21
|
const { GIT_REPOSITORY_URL, GIT_COMMIT_SHA } = require('../../plugins/util/tags')
|
|
20
22
|
const { sendGitMetadata: sendGitMetadataRequest } = require('./git/git_metadata')
|
|
@@ -22,14 +24,25 @@ const { sendGitMetadata: sendGitMetadataRequest } = require('./git/git_metadata'
|
|
|
22
24
|
const hostname = getHostname()
|
|
23
25
|
const EMPTY_SETTINGS = Object.freeze({})
|
|
24
26
|
|
|
27
|
+
/**
|
|
28
|
+
* Test session identity sent with every request. Fields are optional because the CI provider,
|
|
29
|
+
* the git repository or the runtime may not expose them.
|
|
30
|
+
*
|
|
31
|
+
* @typedef {{
|
|
32
|
+
* repositoryUrl?: string, sha?: string, branch?: string, tag?: string, testLevel?: string,
|
|
33
|
+
* osVersion?: string, osPlatform?: string, osArchitecture?: string,
|
|
34
|
+
* runtimeName?: string, runtimeVersion?: string, commitMessage?: string,
|
|
35
|
+
* pullRequestBaseSha?: string, commitHeadSha?: string, commitHeadMessage?: string,
|
|
36
|
+
* }} TestConfiguration
|
|
37
|
+
*/
|
|
38
|
+
|
|
25
39
|
function getTestConfigurationTags (tags) {
|
|
26
40
|
if (!tags) {
|
|
27
41
|
return {}
|
|
28
42
|
}
|
|
29
43
|
return Object.keys(tags).reduce((acc, key) => {
|
|
30
44
|
if (key.startsWith('test.configuration.')) {
|
|
31
|
-
|
|
32
|
-
acc[configKey] = tags[key]
|
|
45
|
+
acc[getSegment(key, 'test.configuration.', 1)] = tags[key]
|
|
33
46
|
}
|
|
34
47
|
return acc
|
|
35
48
|
}, {})
|
|
@@ -190,6 +203,7 @@ class CiVisibilityExporter extends BufferingExporter {
|
|
|
190
203
|
const cachedSkippableSuites = this._testOptimizationHttpCache.readSkippableSuites({
|
|
191
204
|
testLevel: requestConfiguration.testLevel,
|
|
192
205
|
isCoverageReportUploadEnabled: requestConfiguration.isCoverageReportUploadEnabled,
|
|
206
|
+
isLineCoverageSupported: requestConfiguration.isLineCoverageSupported,
|
|
193
207
|
})
|
|
194
208
|
if (cachedSkippableSuites !== CACHE_MISS) {
|
|
195
209
|
const { skippableSuites, correlationId, coverage } = cachedSkippableSuites
|
|
@@ -238,6 +252,10 @@ class CiVisibilityExporter extends BufferingExporter {
|
|
|
238
252
|
/**
|
|
239
253
|
* We can't request library configuration until we know whether we can use the
|
|
240
254
|
* CI Visibility Protocol, hence the this._canUseCiVisProtocol promise.
|
|
255
|
+
*
|
|
256
|
+
* @param {TestConfiguration} testConfiguration
|
|
257
|
+
* @param {(error: Error | null, libraryConfig?: Readonly<Record<string, unknown>>) => void} callback
|
|
258
|
+
* @returns {void}
|
|
241
259
|
*/
|
|
242
260
|
getLibraryConfiguration (testConfiguration, callback) {
|
|
243
261
|
const { repositoryUrl } = testConfiguration
|
|
@@ -321,21 +339,14 @@ class CiVisibilityExporter extends BufferingExporter {
|
|
|
321
339
|
DD_TEST_MANAGEMENT_ATTEMPT_TO_FIX_RETRIES: configuredAttemptToFixRetries = 0,
|
|
322
340
|
DD_TEST_MANAGEMENT_ENABLED: isTestManagementAllowed,
|
|
323
341
|
} = testOptimization
|
|
324
|
-
const
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
? Object.freeze({
|
|
342
|
+
const earlyFlakeDetectionRetryPolicy = earlyFlakeDetectionRetryCount === undefined
|
|
343
|
+
? remoteConfiguration.earlyFlakeDetectionRetryPolicy ?? EMPTY_EFD_RETRY_POLICY
|
|
344
|
+
: createEfdRetryPolicy({
|
|
328
345
|
'5s': earlyFlakeDetectionRetryCount,
|
|
329
346
|
'10s': earlyFlakeDetectionRetryCount,
|
|
330
347
|
'30s': earlyFlakeDetectionRetryCount,
|
|
331
348
|
'5m': earlyFlakeDetectionRetryCount,
|
|
332
349
|
})
|
|
333
|
-
: Object.isFrozen(configuredSlowTestRetries)
|
|
334
|
-
? configuredSlowTestRetries
|
|
335
|
-
: Object.freeze({ ...configuredSlowTestRetries })
|
|
336
|
-
const earlyFlakeDetectionNumRetries = hasEarlyFlakeDetectionRetryCount
|
|
337
|
-
? earlyFlakeDetectionRetryCount
|
|
338
|
-
: remoteConfiguration.earlyFlakeDetectionNumRetries ?? 0
|
|
339
350
|
const testManagementAttemptToFixRetries =
|
|
340
351
|
remoteConfiguration.testManagementAttemptToFixRetries ?? configuredAttemptToFixRetries
|
|
341
352
|
|
|
@@ -346,8 +357,7 @@ class CiVisibilityExporter extends BufferingExporter {
|
|
|
346
357
|
requireGit: remoteConfiguration.requireGit === true,
|
|
347
358
|
isEarlyFlakeDetectionEnabled:
|
|
348
359
|
remoteConfiguration.isEarlyFlakeDetectionEnabled === true && isEarlyFlakeDetectionAllowed === true,
|
|
349
|
-
|
|
350
|
-
earlyFlakeDetectionSlowTestRetries,
|
|
360
|
+
earlyFlakeDetectionRetryPolicy,
|
|
351
361
|
earlyFlakeDetectionFaultyThreshold: remoteConfiguration.earlyFlakeDetectionFaultyThreshold ?? 30,
|
|
352
362
|
isFlakyTestRetriesEnabled:
|
|
353
363
|
remoteConfiguration.isFlakyTestRetriesEnabled === true && isFlakyTestRetriesAllowed === true,
|
|
@@ -521,17 +531,21 @@ class CiVisibilityExporter extends BufferingExporter {
|
|
|
521
531
|
* Uploads a single coverage report to the CI intake.
|
|
522
532
|
* @param {object} options - Upload options
|
|
523
533
|
* @param {string} options.filePath - Path to the coverage report file
|
|
534
|
+
* @param {bigint} options.fileDevice - Device containing the discovered report
|
|
535
|
+
* @param {bigint} options.fileInode - Inode of the discovered report
|
|
524
536
|
* @param {string} options.format - Format of the coverage report
|
|
525
537
|
* @param {object} options.testEnvironmentMetadata - Test environment metadata containing git/CI tags
|
|
526
538
|
* @param {(error: Error|null) => void} callback - Callback function
|
|
527
539
|
*/
|
|
528
|
-
uploadCoverageReport ({ filePath, format, testEnvironmentMetadata }, callback) {
|
|
540
|
+
uploadCoverageReport ({ filePath, fileDevice, fileInode, format, testEnvironmentMetadata }, callback) {
|
|
529
541
|
if (!this._codeCoverageReportUrl) {
|
|
530
542
|
return callback(new Error('Coverage report upload URL not configured'))
|
|
531
543
|
}
|
|
532
544
|
|
|
533
545
|
uploadCoverageReportRequest({
|
|
534
546
|
filePath,
|
|
547
|
+
fileDevice,
|
|
548
|
+
fileInode,
|
|
535
549
|
format,
|
|
536
550
|
flags: this._coverageReportFlags,
|
|
537
551
|
testEnvironmentMetadata,
|
|
@@ -8,6 +8,7 @@ const FormData = require('../../../exporters/common/form-data')
|
|
|
8
8
|
const request = require('../../../exporters/common/request')
|
|
9
9
|
|
|
10
10
|
const log = require('../../../log')
|
|
11
|
+
const { getSegment } = require('../../../util')
|
|
11
12
|
const {
|
|
12
13
|
getLatestCommits,
|
|
13
14
|
getRepositoryUrl,
|
|
@@ -31,6 +32,9 @@ const {
|
|
|
31
32
|
TELEMETRY_GIT_REQUESTS_OBJECT_PACKFILES_BYTES,
|
|
32
33
|
} = require('../../../ci-visibility/telemetry')
|
|
33
34
|
|
|
35
|
+
/** @typedef {{ isEvpProxy: boolean, evpProxyPrefix: string }} EvpProxyConfiguration */
|
|
36
|
+
/** @typedef {EvpProxyConfiguration & { url: URL, repositoryUrl: string }} GitUploadTarget */
|
|
37
|
+
|
|
34
38
|
const isValidSha1 = (sha) => /^[0-9a-f]{40}$/.test(sha)
|
|
35
39
|
const isValidSha256 = (sha) => /^[0-9a-f]{64}$/.test(sha)
|
|
36
40
|
|
|
@@ -61,6 +65,10 @@ function getCommonRequestOptions (url) {
|
|
|
61
65
|
* This function posts the SHAs of the commits of the last month
|
|
62
66
|
* The response are the commits for which the backend already has information
|
|
63
67
|
* This response is used to know which commits can be ignored from there on
|
|
68
|
+
*
|
|
69
|
+
* @param {GitUploadTarget & { latestCommits: string[] }} options
|
|
70
|
+
* @param {(error: Error | null, commitsToUpload?: string[]) => void} callback
|
|
71
|
+
* @returns {void}
|
|
64
72
|
*/
|
|
65
73
|
function getCommitsToUpload ({ url, repositoryUrl, latestCommits, isEvpProxy, evpProxyPrefix }, callback) {
|
|
66
74
|
const commonOptions = getCommonRequestOptions(url)
|
|
@@ -126,6 +134,10 @@ function getCommitsToUpload ({ url, repositoryUrl, latestCommits, isEvpProxy, ev
|
|
|
126
134
|
|
|
127
135
|
/**
|
|
128
136
|
* This function uploads a git packfile
|
|
137
|
+
*
|
|
138
|
+
* @param {GitUploadTarget & { packFileToUpload: string, headCommit: string }} options
|
|
139
|
+
* @param {(error: Error | null, uploadSize?: number) => void} callback
|
|
140
|
+
* @returns {void}
|
|
129
141
|
*/
|
|
130
142
|
function uploadPackFile ({ url, isEvpProxy, evpProxyPrefix, packFileToUpload, repositoryUrl, headCommit }, callback) {
|
|
131
143
|
const form = new FormData()
|
|
@@ -145,7 +157,7 @@ function uploadPackFile ({ url, isEvpProxy, evpProxyPrefix, packFileToUpload, re
|
|
|
145
157
|
try {
|
|
146
158
|
const packFileContent = fs.readFileSync(packFileToUpload)
|
|
147
159
|
// The original filename includes a random prefix, so we remove it here
|
|
148
|
-
const
|
|
160
|
+
const filename = getSegment(path.basename(packFileToUpload), '-', 1)
|
|
149
161
|
form.append('packfile', packFileContent, {
|
|
150
162
|
filename,
|
|
151
163
|
contentType: 'application/octet-stream',
|
|
@@ -244,6 +256,12 @@ function generateAndUploadPackFiles ({
|
|
|
244
256
|
|
|
245
257
|
/**
|
|
246
258
|
* This function uploads git metadata to CI Visibility's backend.
|
|
259
|
+
*
|
|
260
|
+
* @param {URL} url
|
|
261
|
+
* @param {EvpProxyConfiguration} evpProxyConfiguration
|
|
262
|
+
* @param {string | undefined} configRepositoryUrl repository URL from the configuration, if set
|
|
263
|
+
* @param {(error?: Error | null) => void} callback
|
|
264
|
+
* @returns {void}
|
|
247
265
|
*/
|
|
248
266
|
function sendGitMetadata (url, { isEvpProxy, evpProxyPrefix }, configRepositoryUrl, callback) {
|
|
249
267
|
if (!isGitAvailable()) {
|
|
@@ -5,11 +5,17 @@ const {
|
|
|
5
5
|
JEST_WORKER_TRACE_PAYLOAD_CODE,
|
|
6
6
|
JEST_WORKER_TELEMETRY_PAYLOAD_CODE,
|
|
7
7
|
CUCUMBER_WORKER_TRACE_PAYLOAD_CODE,
|
|
8
|
+
CUCUMBER_WORKER_TELEMETRY_PAYLOAD_CODE,
|
|
9
|
+
MOCHA_WORKER_LOGS_PAYLOAD_CODE,
|
|
10
|
+
MOCHA_WORKER_TELEMETRY_PAYLOAD_CODE,
|
|
8
11
|
MOCHA_WORKER_TRACE_PAYLOAD_CODE,
|
|
9
12
|
JEST_WORKER_LOGS_PAYLOAD_CODE,
|
|
13
|
+
PLAYWRIGHT_WORKER_TELEMETRY_PAYLOAD_CODE,
|
|
10
14
|
PLAYWRIGHT_WORKER_TRACE_PAYLOAD_CODE,
|
|
11
15
|
VITEST_WORKER_TRACE_PAYLOAD_CODE,
|
|
16
|
+
VITEST_WORKER_COVERAGE_PAYLOAD_CODE,
|
|
12
17
|
VITEST_WORKER_LOGS_PAYLOAD_CODE,
|
|
18
|
+
VITEST_WORKER_TELEMETRY_PAYLOAD_CODE,
|
|
13
19
|
} = require('../../../plugins/util/test')
|
|
14
20
|
const getConfig = require('../../../config')
|
|
15
21
|
const { getEnvironmentVariable } = require('../../../config/helper')
|
|
@@ -43,6 +49,9 @@ function getInterprocessCoverageCode () {
|
|
|
43
49
|
if (getEnvironmentVariable('JEST_WORKER_ID')) {
|
|
44
50
|
return JEST_WORKER_COVERAGE_PAYLOAD_CODE
|
|
45
51
|
}
|
|
52
|
+
if (getEnvironmentVariable('TINYPOOL_WORKER_ID') || getConfig().DD_VITEST_WORKER) {
|
|
53
|
+
return VITEST_WORKER_COVERAGE_PAYLOAD_CODE
|
|
54
|
+
}
|
|
46
55
|
return null
|
|
47
56
|
}
|
|
48
57
|
|
|
@@ -50,6 +59,9 @@ function getInterprocessLogsCode () {
|
|
|
50
59
|
if (getEnvironmentVariable('JEST_WORKER_ID')) {
|
|
51
60
|
return JEST_WORKER_LOGS_PAYLOAD_CODE
|
|
52
61
|
}
|
|
62
|
+
if (getEnvironmentVariable('MOCHA_WORKER_ID') === 'webdriverio') {
|
|
63
|
+
return MOCHA_WORKER_LOGS_PAYLOAD_CODE
|
|
64
|
+
}
|
|
53
65
|
if (getEnvironmentVariable('TINYPOOL_WORKER_ID')) {
|
|
54
66
|
return VITEST_WORKER_LOGS_PAYLOAD_CODE
|
|
55
67
|
}
|
|
@@ -63,6 +75,19 @@ function getInterprocessTelemetryCode () {
|
|
|
63
75
|
if (getEnvironmentVariable('JEST_WORKER_ID')) {
|
|
64
76
|
return JEST_WORKER_TELEMETRY_PAYLOAD_CODE
|
|
65
77
|
}
|
|
78
|
+
if (getEnvironmentVariable('CUCUMBER_WORKER_ID')) {
|
|
79
|
+
return CUCUMBER_WORKER_TELEMETRY_PAYLOAD_CODE
|
|
80
|
+
}
|
|
81
|
+
if (getEnvironmentVariable('MOCHA_WORKER_ID')) {
|
|
82
|
+
return MOCHA_WORKER_TELEMETRY_PAYLOAD_CODE
|
|
83
|
+
}
|
|
84
|
+
const { DD_PLAYWRIGHT_WORKER, DD_VITEST_WORKER } = getConfig()
|
|
85
|
+
if (DD_PLAYWRIGHT_WORKER) {
|
|
86
|
+
return PLAYWRIGHT_WORKER_TELEMETRY_PAYLOAD_CODE
|
|
87
|
+
}
|
|
88
|
+
if (getEnvironmentVariable('TINYPOOL_WORKER_ID') || DD_VITEST_WORKER) {
|
|
89
|
+
return VITEST_WORKER_TELEMETRY_PAYLOAD_CODE
|
|
90
|
+
}
|
|
66
91
|
return null
|
|
67
92
|
}
|
|
68
93
|
|
|
@@ -81,7 +106,6 @@ class TestWorkerCiVisibilityExporter {
|
|
|
81
106
|
this._writer = new Writer(interprocessTraceCode)
|
|
82
107
|
this._coverageWriter = new Writer(interprocessCoverageCode)
|
|
83
108
|
this._logsWriter = new Writer(interprocessLogsCode)
|
|
84
|
-
// TODO: add support for test workers other than Jest
|
|
85
109
|
if (interprocessTelemetryCode) {
|
|
86
110
|
this._telemetryWriter = new Writer(interprocessTelemetryCode)
|
|
87
111
|
this.exportTelemetry = function (telemetryEvent) {
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const WEBDRIVERIO_WORKER_ENV = '_DD_TEST_OPTIMIZATION_WEBDRIVERIO_WORKER'
|
|
4
|
+
const WEBDRIVERIO_WORKER_EVENT = 'workerEvent'
|
|
5
|
+
const WEBDRIVERIO_WORKER_ORIGIN = 'datadog'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Wraps an internal worker payload in the event WebdriverIO reserves for worker messages.
|
|
9
|
+
*
|
|
10
|
+
* @param {unknown} args
|
|
11
|
+
* @returns {{args: unknown, name: string, origin: string}}
|
|
12
|
+
*/
|
|
13
|
+
function createWebdriverioWorkerMessage (args) {
|
|
14
|
+
return {
|
|
15
|
+
origin: WEBDRIVERIO_WORKER_ORIGIN,
|
|
16
|
+
name: WEBDRIVERIO_WORKER_EVENT,
|
|
17
|
+
args,
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
module.exports = {
|
|
22
|
+
createWebdriverioWorkerMessage,
|
|
23
|
+
WEBDRIVERIO_WORKER_ENV,
|
|
24
|
+
WEBDRIVERIO_WORKER_EVENT,
|
|
25
|
+
WEBDRIVERIO_WORKER_ORIGIN,
|
|
26
|
+
}
|
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
const { JSONEncoder } = require('../../encode/json-encoder')
|
|
3
3
|
const { getEnvironmentVariable } = require('../../../config/helper')
|
|
4
4
|
const log = require('../../../log')
|
|
5
|
+
const {
|
|
6
|
+
createWebdriverioWorkerMessage,
|
|
7
|
+
WEBDRIVERIO_WORKER_ENV,
|
|
8
|
+
} = require('./webdriverio')
|
|
5
9
|
|
|
6
10
|
function getVitestWorkerPort () {
|
|
7
11
|
const port = globalThis.__vitest_worker__?.ctx?.port
|
|
@@ -13,6 +17,7 @@ class Writer {
|
|
|
13
17
|
this._encoder = new JSONEncoder()
|
|
14
18
|
// Code used to identify the type of payload being sent to the main process
|
|
15
19
|
this._interprocessCode = interprocessCode
|
|
20
|
+
this._isWebdriverioWorker = !!getEnvironmentVariable(WEBDRIVERIO_WORKER_ENV)
|
|
16
21
|
}
|
|
17
22
|
|
|
18
23
|
/**
|
|
@@ -43,9 +48,12 @@ class Writer {
|
|
|
43
48
|
// Old because vitest@>=4 uses `DD_VITEST_WORKER` and reports arrays just like other frameworks
|
|
44
49
|
// Before vitest@>=4, we need the `__tinypool_worker_message__` property, or tinypool will crash
|
|
45
50
|
const isVitestWorkerOld = !!getEnvironmentVariable('TINYPOOL_WORKER_ID')
|
|
46
|
-
|
|
51
|
+
let payload = isVitestWorkerOld
|
|
47
52
|
? { __tinypool_worker_message__: true, interprocessCode: this._interprocessCode, data }
|
|
48
53
|
: [this._interprocessCode, data]
|
|
54
|
+
if (this._isWebdriverioWorker) {
|
|
55
|
+
payload = createWebdriverioWorkerMessage(payload)
|
|
56
|
+
}
|
|
49
57
|
|
|
50
58
|
const vitestWorkerPort = getVitestWorkerPort()
|
|
51
59
|
if (vitestWorkerPort) {
|
|
@@ -25,6 +25,7 @@ function parseSkippableSuitesResponse (
|
|
|
25
25
|
{
|
|
26
26
|
testLevel = 'suite',
|
|
27
27
|
isCoverageReportUploadEnabled = false,
|
|
28
|
+
isLineCoverageSupported = true,
|
|
28
29
|
validateRequiredFields = false,
|
|
29
30
|
validationMode = false,
|
|
30
31
|
} = {}
|
|
@@ -34,8 +35,11 @@ function parseSkippableSuitesResponse (
|
|
|
34
35
|
validateSkippableTestsResponse(parsedResponse, { validationMode })
|
|
35
36
|
}
|
|
36
37
|
const coverage = {}
|
|
37
|
-
|
|
38
|
-
|
|
38
|
+
const coverageByFilename = parsedResponse.meta?.coverage
|
|
39
|
+
if (coverageByFilename) {
|
|
40
|
+
for (const [filename, bitmap] of Object.entries(coverageByFilename)) {
|
|
41
|
+
coverage[filename.replaceAll('\\', '/')] = bitmap
|
|
42
|
+
}
|
|
39
43
|
}
|
|
40
44
|
|
|
41
45
|
const skippableItems = parsedResponse
|
|
@@ -51,7 +55,7 @@ function parseSkippableSuitesResponse (
|
|
|
51
55
|
},
|
|
52
56
|
} of skippableItems) {
|
|
53
57
|
// Only reject candidates without backend line coverage when we need that coverage to backfill reports.
|
|
54
|
-
if (isCoverageReportUploadEnabled && isMissingLineCodeCoverage) {
|
|
58
|
+
if (isCoverageReportUploadEnabled && isLineCoverageSupported && isMissingLineCodeCoverage) {
|
|
55
59
|
numExcludedByMissingLineCoverage++
|
|
56
60
|
continue
|
|
57
61
|
}
|
|
@@ -78,10 +82,11 @@ function parseSkippableSuitesResponse (
|
|
|
78
82
|
* numExcludedByMissingLineCoverage?: number
|
|
79
83
|
* }} result - Parsed skippable response.
|
|
80
84
|
* @param {'suite'|'test'} testLevel - Test optimization skipping level.
|
|
85
|
+
* @param {boolean} shouldConsiderLineCoverage - Whether line coverage can affect suite skipping.
|
|
81
86
|
* @returns {void}
|
|
82
87
|
*/
|
|
83
|
-
function logSkippableSuitesResponse (result, testLevel) {
|
|
84
|
-
if (result.numReceivedSkippableItems === undefined) {
|
|
88
|
+
function logSkippableSuitesResponse (result, testLevel, shouldConsiderLineCoverage) {
|
|
89
|
+
if (!shouldConsiderLineCoverage || result.numReceivedSkippableItems === undefined) {
|
|
85
90
|
log.debug('Number of received skippable %ss: %d', testLevel, result.skippableSuites.length)
|
|
86
91
|
return
|
|
87
92
|
}
|
|
@@ -122,10 +127,12 @@ function getSkippableSuites ({
|
|
|
122
127
|
custom,
|
|
123
128
|
testLevel = 'suite',
|
|
124
129
|
isCoverageReportUploadEnabled = false,
|
|
130
|
+
isLineCoverageSupported = true,
|
|
125
131
|
}, done) {
|
|
132
|
+
const shouldConsiderLineCoverage = isCoverageReportUploadEnabled && isLineCoverageSupported
|
|
126
133
|
const cacheKey = buildCacheKey('skippable', [
|
|
127
134
|
sha, service, env, repositoryUrl, osPlatform, osVersion, osArchitecture,
|
|
128
|
-
runtimeName, runtimeVersion, testLevel, custom,
|
|
135
|
+
runtimeName, runtimeVersion, testLevel, custom, shouldConsiderLineCoverage,
|
|
129
136
|
])
|
|
130
137
|
|
|
131
138
|
withCache(cacheKey, (activeCacheKey, cb) => {
|
|
@@ -146,11 +153,16 @@ function getSkippableSuites ({
|
|
|
146
153
|
custom,
|
|
147
154
|
testLevel,
|
|
148
155
|
isCoverageReportUploadEnabled,
|
|
156
|
+
isLineCoverageSupported,
|
|
149
157
|
cacheKey: activeCacheKey,
|
|
150
158
|
}, cb)
|
|
151
159
|
}, (err, data) => {
|
|
152
160
|
if (err) return done(err)
|
|
153
|
-
logSkippableSuitesResponse(
|
|
161
|
+
logSkippableSuitesResponse(
|
|
162
|
+
data,
|
|
163
|
+
testLevel,
|
|
164
|
+
shouldConsiderLineCoverage
|
|
165
|
+
)
|
|
154
166
|
done(null, data.skippableSuites, data.correlationId, data.coverage)
|
|
155
167
|
})
|
|
156
168
|
}
|
|
@@ -175,6 +187,7 @@ function getSkippableSuites ({
|
|
|
175
187
|
* @param {object} [params.custom]
|
|
176
188
|
* @param {string} [params.testLevel]
|
|
177
189
|
* @param {boolean} [params.isCoverageReportUploadEnabled]
|
|
190
|
+
* @param {boolean} [params.isLineCoverageSupported]
|
|
178
191
|
* @param {string | null} params.cacheKey
|
|
179
192
|
* @param {Function} done
|
|
180
193
|
*/
|
|
@@ -195,6 +208,7 @@ function fetchFromApi ({
|
|
|
195
208
|
custom,
|
|
196
209
|
testLevel,
|
|
197
210
|
isCoverageReportUploadEnabled,
|
|
211
|
+
isLineCoverageSupported,
|
|
198
212
|
cacheKey,
|
|
199
213
|
}, done) {
|
|
200
214
|
const options = {
|
|
@@ -258,6 +272,7 @@ function fetchFromApi ({
|
|
|
258
272
|
const result = parseSkippableSuitesResponse(parsedResponse, {
|
|
259
273
|
testLevel,
|
|
260
274
|
isCoverageReportUploadEnabled,
|
|
275
|
+
isLineCoverageSupported,
|
|
261
276
|
validateRequiredFields: true,
|
|
262
277
|
})
|
|
263
278
|
const skippableItems = parsedResponse.data.filter(({ type }) => type === testLevel)
|