dd-trace 6.3.0 → 6.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.electron.md +7 -0
- package/README.md +17 -0
- package/ci/diagnose.js +2100 -0
- package/ci/init.js +23 -2
- package/ci/runbook.md +198 -0
- package/ci/test-optimization-validation/approval-artifacts.js +143 -0
- package/ci/test-optimization-validation/approval.js +299 -0
- package/ci/test-optimization-validation/artifact-id.js +20 -0
- package/ci/test-optimization-validation/bounded-json.js +121 -0
- package/ci/test-optimization-validation/ci-command-candidate.js +83 -0
- package/ci/test-optimization-validation/ci-discovery.js +181 -0
- package/ci/test-optimization-validation/ci-remediation.js +210 -0
- package/ci/test-optimization-validation/cli.js +903 -0
- package/ci/test-optimization-validation/command-blocker.js +81 -0
- package/ci/test-optimization-validation/command-output-policy.js +206 -0
- package/ci/test-optimization-validation/command-runner.js +707 -0
- package/ci/test-optimization-validation/command-suitability.js +471 -0
- package/ci/test-optimization-validation/executable-approval.js +48 -0
- package/ci/test-optimization-validation/executable.js +480 -0
- package/ci/test-optimization-validation/generated-file-policy.js +63 -0
- package/ci/test-optimization-validation/generated-files.js +351 -0
- package/ci/test-optimization-validation/generated-verifier.js +196 -0
- package/ci/test-optimization-validation/init-probe-preload.js +163 -0
- package/ci/test-optimization-validation/init-probe.js +246 -0
- package/ci/test-optimization-validation/late-initialization.js +63 -0
- package/ci/test-optimization-validation/local-command.js +163 -0
- package/ci/test-optimization-validation/manifest-loader.js +133 -0
- package/ci/test-optimization-validation/manifest-scaffold.js +738 -0
- package/ci/test-optimization-validation/manifest-schema.js +862 -0
- package/ci/test-optimization-validation/offline-fixtures.js +327 -0
- package/ci/test-optimization-validation/offline-output.js +406 -0
- package/ci/test-optimization-validation/payload-normalizer.js +72 -0
- package/ci/test-optimization-validation/plan-writer.js +1120 -0
- package/ci/test-optimization-validation/preflight-runner.js +114 -0
- package/ci/test-optimization-validation/redaction.js +331 -0
- package/ci/test-optimization-validation/report-writer.js +1508 -0
- package/ci/test-optimization-validation/safe-files.js +203 -0
- package/ci/test-optimization-validation/scenarios/auto-test-retries.js +159 -0
- package/ci/test-optimization-validation/scenarios/basic-reporting.js +657 -0
- package/ci/test-optimization-validation/scenarios/ci-wiring.js +780 -0
- package/ci/test-optimization-validation/scenarios/early-flake-detection.js +141 -0
- package/ci/test-optimization-validation/scenarios/helpers.js +539 -0
- package/ci/test-optimization-validation/scenarios/test-management.js +218 -0
- package/ci/test-optimization-validation/setup-runner.js +97 -0
- package/ci/test-optimization-validation/static-diagnosis.js +159 -0
- package/ci/test-optimization-validation/test-output.js +129 -0
- package/ci/test-optimization-validation-manifest.schema.json +1 -0
- package/ci/validate-test-optimization.js +3 -0
- package/ext/exporters.js +1 -0
- package/index.d.ts +121 -8
- package/index.electron.js +3 -0
- package/init.js +18 -0
- package/initialize.mjs +1 -1
- package/loader-hook.mjs +28 -18
- package/openfeature.d.ts +1 -0
- package/openfeature.js +4 -0
- package/package.json +15 -8
- package/packages/datadog-instrumentations/src/ai.js +37 -26
- package/packages/datadog-instrumentations/src/child_process.js +1 -1
- package/packages/datadog-instrumentations/src/claude-agent-sdk.js +1 -1
- package/packages/datadog-instrumentations/src/cucumber-worker-threads.js +9 -3
- package/packages/datadog-instrumentations/src/cucumber.js +24 -10
- package/packages/datadog-instrumentations/src/cypress-config.js +398 -52
- package/packages/datadog-instrumentations/src/express.js +20 -2
- package/packages/datadog-instrumentations/src/fastify.js +7 -11
- package/packages/datadog-instrumentations/src/grpc/server.js +18 -0
- package/packages/datadog-instrumentations/src/helpers/hook.js +27 -6
- package/packages/datadog-instrumentations/src/helpers/optional-peer-bundler.js +8 -8
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/playwright.js +28 -2
- package/packages/datadog-instrumentations/src/helpers/router-helper.js +8 -8
- package/packages/datadog-instrumentations/src/http2/server.js +143 -17
- package/packages/datadog-instrumentations/src/jest.js +36 -4
- package/packages/datadog-instrumentations/src/mariadb.js +1 -1
- package/packages/datadog-instrumentations/src/mocha/main.js +0 -2
- package/packages/datadog-instrumentations/src/mocha/utils.js +0 -4
- package/packages/datadog-instrumentations/src/mongodb.js +3 -3
- package/packages/datadog-instrumentations/src/mongoose.js +3 -3
- package/packages/datadog-instrumentations/src/mquery.js +2 -2
- package/packages/datadog-instrumentations/src/mysql.js +1 -1
- package/packages/datadog-instrumentations/src/next.js +24 -0
- package/packages/datadog-instrumentations/src/nyc.js +0 -2
- package/packages/datadog-instrumentations/src/oracledb.js +2 -2
- package/packages/datadog-instrumentations/src/pg.js +2 -2
- package/packages/datadog-instrumentations/src/playwright.js +159 -22
- package/packages/datadog-instrumentations/src/process.js +3 -0
- package/packages/datadog-instrumentations/src/router.js +191 -106
- package/packages/datadog-instrumentations/src/selenium.js +52 -26
- package/packages/datadog-instrumentations/src/vitest-main-no-worker-init.js +2 -2
- package/packages/datadog-instrumentations/src/vitest-main.js +0 -2
- package/packages/datadog-instrumentations/src/ws.js +2 -1
- package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/context.js +2 -2
- package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/util.js +2 -1
- package/packages/datadog-plugin-aws-sdk/src/services/kinesis.js +41 -16
- package/packages/datadog-plugin-aws-sdk/src/services/sns.js +21 -21
- package/packages/datadog-plugin-aws-sdk/src/services/sqs.js +14 -8
- package/packages/datadog-plugin-aws-sdk/src/util.js +2 -24
- package/packages/datadog-plugin-cucumber/src/index.js +1 -1
- package/packages/datadog-plugin-cypress/src/cypress-plugin.js +24 -62
- package/packages/datadog-plugin-cypress/src/index.js +6 -1
- package/packages/datadog-plugin-cypress/src/support.js +92 -26
- package/packages/datadog-plugin-electron/src/ipc.js +1 -1
- package/packages/datadog-plugin-graphql/src/execute.js +177 -59
- package/packages/datadog-plugin-graphql/src/parse.js +22 -1
- package/packages/datadog-plugin-graphql/src/request.js +32 -1
- package/packages/datadog-plugin-graphql/src/utils.js +42 -0
- package/packages/datadog-plugin-graphql/src/validate.js +15 -3
- package/packages/datadog-plugin-http2/src/client.js +1 -1
- package/packages/datadog-plugin-http2/src/server.js +38 -6
- package/packages/datadog-plugin-jest/src/util.js +21 -6
- package/packages/datadog-plugin-langchain/src/handlers/embedding.js +2 -1
- package/packages/datadog-plugin-langchain/src/handlers/language_models.js +2 -1
- package/packages/datadog-plugin-langchain/src/tokens.js +2 -2
- package/packages/datadog-plugin-playwright/src/index.js +177 -64
- package/packages/datadog-plugin-router/src/index.js +11 -2
- package/packages/datadog-plugin-selenium/src/index.js +5 -36
- package/packages/datadog-plugin-vitest/src/index.js +4 -2
- package/packages/datadog-plugin-ws/src/close.js +2 -1
- package/packages/datadog-plugin-ws/src/producer.js +2 -1
- package/packages/datadog-plugin-ws/src/receiver.js +2 -1
- package/packages/datadog-plugin-ws/src/server.js +2 -1
- package/packages/dd-trace/index.electron.js +3 -0
- package/packages/dd-trace/index.js +2 -37
- package/packages/dd-trace/src/aiguard/sdk.js +1 -1
- package/packages/dd-trace/src/appsec/iast/taint-tracking/plugin.js +2 -2
- package/packages/dd-trace/src/appsec/iast/telemetry/span-tags.js +3 -1
- package/packages/dd-trace/src/azure_metadata.js +2 -1
- package/packages/dd-trace/src/bootstrap.js +39 -0
- package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/index.js +93 -28
- package/packages/dd-trace/src/ci-visibility/early-flake-detection/get-known-tests.js +1 -1
- package/packages/dd-trace/src/ci-visibility/exporters/ci-validation/index.js +160 -0
- package/packages/dd-trace/src/ci-visibility/exporters/ci-validation/msgpack-to-json.js +288 -0
- package/packages/dd-trace/src/ci-visibility/exporters/ci-validation/payload-projection.js +84 -0
- package/packages/dd-trace/src/ci-visibility/exporters/ci-validation/sink.js +369 -0
- package/packages/dd-trace/src/ci-visibility/exporters/ci-validation/writer.js +60 -0
- package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +55 -5
- package/packages/dd-trace/src/ci-visibility/exporters/test-worker/index.js +22 -6
- package/packages/dd-trace/src/ci-visibility/exporters/test-worker/writer.js +5 -0
- package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +62 -7
- package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +9 -2
- package/packages/dd-trace/src/ci-visibility/requests/upload-coverage-report.js +6 -2
- package/packages/dd-trace/src/ci-visibility/rum.js +7 -0
- package/packages/dd-trace/src/ci-visibility/test-api-manual/test-api-manual-plugin.js +4 -0
- package/packages/dd-trace/src/ci-visibility/test-management/get-test-management-tests.js +1 -1
- package/packages/dd-trace/src/ci-visibility/test-optimization-http-cache-schema.js +137 -9
- package/packages/dd-trace/src/ci-visibility/test-optimization-http-cache.js +173 -9
- package/packages/dd-trace/src/ci-visibility/test-screenshot.js +90 -0
- package/packages/dd-trace/src/config/generated-config-types.d.ts +20 -0
- package/packages/dd-trace/src/config/helper.js +1 -0
- package/packages/dd-trace/src/config/index.js +10 -0
- package/packages/dd-trace/src/config/supported-configurations.json +83 -0
- package/packages/dd-trace/src/constants.js +7 -0
- package/packages/dd-trace/src/datastreams/pathway.js +6 -4
- package/packages/dd-trace/src/exporter.js +2 -0
- package/packages/dd-trace/src/exporters/common/client-library-headers.js +21 -0
- package/packages/dd-trace/src/exporters/common/request.js +59 -30
- package/packages/dd-trace/src/exporters/common/url.js +15 -1
- package/packages/dd-trace/src/feature-registry.js +29 -0
- package/packages/dd-trace/src/llmobs/constants/tags.js +1 -0
- package/packages/dd-trace/src/llmobs/experiments/client.js +113 -0
- package/packages/dd-trace/src/llmobs/experiments/dataset.js +154 -0
- package/packages/dd-trace/src/llmobs/experiments/experiment.js +283 -0
- package/packages/dd-trace/src/llmobs/experiments/index.js +152 -0
- package/packages/dd-trace/src/llmobs/experiments/noop.js +30 -0
- package/packages/dd-trace/src/llmobs/experiments/result.js +34 -0
- package/packages/dd-trace/src/llmobs/index.js +10 -2
- package/packages/dd-trace/src/llmobs/noop.js +6 -0
- package/packages/dd-trace/src/llmobs/plugins/ai/vercelTelemetry.js +20 -3
- package/packages/dd-trace/src/llmobs/plugins/anthropic/index.js +2 -2
- package/packages/dd-trace/src/llmobs/plugins/langgraph/index.js +1 -1
- package/packages/dd-trace/src/llmobs/plugins/openai/constants.js +8 -0
- package/packages/dd-trace/src/llmobs/plugins/openai/index.js +48 -14
- package/packages/dd-trace/src/llmobs/plugins/openai/utils.js +43 -0
- package/packages/dd-trace/src/llmobs/sdk.js +17 -0
- package/packages/dd-trace/src/llmobs/tagger.js +79 -7
- package/packages/dd-trace/src/llmobs/telemetry.js +2 -1
- package/packages/dd-trace/src/llmobs/util.js +32 -0
- package/packages/dd-trace/src/noop/proxy.js +5 -4
- package/packages/dd-trace/src/openfeature/agentless_configuration_source.js +322 -0
- package/packages/dd-trace/src/openfeature/configuration_source.js +90 -0
- package/packages/dd-trace/src/openfeature/flagging_provider.js +14 -23
- package/packages/dd-trace/src/openfeature/index.js +0 -2
- package/packages/dd-trace/src/openfeature/noop.js +1 -28
- package/packages/dd-trace/src/openfeature/register.js +35 -0
- package/packages/dd-trace/src/openfeature/remote_config.js +15 -18
- package/packages/dd-trace/src/openfeature/require-provider.js +18 -0
- package/packages/dd-trace/src/opentelemetry/span-ending-hook.js +10 -0
- package/packages/dd-trace/src/opentelemetry/span.js +5 -0
- package/packages/dd-trace/src/opentracing/propagation/log.js +11 -2
- package/packages/dd-trace/src/opentracing/propagation/text_map.js +80 -14
- package/packages/dd-trace/src/opentracing/propagation/text_map_dsm.js +10 -1
- package/packages/dd-trace/src/opentracing/tracer.js +7 -1
- package/packages/dd-trace/src/plugins/ci_plugin.js +20 -6
- package/packages/dd-trace/src/plugins/util/git.js +3 -2
- package/packages/dd-trace/src/plugins/util/inferred_proxy.js +29 -5
- package/packages/dd-trace/src/plugins/util/test.js +65 -8
- package/packages/dd-trace/src/plugins/util/web.js +12 -0
- package/packages/dd-trace/src/proxy.js +99 -10
- package/packages/dd-trace/src/span_processor.js +5 -0
- package/packages/dd-trace/src/standalone/index.js +0 -36
- package/packages/dd-trace/src/telemetry/send-data.js +2 -2
- package/packages/dd-trace/src/util.js +26 -0
- package/vendor/dist/@apm-js-collab/code-transformer/index.js +1 -1
- package/vendor/dist/@datadog/sketches-js/index.js +1 -1
- package/vendor/dist/protobufjs/index.js +1 -1
- package/vendor/dist/protobufjs/minimal/index.js +1 -1
- package/vendor/dist/shell-quote/index.js +1 -1
- package/packages/datadog-instrumentations/src/helpers/require-optional-peer.js +0 -17
|
@@ -0,0 +1,369 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const crypto = require('node:crypto')
|
|
4
|
+
const fs = require('node:fs')
|
|
5
|
+
const path = require('node:path')
|
|
6
|
+
|
|
7
|
+
const { MAX_OUTPUT_BYTES, msgpackToJson } = require('./msgpack-to-json')
|
|
8
|
+
const { projectTestCyclePayload } = require('./payload-projection')
|
|
9
|
+
|
|
10
|
+
const MAX_OUTPUT_FILES = 10_000
|
|
11
|
+
const MAX_SAMPLED_TESTS = 8
|
|
12
|
+
const MAX_SUMMARY_ERRORS = 20
|
|
13
|
+
const SUMMARY_PREFIX = 'DD_TEST_OPTIMIZATION_VALIDATION_V1 '
|
|
14
|
+
|
|
15
|
+
let payloadFileSequence = 0
|
|
16
|
+
|
|
17
|
+
class CiValidationSink {
|
|
18
|
+
#bytesWritten = 0
|
|
19
|
+
#captureMode
|
|
20
|
+
#completionDirectory
|
|
21
|
+
#errors = []
|
|
22
|
+
#eventsObserved = 0
|
|
23
|
+
#eventsRetained = 0
|
|
24
|
+
#fileCount = 0
|
|
25
|
+
#inputs = Object.create(null)
|
|
26
|
+
#outputRoot
|
|
27
|
+
#outputRootDirectory
|
|
28
|
+
#payloadsDirectory
|
|
29
|
+
#processId = crypto.randomBytes(16).toString('hex')
|
|
30
|
+
#sampledLifecycle = new Map()
|
|
31
|
+
#sampledTests = []
|
|
32
|
+
#summaryWritten = false
|
|
33
|
+
#testsDirectory
|
|
34
|
+
#testsObserved = 0
|
|
35
|
+
#testPayloadFileCount = 0
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Creates a bounded payload-file sink under a validator-owned output root.
|
|
39
|
+
*
|
|
40
|
+
* @param {string} outputRoot absolute validator-owned output directory
|
|
41
|
+
* @param {object} [options] sink options
|
|
42
|
+
* @param {'sample'|'strict'} [options.captureMode] evidence retention mode
|
|
43
|
+
*/
|
|
44
|
+
constructor (outputRoot, { captureMode = 'strict' } = {}) {
|
|
45
|
+
if (typeof outputRoot !== 'string' || !path.isAbsolute(outputRoot)) {
|
|
46
|
+
throw new Error('Offline Test Optimization validation output root must be absolute.')
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (!['sample', 'strict'].includes(captureMode)) {
|
|
50
|
+
throw new Error('Offline Test Optimization validation capture mode is invalid.')
|
|
51
|
+
}
|
|
52
|
+
this.#captureMode = captureMode
|
|
53
|
+
this.#outputRoot = outputRoot
|
|
54
|
+
this.#outputRootDirectory = captureDirectory(outputRoot, 'output root')
|
|
55
|
+
const payloadsRoot = path.join(outputRoot, 'payloads')
|
|
56
|
+
this.#payloadsDirectory = createDirectory(outputRoot, this.#outputRootDirectory, payloadsRoot, 'payloads')
|
|
57
|
+
this.#testsDirectory = createDirectory(
|
|
58
|
+
payloadsRoot,
|
|
59
|
+
this.#payloadsDirectory,
|
|
60
|
+
path.join(payloadsRoot, 'tests'),
|
|
61
|
+
'tests'
|
|
62
|
+
)
|
|
63
|
+
this.#completionDirectory = createDirectory(
|
|
64
|
+
outputRoot,
|
|
65
|
+
this.#outputRootDirectory,
|
|
66
|
+
path.join(outputRoot, 'completions'),
|
|
67
|
+
'completions'
|
|
68
|
+
)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Converts and writes one Test Optimization payload using the Bazel-compatible JSON layout.
|
|
73
|
+
*
|
|
74
|
+
* @param {Buffer} payload encoded Test Optimization payload
|
|
75
|
+
*/
|
|
76
|
+
writeTestCycle (payload) {
|
|
77
|
+
let decoded
|
|
78
|
+
try {
|
|
79
|
+
decoded = JSON.parse(msgpackToJson(payload).toString('utf8'))
|
|
80
|
+
} catch {
|
|
81
|
+
this.#fail('output_payload_decode_failed')
|
|
82
|
+
return
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
let projected
|
|
86
|
+
try {
|
|
87
|
+
projected = projectTestCyclePayload(decoded)
|
|
88
|
+
} catch {
|
|
89
|
+
this.#fail('output_payload_projection_failed')
|
|
90
|
+
return
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
this.#eventsObserved += projected.events.length
|
|
94
|
+
if (this.#captureMode === 'sample') {
|
|
95
|
+
this.#retainSample(projected.events)
|
|
96
|
+
return
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const json = Buffer.from(JSON.stringify(projected))
|
|
100
|
+
if (this.#writePayloadFile(json)) {
|
|
101
|
+
this.#eventsRetained += projected.events.length
|
|
102
|
+
this.#testPayloadFileCount++
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Records whether a control-plane fixture was loaded from the filesystem cache.
|
|
108
|
+
*
|
|
109
|
+
* @param {string} name fixed fixture name
|
|
110
|
+
* @param {Error|undefined|null} error cache-load error
|
|
111
|
+
*/
|
|
112
|
+
writeInputResult (name, error) {
|
|
113
|
+
if (error) this.#addError(`invalid_${name}`)
|
|
114
|
+
this.#inputs[name] = { status: error ? 'error' : 'loaded' }
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Records an exporter failure in the bounded validation summary.
|
|
119
|
+
*
|
|
120
|
+
* @param {string} code stable failure code
|
|
121
|
+
*/
|
|
122
|
+
recordError (code) {
|
|
123
|
+
this.#fail(code)
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Emits this process's single bounded stderr summary for validator aggregation.
|
|
128
|
+
*/
|
|
129
|
+
writeSummary () {
|
|
130
|
+
if (this.#summaryWritten) return
|
|
131
|
+
this.#summaryWritten = true
|
|
132
|
+
if (this.#captureMode === 'sample') this.#writeSampledEvents()
|
|
133
|
+
const summary = {
|
|
134
|
+
events: this.#eventsRetained,
|
|
135
|
+
payloadFiles: this.#testPayloadFileCount,
|
|
136
|
+
input: 'filesystem-cache',
|
|
137
|
+
inputs: this.#inputs,
|
|
138
|
+
errors: this.#errors,
|
|
139
|
+
}
|
|
140
|
+
if (!this.#writeCompletionRecord()) this.#fail('completion_write_failed')
|
|
141
|
+
process.stderr.write(`${SUMMARY_PREFIX}${JSON.stringify(summary)}\n`)
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Retains bounded first/late test evidence and the latest lifecycle event of each type.
|
|
146
|
+
*
|
|
147
|
+
* @param {object[]} events projected events
|
|
148
|
+
*/
|
|
149
|
+
#retainSample (events) {
|
|
150
|
+
for (const event of events) {
|
|
151
|
+
if (event.type !== 'test') {
|
|
152
|
+
this.#sampledLifecycle.set(event.type, event)
|
|
153
|
+
continue
|
|
154
|
+
}
|
|
155
|
+
this.#testsObserved++
|
|
156
|
+
if (this.#sampledTests.length < MAX_SAMPLED_TESTS) {
|
|
157
|
+
this.#sampledTests.push(event)
|
|
158
|
+
} else {
|
|
159
|
+
const lateIndex = Math.floor(MAX_SAMPLED_TESTS / 2) +
|
|
160
|
+
(this.#testsObserved % Math.ceil(MAX_SAMPLED_TESTS / 2))
|
|
161
|
+
this.#sampledTests[lateIndex] = event
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Persists the final bounded CI-replay sample after all process-local events have been observed.
|
|
168
|
+
*/
|
|
169
|
+
#writeSampledEvents () {
|
|
170
|
+
const events = [...this.#sampledTests, ...this.#sampledLifecycle.values()]
|
|
171
|
+
if (events.length === 0) return
|
|
172
|
+
const payload = Buffer.from(JSON.stringify({ version: 1, events }))
|
|
173
|
+
if (this.#writePayloadFile(payload)) {
|
|
174
|
+
this.#eventsRetained = events.length
|
|
175
|
+
this.#testPayloadFileCount++
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Atomically publishes this process's bounded completion evidence.
|
|
181
|
+
*
|
|
182
|
+
* @returns {boolean} whether the record was published
|
|
183
|
+
*/
|
|
184
|
+
#writeCompletionRecord () {
|
|
185
|
+
const directoryPath = path.join(this.#outputRoot, 'completions')
|
|
186
|
+
const filename = path.join(directoryPath, `completion-${this.#processId}.json`)
|
|
187
|
+
const temporary = `${filename}.tmp`
|
|
188
|
+
const completion = Buffer.from(JSON.stringify({
|
|
189
|
+
version: 1,
|
|
190
|
+
processId: this.#processId,
|
|
191
|
+
captureMode: this.#captureMode,
|
|
192
|
+
counts: {
|
|
193
|
+
eventsObserved: this.#eventsObserved,
|
|
194
|
+
eventsRetained: this.#eventsRetained,
|
|
195
|
+
payloadFiles: this.#testPayloadFileCount,
|
|
196
|
+
},
|
|
197
|
+
inputs: this.#inputs,
|
|
198
|
+
errors: this.#errors,
|
|
199
|
+
}))
|
|
200
|
+
|
|
201
|
+
try {
|
|
202
|
+
assertDirectoryUnchanged(this.#outputRoot, this.#outputRootDirectory, 'output root')
|
|
203
|
+
assertDirectoryUnchanged(directoryPath, this.#completionDirectory, 'completions')
|
|
204
|
+
writeNewFile(temporary, completion)
|
|
205
|
+
assertDirectoryUnchanged(directoryPath, this.#completionDirectory, 'completions')
|
|
206
|
+
fs.renameSync(temporary, filename)
|
|
207
|
+
return true
|
|
208
|
+
} catch {
|
|
209
|
+
removePartialFile(temporary, directoryPath, this.#completionDirectory)
|
|
210
|
+
return false
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Writes one completed JSON payload to a unique file.
|
|
216
|
+
*
|
|
217
|
+
* @param {Buffer} payload JSON payload
|
|
218
|
+
* @returns {boolean} whether the payload was written
|
|
219
|
+
*/
|
|
220
|
+
#writePayloadFile (payload) {
|
|
221
|
+
if (this.#fileCount >= MAX_OUTPUT_FILES) {
|
|
222
|
+
this.#fail('output_file_limit_exceeded')
|
|
223
|
+
return false
|
|
224
|
+
}
|
|
225
|
+
if (this.#bytesWritten + payload.length > MAX_OUTPUT_BYTES) {
|
|
226
|
+
this.#fail('output_byte_limit_exceeded')
|
|
227
|
+
return false
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
const directoryPath = path.join(this.#outputRoot, 'payloads', 'tests')
|
|
231
|
+
const sequence = ++payloadFileSequence
|
|
232
|
+
const timestamp = BigInt(Date.now()) * 1_000_000n + process.hrtime.bigint() % 1_000_000n
|
|
233
|
+
const filename = path.join(
|
|
234
|
+
directoryPath,
|
|
235
|
+
`tests-${this.#processId}-${timestamp}-${process.pid}-${sequence}.json`
|
|
236
|
+
)
|
|
237
|
+
let writeFailed = false
|
|
238
|
+
try {
|
|
239
|
+
assertDirectoryUnchanged(this.#outputRoot, this.#outputRootDirectory, 'output root')
|
|
240
|
+
assertDirectoryUnchanged(path.join(this.#outputRoot, 'payloads'), this.#payloadsDirectory, 'payloads')
|
|
241
|
+
assertDirectoryUnchanged(directoryPath, this.#testsDirectory, 'tests')
|
|
242
|
+
writeNewFile(filename, payload)
|
|
243
|
+
} catch {
|
|
244
|
+
writeFailed = true
|
|
245
|
+
}
|
|
246
|
+
if (writeFailed) {
|
|
247
|
+
this.#fail('output_write_failed')
|
|
248
|
+
removePartialFile(filename, directoryPath, this.#testsDirectory)
|
|
249
|
+
return false
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
this.#bytesWritten += payload.length
|
|
253
|
+
this.#fileCount++
|
|
254
|
+
return true
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Records a sink failure and makes the process unsuccessful.
|
|
259
|
+
*
|
|
260
|
+
* @param {string} code stable failure code
|
|
261
|
+
*/
|
|
262
|
+
#fail (code) {
|
|
263
|
+
this.#addError(code)
|
|
264
|
+
process.exitCode = 1
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Adds one unique bounded summary error.
|
|
269
|
+
*
|
|
270
|
+
* @param {string} code stable failure code
|
|
271
|
+
*/
|
|
272
|
+
#addError (code) {
|
|
273
|
+
if (this.#errors.length >= MAX_SUMMARY_ERRORS || this.#errors.includes(code)) return
|
|
274
|
+
this.#errors.push(code)
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Writes one new private regular file and rejects hard links or non-files.
|
|
280
|
+
*
|
|
281
|
+
* @param {string} filename output filename
|
|
282
|
+
* @param {Buffer} payload output bytes
|
|
283
|
+
*/
|
|
284
|
+
function writeNewFile (filename, payload) {
|
|
285
|
+
const flags = fs.constants.O_WRONLY |
|
|
286
|
+
fs.constants.O_CREAT |
|
|
287
|
+
fs.constants.O_EXCL |
|
|
288
|
+
(fs.constants.O_NOFOLLOW || 0)
|
|
289
|
+
const file = fs.openSync(filename, flags, 0o600)
|
|
290
|
+
try {
|
|
291
|
+
const stat = fs.fstatSync(file)
|
|
292
|
+
if (!stat.isFile() || stat.nlink !== 1) {
|
|
293
|
+
throw new Error('Offline Test Optimization output is not a private regular file.')
|
|
294
|
+
}
|
|
295
|
+
fs.writeFileSync(file, payload)
|
|
296
|
+
} finally {
|
|
297
|
+
fs.closeSync(file)
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* Captures the identity of one existing non-symbolic directory.
|
|
303
|
+
*
|
|
304
|
+
* @param {string} directory directory path
|
|
305
|
+
* @param {string} label directory label
|
|
306
|
+
* @returns {{dev: number, ino: number}} stable directory identity
|
|
307
|
+
*/
|
|
308
|
+
function captureDirectory (directory, label) {
|
|
309
|
+
const stat = fs.lstatSync(directory)
|
|
310
|
+
if (!stat.isDirectory() || stat.isSymbolicLink()) {
|
|
311
|
+
throw new Error(`Offline Test Optimization validation ${label} must be a regular directory.`)
|
|
312
|
+
}
|
|
313
|
+
return { dev: stat.dev, ino: stat.ino }
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* Creates or validates one child directory without accepting symbolic links.
|
|
318
|
+
*
|
|
319
|
+
* @param {string} parent parent directory path
|
|
320
|
+
* @param {{dev: number, ino: number}} parentIdentity expected parent identity
|
|
321
|
+
* @param {string} directory child directory path
|
|
322
|
+
* @param {string} label directory label
|
|
323
|
+
* @returns {{dev: number, ino: number}} stable child identity
|
|
324
|
+
*/
|
|
325
|
+
function createDirectory (parent, parentIdentity, directory, label) {
|
|
326
|
+
assertDirectoryUnchanged(parent, parentIdentity, 'parent output')
|
|
327
|
+
try {
|
|
328
|
+
fs.mkdirSync(directory, { mode: 0o700 })
|
|
329
|
+
} catch (error) {
|
|
330
|
+
if (error.code !== 'EEXIST') throw error
|
|
331
|
+
}
|
|
332
|
+
return captureDirectory(directory, `${label} output directory`)
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* Rejects a directory that changed after sink construction.
|
|
337
|
+
*
|
|
338
|
+
* @param {string} directory directory path
|
|
339
|
+
* @param {{dev: number, ino: number}} identity expected directory identity
|
|
340
|
+
* @param {string} label directory label
|
|
341
|
+
*/
|
|
342
|
+
function assertDirectoryUnchanged (directory, identity, label) {
|
|
343
|
+
const current = captureDirectory(directory, label)
|
|
344
|
+
if (current.dev !== identity.dev || current.ino !== identity.ino) {
|
|
345
|
+
throw new Error(`Offline Test Optimization validation ${label} changed during execution.`)
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
/**
|
|
350
|
+
* Removes a partially written final path without following symbolic links.
|
|
351
|
+
*
|
|
352
|
+
* @param {string} filename partial payload path
|
|
353
|
+
* @param {string} directory expected parent directory
|
|
354
|
+
* @param {{dev: number, ino: number}} identity expected parent identity
|
|
355
|
+
*/
|
|
356
|
+
function removePartialFile (filename, directory, identity) {
|
|
357
|
+
try {
|
|
358
|
+
assertDirectoryUnchanged(directory, identity, 'partial-file parent')
|
|
359
|
+
const stat = fs.lstatSync(filename)
|
|
360
|
+
if (stat.isFile() || stat.isSymbolicLink()) fs.unlinkSync(filename)
|
|
361
|
+
} catch {}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
module.exports = {
|
|
365
|
+
CiValidationSink,
|
|
366
|
+
MAX_OUTPUT_BYTES,
|
|
367
|
+
MAX_OUTPUT_FILES,
|
|
368
|
+
SUMMARY_PREFIX,
|
|
369
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { AgentlessCiVisibilityEncoder } = require('../../../encode/agentless-ci-visibility')
|
|
4
|
+
|
|
5
|
+
class CiValidationWriter {
|
|
6
|
+
/**
|
|
7
|
+
* Creates an offline writer that preserves CI Visibility event encoding.
|
|
8
|
+
*
|
|
9
|
+
* @param {object} options writer options
|
|
10
|
+
* @param {object} options.sink bounded validation sink
|
|
11
|
+
* @param {object} options.tags tracer tags
|
|
12
|
+
*/
|
|
13
|
+
constructor ({ sink, tags }) {
|
|
14
|
+
const { 'runtime-id': runtimeId, env, service } = tags
|
|
15
|
+
this._sink = sink
|
|
16
|
+
this._encoder = new AgentlessCiVisibilityEncoder(this, { runtimeId, env, service })
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Encodes a CI Visibility trace without using an HTTP writer.
|
|
21
|
+
*
|
|
22
|
+
* @param {object[]} trace formatted trace
|
|
23
|
+
*/
|
|
24
|
+
append (trace) {
|
|
25
|
+
this._encoder.encode(trace)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Flushes encoded events synchronously to an offline payload file.
|
|
30
|
+
*
|
|
31
|
+
* @param {Function} [done] completion callback
|
|
32
|
+
*/
|
|
33
|
+
flush (done = () => {}) {
|
|
34
|
+
if (this._encoder.count() > 0) {
|
|
35
|
+
let payload
|
|
36
|
+
try {
|
|
37
|
+
payload = this._encoder.makePayload()
|
|
38
|
+
} catch (error) {
|
|
39
|
+
if (error.code !== 'ERR_MSGPACK_CHUNK_OVERFLOW') throw error
|
|
40
|
+
this._encoder.reset()
|
|
41
|
+
this._sink.recordError('output_payload_too_large')
|
|
42
|
+
done()
|
|
43
|
+
return
|
|
44
|
+
}
|
|
45
|
+
this._sink.writeTestCycle(payload)
|
|
46
|
+
}
|
|
47
|
+
done()
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Adds CI event metadata before the next payload is encoded.
|
|
52
|
+
*
|
|
53
|
+
* @param {object} tags metadata grouped by event type
|
|
54
|
+
*/
|
|
55
|
+
addMetadataTags (tags) {
|
|
56
|
+
this._encoder.addMetadataTags(tags)
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
module.exports = CiValidationWriter
|
|
@@ -13,6 +13,7 @@ const { writeSettingsToCache } = require('../test-optimization-cache')
|
|
|
13
13
|
const { CACHE_MISS, TestOptimizationHttpCache } = require('../test-optimization-http-cache')
|
|
14
14
|
const { uploadCoverageReport: uploadCoverageReportRequest } = require('../requests/upload-coverage-report')
|
|
15
15
|
const { uploadTestScreenshot: uploadTestScreenshotRequest } = require('../requests/upload-test-screenshot')
|
|
16
|
+
const { parsers } = require('../../config/parsers')
|
|
16
17
|
const log = require('../../log')
|
|
17
18
|
const BufferingExporter = require('../../exporters/common/buffering-exporter')
|
|
18
19
|
const { GIT_REPOSITORY_URL, GIT_COMMIT_SHA } = require('../../plugins/util/tags')
|
|
@@ -41,6 +42,7 @@ function getIsTestSessionTrace (trace) {
|
|
|
41
42
|
|
|
42
43
|
const GIT_UPLOAD_TIMEOUT = 60_000 // 60 seconds
|
|
43
44
|
const CAN_USE_CI_VIS_PROTOCOL_TIMEOUT = GIT_UPLOAD_TIMEOUT
|
|
45
|
+
const MAX_COVERAGE_REPORT_FLAGS = 32
|
|
44
46
|
|
|
45
47
|
function appendLogTag (tags, key, value) {
|
|
46
48
|
if (value !== undefined) {
|
|
@@ -71,13 +73,24 @@ function getLogTags (logMessage, { env, version }, gitRepositoryUrl, gitCommitSh
|
|
|
71
73
|
}
|
|
72
74
|
|
|
73
75
|
class CiVisibilityExporter extends BufferingExporter {
|
|
74
|
-
constructor (config) {
|
|
76
|
+
constructor (config, options = {}) {
|
|
75
77
|
super(config)
|
|
76
78
|
this._timer = undefined
|
|
77
79
|
this._coverageTimer = undefined
|
|
78
80
|
this._logsTimer = undefined
|
|
79
81
|
this._coverageBuffer = []
|
|
80
|
-
this._testOptimizationHttpCache = new TestOptimizationHttpCache()
|
|
82
|
+
this._testOptimizationHttpCache = options.testOptimizationHttpCache || new TestOptimizationHttpCache()
|
|
83
|
+
this._isTestOptimizationCacheOnly = options.cacheOnly === true
|
|
84
|
+
const coverageReportFlags = parsers.ARRAY(config?.testOptimization?.DD_CODE_COVERAGE_FLAGS)
|
|
85
|
+
if (coverageReportFlags?.length > MAX_COVERAGE_REPORT_FLAGS) {
|
|
86
|
+
log.warn(
|
|
87
|
+
'Maximum of %d coverage report flags allowed, but %d flags were provided. Omitting coverage report flags.',
|
|
88
|
+
MAX_COVERAGE_REPORT_FLAGS,
|
|
89
|
+
coverageReportFlags.length
|
|
90
|
+
)
|
|
91
|
+
} else if (coverageReportFlags?.length) {
|
|
92
|
+
this._coverageReportFlags = [...coverageReportFlags]
|
|
93
|
+
}
|
|
81
94
|
// The library can use new features like ITR and test suite level visibility
|
|
82
95
|
// AKA CI Vis Protocol
|
|
83
96
|
this._canUseCiVisProtocol = false
|
|
@@ -181,6 +194,9 @@ class CiVisibilityExporter extends BufferingExporter {
|
|
|
181
194
|
const { skippableSuites, correlationId, coverage } = cachedSkippableSuites
|
|
182
195
|
return callback(null, skippableSuites, correlationId, coverage)
|
|
183
196
|
}
|
|
197
|
+
if (this._isTestOptimizationCacheOnly) {
|
|
198
|
+
return callback(this._getCacheOnlyError('skippable tests'), [])
|
|
199
|
+
}
|
|
184
200
|
|
|
185
201
|
this._gitUploadPromise.then(gitUploadError => {
|
|
186
202
|
if (gitUploadError) {
|
|
@@ -198,6 +214,9 @@ class CiVisibilityExporter extends BufferingExporter {
|
|
|
198
214
|
if (cachedKnownTests !== CACHE_MISS) {
|
|
199
215
|
return callback(null, cachedKnownTests)
|
|
200
216
|
}
|
|
217
|
+
if (this._isTestOptimizationCacheOnly) {
|
|
218
|
+
return callback(this._getCacheOnlyError('known tests'))
|
|
219
|
+
}
|
|
201
220
|
getKnownTestsRequest(this.getRequestConfiguration(testConfiguration), callback)
|
|
202
221
|
}
|
|
203
222
|
|
|
@@ -209,6 +228,9 @@ class CiVisibilityExporter extends BufferingExporter {
|
|
|
209
228
|
if (cachedTestManagementTests !== CACHE_MISS) {
|
|
210
229
|
return callback(null, cachedTestManagementTests)
|
|
211
230
|
}
|
|
231
|
+
if (this._isTestOptimizationCacheOnly) {
|
|
232
|
+
return callback(this._getCacheOnlyError('test management tests'))
|
|
233
|
+
}
|
|
212
234
|
getTestManagementTestsRequest(this.getRequestConfiguration(testConfiguration), callback)
|
|
213
235
|
}
|
|
214
236
|
|
|
@@ -241,6 +263,10 @@ class CiVisibilityExporter extends BufferingExporter {
|
|
|
241
263
|
return callback(null, this._libraryConfig)
|
|
242
264
|
}
|
|
243
265
|
|
|
266
|
+
if (this._isTestOptimizationCacheOnly) {
|
|
267
|
+
return callback(this._getCacheOnlyError('settings'), {})
|
|
268
|
+
}
|
|
269
|
+
|
|
244
270
|
this.sendGitMetadata(repositoryUrl)
|
|
245
271
|
getLibraryConfigurationRequest(configuration, (err, libraryConfig) => {
|
|
246
272
|
/**
|
|
@@ -270,6 +296,17 @@ class CiVisibilityExporter extends BufferingExporter {
|
|
|
270
296
|
})
|
|
271
297
|
}
|
|
272
298
|
|
|
299
|
+
/**
|
|
300
|
+
* Returns the deterministic cache error for offline exporters.
|
|
301
|
+
*
|
|
302
|
+
* @param {string} input required cache input
|
|
303
|
+
* @returns {Error} cache error
|
|
304
|
+
*/
|
|
305
|
+
_getCacheOnlyError (input) {
|
|
306
|
+
return this._testOptimizationHttpCache.getLastError?.() ||
|
|
307
|
+
new Error(`Offline Test Optimization validation requires a valid ${input} cache fixture.`)
|
|
308
|
+
}
|
|
309
|
+
|
|
273
310
|
// Takes into account potential kill switches
|
|
274
311
|
filterConfiguration (remoteConfiguration) {
|
|
275
312
|
if (!remoteConfiguration) {
|
|
@@ -293,6 +330,9 @@ class CiVisibilityExporter extends BufferingExporter {
|
|
|
293
330
|
isCoverageReportUploadEnabled,
|
|
294
331
|
} = remoteConfiguration
|
|
295
332
|
const { testOptimization } = this._config
|
|
333
|
+
const earlyFlakeDetectionRetryCount =
|
|
334
|
+
testOptimization.DD_TEST_EARLY_FLAKE_DETECTION_RETRY_COUNT
|
|
335
|
+
const hasEarlyFlakeDetectionRetryCount = earlyFlakeDetectionRetryCount !== undefined
|
|
296
336
|
return {
|
|
297
337
|
isCodeCoverageEnabled,
|
|
298
338
|
isSuitesSkippingEnabled,
|
|
@@ -300,8 +340,16 @@ class CiVisibilityExporter extends BufferingExporter {
|
|
|
300
340
|
requireGit,
|
|
301
341
|
isEarlyFlakeDetectionEnabled:
|
|
302
342
|
isEarlyFlakeDetectionEnabled && testOptimization.DD_CIVISIBILITY_EARLY_FLAKE_DETECTION_ENABLED,
|
|
303
|
-
earlyFlakeDetectionNumRetries
|
|
304
|
-
|
|
343
|
+
earlyFlakeDetectionNumRetries:
|
|
344
|
+
hasEarlyFlakeDetectionRetryCount ? earlyFlakeDetectionRetryCount : earlyFlakeDetectionNumRetries,
|
|
345
|
+
earlyFlakeDetectionSlowTestRetries: hasEarlyFlakeDetectionRetryCount
|
|
346
|
+
? {
|
|
347
|
+
'5s': earlyFlakeDetectionRetryCount,
|
|
348
|
+
'10s': earlyFlakeDetectionRetryCount,
|
|
349
|
+
'30s': earlyFlakeDetectionRetryCount,
|
|
350
|
+
'5m': earlyFlakeDetectionRetryCount,
|
|
351
|
+
}
|
|
352
|
+
: earlyFlakeDetectionSlowTestRetries,
|
|
305
353
|
earlyFlakeDetectionFaultyThreshold,
|
|
306
354
|
isFlakyTestRetriesEnabled: isFlakyTestRetriesEnabled && testOptimization.DD_CIVISIBILITY_FLAKY_RETRY_ENABLED,
|
|
307
355
|
flakyTestRetriesCount: testOptimization.DD_CIVISIBILITY_FLAKY_RETRY_COUNT,
|
|
@@ -318,6 +366,7 @@ class CiVisibilityExporter extends BufferingExporter {
|
|
|
318
366
|
|
|
319
367
|
sendGitMetadata (repositoryUrl) {
|
|
320
368
|
if (!this._config.testOptimization.DD_CIVISIBILITY_GIT_UPLOAD_ENABLED) {
|
|
369
|
+
this._resolveGit()
|
|
321
370
|
return
|
|
322
371
|
}
|
|
323
372
|
this._canUseCiVisProtocolPromise.then((canUseCiVisProtocol) => {
|
|
@@ -475,7 +524,7 @@ class CiVisibilityExporter extends BufferingExporter {
|
|
|
475
524
|
* @param {string} options.filePath - Path to the coverage report file
|
|
476
525
|
* @param {string} options.format - Format of the coverage report
|
|
477
526
|
* @param {object} options.testEnvironmentMetadata - Test environment metadata containing git/CI tags
|
|
478
|
-
* @param {
|
|
527
|
+
* @param {(error: Error|null) => void} callback - Callback function
|
|
479
528
|
*/
|
|
480
529
|
uploadCoverageReport ({ filePath, format, testEnvironmentMetadata }, callback) {
|
|
481
530
|
if (!this._codeCoverageReportUrl) {
|
|
@@ -485,6 +534,7 @@ class CiVisibilityExporter extends BufferingExporter {
|
|
|
485
534
|
uploadCoverageReportRequest({
|
|
486
535
|
filePath,
|
|
487
536
|
format,
|
|
537
|
+
flags: this._coverageReportFlags,
|
|
488
538
|
testEnvironmentMetadata,
|
|
489
539
|
url: this._codeCoverageReportUrl,
|
|
490
540
|
isEvpProxy: !!this._isUsingEvpProxy,
|
|
@@ -102,14 +102,30 @@ class TestWorkerCiVisibilityExporter {
|
|
|
102
102
|
this._logsWriter.append({ testEnvironmentMetadata, logMessage })
|
|
103
103
|
}
|
|
104
104
|
|
|
105
|
-
|
|
105
|
+
/**
|
|
106
|
+
* @param {() => void} [onDone]
|
|
107
|
+
*/
|
|
106
108
|
flush (onDone) {
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
this._telemetryWriter
|
|
109
|
+
if (!onDone) {
|
|
110
|
+
this._writer.flush()
|
|
111
|
+
this._coverageWriter.flush()
|
|
112
|
+
this._logsWriter.flush()
|
|
113
|
+
if (this._telemetryWriter) {
|
|
114
|
+
this._telemetryWriter.flush()
|
|
115
|
+
}
|
|
116
|
+
return
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
let pendingWriters = this._telemetryWriter ? 4 : 3
|
|
120
|
+
const onWriterFlushed = () => {
|
|
121
|
+
pendingWriters--
|
|
122
|
+
if (pendingWriters === 0) onDone()
|
|
112
123
|
}
|
|
124
|
+
|
|
125
|
+
this._writer.flush(onWriterFlushed)
|
|
126
|
+
this._coverageWriter.flush(onWriterFlushed)
|
|
127
|
+
this._logsWriter.flush(onWriterFlushed)
|
|
128
|
+
this._telemetryWriter?.flush(onWriterFlushed)
|
|
113
129
|
}
|
|
114
130
|
}
|
|
115
131
|
|
|
@@ -15,6 +15,9 @@ class Writer {
|
|
|
15
15
|
this._interprocessCode = interprocessCode
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
+
/**
|
|
19
|
+
* @param {() => void} [onDone]
|
|
20
|
+
*/
|
|
18
21
|
flush (onDone) {
|
|
19
22
|
const count = this._encoder.count()
|
|
20
23
|
|
|
@@ -22,6 +25,8 @@ class Writer {
|
|
|
22
25
|
const payload = this._encoder.makePayload()
|
|
23
26
|
|
|
24
27
|
this._sendPayload(payload, onDone)
|
|
28
|
+
} else {
|
|
29
|
+
onDone?.()
|
|
25
30
|
}
|
|
26
31
|
}
|
|
27
32
|
|