dd-trace 6.20.1 → 6.22.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-setup.mjs +10 -7
- package/index.d.ts +116 -17
- package/initialize.mjs +1 -3
- package/openfeature/rules-based.d.ts +11 -0
- package/openfeature/rules-based.js +7 -0
- package/package.json +13 -11
- package/packages/datadog-core/src/storage.js +16 -0
- package/packages/datadog-esbuild/index.js +18 -12
- 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 +48 -33
- 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 +2 -2
- package/packages/datadog-instrumentations/src/helpers/check-require-cache.js +1 -2
- package/packages/datadog-instrumentations/src/helpers/instrument.js +1 -1
- package/packages/datadog-instrumentations/src/helpers/register.js +36 -1
- package/packages/datadog-instrumentations/src/helpers/rewriter/index.js +2 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentation-registry.js +87 -6
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/graphql-jit.js +5 -6
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/postgres.js +48 -31
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/webdriverio.js +64 -11
- package/packages/datadog-instrumentations/src/helpers/rewriter/targets.json +1 -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 +122 -11
- 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 +127 -38
- package/packages/datadog-instrumentations/src/mocha/webdriverio-protocol.js +61 -34
- package/packages/datadog-instrumentations/src/mocha/worker.js +7 -5
- 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 +483 -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 +3 -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/vitest-main-no-worker-init.js +7 -5
- package/packages/datadog-instrumentations/src/vitest-main.js +9 -17
- package/packages/datadog-instrumentations/src/vitest-util.js +2 -2
- package/packages/datadog-instrumentations/src/vitest-worker.js +1 -2
- package/packages/datadog-instrumentations/src/vitest.js +2 -2
- package/packages/datadog-instrumentations/src/webdriverio.js +118 -21
- 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 +18 -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 +1 -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 +223 -39
- package/packages/datadog-plugin-mocha/src/webdriverio-video.js +453 -0
- 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/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/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 +1 -2
- package/packages/dd-trace/src/ci-visibility/dynamic-atr-retries.js +3 -6
- 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 +8 -0
- package/packages/dd-trace/src/config/helper.js +34 -3
- package/packages/dd-trace/src/config/index.js +10 -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 +35 -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/constants.js +1 -0
- package/packages/dd-trace/src/debugger/devtools_client/index.js +5 -3
- package/packages/dd-trace/src/debugger/devtools_client/remote_config.js +17 -12
- package/packages/dd-trace/src/debugger/devtools_client/send.js +7 -2
- package/packages/dd-trace/src/debugger/devtools_client/status.js +1 -0
- package/packages/dd-trace/src/debugger/devtools_client/worker-error.js +21 -0
- package/packages/dd-trace/src/debugger/guardrail-metrics.js +2 -2
- package/packages/dd-trace/src/debugger/index.js +24 -2
- package/packages/dd-trace/src/debugger/probe_sampler.js +108 -11
- 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 +2 -5
- package/packages/dd-trace/src/exporters/common/url.js +1 -1
- package/packages/dd-trace/src/id.js +15 -5
- 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 +296 -0
- package/packages/dd-trace/src/llmobs/audio-utils.js +3 -2
- package/packages/dd-trace/src/llmobs/constants/tags.js +13 -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/index.js +56 -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 +23 -6
- package/packages/dd-trace/src/llmobs/span_processor.js +18 -8
- package/packages/dd-trace/src/llmobs/tagger.js +56 -3
- package/packages/dd-trace/src/llmobs/util.js +1 -2
- package/packages/dd-trace/src/llmobs/writers/base.js +7 -6
- package/packages/dd-trace/src/llmobs/writers/util.js +3 -1
- package/packages/dd-trace/src/log/log.js +1 -5
- package/packages/dd-trace/src/openfeature/agentless_configuration_source.js +30 -1
- package/packages/dd-trace/src/openfeature/configuration_source.js +17 -2
- package/packages/dd-trace/src/openfeature/debug-logging-hook.js +25 -0
- package/packages/dd-trace/src/openfeature/evaluation-telemetry-opt-out-hook.js +51 -0
- package/packages/dd-trace/src/openfeature/flagging_provider.js +59 -3
- package/packages/dd-trace/src/openfeature/remote_config.js +9 -0
- package/packages/dd-trace/src/openfeature/writers/util.js +43 -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/resource-attributes.js +1 -1
- package/packages/dd-trace/src/opentelemetry/trace/otlp_transformer.js +6 -23
- 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 +81 -15
- package/packages/dd-trace/src/plugins/database.js +7 -16
- package/packages/dd-trace/src/plugins/util/git.js +9 -25
- package/packages/dd-trace/src/plugins/util/inferred_proxy.js +1 -5
- package/packages/dd-trace/src/plugins/util/test.js +48 -4
- 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/profiling/profilers/wall.js +4 -10
- package/packages/dd-trace/src/proxy.js +3 -2
- package/packages/dd-trace/src/ritm.js +4 -2
- package/packages/dd-trace/src/serverless.js +10 -0
- package/packages/dd-trace/src/span_processor.js +13 -17
- package/packages/dd-trace/src/storage-channels.js +112 -46
- package/packages/dd-trace/src/telemetry/endpoints.js +1 -3
- package/packages/dd-trace/src/util.js +29 -0
- package/packages/dd-trace/src/web-tags-cache.js +6 -2
- 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
|
|
@@ -26,6 +26,7 @@ const shimmer = require('../../../datadog-shimmer')
|
|
|
26
26
|
const testStartCh = channel('ci:mocha:test:start')
|
|
27
27
|
const testFinishCh = channel('ci:mocha:test:finish')
|
|
28
28
|
const testDiWaitCh = channel('ci:mocha:test:di:wait')
|
|
29
|
+
const testAfterEachFinishCh = channel('ci:mocha:after-each:finish')
|
|
29
30
|
// after a test has failed, we'll publish to this channel
|
|
30
31
|
const testRetryCh = channel('ci:mocha:test:retry')
|
|
31
32
|
const errorCh = channel('ci:mocha:test:error')
|
|
@@ -54,6 +55,7 @@ const testsQuarantined = new Set()
|
|
|
54
55
|
const testsStatuses = new Map()
|
|
55
56
|
const efdRetryCountByTestFullName = new Map()
|
|
56
57
|
const efdSlowAbortedTests = new Set()
|
|
58
|
+
const suiteHookFailureSuites = new Set()
|
|
57
59
|
let dynamicAtrRetryCountByTest = new WeakMap()
|
|
58
60
|
const attemptToFixExecutions = new Map()
|
|
59
61
|
const isMochaWorker = !!getEnvironmentVariable('MOCHA_WORKER_ID')
|
|
@@ -343,6 +345,7 @@ function resetRunState (rootSuite) {
|
|
|
343
345
|
testsStatuses.clear()
|
|
344
346
|
efdRetryCountByTestFullName.clear()
|
|
345
347
|
efdSlowAbortedTests.clear()
|
|
348
|
+
suiteHookFailureSuites.clear()
|
|
346
349
|
dynamicAtrRetryCountByTest = new WeakMap()
|
|
347
350
|
attemptToFixExecutions.clear()
|
|
348
351
|
loggedAttemptToFixTests.clear()
|
|
@@ -379,7 +382,9 @@ function resetRunState (rootSuite) {
|
|
|
379
382
|
delete test._ddTestFinishPublished
|
|
380
383
|
delete test._ddIsFinalAttempt
|
|
381
384
|
delete test._ddHookFailed
|
|
385
|
+
delete test._ddSuppressSyntheticTestLifecycle
|
|
382
386
|
delete test._ddPendingRetry
|
|
387
|
+
delete test._ddPendingHookFailureFinish
|
|
383
388
|
delete test._ddReporterStartFailed
|
|
384
389
|
delete test._ddReporterTerminalFailed
|
|
385
390
|
originalTests.push(test)
|
|
@@ -439,6 +444,21 @@ function recordTestAttempt (testsByFullName, test) {
|
|
|
439
444
|
}
|
|
440
445
|
}
|
|
441
446
|
|
|
447
|
+
function recordRetryTrackedAttempt (test) {
|
|
448
|
+
const {
|
|
449
|
+
_ddIsAttemptToFix: isAttemptToFix,
|
|
450
|
+
_ddIsModified: isModified,
|
|
451
|
+
_ddIsNew: isNew,
|
|
452
|
+
} = test
|
|
453
|
+
|
|
454
|
+
if (isNew) {
|
|
455
|
+
recordTestAttempt(newTests, test)
|
|
456
|
+
}
|
|
457
|
+
if (!isAttemptToFix && (isNew || isModified)) {
|
|
458
|
+
recordTestAttempt(efdTests, test)
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
|
|
442
462
|
function getTestStatus (test) {
|
|
443
463
|
if (test.isPending()) {
|
|
444
464
|
return 'skip'
|
|
@@ -476,6 +496,15 @@ function startTestFinish (test) {
|
|
|
476
496
|
return true
|
|
477
497
|
}
|
|
478
498
|
|
|
499
|
+
function hasHandledHookFailure (test) {
|
|
500
|
+
return test._ddHookFailed && (test._ddTestFinishPublished || test._ddPendingHookFailureFinish)
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
function suppressSyntheticTestLifecycle (test) {
|
|
504
|
+
test._ddSuppressSyntheticTestLifecycle = true
|
|
505
|
+
test._ddTestFinishPublished = true
|
|
506
|
+
}
|
|
507
|
+
|
|
479
508
|
/**
|
|
480
509
|
* Copies Test Management metadata from Mocha's original runnable to its native retry clone.
|
|
481
510
|
* @param {{
|
|
@@ -571,6 +600,14 @@ function runnableWrapper (RunnablePackage, libraryConfig) {
|
|
|
571
600
|
}
|
|
572
601
|
}
|
|
573
602
|
|
|
603
|
+
const failedTestReplayPromise = (isTestHook || this.type === 'test') &&
|
|
604
|
+
test._retriedTest?._ddFailedTestReplayPromise
|
|
605
|
+
if (failedTestReplayPromise) {
|
|
606
|
+
delete test._retriedTest._ddFailedTestReplayPromise
|
|
607
|
+
const runRunnable = () => run.apply(this, args)
|
|
608
|
+
return failedTestReplayPromise.then(runRunnable, runRunnable)
|
|
609
|
+
}
|
|
610
|
+
|
|
574
611
|
return run.apply(this, args)
|
|
575
612
|
})
|
|
576
613
|
return RunnablePackage
|
|
@@ -578,6 +615,8 @@ function runnableWrapper (RunnablePackage, libraryConfig) {
|
|
|
578
615
|
|
|
579
616
|
function getOnTestHandler (isMain) {
|
|
580
617
|
return function (test) {
|
|
618
|
+
if (test._ddSuppressSyntheticTestLifecycle || hasHandledHookFailure(test)) return
|
|
619
|
+
|
|
581
620
|
const testStartLine = testToStartLine.get(test)
|
|
582
621
|
|
|
583
622
|
// This may be a retry. If this is the case, `test.fn` is already wrapped,
|
|
@@ -586,6 +625,12 @@ function getOnTestHandler (isMain) {
|
|
|
586
625
|
|
|
587
626
|
inheritDatadogPropertiesFromRetriedTest(test)
|
|
588
627
|
|
|
628
|
+
if (isInFailedSuite(test.parent)) {
|
|
629
|
+
recordRetryTrackedAttempt(test)
|
|
630
|
+
suppressSyntheticTestLifecycle(test)
|
|
631
|
+
return
|
|
632
|
+
}
|
|
633
|
+
|
|
589
634
|
const {
|
|
590
635
|
file: testSuiteAbsolutePath,
|
|
591
636
|
title,
|
|
@@ -636,13 +681,8 @@ function getOnTestHandler (isMain) {
|
|
|
636
681
|
loggedAttemptToFixTests
|
|
637
682
|
)
|
|
638
683
|
}
|
|
639
|
-
//
|
|
640
|
-
|
|
641
|
-
recordTestAttempt(newTests, test)
|
|
642
|
-
}
|
|
643
|
-
if (!isAttemptToFix && (isNew || isModified)) {
|
|
644
|
-
recordTestAttempt(efdTests, test)
|
|
645
|
-
}
|
|
684
|
+
// Keep retry accounting aligned with Mocha's runner failure counts.
|
|
685
|
+
recordRetryTrackedAttempt(test)
|
|
646
686
|
|
|
647
687
|
if (!isAttemptToFix && isDisabled) {
|
|
648
688
|
if (!originalPendingByTest.has(test)) {
|
|
@@ -657,6 +697,14 @@ function getOnTestHandler (isMain) {
|
|
|
657
697
|
}
|
|
658
698
|
}
|
|
659
699
|
|
|
700
|
+
function isInFailedSuite (suite) {
|
|
701
|
+
while (suite) {
|
|
702
|
+
if (suiteHookFailureSuites.has(suite)) return true
|
|
703
|
+
suite = suite.parent
|
|
704
|
+
}
|
|
705
|
+
return false
|
|
706
|
+
}
|
|
707
|
+
|
|
660
708
|
function getFinalStatus ({
|
|
661
709
|
status,
|
|
662
710
|
hasFailedAllRetries,
|
|
@@ -842,7 +890,7 @@ function getTestFinishInfo (test, status, config, error) {
|
|
|
842
890
|
|
|
843
891
|
function getOnTestEndHandler (config, finalAttemptHandlers) {
|
|
844
892
|
return async function (test) {
|
|
845
|
-
if (test._ddShouldSkipEfdRetry) {
|
|
893
|
+
if (test._ddShouldSkipEfdRetry || test._ddSuppressSyntheticTestLifecycle || hasHandledHookFailure(test)) {
|
|
846
894
|
return
|
|
847
895
|
}
|
|
848
896
|
const shouldWaitForHitProbe = test._retriedTest?._ddShouldWaitForHitProbe
|
|
@@ -851,6 +899,7 @@ function getOnTestEndHandler (config, finalAttemptHandlers) {
|
|
|
851
899
|
const shouldFinishTest = ctx && (
|
|
852
900
|
test._ddReporterStartFailed ||
|
|
853
901
|
test._ddReporterTerminalFailed ||
|
|
902
|
+
!test.fn ||
|
|
854
903
|
!getAfterEachHooks(test).length ||
|
|
855
904
|
(test._ddIsDisabled && !test._ddIsAttemptToFix)
|
|
856
905
|
)
|
|
@@ -858,8 +907,8 @@ function getOnTestEndHandler (config, finalAttemptHandlers) {
|
|
|
858
907
|
let testFinishInfo
|
|
859
908
|
let isFinalAttempt = false
|
|
860
909
|
|
|
861
|
-
// If there are afterEach to
|
|
862
|
-
//
|
|
910
|
+
// If there are afterEach hooks to run, we don't finish the test yet.
|
|
911
|
+
// Static skips have no function and never execute hooks. Disabled tests also finish immediately.
|
|
863
912
|
// In older mocha versions, pending tests don't run afterEach hooks, so we can't rely on
|
|
864
913
|
// getOnHookEndHandler to finish the test. This mirrors Jest's approach where the skip handler
|
|
865
914
|
// directly sets finalStatus without waiting for hooks
|
|
@@ -880,24 +929,33 @@ function getOnTestEndHandler (config, finalAttemptHandlers) {
|
|
|
880
929
|
finalAttemptHandlers?.onStart?.(test)
|
|
881
930
|
}
|
|
882
931
|
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
932
|
+
const finish = () => {
|
|
933
|
+
if (shouldFinishTest) {
|
|
934
|
+
test._ddTestFinishPublished = true
|
|
935
|
+
testFinishCh.publish({
|
|
936
|
+
status,
|
|
937
|
+
hasBeenRetried: isMochaRetry(test),
|
|
938
|
+
isLastRetry: getIsLastRetry(test),
|
|
939
|
+
...testFinishInfo,
|
|
940
|
+
...ctx.currentStore,
|
|
941
|
+
})
|
|
942
|
+
}
|
|
886
943
|
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
status,
|
|
891
|
-
hasBeenRetried: isMochaRetry(test),
|
|
892
|
-
isLastRetry: getIsLastRetry(test),
|
|
893
|
-
...testFinishInfo,
|
|
894
|
-
...ctx.currentStore,
|
|
895
|
-
})
|
|
944
|
+
if (isFinalAttempt) {
|
|
945
|
+
finalAttemptHandlers?.onFinish?.(test)
|
|
946
|
+
}
|
|
896
947
|
}
|
|
897
948
|
|
|
898
|
-
if (
|
|
899
|
-
|
|
949
|
+
if (shouldWaitForHitProbe) {
|
|
950
|
+
const waitForHitProbePromise = waitForHitProbe()
|
|
951
|
+
if (shouldFinishTest && config.isTestFailureVideosEnabled && !test.isPending()) {
|
|
952
|
+
// Mocha does not await test-end listeners. Its hookUp continuation must wait even without teardown hooks.
|
|
953
|
+
test._ddDeferredHookEnd = { waitForHitProbePromise, publishTestFinish: finish }
|
|
954
|
+
return
|
|
955
|
+
}
|
|
956
|
+
await waitForHitProbePromise
|
|
900
957
|
}
|
|
958
|
+
finish()
|
|
901
959
|
}
|
|
902
960
|
}
|
|
903
961
|
|
|
@@ -978,17 +1036,30 @@ function finishDeferredHookEnd (test) {
|
|
|
978
1036
|
* @returns {unknown}
|
|
979
1037
|
*/
|
|
980
1038
|
function runFailedTestReplayHookUpCallback (fn, test, failedTestReplayPromise, hookThis, args) {
|
|
1039
|
+
if (test._ddPendingHookFailureFinish) {
|
|
1040
|
+
// Mocha restarts teardown at the failed suite's parent. Only the last hookUp completes the attempt.
|
|
1041
|
+
const failedSuite = /** @type {import('mocha').Suite|undefined} */ (args[1])
|
|
1042
|
+
if (args[0] && failedSuite?.parent) return fn.apply(hookThis, /** @type {unknown[]} */ (args))
|
|
1043
|
+
const finish = test._ddPendingHookFailureFinish
|
|
1044
|
+
delete test._ddPendingHookFailureFinish
|
|
1045
|
+
finish()
|
|
1046
|
+
}
|
|
981
1047
|
const pendingRetry = test._ddPendingRetry
|
|
982
1048
|
if (pendingRetry) {
|
|
983
1049
|
delete test._ddPendingRetry
|
|
984
1050
|
failedTestReplayPromise = pendingRetry()
|
|
985
1051
|
}
|
|
1052
|
+
const continueAfterCapture = () => {
|
|
1053
|
+
const context = { onDone: () => fn.apply(hookThis, args), isWaiting: false }
|
|
1054
|
+
testAfterEachFinishCh.publish(context)
|
|
1055
|
+
return context.isWaiting ? undefined : context.onDone()
|
|
1056
|
+
}
|
|
986
1057
|
const continueAfterProbe = () => {
|
|
987
1058
|
const deferredHookEndPromise = finishDeferredHookEnd(test)
|
|
988
1059
|
if (deferredHookEndPromise) {
|
|
989
|
-
return deferredHookEndPromise.then(
|
|
1060
|
+
return deferredHookEndPromise.then(continueAfterCapture, continueAfterCapture)
|
|
990
1061
|
}
|
|
991
|
-
return
|
|
1062
|
+
return continueAfterCapture()
|
|
992
1063
|
}
|
|
993
1064
|
|
|
994
1065
|
if (failedTestReplayPromise) {
|
|
@@ -1037,15 +1108,23 @@ function getOnFailHandler (isMain, config) {
|
|
|
1037
1108
|
const testFile = testOrHook.file
|
|
1038
1109
|
let test = testOrHook
|
|
1039
1110
|
const isHook = testOrHook.type === 'hook'
|
|
1111
|
+
const isBeforeEachHook = isHook && testOrHook.parent?._beforeEach?.includes(testOrHook)
|
|
1112
|
+
const isAfterEachHook = isHook && testOrHook.parent?._afterEach?.includes(testOrHook)
|
|
1113
|
+
const isTestHook = isBeforeEachHook || isAfterEachHook
|
|
1114
|
+
if (isHook && !isTestHook && testOrHook.parent) {
|
|
1115
|
+
suiteHookFailureSuites.add(testOrHook.parent)
|
|
1116
|
+
}
|
|
1040
1117
|
if (isHook && testOrHook.ctx) {
|
|
1041
1118
|
test = testOrHook.ctx.currentTest
|
|
1042
1119
|
}
|
|
1043
1120
|
let testContext
|
|
1044
1121
|
if (test) {
|
|
1122
|
+
if (!isHook && (test._ddSuppressSyntheticTestLifecycle || hasHandledHookFailure(test))) return
|
|
1123
|
+
|
|
1045
1124
|
testContext = getTestContext(test)
|
|
1046
1125
|
}
|
|
1047
1126
|
if (testContext) {
|
|
1048
|
-
if (
|
|
1127
|
+
if (isTestHook && startTestFinish(test)) {
|
|
1049
1128
|
delete test._ddPendingRetry
|
|
1050
1129
|
const hookError = new Error(`${testOrHook.fullTitle()}: ${err.message}`, { cause: err })
|
|
1051
1130
|
hookError.name = err.name
|
|
@@ -1054,17 +1133,27 @@ function getOnFailHandler (isMain, config) {
|
|
|
1054
1133
|
errorCh.runStores(testContext, () => {})
|
|
1055
1134
|
// Mocha marks the hook failed, so record the test failure before computing final metadata.
|
|
1056
1135
|
test._ddHookFailed = true
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1136
|
+
if (isBeforeEachHook && testOrHook.parent) {
|
|
1137
|
+
suiteHookFailureSuites.add(testOrHook.parent)
|
|
1138
|
+
}
|
|
1139
|
+
const finish = () => {
|
|
1140
|
+
const testFinishInfo = getTestFinishInfo(test, 'fail', config, testContext.err)
|
|
1141
|
+
if (testFinishInfo.finalStatus !== undefined) {
|
|
1142
|
+
test._ddIsFinalAttempt = true
|
|
1143
|
+
}
|
|
1144
|
+
test._ddTestFinishPublished = true
|
|
1145
|
+
testFinishCh.publish({
|
|
1146
|
+
status: 'fail',
|
|
1147
|
+
hasBeenRetried: isMochaRetry(test),
|
|
1148
|
+
...testFinishInfo,
|
|
1149
|
+
...testContext.currentStore,
|
|
1150
|
+
})
|
|
1151
|
+
}
|
|
1152
|
+
if (config.isTestFailureVideosEnabled) {
|
|
1153
|
+
test._ddPendingHookFailureFinish = finish
|
|
1154
|
+
} else {
|
|
1155
|
+
finish()
|
|
1060
1156
|
}
|
|
1061
|
-
test._ddTestFinishPublished = true
|
|
1062
|
-
testFinishCh.publish({
|
|
1063
|
-
status: 'fail',
|
|
1064
|
-
hasBeenRetried: isMochaRetry(test),
|
|
1065
|
-
...testFinishInfo,
|
|
1066
|
-
...testContext.currentStore,
|
|
1067
|
-
})
|
|
1068
1157
|
} else {
|
|
1069
1158
|
testContext.err = err
|
|
1070
1159
|
errorCh.runStores(testContext, () => {})
|
|
@@ -1105,7 +1194,7 @@ function getOnTestRetryHandler (config) {
|
|
|
1105
1194
|
test._retries = dynamicCount
|
|
1106
1195
|
}
|
|
1107
1196
|
|
|
1108
|
-
if (config.isFlakyTestRetriesEnabled && getAfterEachHooks(test).length) {
|
|
1197
|
+
if ((config.isFlakyTestRetriesEnabled || config.isTestFailureVideosEnabled) && getAfterEachHooks(test).length) {
|
|
1109
1198
|
// Mocha queues retries before afterEach; a hook failure can still cancel the retry.
|
|
1110
1199
|
test._ddPendingRetry = () => publishTestRetry(test, err, config)
|
|
1111
1200
|
return
|