dd-trace 5.132.0 → 5.134.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 +9 -14
- package/ci/init.js +1 -2
- package/ci/test-optimization-validation/ci-discovery.js +9 -4
- package/ci/test-optimization-validation/executable.js +1 -5
- package/ci/test-optimization-validation/framework-adapters/cypress.js +1 -4
- package/ci/test-optimization-validation/framework-adapters/vitest.js +1 -1
- package/ci/test-optimization-validation/generated-files.js +6 -4
- package/ci/test-optimization-validation/generated-test-contract.js +7 -3
- package/ci/test-optimization-validation/manifest-scaffold.js +7 -2
- package/ci/test-optimization-validation/manifest-schema.js +6 -7
- package/ci/test-optimization-validation/report-writer.js +6 -2
- package/ci/test-optimization-validation/runner-command.js +2 -4
- package/ci/test-optimization-validation/runner-contract.js +1 -2
- package/ci/test-optimization-validation/scenarios/auto-test-retries.js +1 -15
- package/ci/vitest-no-worker-init-runner-setup.mjs +27 -0
- package/ci/vitest-no-worker-init-setup.mjs +10 -7
- package/index.d.ts +41 -19
- package/initialize.mjs +1 -3
- package/openfeature/rules-based.d.ts +11 -0
- package/openfeature/rules-based.js +7 -0
- package/package.json +25 -22
- package/packages/datadog-core/src/storage.js +16 -0
- package/packages/datadog-esbuild/index.js +18 -12
- package/packages/datadog-instrumentations/index.js +2 -1
- package/packages/datadog-instrumentations/src/aerospike.js +3 -3
- package/packages/datadog-instrumentations/src/ai.js +3 -3
- package/packages/datadog-instrumentations/src/aws-durable-execution-sdk-js.js +0 -6
- package/packages/datadog-instrumentations/src/aws-sdk.js +21 -13
- package/packages/datadog-instrumentations/src/cassandra-driver.js +25 -20
- package/packages/datadog-instrumentations/src/claude-agent-sdk.js +9 -23
- package/packages/datadog-instrumentations/src/cucumber.js +65 -41
- package/packages/datadog-instrumentations/src/elasticsearch.js +1 -5
- package/packages/datadog-instrumentations/src/electron.js +6 -6
- package/packages/datadog-instrumentations/src/fastify.js +13 -4
- package/packages/datadog-instrumentations/src/fs.js +2 -2
- package/packages/datadog-instrumentations/src/google-cloud-pubsub.js +9 -5
- package/packages/datadog-instrumentations/src/grpc/client.js +2 -0
- package/packages/datadog-instrumentations/src/helpers/bundler-register.js +28 -9
- package/packages/datadog-instrumentations/src/helpers/check-require-cache.js +1 -2
- package/packages/datadog-instrumentations/src/helpers/hooks.js +1 -0
- package/packages/datadog-instrumentations/src/helpers/instrument.js +1 -1
- package/packages/datadog-instrumentations/src/helpers/register.js +45 -1
- package/packages/datadog-instrumentations/src/helpers/rewriter/index.js +2 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentation-registry.js +89 -6
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/graphql-jit.js +5 -6
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/jest.js +12 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/postgres.js +48 -31
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/vitest.js +8 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/webdriverio.js +7 -7
- package/packages/datadog-instrumentations/src/helpers/rewriter/targets.json +5 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/transforms/postgres.js +42 -4
- package/packages/datadog-instrumentations/src/helpers/rewriter/transforms.js +7 -10
- package/packages/datadog-instrumentations/src/ioredis.js +7 -5
- package/packages/datadog-instrumentations/src/iovalkey.js +7 -5
- package/packages/datadog-instrumentations/src/jest.js +72 -67
- package/packages/datadog-instrumentations/src/knex.js +9 -5
- package/packages/datadog-instrumentations/src/mariadb-bundle.js +3 -3
- package/packages/datadog-instrumentations/src/mocha/common.js +20 -8
- package/packages/datadog-instrumentations/src/mocha/main.js +140 -43
- package/packages/datadog-instrumentations/src/mocha/utils.js +38 -4
- package/packages/datadog-instrumentations/src/mocha/worker.js +1 -2
- package/packages/datadog-instrumentations/src/mongodb-core.js +3 -3
- package/packages/datadog-instrumentations/src/mongoose.js +3 -3
- package/packages/datadog-instrumentations/src/nova-sonic/audio.js +173 -0
- package/packages/datadog-instrumentations/src/nova-sonic/index.js +209 -0
- package/packages/datadog-instrumentations/src/nova-sonic/session.js +492 -0
- package/packages/datadog-instrumentations/src/openai-realtime/index.js +18 -6
- package/packages/datadog-instrumentations/src/openai-realtime/session.js +1 -1
- package/packages/datadog-instrumentations/src/openai.js +7 -0
- package/packages/datadog-instrumentations/src/pino.js +5 -6
- package/packages/datadog-instrumentations/src/playwright.js +2 -0
- package/packages/datadog-instrumentations/src/redis.js +27 -13
- package/packages/datadog-instrumentations/src/router.js +30 -0
- package/packages/datadog-instrumentations/src/rum-browser-scripts.js +2 -6
- package/packages/datadog-instrumentations/src/stripe.js +23 -12
- package/packages/datadog-instrumentations/src/vitest-main-no-worker-init.js +8 -5
- package/packages/datadog-instrumentations/src/vitest-main.js +16 -18
- package/packages/datadog-instrumentations/src/vitest-util.js +7 -4
- package/packages/datadog-instrumentations/src/vitest-worker.js +1 -2
- package/packages/datadog-instrumentations/src/vitest.js +9 -1
- package/packages/datadog-instrumentations/src/webdriverio.js +5 -8
- package/packages/datadog-instrumentations/src/ws.js +1 -4
- package/packages/datadog-plugin-amqplib/src/client.js +1 -2
- package/packages/datadog-plugin-avsc/src/schema_iterator.js +1 -5
- package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/handler.js +1 -2
- package/packages/datadog-plugin-aws-sdk/src/base.js +5 -8
- package/packages/datadog-plugin-aws-sdk/src/services/bedrockruntime/index.js +18 -0
- package/packages/datadog-plugin-aws-sdk/src/services/bedrockruntime/sonic.js +45 -0
- package/packages/datadog-plugin-aws-sdk/src/services/kinesis.js +9 -3
- package/packages/datadog-plugin-aws-sdk/src/services/sqs.js +2 -5
- package/packages/datadog-plugin-azure-durable-functions/src/index.js +5 -4
- package/packages/datadog-plugin-azure-functions/src/index.js +5 -3
- package/packages/datadog-plugin-claude-agent-sdk/src/index.js +0 -2
- package/packages/datadog-plugin-cypress/src/cypress-plugin.js +4 -5
- package/packages/datadog-plugin-cypress/src/index.js +6 -6
- package/packages/datadog-plugin-cypress/src/source-map-utils.js +1 -2
- package/packages/datadog-plugin-cypress/src/support.js +3 -4
- package/packages/datadog-plugin-express/src/code_origin.js +1 -2
- package/packages/datadog-plugin-google-cloud-pubsub/src/consumer.js +2 -2
- package/packages/datadog-plugin-google-cloud-pubsub/src/producer.js +1 -2
- package/packages/datadog-plugin-graphql/src/execute.js +5 -5
- package/packages/datadog-plugin-grpc/src/util.js +1 -2
- package/packages/datadog-plugin-kafkajs/src/producer.js +1 -2
- package/packages/datadog-plugin-mocha/src/index.js +7 -10
- package/packages/datadog-plugin-mocha/src/webdriverio-video.js +8 -4
- package/packages/datadog-plugin-mongodb-core/src/query.js +8 -5
- package/packages/datadog-plugin-next/src/index.js +1 -2
- package/packages/datadog-plugin-openai/src/tracing.js +5 -2
- package/packages/datadog-plugin-openai-agents/src/integration.js +6 -2
- package/packages/datadog-plugin-openai-agents/src/processor.js +2 -4
- package/packages/datadog-plugin-postgres/src/index.js +24 -0
- package/packages/datadog-plugin-protobufjs/src/schema_iterator.js +1 -5
- package/packages/datadog-plugin-rhea/src/consumer.js +1 -4
- package/packages/datadog-plugin-vitest/src/index.js +5 -2
- package/packages/datadog-shimmer/src/shimmer.js +5 -4
- package/packages/datadog-webpack/index.js +55 -43
- package/packages/datadog-webpack/src/loader.js +56 -8
- package/packages/dd-trace/src/aiguard/evaluation.js +143 -1
- package/packages/dd-trace/src/aiguard/integrations/vercel-ai.js +2 -4
- package/packages/dd-trace/src/aiguard/messages/utils.js +1 -2
- package/packages/dd-trace/src/aiguard/redaction.js +8 -2
- package/packages/dd-trace/src/appsec/api_security/normalized-route.js +3 -2
- package/packages/dd-trace/src/appsec/api_security/sampler.js +2 -6
- package/packages/dd-trace/src/appsec/downstream_requests.js +1 -9
- package/packages/dd-trace/src/appsec/handlers/http-request.js +3 -9
- package/packages/dd-trace/src/appsec/handlers/http-response.js +7 -2
- package/packages/dd-trace/src/appsec/iast/analyzers/path-traversal-analyzer.js +4 -3
- package/packages/dd-trace/src/appsec/iast/analyzers/set-cookies-header-interceptor.js +1 -4
- package/packages/dd-trace/src/appsec/iast/path-line.js +2 -1
- package/packages/dd-trace/src/appsec/iast/taint-tracking/operations-taint-object.js +7 -1
- package/packages/dd-trace/src/appsec/iast/taint-tracking/plugin.js +1 -3
- package/packages/dd-trace/src/appsec/iast/taint-tracking/rewriter-esm.mjs +1 -3
- package/packages/dd-trace/src/appsec/iast/telemetry/iast-metric.js +1 -6
- package/packages/dd-trace/src/appsec/lambda.js +1 -3
- package/packages/dd-trace/src/appsec/rasp/fs-plugin.js +37 -27
- package/packages/dd-trace/src/appsec/rasp/lfi.js +9 -5
- package/packages/dd-trace/src/appsec/reporter.js +21 -1
- package/packages/dd-trace/src/appsec/stack_trace.js +4 -4
- package/packages/dd-trace/src/ci-visibility/dynamic-atr-retries.js +3 -6
- package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/worker/index.js +3 -2
- package/packages/dd-trace/src/ci-visibility/exporters/ci-validation/msgpack-to-json.js +1 -1
- package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +13 -7
- package/packages/dd-trace/src/ci-visibility/exporters/settings-cache-key.js +1 -2
- package/packages/dd-trace/src/ci-visibility/exporters/test-worker/index.js +2 -8
- package/packages/dd-trace/src/ci-visibility/resolve-runfile-path.js +54 -0
- package/packages/dd-trace/src/ci-visibility/test-optimization-http-cache-schema.js +5 -8
- package/packages/dd-trace/src/ci-visibility/test-optimization-http-cache.js +2 -53
- package/packages/dd-trace/src/config/config-types.d.ts +2 -2
- package/packages/dd-trace/src/config/defaults.js +5 -5
- package/packages/dd-trace/src/config/generated-config-types.d.ts +10 -0
- package/packages/dd-trace/src/config/helper.js +34 -3
- package/packages/dd-trace/src/config/index.js +55 -9
- package/packages/dd-trace/src/config/normalize-service.js +1 -1
- package/packages/dd-trace/src/config/parsers.js +2 -2
- package/packages/dd-trace/src/config/supported-configurations.json +44 -0
- package/packages/dd-trace/src/datastreams/processor.js +3 -2
- package/packages/dd-trace/src/datastreams/schemas/schema_builder.js +1 -4
- package/packages/dd-trace/src/debugger/devtools_client/breakpoints.js +6 -5
- package/packages/dd-trace/src/debugger/devtools_client/condition.js +5 -5
- package/packages/dd-trace/src/debugger/devtools_client/send.js +1 -0
- package/packages/dd-trace/src/debugger/devtools_client/snapshot/collector.js +4 -6
- package/packages/dd-trace/src/debugger/devtools_client/status.js +1 -0
- package/packages/dd-trace/src/debugger/guardrail-metrics.js +2 -2
- package/packages/dd-trace/src/debugger/unsupported-runtime.js +16 -0
- package/packages/dd-trace/src/dogstatsd.js +1 -1
- package/packages/dd-trace/src/encode/tags-processors.js +2 -2
- package/packages/dd-trace/src/exporters/common/request.js +1 -5
- package/packages/dd-trace/src/id.js +14 -1
- package/packages/dd-trace/src/lambda/handler.js +179 -19
- package/packages/dd-trace/src/lambda/index.js +15 -1
- package/packages/dd-trace/src/lambda/runtime/patch.js +34 -7
- package/packages/dd-trace/src/llmobs/agent-manifest.js +19 -2
- package/packages/dd-trace/src/llmobs/audio-utils.js +3 -2
- package/packages/dd-trace/src/llmobs/constants/tags.js +15 -0
- package/packages/dd-trace/src/llmobs/experiments/experiment.js +1 -1
- package/packages/dd-trace/src/llmobs/plugins/anthropic/index.js +15 -0
- package/packages/dd-trace/src/llmobs/plugins/genai/util.js +1 -2
- package/packages/dd-trace/src/llmobs/plugins/langchain/handlers/chat_model.js +1 -3
- package/packages/dd-trace/src/llmobs/plugins/langgraph/index.js +1 -1
- package/packages/dd-trace/src/llmobs/plugins/nova-sonic.js +92 -0
- package/packages/dd-trace/src/llmobs/plugins/openai/constants.js +5 -0
- package/packages/dd-trace/src/llmobs/plugins/openai/index.js +64 -2
- package/packages/dd-trace/src/llmobs/plugins/openai/utils.js +38 -0
- package/packages/dd-trace/src/llmobs/prompts/manager.js +5 -7
- package/packages/dd-trace/src/llmobs/prompts/prompt.js +1 -1
- package/packages/dd-trace/src/llmobs/sdk.js +15 -4
- package/packages/dd-trace/src/llmobs/span_processor.js +13 -2
- package/packages/dd-trace/src/llmobs/tagger.js +20 -10
- package/packages/dd-trace/src/llmobs/util.js +11 -6
- package/packages/dd-trace/src/llmobs/writers/base.js +4 -5
- package/packages/dd-trace/src/log/log.js +1 -5
- package/packages/dd-trace/src/openfeature/evaluation-telemetry-opt-out-hook.js +51 -0
- package/packages/dd-trace/src/openfeature/flagging_provider.js +48 -21
- package/packages/dd-trace/src/openfeature/index.js +6 -0
- package/packages/dd-trace/src/openfeature/writers/util.js +4 -4
- package/packages/dd-trace/src/opentelemetry/logs/otlp_transformer.js +1 -1
- package/packages/dd-trace/src/opentelemetry/metrics/otlp_transformer.js +1 -1
- package/packages/dd-trace/src/opentelemetry/metrics/periodic_metric_reader.js +1 -2
- package/packages/dd-trace/src/opentelemetry/otlp/common.proto +1 -1
- package/packages/dd-trace/src/opentelemetry/trace/otlp_transformer.js +24 -24
- package/packages/dd-trace/src/opentracing/propagation/text_map.js +53 -13
- package/packages/dd-trace/src/otel-sampling.js +4 -5
- package/packages/dd-trace/src/otel-thread-ctx.js +79 -13
- package/packages/dd-trace/src/plugin_manager.js +1 -2
- package/packages/dd-trace/src/plugins/database.js +7 -16
- package/packages/dd-trace/src/plugins/tracing.js +0 -1
- package/packages/dd-trace/src/plugins/util/git.js +99 -59
- package/packages/dd-trace/src/plugins/util/inferred_proxy.js +1 -5
- package/packages/dd-trace/src/plugins/util/stacktrace.js +2 -1
- package/packages/dd-trace/src/plugins/util/test.js +50 -4
- package/packages/dd-trace/src/plugins/util/web.js +90 -20
- package/packages/dd-trace/src/priority_sampler.js +1 -2
- package/packages/dd-trace/src/profiling/profiler.js +4 -6
- package/packages/dd-trace/src/proxy.js +1 -2
- package/packages/dd-trace/src/ritm.js +4 -2
- package/packages/dd-trace/src/serverless.js +5 -0
- package/packages/dd-trace/src/span_processor.js +13 -17
- package/packages/dd-trace/src/span_stats.js +4 -1
- package/packages/dd-trace/src/telemetry/endpoints.js +1 -3
- package/packages/dd-trace/src/telemetry/logs/log-collector.js +1 -1
- package/packages/dd-trace/src/util.js +37 -4
- package/packages/dd-trace/src/web-tags-cache.js +6 -2
- package/vendor/dist/@apm-js-collab/code-transformer/index.js +8 -8
- package/vendor/dist/@datadog/openfeature-node-server/index.js +1 -1
- package/vendor/dist/@datadog/openfeature-node-server/rules-based/LICENSE +201 -0
- package/vendor/dist/@datadog/openfeature-node-server/rules-based/index.d.ts +925 -0
- package/vendor/dist/@datadog/openfeature-node-server/rules-based/index.js +1 -0
- 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
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
3
|
const { AsyncResource } = require('node:async_hooks')
|
|
4
|
+
const { realpathSync } = require('node:fs')
|
|
5
|
+
const { resolve, sep } = require('node:path')
|
|
6
|
+
const { fileURLToPath } = require('node:url')
|
|
4
7
|
|
|
5
8
|
const { createCoverageMap } = require('../../../../vendor/dist/istanbul-lib-coverage')
|
|
6
9
|
const satisfies = require('../../../../vendor/dist/semifies')
|
|
@@ -16,6 +19,7 @@ const { EMPTY_EFD_RETRY_POLICY } = require('../../../dd-trace/src/ci-visibility/
|
|
|
16
19
|
const { writeCoverageBackfillToCache } = require('../../../dd-trace/src/ci-visibility/test-optimization-cache')
|
|
17
20
|
const log = require('../../../dd-trace/src/log')
|
|
18
21
|
const { getEnvironmentVariable } = require('../../../dd-trace/src/config/helper')
|
|
22
|
+
const { getCallSites } = require('../../../dd-trace/src/plugins/util/stacktrace')
|
|
19
23
|
const {
|
|
20
24
|
getTestSuitePath,
|
|
21
25
|
MOCHA_WORKER_TELEMETRY_PAYLOAD_CODE,
|
|
@@ -88,6 +92,9 @@ const runnerRecoveryStates = new WeakMap()
|
|
|
88
92
|
const runnersWithPendingCoverageReset = new WeakSet()
|
|
89
93
|
const parallelRunners = new WeakSet()
|
|
90
94
|
const wrappedRunnerEmitPrototypes = new WeakSet()
|
|
95
|
+
const rootSuitesWithHookFiles = new WeakSet()
|
|
96
|
+
const ROOT_HOOK_NAMES = ['_beforeAll', '_afterAll', '_beforeEach', '_afterEach']
|
|
97
|
+
const ROOT_SUITE_ARRAY_NAMES = ['tests', 'suites', '_onlyTests', '_onlySuites', ...ROOT_HOOK_NAMES]
|
|
91
98
|
let hasWarnedDeprecatedMochaVersion = false
|
|
92
99
|
|
|
93
100
|
const unskippableSuites = []
|
|
@@ -169,6 +176,40 @@ function haveRootTestsFinished (rootTests) {
|
|
|
169
176
|
return true
|
|
170
177
|
}
|
|
171
178
|
|
|
179
|
+
/**
|
|
180
|
+
* @param {{ file: string }} test
|
|
181
|
+
*/
|
|
182
|
+
function startRootSuite (test) {
|
|
183
|
+
const ctx = testFileToSuiteCtx.get(test.file)
|
|
184
|
+
if (ctx?._pendingRootStart) {
|
|
185
|
+
ctx._pendingRootStart = false
|
|
186
|
+
testSuiteStartCh.runStores(ctx, () => {})
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* @param {string} file
|
|
192
|
+
* @param {'pass' | 'fail' | 'skip'} status
|
|
193
|
+
* @param {object} [runner] Defers coverage reset until this runner's suite-end reporters finish.
|
|
194
|
+
*/
|
|
195
|
+
function finishRootSuite (file, status, runner) {
|
|
196
|
+
const ctx = testFileToSuiteCtx.get(file)
|
|
197
|
+
if (!ctx) {
|
|
198
|
+
log.warn('No ctx found for suite', file)
|
|
199
|
+
return
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
if (global.__coverage__) {
|
|
203
|
+
const coverageFiles = getCoveredFilesFromCoverage(global.__coverage__)
|
|
204
|
+
testSuiteCodeCoverageCh.publish({ coverageFiles, suiteFile: file })
|
|
205
|
+
mergeCoverage(global.__coverage__, originalCoverageMap)
|
|
206
|
+
if (runner) runnersWithPendingCoverageReset.add(runner)
|
|
207
|
+
else resetCoverage(global.__coverage__)
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
testSuiteFinishCh.publish({ status, ...ctx.currentStore }, () => {})
|
|
211
|
+
}
|
|
212
|
+
|
|
172
213
|
function getSuitePath (suite) {
|
|
173
214
|
return getTestSuitePath(suite.file, process.cwd())
|
|
174
215
|
}
|
|
@@ -931,6 +972,23 @@ function getExecutionConfiguration (runner, isParallel, frameworkVersion, onFini
|
|
|
931
972
|
return
|
|
932
973
|
}
|
|
933
974
|
|
|
975
|
+
if (runner._opts?.cleanReferencesAfterRun === false) {
|
|
976
|
+
const rootSuite = runner.suite
|
|
977
|
+
const originalArrays = ROOT_SUITE_ARRAY_NAMES.map(name => rootSuite[name])
|
|
978
|
+
// Restore definitions before completion callbacks can start another run, including with the plugin disabled.
|
|
979
|
+
runner.prependOnceListener('end', () => {
|
|
980
|
+
for (let index = 0; index < ROOT_SUITE_ARRAY_NAMES.length; index++) {
|
|
981
|
+
rootSuite[ROOT_SUITE_ARRAY_NAMES[index]] = originalArrays[index]
|
|
982
|
+
}
|
|
983
|
+
})
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
// Root hooks must be selected before .only() removes other files from the test tree.
|
|
987
|
+
for (const name of ROOT_HOOK_NAMES) {
|
|
988
|
+
runner.suite[name] = runner.suite[name].filter(hook => !hook.file ||
|
|
989
|
+
!suitesToSkip.includes(getSuitePath(hook)) || unskippableSuites.includes(hook.file))
|
|
990
|
+
}
|
|
991
|
+
|
|
934
992
|
// We remove the suites that we skip through ITR
|
|
935
993
|
// Mocha normally applies exclusivity after this asynchronous configuration step.
|
|
936
994
|
if (typeof runner.suite.hasOnly === 'function') {
|
|
@@ -940,17 +998,28 @@ function getExecutionConfiguration (runner, isParallel, frameworkVersion, onFini
|
|
|
940
998
|
}
|
|
941
999
|
const numTestsToRun = runner.grepTotal(runner.suite)
|
|
942
1000
|
const filteredSuites = getFilteredSuites(runner.suite.suites)
|
|
1001
|
+
// Root tests share a Mocha suite across files, but TIA still selects them by file.
|
|
1002
|
+
const filteredRootTests = getFilteredSuites(runner.suite.tests)
|
|
943
1003
|
const { suitesToRun, suitesToSkipForRun } = filteredSuites
|
|
944
1004
|
|
|
945
|
-
isSuitesSkipped = suitesToRun.length !== runner.suite.suites.length
|
|
1005
|
+
isSuitesSkipped = suitesToRun.length !== runner.suite.suites.length ||
|
|
1006
|
+
filteredRootTests.suitesToRun.length !== runner.suite.tests.length
|
|
946
1007
|
|
|
947
1008
|
log.debug('%d out of %d suites are going to run.', suitesToRun.length, runner.suite.suites.length)
|
|
948
1009
|
|
|
949
1010
|
runner.suite.suites = suitesToRun
|
|
1011
|
+
runner.suite.tests = filteredRootTests.suitesToRun
|
|
1012
|
+
// Mocha reapplies exclusivity when the delayed runner resumes.
|
|
1013
|
+
if (runner.suite._onlyTests.length) {
|
|
1014
|
+
runner.suite._onlyTests = runner.suite.tests
|
|
1015
|
+
}
|
|
1016
|
+
if (runner.suite._onlySuites.length) {
|
|
1017
|
+
runner.suite._onlySuites = runner.suite._onlySuites.filter(suite => suitesToRun.includes(suite))
|
|
1018
|
+
}
|
|
950
1019
|
areAllSuitesSkipped = isSuitesSkipped && numTestsToRun > 0 && runner.grepTotal(runner.suite) === 0
|
|
951
1020
|
|
|
952
|
-
skippedSuites = [...filteredSuites.skippedSuites]
|
|
953
|
-
suitesToSkip = suitesToSkipForRun
|
|
1021
|
+
skippedSuites = [...new Set([...filteredSuites.skippedSuites, ...filteredRootTests.skippedSuites])]
|
|
1022
|
+
suitesToSkip = [...new Set([...suitesToSkipForRun, ...filteredRootTests.suitesToSkipForRun])]
|
|
954
1023
|
skippedSuitesCoverage = getSkippedSuitesCoverageForRun()
|
|
955
1024
|
writeCoverageBackfillToCache(skippedSuitesCoverage, getCoverageRootDir())
|
|
956
1025
|
|
|
@@ -1095,11 +1164,51 @@ function runMochaRunner (run, runner, args) {
|
|
|
1095
1164
|
/**
|
|
1096
1165
|
* @param {Function} Mocha
|
|
1097
1166
|
* @param {string} frameworkVersion
|
|
1167
|
+
* @param {import('./common').MochaHookMeta} hookMeta
|
|
1098
1168
|
* @returns {Function}
|
|
1099
1169
|
*/
|
|
1100
|
-
function wrapMochaRun (Mocha, frameworkVersion) {
|
|
1170
|
+
function wrapMochaRun (Mocha, frameworkVersion, hookMeta) {
|
|
1101
1171
|
warnDeprecatedMochaVersion(frameworkVersion)
|
|
1102
1172
|
|
|
1173
|
+
// Shimmer is required to attach listeners to the root suite created by each Mocha instance.
|
|
1174
|
+
shimmer.wrap(Mocha.prototype, 'ui', ui => function (...args) {
|
|
1175
|
+
const result = ui.apply(this, args)
|
|
1176
|
+
const suite = this.suite
|
|
1177
|
+
if (!rootSuitesWithHookFiles.has(suite)) {
|
|
1178
|
+
rootSuitesWithHookFiles.add(suite)
|
|
1179
|
+
let file
|
|
1180
|
+
suite.prependListener('pre-require', (context, testFile) => {
|
|
1181
|
+
file = testFile
|
|
1182
|
+
})
|
|
1183
|
+
suite.on('post-require', () => {
|
|
1184
|
+
file = undefined
|
|
1185
|
+
})
|
|
1186
|
+
// Hook objects are created at runtime; inspect the caller while registration is still on the stack.
|
|
1187
|
+
shimmer.wrap(suite, '_createHook', createHook => function createHookWithFile (...args) {
|
|
1188
|
+
const hook = createHook.apply(this, args)
|
|
1189
|
+
if (file && !hook.file) {
|
|
1190
|
+
try {
|
|
1191
|
+
const { moduleBaseDir } = hookMeta
|
|
1192
|
+
const mochaPathPrefix = moduleBaseDir && moduleBaseDir + sep
|
|
1193
|
+
for (const site of getCallSites(createHookWithFile)) {
|
|
1194
|
+
const source = site.getFileName()
|
|
1195
|
+
if (!source || source.startsWith('node:')) continue
|
|
1196
|
+
const sourceFile = source.startsWith('file:') ? fileURLToPath(source) : source
|
|
1197
|
+
if (mochaPathPrefix && sourceFile.startsWith(mochaPathPrefix)) continue
|
|
1198
|
+
// Helpers register shared setup; only direct declarations belong to the loading test file.
|
|
1199
|
+
if (sourceFile === file || realpathSync(sourceFile) === realpathSync(file)) hook.file = file
|
|
1200
|
+
break
|
|
1201
|
+
}
|
|
1202
|
+
} catch (error) {
|
|
1203
|
+
log.debug('Error capturing Mocha root hook source: %s', error)
|
|
1204
|
+
}
|
|
1205
|
+
}
|
|
1206
|
+
return hook
|
|
1207
|
+
})
|
|
1208
|
+
}
|
|
1209
|
+
return result
|
|
1210
|
+
})
|
|
1211
|
+
|
|
1103
1212
|
// Shimmer is required because run must return its Runner while execution is paused and resumed after configuration.
|
|
1104
1213
|
shimmer.wrap(Mocha.prototype, 'run', run => function (...args) {
|
|
1105
1214
|
// Workers do not need to request any data, just run the tests
|
|
@@ -1123,7 +1232,7 @@ function wrapMochaRun (Mocha, frameworkVersion) {
|
|
|
1123
1232
|
this.files.forEach((path) => {
|
|
1124
1233
|
const isUnskippable = isMarkedAsUnskippable({ path })
|
|
1125
1234
|
if (isUnskippable) {
|
|
1126
|
-
unskippableSuites.push(path)
|
|
1235
|
+
unskippableSuites.push(resolve(path))
|
|
1127
1236
|
}
|
|
1128
1237
|
})
|
|
1129
1238
|
|
|
@@ -1200,8 +1309,7 @@ addHook({
|
|
|
1200
1309
|
filePattern: String.raw`lib/runner\.(?:c?js)$`,
|
|
1201
1310
|
}, function (runnerPackage, frameworkVersion) {
|
|
1202
1311
|
const Runner = runnerPackage.Runner ?? runnerPackage.default ?? runnerPackage
|
|
1203
|
-
if (typeof Runner !== 'function') return
|
|
1204
|
-
if (patched.has(Runner)) return
|
|
1312
|
+
if (typeof Runner !== 'function' || patched.has(Runner)) return
|
|
1205
1313
|
|
|
1206
1314
|
patched.add(Runner)
|
|
1207
1315
|
wrapRunnerEmit(Runner)
|
|
@@ -1226,12 +1334,12 @@ addHook({
|
|
|
1226
1334
|
runnerFrameworkErrors.delete(this)
|
|
1227
1335
|
}
|
|
1228
1336
|
|
|
1229
|
-
|
|
1337
|
+
let { suitesByTestFile, numSuitesByTestFile } = getSuitesByTestFile(this.suite)
|
|
1230
1338
|
// Root-level tests (direct children of root, no describe wrapper) keyed by file.
|
|
1231
1339
|
// Populated during the root 'suite' event so the normal finish path can include them
|
|
1232
1340
|
// in mixed-file status calculation.
|
|
1233
1341
|
const rootTestsByFile = new Map()
|
|
1234
|
-
// Counts how many
|
|
1342
|
+
// Counts how many selected tests per pure-root file still need their final attempt.
|
|
1235
1343
|
// Hits zero when the last test's lifecycle completes, triggering the suite finish.
|
|
1236
1344
|
const rootPendingCountByFile = new Map()
|
|
1237
1345
|
const rootFinalizationPendingCountByFile = new Map()
|
|
@@ -1304,33 +1412,18 @@ addHook({
|
|
|
1304
1412
|
}
|
|
1305
1413
|
rootPendingCountByFile.delete(file)
|
|
1306
1414
|
|
|
1307
|
-
const ctx = testFileToSuiteCtx.get(file)
|
|
1308
|
-
if (!ctx) {
|
|
1309
|
-
log.warn('No ctx found for suite', file)
|
|
1310
|
-
return
|
|
1311
|
-
}
|
|
1312
|
-
|
|
1313
1415
|
const rootTests = rootTestsByFile.get(file) || []
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
if (global.__coverage__) {
|
|
1317
|
-
const coverageFiles = getCoveredFilesFromCoverage(global.__coverage__)
|
|
1318
|
-
testSuiteCodeCoverageCh.publish({ coverageFiles, suiteFile: file })
|
|
1319
|
-
mergeCoverage(global.__coverage__, originalCoverageMap)
|
|
1320
|
-
resetCoverage(global.__coverage__)
|
|
1321
|
-
}
|
|
1322
|
-
|
|
1323
|
-
testSuiteFinishCh.publish({ status, ...ctx.currentStore }, () => {})
|
|
1416
|
+
finishRootSuite(file, getRootSuiteStatus(rootTests))
|
|
1324
1417
|
}
|
|
1325
1418
|
|
|
1326
|
-
function finishRootSuiteFallbackForFile (file) {
|
|
1419
|
+
function finishRootSuiteFallbackForFile (file, runner) {
|
|
1327
1420
|
const ctx = testFileToSuiteCtx.get(file)
|
|
1328
1421
|
if (!ctx || !rootPendingCountByFile.has(file)) return
|
|
1329
1422
|
|
|
1330
1423
|
const rootTests = rootTestsByFile.get(file) || []
|
|
1331
1424
|
const status = haveRootTestsFinished(rootTests) ? getRootSuiteStatus(rootTests) : 'fail'
|
|
1332
1425
|
rootPendingCountByFile.delete(file)
|
|
1333
|
-
|
|
1426
|
+
finishRootSuite(file, status, runner)
|
|
1334
1427
|
}
|
|
1335
1428
|
|
|
1336
1429
|
function finishRootSuiteAfterFinalAttempt (test) {
|
|
@@ -1361,13 +1454,7 @@ addHook({
|
|
|
1361
1454
|
this.prependListener('test', getOnTestHandler(true))
|
|
1362
1455
|
// Reporters are registered before Runner#run, so prepend this after the test handler
|
|
1363
1456
|
// to start the suite first and preserve the test event if a reporter throws.
|
|
1364
|
-
this.prependListener('test',
|
|
1365
|
-
const ctx = testFileToSuiteCtx.get(test.file)
|
|
1366
|
-
if (ctx?._pendingRootStart) {
|
|
1367
|
-
ctx._pendingRootStart = false
|
|
1368
|
-
testSuiteStartCh.runStores(ctx, () => {})
|
|
1369
|
-
}
|
|
1370
|
-
})
|
|
1457
|
+
this.prependListener('test', startRootSuite)
|
|
1371
1458
|
|
|
1372
1459
|
// Reporters are registered before this run wrapper. Prepend terminal handlers
|
|
1373
1460
|
// so a reporter error cannot strand a test span that Mocha already completed.
|
|
@@ -1407,8 +1494,16 @@ addHook({
|
|
|
1407
1494
|
this.prependListener('fail', getOnFailHandler(true, config))
|
|
1408
1495
|
|
|
1409
1496
|
this.prependListener('pending', getOnPendingHandler())
|
|
1497
|
+
// Static skips emit 'pending' without 'test', so their suite must start here too.
|
|
1498
|
+
this.prependListener('pending', startRootSuite)
|
|
1410
1499
|
|
|
1411
1500
|
this.prependListener('suite', function (suite) {
|
|
1501
|
+
if (suite.root) {
|
|
1502
|
+
// TIA and .only() have now selected the suites that can finish during this run.
|
|
1503
|
+
const selectedSuites = getSuitesByTestFile(suite)
|
|
1504
|
+
suitesByTestFile = selectedSuites.suitesByTestFile
|
|
1505
|
+
numSuitesByTestFile = selectedSuites.numSuitesByTestFile
|
|
1506
|
+
}
|
|
1412
1507
|
if (suite.root || !suite.tests.length) {
|
|
1413
1508
|
// This branch can be triggered when we have top level it(...) inside test files.
|
|
1414
1509
|
// In that case, they all (even if they are from different files) are going to be
|
|
@@ -1418,9 +1513,14 @@ addHook({
|
|
|
1418
1513
|
// below. Since 'suite' fires for root first, in the mixed case the ctx is created
|
|
1419
1514
|
// here and the describe-based handler finds it already set.
|
|
1420
1515
|
if (suite.root && suite.tests.length > 0) {
|
|
1516
|
+
const { _grep: grep, _invert: invert } = this
|
|
1517
|
+
const lastIndex = grep.lastIndex
|
|
1518
|
+
const selectedTests = suite.tests.filter(test => grep.test(test.fullTitle()) !== !!invert)
|
|
1519
|
+
// Inspecting a stateful grep must not change which tests Mocha subsequently selects.
|
|
1520
|
+
if (grep.lastIndex !== lastIndex) grep.lastIndex = lastIndex
|
|
1421
1521
|
const files = new Set(suite.tests.map(test => test.file).filter(Boolean))
|
|
1422
1522
|
for (const file of files) {
|
|
1423
|
-
const testsForFile =
|
|
1523
|
+
const testsForFile = selectedTests.filter(t => t.file === file)
|
|
1424
1524
|
rootTestsByFile.set(file, testsForFile)
|
|
1425
1525
|
// Only track the countdown for pure root-level files.
|
|
1426
1526
|
// Mixed files are finished by the normal 'suite end' path.
|
|
@@ -1461,10 +1561,10 @@ addHook({
|
|
|
1461
1561
|
this.prependListener('suite end', function (suite) {
|
|
1462
1562
|
if (suite.root) {
|
|
1463
1563
|
// Normal case: pure root-level files are finished by the 'test end' / 'hook end'
|
|
1464
|
-
// listeners via finishRootSuiteForFile.
|
|
1564
|
+
// listeners via finishRootSuiteForFile. Fallback cases remain here:
|
|
1465
1565
|
//
|
|
1466
|
-
// 1.
|
|
1467
|
-
// Start and immediately finish with 'skip'.
|
|
1566
|
+
// 1. Unstarted file: no 'test' or 'pending' event fired (e.g. all tests
|
|
1567
|
+
// were filtered out). Start and immediately finish with 'skip'.
|
|
1468
1568
|
//
|
|
1469
1569
|
// 2. Aborted mid-run (e.g. a beforeEach hook failure): Mocha skips remaining
|
|
1470
1570
|
// tests and jumps straight to 'suite end'. rootPendingCountByFile still has
|
|
@@ -1491,7 +1591,7 @@ addHook({
|
|
|
1491
1591
|
continue
|
|
1492
1592
|
}
|
|
1493
1593
|
|
|
1494
|
-
finishRootSuiteFallbackForFile(test.file)
|
|
1594
|
+
finishRootSuiteFallbackForFile(test.file, this)
|
|
1495
1595
|
}
|
|
1496
1596
|
}
|
|
1497
1597
|
return
|
|
@@ -1584,10 +1684,7 @@ addHook({
|
|
|
1584
1684
|
file: 'src/WorkerHandler.js',
|
|
1585
1685
|
}, (workerHandlerPackage) => {
|
|
1586
1686
|
shimmer.wrap(workerHandlerPackage.prototype, 'exec', exec => function (_, path) {
|
|
1587
|
-
if (!testFinishCh.hasSubscribers) {
|
|
1588
|
-
return exec.apply(this, arguments)
|
|
1589
|
-
}
|
|
1590
|
-
if (!path?.length) {
|
|
1687
|
+
if (!testFinishCh.hasSubscribers || !path?.length) {
|
|
1591
1688
|
return exec.apply(this, arguments)
|
|
1592
1689
|
}
|
|
1593
1690
|
const [testSuiteAbsolutePath] = path
|
|
@@ -43,6 +43,9 @@ const originalEfdFns = new WeakMap()
|
|
|
43
43
|
const originalFns = new WeakMap()
|
|
44
44
|
const originalPendingByTest = new WeakMap()
|
|
45
45
|
const originalRetriesByTest = new WeakMap()
|
|
46
|
+
let automaticRetryTests = new WeakSet()
|
|
47
|
+
let automaticRetryAttempts = new WeakMap()
|
|
48
|
+
let nativeRetryTests = new WeakSet()
|
|
46
49
|
const testToStartLine = new WeakMap()
|
|
47
50
|
const testFileToSuiteCtx = new Map()
|
|
48
51
|
const datadogRetryOriginals = new WeakMap()
|
|
@@ -346,6 +349,9 @@ function resetRunState (rootSuite) {
|
|
|
346
349
|
efdRetryCountByTestFullName.clear()
|
|
347
350
|
efdSlowAbortedTests.clear()
|
|
348
351
|
suiteHookFailureSuites.clear()
|
|
352
|
+
automaticRetryTests = new WeakSet()
|
|
353
|
+
automaticRetryAttempts = new WeakMap()
|
|
354
|
+
nativeRetryTests = new WeakSet()
|
|
349
355
|
dynamicAtrRetryCountByTest = new WeakMap()
|
|
350
356
|
attemptToFixExecutions.clear()
|
|
351
357
|
loggedAttemptToFixTests.clear()
|
|
@@ -423,7 +429,7 @@ function isMochaRetry (test) {
|
|
|
423
429
|
}
|
|
424
430
|
|
|
425
431
|
function getIsLastRetry (test) {
|
|
426
|
-
return test._currentRetry
|
|
432
|
+
return test._currentRetry >= test._retries
|
|
427
433
|
}
|
|
428
434
|
|
|
429
435
|
function getTestFullName (test) {
|
|
@@ -534,6 +540,18 @@ function inheritDatadogPropertiesFromRetriedTest (test) {
|
|
|
534
540
|
|
|
535
541
|
function runnableWrapper (RunnablePackage, libraryConfig) {
|
|
536
542
|
const Runnable = RunnablePackage.Runnable ?? RunnablePackage
|
|
543
|
+
// Mocha 12 can load its ESM class through require() without the ESM rewriter.
|
|
544
|
+
// Intercept the exported prototype to observe setters in both injection modes.
|
|
545
|
+
shimmer.wrap(Runnable.prototype, 'retries', retries => function () {
|
|
546
|
+
if (arguments.length && automaticRetryTests.delete(this)) {
|
|
547
|
+
const originalTest = this._retriedTest || this
|
|
548
|
+
// afterEach can change this runnable after Mocha has already copied its budget into a queued clone.
|
|
549
|
+
if (!automaticRetryAttempts.get(originalTest)?.has(this._currentRetry + 1)) {
|
|
550
|
+
nativeRetryTests.add(originalTest)
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
return retries.apply(this, arguments)
|
|
554
|
+
})
|
|
537
555
|
shimmer.wrap(Runnable.prototype, 'run', run => function (...args) {
|
|
538
556
|
if (!testFinishCh.hasSubscribers) {
|
|
539
557
|
return run.apply(this, args)
|
|
@@ -559,11 +577,12 @@ function runnableWrapper (RunnablePackage, libraryConfig) {
|
|
|
559
577
|
return onRunnableFinished.apply(this, arguments)
|
|
560
578
|
}
|
|
561
579
|
}
|
|
562
|
-
} else if (libraryConfig?.isFlakyTestRetriesEnabled) {
|
|
580
|
+
} else if (libraryConfig?.isFlakyTestRetriesEnabled && !nativeRetryTests.has(test._retriedTest || test)) {
|
|
563
581
|
if (!originalRetriesByTest.has(test)) {
|
|
564
582
|
const originalRetries = originalRetriesByTest.get(test._retriedTest) ?? test.retries()
|
|
565
583
|
originalRetriesByTest.set(test, originalRetries)
|
|
566
584
|
}
|
|
585
|
+
automaticRetryTests.delete(test)
|
|
567
586
|
if (libraryConfig.isDynamicAtrEnabled) {
|
|
568
587
|
// Dynamic ATR: set the max possible retries initially.
|
|
569
588
|
// The actual duration-based count is computed after the first attempt.
|
|
@@ -581,6 +600,7 @@ function runnableWrapper (RunnablePackage, libraryConfig) {
|
|
|
581
600
|
} else {
|
|
582
601
|
test.retries(libraryConfig.flakyTestRetriesCount)
|
|
583
602
|
}
|
|
603
|
+
automaticRetryTests.add(test)
|
|
584
604
|
}
|
|
585
605
|
|
|
586
606
|
if (isTestHook || this.type === 'test') {
|
|
@@ -833,6 +853,7 @@ function getTestFinishInfo (test, status, config, error) {
|
|
|
833
853
|
|
|
834
854
|
const isAttemptToFixRetry = test._ddIsAttemptToFix && testStatuses.length > 1
|
|
835
855
|
const isAtrRetry = config.isFlakyTestRetriesEnabled &&
|
|
856
|
+
automaticRetryAttempts.get(test._retriedTest || test)?.has(test._currentRetry) &&
|
|
836
857
|
!test._ddIsAttemptToFix &&
|
|
837
858
|
!test._ddIsEfdRetry
|
|
838
859
|
const isFinalAttempt = isTerminalHookFailure || status !== 'fail' || test._currentRetry >= test._retries
|
|
@@ -899,6 +920,7 @@ function getOnTestEndHandler (config, finalAttemptHandlers) {
|
|
|
899
920
|
const shouldFinishTest = ctx && (
|
|
900
921
|
test._ddReporterStartFailed ||
|
|
901
922
|
test._ddReporterTerminalFailed ||
|
|
923
|
+
!test.fn ||
|
|
902
924
|
!getAfterEachHooks(test).length ||
|
|
903
925
|
(test._ddIsDisabled && !test._ddIsAttemptToFix)
|
|
904
926
|
)
|
|
@@ -906,8 +928,8 @@ function getOnTestEndHandler (config, finalAttemptHandlers) {
|
|
|
906
928
|
let testFinishInfo
|
|
907
929
|
let isFinalAttempt = false
|
|
908
930
|
|
|
909
|
-
// If there are afterEach to
|
|
910
|
-
//
|
|
931
|
+
// If there are afterEach hooks to run, we don't finish the test yet.
|
|
932
|
+
// Static skips have no function and never execute hooks. Disabled tests also finish immediately.
|
|
911
933
|
// In older mocha versions, pending tests don't run afterEach hooks, so we can't rely on
|
|
912
934
|
// getOnHookEndHandler to finish the test. This mirrors Jest's approach where the skip handler
|
|
913
935
|
// directly sets finalStatus without waiting for hooks
|
|
@@ -1177,9 +1199,20 @@ function getOnFailHandler (isMain, config) {
|
|
|
1177
1199
|
|
|
1178
1200
|
function getOnTestRetryHandler (config) {
|
|
1179
1201
|
return function (test, err) {
|
|
1202
|
+
if (automaticRetryTests.has(test)) {
|
|
1203
|
+
// Mocha has already queued the clone; later budget changes cannot change its scheduling owner.
|
|
1204
|
+
const originalTest = test._retriedTest || test
|
|
1205
|
+
let attempts = automaticRetryAttempts.get(originalTest)
|
|
1206
|
+
if (!attempts) {
|
|
1207
|
+
attempts = new Set()
|
|
1208
|
+
automaticRetryAttempts.set(originalTest, attempts)
|
|
1209
|
+
}
|
|
1210
|
+
attempts.add(test._currentRetry + 1)
|
|
1211
|
+
}
|
|
1180
1212
|
const isFirstAttempt = test._currentRetry === 0
|
|
1181
1213
|
const isDynamicAtrTest = config.isDynamicAtrEnabled &&
|
|
1182
1214
|
config.isFlakyTestRetriesEnabled &&
|
|
1215
|
+
automaticRetryTests.has(test) &&
|
|
1183
1216
|
!test._ddIsAttemptToFix &&
|
|
1184
1217
|
!isEarlyFlakeDetectionTest(test, config)
|
|
1185
1218
|
if (isDynamicAtrTest && isFirstAttempt) {
|
|
@@ -1216,6 +1249,7 @@ function publishTestRetry (test, err, config) {
|
|
|
1216
1249
|
const willBeRetried = test._currentRetry < test._retries
|
|
1217
1250
|
const isAtrRetry = !isFirstAttempt &&
|
|
1218
1251
|
config.isFlakyTestRetriesEnabled &&
|
|
1252
|
+
automaticRetryAttempts.get(test._retriedTest || test)?.has(test._currentRetry) &&
|
|
1219
1253
|
!test._ddIsAttemptToFix &&
|
|
1220
1254
|
!test._ddIsEfdRetry
|
|
1221
1255
|
const promises = {}
|
|
@@ -443,8 +443,7 @@ addHook({
|
|
|
443
443
|
filePattern: String.raw`lib/runner\.(?:c?js)$`,
|
|
444
444
|
}, function (runnerPackage) {
|
|
445
445
|
const Runner = runnerPackage.Runner ?? runnerPackage.default ?? runnerPackage
|
|
446
|
-
if (typeof Runner !== 'function') return
|
|
447
|
-
if (patchedRunners.has(Runner)) return
|
|
446
|
+
if (typeof Runner !== 'function' || patchedRunners.has(Runner)) return
|
|
448
447
|
|
|
449
448
|
patchedRunners.add(Runner)
|
|
450
449
|
shimmer.wrap(Runner.prototype, 'runTests', runTests => getRunTestsWrapper(runTests, config))
|
|
@@ -45,7 +45,7 @@ addHook({ name: 'mongodb', versions: ['>=4.6.0 <6.4.0'], file: 'lib/cmap/connect
|
|
|
45
45
|
|
|
46
46
|
addHook({ name: 'mongodb', versions: ['>=6.4.0'], file: 'lib/cmap/connection.js' }, Connection => {
|
|
47
47
|
const proto = Connection.Connection.prototype
|
|
48
|
-
shimmer.wrap(proto, 'command', command => wrapConnectionCommand(command, 'command',
|
|
48
|
+
shimmer.wrap(proto, 'command', command => wrapConnectionCommand(command, 'command', instrumentPromise))
|
|
49
49
|
})
|
|
50
50
|
|
|
51
51
|
addHook({ name: 'mongodb', versions: ['>=3.3 <4'], file: 'lib/core/wireprotocol/index.js' }, wp => wrapWp(wp))
|
|
@@ -95,8 +95,8 @@ function wrapUnifiedCommand (command, operation, name) {
|
|
|
95
95
|
}
|
|
96
96
|
}
|
|
97
97
|
|
|
98
|
-
function wrapConnectionCommand (command, operation,
|
|
99
|
-
const opts = { name }
|
|
98
|
+
function wrapConnectionCommand (command, operation, instrumentFn = instrument) {
|
|
99
|
+
const opts = { name: undefined }
|
|
100
100
|
return function (ns, ops) {
|
|
101
101
|
if (!startCh.hasSubscribers) {
|
|
102
102
|
return command.apply(this, arguments)
|
|
@@ -83,7 +83,7 @@ addHook({
|
|
|
83
83
|
|
|
84
84
|
let callbackWrapped = false
|
|
85
85
|
|
|
86
|
-
const wrapCallbackIfExist = (args
|
|
86
|
+
const wrapCallbackIfExist = (args) => {
|
|
87
87
|
const lastArgumentIndex = args.length - 1
|
|
88
88
|
|
|
89
89
|
if (typeof args[lastArgumentIndex] === 'function') {
|
|
@@ -106,7 +106,7 @@ addHook({
|
|
|
106
106
|
}
|
|
107
107
|
|
|
108
108
|
return startCh.runStores(ctx, () => {
|
|
109
|
-
wrapCallbackIfExist(args
|
|
109
|
+
wrapCallbackIfExist(args)
|
|
110
110
|
|
|
111
111
|
// A callback query executes synchronously inside `method.apply`, so the
|
|
112
112
|
// exec scope (and the deferred driver call it spawns) must be entered
|
|
@@ -121,7 +121,7 @@ addHook({
|
|
|
121
121
|
shimmer.wrap(res, 'exec', originalExec => {
|
|
122
122
|
return function wrappedExec (...args) {
|
|
123
123
|
if (!callbackWrapped) {
|
|
124
|
-
wrapCallbackIfExist(args
|
|
124
|
+
wrapCallbackIfExist(args)
|
|
125
125
|
}
|
|
126
126
|
|
|
127
127
|
const execResult = execCh.runStores(ctx, () => originalExec.apply(this, args))
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { LLMOBS_AUDIO_ACCUMULATE_MAX_BYTES } = require('../../../dd-trace/src/llmobs/constants/audio')
|
|
4
|
+
|
|
5
|
+
// Leave room for the WAV header when the retained PCM is encoded.
|
|
6
|
+
const MAX_AUDIO = LLMOBS_AUDIO_ACCUMULATE_MAX_BYTES - 128
|
|
7
|
+
const RATES = new Set([8000, 16_000, 24_000, 48_000])
|
|
8
|
+
const INVALID_BASE64 = /[^A-Za-z0-9+/]/
|
|
9
|
+
|
|
10
|
+
/** @param {object} configuration */
|
|
11
|
+
function pcmRate (configuration = {}) {
|
|
12
|
+
return configuration.mediaType === 'audio/lpcm' && configuration.sampleSizeBits === 16 &&
|
|
13
|
+
configuration.channelCount === 1 && configuration.encoding === 'base64' &&
|
|
14
|
+
RATES.has(configuration.sampleRateHertz)
|
|
15
|
+
? configuration.sampleRateHertz
|
|
16
|
+
: 0
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** @param {string} encoded */
|
|
20
|
+
function decodedLength (encoded) {
|
|
21
|
+
if (typeof encoded !== 'string' || encoded.length % 4) return
|
|
22
|
+
const padding = encoded.endsWith('==') ? 2 : encoded.endsWith('=') ? 1 : 0
|
|
23
|
+
if (INVALID_BASE64.test(encoded.slice(0, encoded.length - padding))) return
|
|
24
|
+
return encoded.length / 4 * 3 - padding
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** A bounded, connection-wide sample timeline. Content-name changes do not reset its origin. */
|
|
28
|
+
class InputAudio {
|
|
29
|
+
rate = 0
|
|
30
|
+
valid = true
|
|
31
|
+
total = 0
|
|
32
|
+
anchor
|
|
33
|
+
#chunks = []
|
|
34
|
+
#bytes = 0
|
|
35
|
+
|
|
36
|
+
/** @param {object} configuration */
|
|
37
|
+
configure (configuration) {
|
|
38
|
+
const rate = pcmRate(configuration)
|
|
39
|
+
if (!rate || (this.total && rate !== this.rate)) this.valid = false
|
|
40
|
+
this.rate = rate
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* @param {string} encoded
|
|
45
|
+
* @param {number} now Milliseconds at SDK consumption, not an acknowledgement from the provider.
|
|
46
|
+
*/
|
|
47
|
+
append (encoded, now) {
|
|
48
|
+
const length = decodedLength(encoded)
|
|
49
|
+
if (length === undefined) {
|
|
50
|
+
this.valid = false
|
|
51
|
+
this.clear()
|
|
52
|
+
return
|
|
53
|
+
}
|
|
54
|
+
if (length % 2) this.valid = false
|
|
55
|
+
this.total += length
|
|
56
|
+
if (this.rate) this.anchor = now - this.total * 1000 / (2 * this.rate)
|
|
57
|
+
if (!this.valid || !length) return
|
|
58
|
+
// Decode from a base64 quartet boundary, retaining at most two alignment bytes
|
|
59
|
+
// beyond the cap instead of allocating the entire incoming frame.
|
|
60
|
+
const start = Math.max(0, length - MAX_AUDIO)
|
|
61
|
+
const retained = Buffer.from(encoded.slice(Math.floor(start / 3) * 4), 'base64').subarray(start % 3)
|
|
62
|
+
this.#chunks.push(retained)
|
|
63
|
+
this.#bytes += retained.length
|
|
64
|
+
while (this.#bytes > MAX_AUDIO) {
|
|
65
|
+
const first = this.#chunks[0]
|
|
66
|
+
const excess = this.#bytes - MAX_AUDIO
|
|
67
|
+
if (first.length <= excess) {
|
|
68
|
+
this.#chunks.shift()
|
|
69
|
+
this.#bytes -= first.length
|
|
70
|
+
} else {
|
|
71
|
+
this.#chunks[0] = Buffer.from(first.subarray(excess))
|
|
72
|
+
this.#bytes -= excess
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* @param {number} startMs
|
|
79
|
+
* @param {number} endMs
|
|
80
|
+
*/
|
|
81
|
+
clip (startMs, endMs) {
|
|
82
|
+
if (!this.valid || !this.rate || !Number.isFinite(startMs) || !Number.isFinite(endMs) || startMs >= endMs) return
|
|
83
|
+
const start = Math.floor(startMs * this.rate / 1000) * 2
|
|
84
|
+
const end = Math.floor(endMs * this.rate / 1000) * 2
|
|
85
|
+
const base = this.total - this.#bytes
|
|
86
|
+
if (start < base || end > this.total) return
|
|
87
|
+
const clip = Buffer.allocUnsafe(end - start)
|
|
88
|
+
let offset = base
|
|
89
|
+
let written = 0
|
|
90
|
+
for (const chunk of this.#chunks) {
|
|
91
|
+
const chunkEnd = offset + chunk.length
|
|
92
|
+
if (chunkEnd > start) {
|
|
93
|
+
written += chunk.copy(clip, written, Math.max(start - offset, 0), Math.min(end - offset, chunk.length))
|
|
94
|
+
}
|
|
95
|
+
offset = chunkEnd
|
|
96
|
+
if (offset >= end) break
|
|
97
|
+
}
|
|
98
|
+
return clip
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
clear () {
|
|
102
|
+
this.#chunks = []
|
|
103
|
+
this.#bytes = 0
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/** Ordered PCM playback projection for one response, including genuine underrun silence. */
|
|
108
|
+
class OutputAudio {
|
|
109
|
+
rate = 0
|
|
110
|
+
start
|
|
111
|
+
end
|
|
112
|
+
interrupted
|
|
113
|
+
generatedBytes = 0
|
|
114
|
+
timingValid = true
|
|
115
|
+
omittedReason
|
|
116
|
+
#chunks = []
|
|
117
|
+
#bytes = 0
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* @param {string} encoded
|
|
121
|
+
* @param {object} configuration
|
|
122
|
+
* @param {number} now
|
|
123
|
+
*/
|
|
124
|
+
append (encoded, configuration, now) {
|
|
125
|
+
const length = decodedLength(encoded)
|
|
126
|
+
if (length !== undefined) this.generatedBytes += length
|
|
127
|
+
// Late chunks cannot resurrect playback or invalidate the clip already cut at interruption.
|
|
128
|
+
if (this.interrupted !== undefined) return
|
|
129
|
+
const rate = pcmRate(configuration)
|
|
130
|
+
if (length === undefined || !rate || length % 2 || (this.rate && this.rate !== rate)) {
|
|
131
|
+
this.timingValid = false
|
|
132
|
+
this.omittedReason = 'invalid_audio'
|
|
133
|
+
this.#chunks = []
|
|
134
|
+
this.#bytes = 0
|
|
135
|
+
return
|
|
136
|
+
}
|
|
137
|
+
if (!length) return
|
|
138
|
+
this.rate ||= rate
|
|
139
|
+
this.start ??= now
|
|
140
|
+
const previousEnd = this.end ?? now
|
|
141
|
+
const gap = Math.floor(Math.max(0, now - previousEnd) * rate / 1000) * 2
|
|
142
|
+
this.end = previousEnd + (gap + length) * 1000 / (2 * rate)
|
|
143
|
+
if (!this.omittedReason && this.#bytes + gap + length <= MAX_AUDIO) {
|
|
144
|
+
if (gap) this.#chunks.push(Buffer.alloc(gap))
|
|
145
|
+
this.#chunks.push(Buffer.from(encoded, 'base64'))
|
|
146
|
+
this.#bytes += gap + length
|
|
147
|
+
} else {
|
|
148
|
+
this.omittedReason ||= 'retention_limit'
|
|
149
|
+
this.#chunks = []
|
|
150
|
+
this.#bytes = 0
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/** @param {number} now */
|
|
155
|
+
interrupt (now) {
|
|
156
|
+
if (this.interrupted !== undefined) return
|
|
157
|
+
this.interrupted = now
|
|
158
|
+
if (this.start === undefined || this.end === undefined || !this.rate) return
|
|
159
|
+
const frames = Math.floor(Math.max(0, now - this.start) * this.rate / 1000)
|
|
160
|
+
this.end = Math.min(this.end, this.start + frames * 1000 / this.rate)
|
|
161
|
+
if (this.#bytes > frames * 2) {
|
|
162
|
+
const clip = Buffer.concat(this.#chunks, this.#bytes).subarray(0, frames * 2)
|
|
163
|
+
this.#chunks = [Buffer.from(clip)]
|
|
164
|
+
this.#bytes = clip.length
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
pcm () {
|
|
169
|
+
if (!this.omittedReason && this.#bytes) return Buffer.concat(this.#chunks, this.#bytes)
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
module.exports = { InputAudio, OutputAudio }
|