dd-trace 5.110.0 → 5.111.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -8
- package/ci/init.js +21 -2
- package/ci/vitest-no-worker-init-setup.mjs +430 -0
- package/ext/tags.js +2 -0
- package/index.d.ts +13 -0
- package/initialize.mjs +5 -6
- package/loader-hook.mjs +76 -55
- package/package.json +9 -9
- package/packages/datadog-instrumentations/src/fs.js +8 -6
- package/packages/datadog-instrumentations/src/graphql.js +26 -495
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/graphql.js +201 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/index.js +1 -0
- package/packages/datadog-instrumentations/src/mongoose.js +14 -2
- package/packages/datadog-instrumentations/src/vitest-main-no-worker-init.js +1293 -0
- package/packages/datadog-instrumentations/src/vitest-main.js +1567 -0
- package/packages/datadog-instrumentations/src/vitest-util.js +249 -0
- package/packages/datadog-instrumentations/src/vitest-worker.js +753 -0
- package/packages/datadog-instrumentations/src/vitest.js +11 -1855
- package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/client.js +2 -2
- package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/context.js +18 -10
- package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/util.js +47 -11
- package/packages/datadog-plugin-cucumber/src/index.js +1 -1
- package/packages/datadog-plugin-cypress/src/cypress-plugin.js +10 -4
- package/packages/datadog-plugin-cypress/src/support.js +45 -1
- package/packages/datadog-plugin-express/src/code_origin.js +1 -1
- package/packages/datadog-plugin-fastify/src/code_origin.js +1 -1
- package/packages/datadog-plugin-graphql/src/execute.js +627 -11
- package/packages/datadog-plugin-graphql/src/index.js +20 -8
- package/packages/datadog-plugin-graphql/src/parse.js +24 -4
- package/packages/datadog-plugin-graphql/src/validate.js +16 -5
- package/packages/datadog-plugin-http/src/index.js +6 -8
- package/packages/datadog-plugin-jest/src/index.js +1 -1
- package/packages/datadog-plugin-mocha/src/index.js +1 -1
- package/packages/datadog-plugin-openai/src/services.js +2 -2
- package/packages/datadog-plugin-playwright/src/index.js +1 -1
- package/packages/datadog-plugin-vitest/src/index.js +69 -63
- package/packages/datadog-shimmer/src/shimmer.js +37 -16
- package/packages/dd-trace/src/aiguard/sdk.js +2 -2
- package/packages/dd-trace/src/appsec/api_security/sampler.js +3 -3
- package/packages/dd-trace/src/appsec/channels.js +3 -1
- package/packages/dd-trace/src/appsec/downstream_requests.js +4 -4
- package/packages/dd-trace/src/appsec/graphql.js +5 -5
- package/packages/dd-trace/src/appsec/iast/analyzers/nosql-injection-mongodb-analyzer.js +26 -19
- package/packages/dd-trace/src/appsec/iast/security-controls/index.js +2 -2
- package/packages/dd-trace/src/appsec/iast/telemetry/index.js +2 -1
- package/packages/dd-trace/src/appsec/index.js +10 -1
- package/packages/dd-trace/src/appsec/lambda.js +135 -0
- package/packages/dd-trace/src/appsec/reporter.js +49 -10
- package/packages/dd-trace/src/appsec/telemetry/index.js +1 -1
- package/packages/dd-trace/src/appsec/waf/index.js +16 -4
- package/packages/dd-trace/src/appsec/waf/waf_context_wrapper.js +4 -4
- package/packages/dd-trace/src/ci-visibility/early-flake-detection/get-known-tests.js +21 -6
- package/packages/dd-trace/src/ci-visibility/exporters/agent-proxy/index.js +2 -2
- package/packages/dd-trace/src/ci-visibility/exporters/agentless/coverage-writer.js +1 -1
- package/packages/dd-trace/src/ci-visibility/exporters/agentless/di-logs-writer.js +1 -1
- package/packages/dd-trace/src/ci-visibility/exporters/agentless/index.js +3 -2
- package/packages/dd-trace/src/ci-visibility/exporters/agentless/writer.js +1 -1
- package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +55 -13
- package/packages/dd-trace/src/ci-visibility/exporters/git/git_metadata.js +2 -2
- package/packages/dd-trace/src/ci-visibility/exporters/test-worker/writer.js +17 -0
- package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +47 -26
- package/packages/dd-trace/src/ci-visibility/log-submission/log-submission-plugin.js +2 -2
- package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +64 -53
- package/packages/dd-trace/src/ci-visibility/requests/upload-coverage-report.js +3 -3
- package/packages/dd-trace/src/ci-visibility/test-management/get-test-management-tests.js +23 -5
- package/packages/dd-trace/src/ci-visibility/test-optimization-http-cache-schema.js +128 -0
- package/packages/dd-trace/src/ci-visibility/test-optimization-http-cache.js +287 -0
- package/packages/dd-trace/src/config/defaults.js +3 -2
- package/packages/dd-trace/src/config/generated-config-types.d.ts +61 -57
- package/packages/dd-trace/src/config/helper.js +1 -0
- package/packages/dd-trace/src/config/index.js +35 -22
- package/packages/dd-trace/src/config/major-overrides.js +4 -2
- package/packages/dd-trace/src/config/remote_config.js +1 -1
- package/packages/dd-trace/src/config/supported-configurations.json +90 -56
- package/packages/dd-trace/src/constants.js +7 -0
- package/packages/dd-trace/src/datastreams/processor.js +11 -3
- package/packages/dd-trace/src/exporters/agent/index.js +1 -1
- package/packages/dd-trace/src/exporters/agentless/writer.js +4 -4
- package/packages/dd-trace/src/llmobs/index.js +1 -1
- package/packages/dd-trace/src/llmobs/plugins/base.js +4 -4
- package/packages/dd-trace/src/llmobs/plugins/langgraph/index.js +1 -1
- package/packages/dd-trace/src/llmobs/sdk.js +3 -3
- package/packages/dd-trace/src/llmobs/span_processor.js +1 -1
- package/packages/dd-trace/src/llmobs/tagger.js +3 -3
- package/packages/dd-trace/src/llmobs/writers/base.js +1 -1
- package/packages/dd-trace/src/opentelemetry/metrics/index.js +54 -5
- package/packages/dd-trace/src/opentelemetry/metrics/otlp_span_stats_exporter.js +40 -0
- package/packages/dd-trace/src/opentelemetry/metrics/otlp_span_stats_transformer.js +169 -0
- package/packages/dd-trace/src/opentelemetry/otlp/otlp_http_exporter_base.js +38 -39
- package/packages/dd-trace/src/opentelemetry/trace/index.js +4 -0
- package/packages/dd-trace/src/opentelemetry/tracer_provider.js +1 -1
- package/packages/dd-trace/src/opentracing/tracer.js +6 -1
- package/packages/dd-trace/src/plugin_manager.js +7 -7
- package/packages/dd-trace/src/plugins/ci_plugin.js +12 -3
- package/packages/dd-trace/src/plugins/util/llm.js +6 -1
- package/packages/dd-trace/src/plugins/util/test.js +11 -9
- package/packages/dd-trace/src/profiling/exporter_cli.js +2 -2
- package/packages/dd-trace/src/profiling/exporters/event_serializer.js +3 -3
- package/packages/dd-trace/src/profiling/profilers/wall.js +1 -1
- package/packages/dd-trace/src/proxy.js +8 -8
- package/packages/dd-trace/src/span_processor.js +5 -6
- package/packages/dd-trace/src/span_stats.js +96 -78
- package/packages/dd-trace/src/startup-log.js +2 -1
- package/packages/dd-trace/src/telemetry/dependencies.js +1 -1
- package/packages/dd-trace/src/telemetry/endpoints.js +6 -4
- package/packages/dd-trace/src/telemetry/index.js +2 -2
- package/packages/dd-trace/src/telemetry/logs/index.js +1 -1
- package/packages/dd-trace/src/telemetry/send-data.js +8 -8
- package/packages/dd-trace/src/telemetry/session-propagation.js +1 -1
- package/packages/dd-trace/src/telemetry/telemetry.js +9 -9
- package/vendor/dist/@datadog/sketches-js/index.js +1 -1
- package/vendor/dist/protobufjs/index.js +1 -1
- package/vendor/dist/protobufjs/minimal/index.js +1 -1
- package/vendor/dist/shell-quote/index.js +1 -1
- package/packages/datadog-plugin-graphql/src/resolve.js +0 -170
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# `dd-trace`: Node.js APM Tracer Library
|
|
2
2
|
|
|
3
|
-
[](https://www.npmjs.com/package/dd-trace)
|
|
4
4
|
[](https://codecov.io/gh/DataDog/dd-trace-js)
|
|
5
5
|
|
|
6
6
|
<img align="right" src="https://user-images.githubusercontent.com/551402/208212084-1d0c07e2-4135-4c61-b2da-8f2fddbc66ed.png" alt="Bits the dog JavaScript" width="200px"/>
|
|
@@ -28,30 +28,35 @@ Most of the documentation for `dd-trace` is available on these webpages:
|
|
|
28
28
|
| [`v2`](https://github.com/DataDog/dd-trace-js/tree/v2.x) |  | `>= v12` | NO | NO | **EOL** | 2022-01-28 | 2023-08-15 |
|
|
29
29
|
| [`v3`](https://github.com/DataDog/dd-trace-js/tree/v3.x) |  | `>= v14` | NO | YES | **EOL** | 2022-08-15 | 2024-05-15 |
|
|
30
30
|
| [`v4`](https://github.com/DataDog/dd-trace-js/tree/v4.x) |  | `>= v16` | YES | YES | **EOL** | 2023-05-12 | 2025-01-11 |
|
|
31
|
-
| [`v5`](https://github.com/DataDog/dd-trace-js/tree/v5.x) | 
|
|
31
|
+
| [`v5`](https://github.com/DataDog/dd-trace-js/tree/v5.x) |  | `>= v18` | YES | YES | **Maintenance** | 2024-01-11 | Unknown |
|
|
32
|
+
| [`v6`](https://github.com/DataDog/dd-trace-js/tree/v6.x) |  | `>= v22` | YES | YES | **Current** | 2026-07-02 | Unknown |
|
|
32
33
|
|
|
33
34
|
* EOL = End-of-life
|
|
34
35
|
* SSI = Single-Step Install
|
|
35
36
|
|
|
36
|
-
We currently maintain
|
|
37
|
+
We currently maintain two release lines: `v6` (current) and `v5` (maintenance).
|
|
37
38
|
|
|
38
|
-
For any new projects it is recommended to use the `
|
|
39
|
+
For any new projects it is recommended to use the `v6` release line:
|
|
39
40
|
|
|
40
41
|
```sh
|
|
41
42
|
$ npm install dd-trace
|
|
42
43
|
$ yarn add dd-trace
|
|
43
44
|
```
|
|
44
45
|
|
|
45
|
-
Existing projects
|
|
46
|
-
|
|
46
|
+
Existing projects on `v5` may continue to use it during its maintenance window by pinning the major version:
|
|
47
|
+
|
|
48
|
+
```sh
|
|
49
|
+
$ npm install dd-trace@5
|
|
50
|
+
$ yarn add dd-trace@5
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Older EOL release lines are no longer maintained, but can still be pinned if needed:
|
|
47
54
|
|
|
48
55
|
```sh
|
|
49
56
|
$ npm install dd-trace@4 # or whatever version you need
|
|
50
57
|
$ yarn add dd-trace@4 # or whatever version you need
|
|
51
58
|
```
|
|
52
59
|
|
|
53
|
-
Note, however, that the end-of-life release lines are no longer maintained and will not receive updates.
|
|
54
|
-
|
|
55
60
|
Any backwards-breaking functionality that is introduced into the library will result in an increase of the major version of the library and therefore a new release line.
|
|
56
61
|
Such releases are kept to a minimum to reduce the pain of upgrading the library.
|
|
57
62
|
|
package/ci/init.js
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
3
|
/* eslint-disable no-console */
|
|
4
|
-
const tracer = require('../packages/dd-trace')
|
|
5
4
|
const log = require('../packages/dd-trace/src/log')
|
|
6
5
|
const { getEnvironmentVariable, getValueFromEnvSources } = require('../packages/dd-trace/src/config/helper')
|
|
6
|
+
const { isTrue } = require('../packages/dd-trace/src/util')
|
|
7
7
|
|
|
8
8
|
const PACKAGE_MANAGERS = ['npm', 'yarn', 'pnpm']
|
|
9
9
|
const DEFAULT_FLUSH_INTERVAL = 5000
|
|
10
10
|
const JEST_FLUSH_INTERVAL = 0
|
|
11
|
+
const VITEST_NO_WORKER_INIT_ACTIVE_ENV = 'DD_TEST_OPT_VITEST_NO_WORKER_INIT_ACTIVE'
|
|
11
12
|
const EXPORTER_MAP = {
|
|
12
13
|
jest: 'jest_worker',
|
|
13
14
|
cucumber: 'cucumber_worker',
|
|
@@ -78,10 +79,28 @@ if (isTestWorker) {
|
|
|
78
79
|
}
|
|
79
80
|
}
|
|
80
81
|
|
|
81
|
-
|
|
82
|
+
const skipVitestWorkerInit = shouldSkipVitestWorkerInit()
|
|
83
|
+
const tracer = skipVitestWorkerInit
|
|
84
|
+
? {
|
|
85
|
+
init () {},
|
|
86
|
+
use () {},
|
|
87
|
+
}
|
|
88
|
+
: require('../packages/dd-trace')
|
|
89
|
+
|
|
90
|
+
if (shouldInit && !skipVitestWorkerInit) {
|
|
82
91
|
tracer.init(baseOptions)
|
|
83
92
|
tracer.use('fs', false)
|
|
84
93
|
tracer.use('child_process', false)
|
|
85
94
|
}
|
|
86
95
|
|
|
87
96
|
module.exports = tracer
|
|
97
|
+
|
|
98
|
+
function shouldSkipVitestWorkerInit () {
|
|
99
|
+
return getValueFromEnvSources('DD_VITEST_WORKER') &&
|
|
100
|
+
isVitestNoWorkerInitActive()
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function isVitestNoWorkerInitActive () {
|
|
104
|
+
// eslint-disable-next-line eslint-rules/eslint-process-env
|
|
105
|
+
return isTrue(process.env[VITEST_NO_WORKER_INIT_ACTIVE_ENV])
|
|
106
|
+
}
|
|
@@ -0,0 +1,430 @@
|
|
|
1
|
+
import { realpathSync } from 'node:fs'
|
|
2
|
+
import { relative } from 'node:path'
|
|
3
|
+
import { performance } from 'node:perf_hooks'
|
|
4
|
+
|
|
5
|
+
import { afterEach, beforeAll, beforeEach } from 'vitest'
|
|
6
|
+
|
|
7
|
+
// Instrumentation-less worker setup for DD_EXPERIMENTAL_TEST_OPT_VITEST_NO_WORKER_INIT.
|
|
8
|
+
// It applies Test Optimization execution changes without initializing dd-trace in Vitest workers.
|
|
9
|
+
const VITEST_NO_WORKER_INIT_ACTIVE_ENV = 'DD_TEST_OPT_VITEST_NO_WORKER_INIT_ACTIVE'
|
|
10
|
+
const isNoWorkerInitActive = getIsNoWorkerInitActive()
|
|
11
|
+
const providedContext = isNoWorkerInitActive ? getProvidedContext() : {}
|
|
12
|
+
const attemptToFixTests = providedContext.attemptToFixTests || {}
|
|
13
|
+
const attemptToFixRetries = providedContext.attemptToFixRetries || 0
|
|
14
|
+
const disabledTests = providedContext.disabledTests || {}
|
|
15
|
+
const earlyFlakeDetectionRetries = providedContext.earlyFlakeDetectionRetries || 0
|
|
16
|
+
const earlyFlakeDetectionRetryThresholds = Array.isArray(providedContext.earlyFlakeDetectionRetryThresholds)
|
|
17
|
+
? providedContext.earlyFlakeDetectionRetryThresholds
|
|
18
|
+
: []
|
|
19
|
+
const earlyFlakeDetectionSlowRetries = providedContext.earlyFlakeDetectionSlowRetries || {}
|
|
20
|
+
const hasEarlyFlakeDetectionSlowRetries = Object.keys(earlyFlakeDetectionSlowRetries).length > 0
|
|
21
|
+
const isEarlyFlakeDetectionEnabled = providedContext.isEarlyFlakeDetectionEnabled === true
|
|
22
|
+
const knownTests = providedContext.knownTests || {}
|
|
23
|
+
const modifiedFiles = providedContext.modifiedFiles || {}
|
|
24
|
+
const quarantinedTests = providedContext.quarantinedTests || {}
|
|
25
|
+
const repositoryRoot = realpath(providedContext.repositoryRoot || process.cwd())
|
|
26
|
+
let setVitestTaskFn
|
|
27
|
+
if (isNoWorkerInitActive) {
|
|
28
|
+
try {
|
|
29
|
+
// Vitest does not expose setFn from the public setup API; keep this optional for strict installers.
|
|
30
|
+
const vitestRunner = await import('@vitest/runner')
|
|
31
|
+
setVitestTaskFn = vitestRunner.setFn
|
|
32
|
+
} catch {}
|
|
33
|
+
}
|
|
34
|
+
const earlyFlakeDetectionRetriesByTask = new WeakMap()
|
|
35
|
+
const earlyFlakeDetectionSkippedResults = new WeakMap()
|
|
36
|
+
const earlyFlakeDetectionStartByTask = new WeakMap()
|
|
37
|
+
const nextAttemptIndexByTask = new WeakMap()
|
|
38
|
+
const realpaths = new Map()
|
|
39
|
+
|
|
40
|
+
if (isNoWorkerInitActive) {
|
|
41
|
+
// eslint-disable-next-line no-empty-pattern
|
|
42
|
+
beforeAll(function ({}, suite) {
|
|
43
|
+
suite ||= arguments[0]
|
|
44
|
+
applyExecutionChanges(suite)
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
beforeEach(function ({ onTestFinished, task, skip }) {
|
|
48
|
+
const testSuite = getTestSuite(task)
|
|
49
|
+
const testName = getTestName(task)
|
|
50
|
+
const isAttemptToFixTest = attemptToFixTests[testSuite]?.[testName]
|
|
51
|
+
const isEarlyFlakeDetectionTestAttempt = isEarlyFlakeDetectionTest(testSuite, testName)
|
|
52
|
+
const isQuarantinedTest = quarantinedTests[testSuite]?.[testName] && !isAttemptToFixTest
|
|
53
|
+
const attemptIndex = getNextAttemptIndex(task)
|
|
54
|
+
if (attemptIndex > 0) {
|
|
55
|
+
recordTestOptimizationStatus(task, attemptIndex - 1)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (disabledTests[testSuite]?.[testName] && !isAttemptToFixTest) {
|
|
59
|
+
skip('Skipped by Datadog Test Optimization')
|
|
60
|
+
} else if (isAttemptToFixTest && attemptIndex > 0) {
|
|
61
|
+
task.result.state = 'run'
|
|
62
|
+
} else if (isEarlyFlakeDetectionTestAttempt) {
|
|
63
|
+
const isSkippedRepeat = prepareEarlyFlakeDetectionAttempt(task, attemptIndex)
|
|
64
|
+
if (!isSkippedRepeat && attemptIndex > 0) {
|
|
65
|
+
task.result.state = 'run'
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (isAttemptToFixTest || isEarlyFlakeDetectionTestAttempt || isQuarantinedTest) {
|
|
70
|
+
onTestFinished(() => {
|
|
71
|
+
if (attemptIndex === getFinalAttemptIndex(task)) {
|
|
72
|
+
if (isAttemptToFixTest || isEarlyFlakeDetectionTestAttempt) {
|
|
73
|
+
recordTestOptimizationStatus(task, attemptIndex, true)
|
|
74
|
+
}
|
|
75
|
+
switchQuarantinedFinalFailure(task, attemptIndex)
|
|
76
|
+
}
|
|
77
|
+
})
|
|
78
|
+
}
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
afterEach(function ({ task }) {
|
|
82
|
+
const attemptIndex = task.meta.__ddTestOptCurrentAttemptIndex
|
|
83
|
+
if (restoreEarlyFlakeDetectionSkippedResult(task)) {
|
|
84
|
+
return
|
|
85
|
+
}
|
|
86
|
+
if (attemptIndex === getFinalAttemptIndex(task)) {
|
|
87
|
+
recordTestOptimizationStatus(task, attemptIndex)
|
|
88
|
+
}
|
|
89
|
+
switchQuarantinedFinalFailure(task, attemptIndex)
|
|
90
|
+
})
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function applyExecutionChanges (suite) {
|
|
94
|
+
for (const task of suite?.tasks || []) {
|
|
95
|
+
if (task.type === 'suite') {
|
|
96
|
+
applyExecutionChanges(task)
|
|
97
|
+
continue
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const testSuite = getTestSuite(task)
|
|
101
|
+
const testName = getTestName(task)
|
|
102
|
+
if (attemptToFixTests[testSuite]?.[testName]) {
|
|
103
|
+
task.retry = 0
|
|
104
|
+
task.repeats = attemptToFixRetries
|
|
105
|
+
task.meta.__ddTestOptAtfRetries = attemptToFixRetries
|
|
106
|
+
} else if (disabledTests[testSuite]?.[testName]) {
|
|
107
|
+
task.mode = 'skip'
|
|
108
|
+
} else if (isEarlyFlakeDetectionTest(testSuite, testName)) {
|
|
109
|
+
task.retry = 0
|
|
110
|
+
task.repeats = earlyFlakeDetectionRetries
|
|
111
|
+
task.meta.__ddTestOptEfdRetries = earlyFlakeDetectionRetries
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function getNextAttemptIndex (task) {
|
|
117
|
+
const attemptIndex = nextAttemptIndexByTask.get(task) || 0
|
|
118
|
+
nextAttemptIndexByTask.set(task, attemptIndex + 1)
|
|
119
|
+
task.meta.__ddTestOptCurrentAttemptIndex = attemptIndex
|
|
120
|
+
return attemptIndex
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function recordTestOptimizationStatus (task, attemptIndex = task.result?.repeatCount || 0, onlyIfNewErrors = false) {
|
|
124
|
+
const testSuite = getTestSuite(task)
|
|
125
|
+
const testName = getTestName(task)
|
|
126
|
+
|
|
127
|
+
if (attemptToFixTests[testSuite]?.[testName]) {
|
|
128
|
+
recordAttemptToFixStatus(task, attemptIndex, onlyIfNewErrors)
|
|
129
|
+
} else if (isEarlyFlakeDetectionTest(testSuite, testName)) {
|
|
130
|
+
recordEarlyFlakeDetectionStatus(task, attemptIndex, onlyIfNewErrors)
|
|
131
|
+
} else if (task.repeats > 0) {
|
|
132
|
+
recordManualRepeatStatus(task, attemptIndex)
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function recordAttemptToFixStatus (task, attemptIndex, onlyIfNewErrors) {
|
|
137
|
+
if (onlyIfNewErrors && !hasNewErrors(task.meta.__ddTestOptAtfErrorCounts, attemptIndex, task)) {
|
|
138
|
+
return
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
task.meta.__ddTestOptAtfStatuses ||= []
|
|
142
|
+
task.meta.__ddTestOptAtfErrorCounts ||= []
|
|
143
|
+
task.meta.__ddTestOptAtfStatuses[attemptIndex] = getAttemptStatus(
|
|
144
|
+
task,
|
|
145
|
+
task.meta.__ddTestOptAtfErrorCounts,
|
|
146
|
+
attemptIndex
|
|
147
|
+
)
|
|
148
|
+
task.meta.__ddTestOptAtfErrorCounts[attemptIndex] = task.result?.errors?.length || 0
|
|
149
|
+
|
|
150
|
+
if (
|
|
151
|
+
attemptIndex === getAttemptToFixRetryCount(task) &&
|
|
152
|
+
task.meta.__ddTestOptAtfStatuses.includes('fail') &&
|
|
153
|
+
task.result?.state === 'pass'
|
|
154
|
+
) {
|
|
155
|
+
task.result.state = 'fail'
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function recordEarlyFlakeDetectionStatus (task, attemptIndex, onlyIfNewErrors) {
|
|
160
|
+
if (restoreEarlyFlakeDetectionSkippedResult(task)) {
|
|
161
|
+
return
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const retryCount = earlyFlakeDetectionRetriesByTask.get(task)
|
|
165
|
+
if (retryCount !== undefined && attemptIndex > retryCount) {
|
|
166
|
+
return
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if (onlyIfNewErrors && !hasNewErrors(task.meta.__ddTestOptEfdErrorCounts, attemptIndex, task)) {
|
|
170
|
+
return
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if (!earlyFlakeDetectionRetriesByTask.has(task)) {
|
|
174
|
+
const retryCount = getEarlyFlakeDetectionRetryCount(task)
|
|
175
|
+
earlyFlakeDetectionRetriesByTask.set(task, retryCount)
|
|
176
|
+
task.repeats = retryCount
|
|
177
|
+
task.meta.__ddTestOptEfdRetries = retryCount
|
|
178
|
+
if (retryCount === 0 && hasEarlyFlakeDetectionSlowRetries) {
|
|
179
|
+
task.meta.__ddTestOptEfdAbortReason = 'slow'
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
task.meta.__ddTestOptEfdStatuses ||= []
|
|
184
|
+
task.meta.__ddTestOptEfdErrorCounts ||= []
|
|
185
|
+
task.meta.__ddTestOptEfdStatuses[attemptIndex] = getAttemptStatus(
|
|
186
|
+
task,
|
|
187
|
+
task.meta.__ddTestOptEfdErrorCounts,
|
|
188
|
+
attemptIndex
|
|
189
|
+
)
|
|
190
|
+
task.meta.__ddTestOptEfdErrorCounts[attemptIndex] = task.result?.errors?.length || 0
|
|
191
|
+
|
|
192
|
+
if (attemptIndex === getEarlyFlakeDetectionRetryCountForTask(task) &&
|
|
193
|
+
task.meta.__ddTestOptEfdStatuses.includes('pass')) {
|
|
194
|
+
task.result.state = 'pass'
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function recordManualRepeatStatus (task, attemptIndex) {
|
|
199
|
+
task.meta.__ddTestOptRepeatStatuses ||= []
|
|
200
|
+
task.meta.__ddTestOptRepeatErrorCounts ||= []
|
|
201
|
+
task.meta.__ddTestOptRepeatStatuses[attemptIndex] = getManualRepeatStatus(
|
|
202
|
+
task,
|
|
203
|
+
task.meta.__ddTestOptRepeatErrorCounts,
|
|
204
|
+
attemptIndex
|
|
205
|
+
)
|
|
206
|
+
task.meta.__ddTestOptRepeatErrorCounts[attemptIndex] = task.result?.errors?.length || 0
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function restoreEarlyFlakeDetectionSkippedResult (task) {
|
|
210
|
+
if (!task.meta.__ddTestOptEfdSkipCurrentAttempt) {
|
|
211
|
+
return false
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
delete task.meta.__ddTestOptEfdSkipCurrentAttempt
|
|
215
|
+
const skippedResult = earlyFlakeDetectionSkippedResults.get(task)
|
|
216
|
+
if (skippedResult) {
|
|
217
|
+
task.result = skippedResult
|
|
218
|
+
earlyFlakeDetectionSkippedResults.delete(task)
|
|
219
|
+
}
|
|
220
|
+
return true
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function hasNewErrors (errorCounts, attemptIndex, task) {
|
|
224
|
+
const recordedErrorCount = errorCounts?.[attemptIndex]
|
|
225
|
+
const previousErrorCount = recordedErrorCount ?? getPreviousErrorCount(errorCounts, attemptIndex)
|
|
226
|
+
return (task.result?.errors?.length || 0) > previousErrorCount
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function getFinalAttemptIndex (task) {
|
|
230
|
+
const testSuite = getTestSuite(task)
|
|
231
|
+
const testName = getTestName(task)
|
|
232
|
+
|
|
233
|
+
if (attemptToFixTests[testSuite]?.[testName]) {
|
|
234
|
+
return getAttemptToFixRetryCount(task)
|
|
235
|
+
}
|
|
236
|
+
if (isEarlyFlakeDetectionTest(testSuite, testName)) {
|
|
237
|
+
return getEarlyFlakeDetectionRetryCountForTask(task)
|
|
238
|
+
}
|
|
239
|
+
return task.repeats
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function switchQuarantinedFinalFailure (task, attemptIndex) {
|
|
243
|
+
const testSuite = getTestSuite(task)
|
|
244
|
+
const testName = getTestName(task)
|
|
245
|
+
if (
|
|
246
|
+
!quarantinedTests[testSuite]?.[testName] ||
|
|
247
|
+
attemptToFixTests[testSuite]?.[testName] ||
|
|
248
|
+
task.result?.state !== 'fail'
|
|
249
|
+
) {
|
|
250
|
+
return
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
const retryCount = task.result?.retryCount || 0
|
|
254
|
+
const retryLimit = task.retry || 0
|
|
255
|
+
if (retryCount < retryLimit || attemptIndex < getFinalAttemptIndex(task)) {
|
|
256
|
+
return
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
task.meta.__ddTestOptQuarantinedFailed = true
|
|
260
|
+
task.result.state = 'pass'
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
function getAttemptToFixRetryCount (task) {
|
|
264
|
+
return task.meta.__ddTestOptAtfRetries ?? task.repeats
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
function getEarlyFlakeDetectionRetryCountForTask (task) {
|
|
268
|
+
return earlyFlakeDetectionRetriesByTask.get(task) ?? task.meta.__ddTestOptEfdRetries ?? task.repeats
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
function getAttemptStatus (task, errorCounts, repeatCount) {
|
|
272
|
+
const errorCount = task.result?.errors?.length || 0
|
|
273
|
+
if (errorCount > getPreviousErrorCount(errorCounts, repeatCount)) {
|
|
274
|
+
return 'fail'
|
|
275
|
+
}
|
|
276
|
+
return task.result?.state === 'fail' ? 'fail' : 'pass'
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
function getManualRepeatStatus (task, errorCounts, repeatCount) {
|
|
280
|
+
const errorCount = task.result?.errors?.length || 0
|
|
281
|
+
return errorCount > getPreviousErrorCount(errorCounts, repeatCount) ? 'fail' : 'pass'
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
function getPreviousErrorCount (errorCounts, repeatCount) {
|
|
285
|
+
for (let index = repeatCount - 1; index >= 0; index--) {
|
|
286
|
+
if (errorCounts[index] !== undefined) {
|
|
287
|
+
return errorCounts[index]
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
return 0
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
function prepareEarlyFlakeDetectionAttempt (task, attemptIndex) {
|
|
294
|
+
if (attemptIndex === 0) {
|
|
295
|
+
earlyFlakeDetectionStartByTask.set(task, performance.now())
|
|
296
|
+
return false
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
let retryCount = earlyFlakeDetectionRetriesByTask.get(task)
|
|
300
|
+
if (retryCount === undefined) {
|
|
301
|
+
retryCount = getEarlyFlakeDetectionRetryCount(task)
|
|
302
|
+
earlyFlakeDetectionRetriesByTask.set(task, retryCount)
|
|
303
|
+
task.repeats = retryCount
|
|
304
|
+
task.meta.__ddTestOptEfdRetries = retryCount
|
|
305
|
+
if (retryCount === 0 && hasEarlyFlakeDetectionSlowRetries) {
|
|
306
|
+
task.meta.__ddTestOptEfdAbortReason = 'slow'
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
if (attemptIndex <= retryCount) {
|
|
311
|
+
earlyFlakeDetectionStartByTask.set(task, performance.now())
|
|
312
|
+
return false
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
if (!canReplaceVitestTaskFn()) {
|
|
316
|
+
earlyFlakeDetectionStartByTask.set(task, performance.now())
|
|
317
|
+
return false
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
if (task.result) {
|
|
321
|
+
earlyFlakeDetectionSkippedResults.set(task, {
|
|
322
|
+
...task.result,
|
|
323
|
+
errors: task.result.errors?.slice(),
|
|
324
|
+
})
|
|
325
|
+
}
|
|
326
|
+
task.meta.__ddTestOptEfdSkipCurrentAttempt = true
|
|
327
|
+
replaceVitestTaskFn(task, noopTest)
|
|
328
|
+
return true
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
function getEarlyFlakeDetectionRetryCount (task) {
|
|
332
|
+
if (!hasEarlyFlakeDetectionSlowRetries) {
|
|
333
|
+
return earlyFlakeDetectionRetries
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
const executionStart = earlyFlakeDetectionStartByTask.get(task)
|
|
337
|
+
const duration = executionStart === undefined ? task.result?.duration ?? 0 : performance.now() - executionStart
|
|
338
|
+
for (const { key, limitMs } of earlyFlakeDetectionRetryThresholds) {
|
|
339
|
+
if (duration < limitMs) {
|
|
340
|
+
return earlyFlakeDetectionSlowRetries[key] ?? 0
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
return 0
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
function noopTest () {}
|
|
347
|
+
|
|
348
|
+
/**
|
|
349
|
+
* Returns whether Vitest's private task function setter is available.
|
|
350
|
+
*
|
|
351
|
+
* @returns {boolean}
|
|
352
|
+
*/
|
|
353
|
+
function canReplaceVitestTaskFn () {
|
|
354
|
+
return typeof setVitestTaskFn === 'function'
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
/**
|
|
358
|
+
* Replaces the function Vitest runs for a task.
|
|
359
|
+
*
|
|
360
|
+
* @param {object} task
|
|
361
|
+
* @param {(...args: unknown[]) => unknown} testFn
|
|
362
|
+
*/
|
|
363
|
+
function replaceVitestTaskFn (task, testFn) {
|
|
364
|
+
setVitestTaskFn(task, testFn)
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
function isEarlyFlakeDetectionTest (testSuite, testName) {
|
|
368
|
+
if (!isEarlyFlakeDetectionEnabled || earlyFlakeDetectionRetries <= 0) return false
|
|
369
|
+
if (isModifiedTest(testSuite)) return true
|
|
370
|
+
const testsForSuite = knownTests[testSuite] || []
|
|
371
|
+
return !testsForSuite.includes(testName)
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
function isModifiedTest (testSuite) {
|
|
375
|
+
return modifiedFiles[testSuite]?.length > 0
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
function getTestSuite (task) {
|
|
379
|
+
let filepath = realpaths.get(task.file.filepath)
|
|
380
|
+
if (filepath === undefined) {
|
|
381
|
+
filepath = realpath(task.file.filepath)
|
|
382
|
+
realpaths.set(task.file.filepath, filepath)
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
return normalizePath(relative(repositoryRoot, filepath))
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
function realpath (filepath) {
|
|
389
|
+
try {
|
|
390
|
+
return realpathSync(filepath)
|
|
391
|
+
} catch {
|
|
392
|
+
return filepath
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
function getTestName (task) {
|
|
397
|
+
let testName = task.name
|
|
398
|
+
let currentTask = task.suite
|
|
399
|
+
|
|
400
|
+
while (currentTask) {
|
|
401
|
+
if (currentTask.name) {
|
|
402
|
+
testName = `${currentTask.name} ${testName}`
|
|
403
|
+
}
|
|
404
|
+
currentTask = currentTask.suite
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
return testName
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
function normalizePath (filepath) {
|
|
411
|
+
return filepath.replaceAll('\\', '/')
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
function getIsNoWorkerInitActive () {
|
|
415
|
+
try {
|
|
416
|
+
// eslint-disable-next-line eslint-rules/eslint-process-env
|
|
417
|
+
const value = process.env[VITEST_NO_WORKER_INIT_ACTIVE_ENV]
|
|
418
|
+
return value === '1' || value === 'true'
|
|
419
|
+
} catch {
|
|
420
|
+
return false
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
function getProvidedContext () {
|
|
425
|
+
try {
|
|
426
|
+
return globalThis.__vitest_worker__.providedContext._ddVitestWorkerSetup || {}
|
|
427
|
+
} catch {
|
|
428
|
+
return {}
|
|
429
|
+
}
|
|
430
|
+
}
|
package/ext/tags.js
CHANGED
package/index.d.ts
CHANGED
|
@@ -2625,6 +2625,18 @@ declare namespace tracer {
|
|
|
2625
2625
|
typeResolver?: any,
|
|
2626
2626
|
}
|
|
2627
2627
|
|
|
2628
|
+
/** Context object passed to the `hooks.resolve` callback for each instrumented field. */
|
|
2629
|
+
interface FieldContext {
|
|
2630
|
+
/** The field name being resolved */
|
|
2631
|
+
fieldName: string;
|
|
2632
|
+
/** The dot-separated field path (e.g. `'user.address.city'`) */
|
|
2633
|
+
path: string;
|
|
2634
|
+
/** The error from the resolver, or `null` if it succeeded */
|
|
2635
|
+
error: Error | null;
|
|
2636
|
+
/** The value returned by the resolver (sync resolvers only; `undefined` for async) */
|
|
2637
|
+
result: unknown;
|
|
2638
|
+
}
|
|
2639
|
+
|
|
2628
2640
|
/**
|
|
2629
2641
|
* This plugin automatically instruments the
|
|
2630
2642
|
* [graphql](https://github.com/graphql/graphql-js) module.
|
|
@@ -2703,6 +2715,7 @@ declare namespace tracer {
|
|
|
2703
2715
|
execute?: (span?: Span, args?: ExecutionArgs, res?: any) => void;
|
|
2704
2716
|
validate?: (span?: Span, document?: any, errors?: any) => void;
|
|
2705
2717
|
parse?: (span?: Span, source?: any, document?: any) => void;
|
|
2718
|
+
resolve?: (span?: Span, field?: FieldContext) => void;
|
|
2706
2719
|
}
|
|
2707
2720
|
}
|
|
2708
2721
|
|
package/initialize.mjs
CHANGED
|
@@ -19,7 +19,7 @@ import { isMainThread } from 'worker_threads'
|
|
|
19
19
|
// This file must support Node.js 12.0.0 syntax
|
|
20
20
|
|
|
21
21
|
import {
|
|
22
|
-
|
|
22
|
+
iitmExclusionRegExp,
|
|
23
23
|
load as hookLoad,
|
|
24
24
|
resolve as hookResolve,
|
|
25
25
|
} from './loader-hook.mjs'
|
|
@@ -67,8 +67,7 @@ const [NODE_MAJOR, NODE_MINOR] = process.versions.node.split('.').map(Number)
|
|
|
67
67
|
const brokenLoaders = NODE_MAJOR === 18 && NODE_MINOR === 0
|
|
68
68
|
|
|
69
69
|
export async function load (url, context, nextLoad) {
|
|
70
|
-
const
|
|
71
|
-
const loadHook = (brokenLoaders || iitmExclusionsMatch) ? nextLoad : hookLoad
|
|
70
|
+
const loadHook = (brokenLoaders || iitmExclusionRegExp.test(url)) ? nextLoad : hookLoad
|
|
72
71
|
return insertInit(await loadHook(url, context, nextLoad), url, context)
|
|
73
72
|
}
|
|
74
73
|
|
|
@@ -78,8 +77,8 @@ if (isMainThread) {
|
|
|
78
77
|
const require = Module.createRequire(import.meta.url)
|
|
79
78
|
require('./init.js')
|
|
80
79
|
if (Module.register) {
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
80
|
+
// The loader builds its own include/exclude matcher in `initialize`, so no
|
|
81
|
+
// options need to cross the registration boundary.
|
|
82
|
+
Module.register('./loader-hook.mjs', import.meta.url)
|
|
84
83
|
}
|
|
85
84
|
}
|