dd-trace 5.83.0 → 5.84.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.
Files changed (44) hide show
  1. package/LICENSE-3rdparty.csv +0 -1
  2. package/index.d.ts +61 -1
  3. package/package.json +3 -3
  4. package/packages/datadog-instrumentations/src/helpers/rewriter/compiler.js +6 -0
  5. package/packages/datadog-instrumentations/src/helpers/rewriter/index.js +1 -0
  6. package/packages/datadog-instrumentations/src/helpers/rewriter/transforms.js +73 -16
  7. package/packages/datadog-instrumentations/src/jest.js +89 -50
  8. package/packages/datadog-instrumentations/src/playwright.js +12 -8
  9. package/packages/datadog-plugin-cucumber/src/index.js +33 -32
  10. package/packages/datadog-plugin-playwright/src/index.js +23 -23
  11. package/packages/datadog-shimmer/src/shimmer.js +2 -5
  12. package/packages/dd-trace/src/agent/info.js +57 -0
  13. package/packages/dd-trace/src/agent/url.js +28 -0
  14. package/packages/dd-trace/src/appsec/index.js +47 -7
  15. package/packages/dd-trace/src/ci-visibility/exporters/agent-proxy/index.js +3 -4
  16. package/packages/dd-trace/src/ci-visibility/exporters/agentless/di-logs-writer.js +3 -3
  17. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +2 -9
  18. package/packages/dd-trace/src/ci-visibility/telemetry.js +6 -2
  19. package/packages/dd-trace/src/config/index.js +15 -1
  20. package/packages/dd-trace/src/config/remote_config.js +1 -0
  21. package/packages/dd-trace/src/config/supported-configurations.json +1 -1
  22. package/packages/dd-trace/src/crashtracking/crashtracker.js +2 -5
  23. package/packages/dd-trace/src/datastreams/writer.js +2 -8
  24. package/packages/dd-trace/src/debugger/devtools_client/config.js +2 -7
  25. package/packages/dd-trace/src/debugger/devtools_client/json-buffer.js +10 -11
  26. package/packages/dd-trace/src/dogstatsd.js +3 -9
  27. package/packages/dd-trace/src/exporters/agent/index.js +4 -8
  28. package/packages/dd-trace/src/exporters/agent/writer.js +3 -2
  29. package/packages/dd-trace/src/exporters/common/{agent-info-exporter.js → buffering-exporter.js} +10 -37
  30. package/packages/dd-trace/src/exporters/span-stats/index.js +3 -10
  31. package/packages/dd-trace/src/llmobs/writers/base.js +2 -8
  32. package/packages/dd-trace/src/llmobs/writers/util.js +3 -9
  33. package/packages/dd-trace/src/log/index.js +45 -30
  34. package/packages/dd-trace/src/log/writer.js +13 -78
  35. package/packages/dd-trace/src/openfeature/writers/base.js +2 -8
  36. package/packages/dd-trace/src/openfeature/writers/util.js +3 -8
  37. package/packages/dd-trace/src/profiling/config.js +3 -6
  38. package/packages/dd-trace/src/remote_config/capabilities.js +1 -0
  39. package/packages/dd-trace/src/remote_config/index.js +2 -7
  40. package/packages/dd-trace/src/startup-log.js +2 -2
  41. package/vendor/dist/@isaacs/ttlcache/index.js +1 -1
  42. package/vendor/dist/esquery/index.js +1 -1
  43. package/vendor/dist/meriyah/index.js +1 -1
  44. package/vendor/dist/protobufjs/index.js +1 -1
@@ -11,7 +11,6 @@
11
11
  "@isaacs/ttlcache","https://github.com/isaacs/ttlcache","['BlueOak-1.0.0']","['Isaac Z. Schlueter']"
12
12
  "@jsep-plugin/assignment","https://github.com/EricSmekens/jsep","['MIT']","['Shelly']"
13
13
  "@jsep-plugin/regex","https://github.com/EricSmekens/jsep","['MIT']","['Shelly']"
14
- "@openfeature/server-sdk","https://github.com/open-feature/js-sdk","['Apache-2.0']","['open-feature']"
15
14
  "@opentelemetry/api","https://github.com/open-telemetry/opentelemetry-js","['Apache-2.0']","['OpenTelemetry Authors']"
16
15
  "@opentelemetry/api-logs","https://github.com/open-telemetry/opentelemetry-js","['Apache-2.0']","['OpenTelemetry Authors']"
17
16
  "@opentelemetry/core","https://github.com/open-telemetry/opentelemetry-js","['Apache-2.0']","['OpenTelemetry Authors']"
package/index.d.ts CHANGED
@@ -148,7 +148,7 @@ interface Tracer extends opentracing.Tracer {
148
148
  * OpenFeature Provider with Remote Config integration.
149
149
  *
150
150
  * Extends DatadogNodeServerProvider with Remote Config integration for dynamic flag configuration.
151
- * Enable with DD_FLAGGING_PROVIDER_ENABLED=true.
151
+ * Enable with DD_EXPERIMENTAL_FLAGGING_PROVIDER_ENABLED=true.
152
152
  *
153
153
  * @beta This feature is in preview and not ready for production use
154
154
  */
@@ -241,6 +241,8 @@ interface Plugins {
241
241
  "express": tracer.plugins.express;
242
242
  "fastify": tracer.plugins.fastify;
243
243
  "fetch": tracer.plugins.fetch;
244
+ "find-my-way": tracer.plugins.find_my_way;
245
+ "fs": tracer.plugins.fs;
244
246
  "generic-pool": tracer.plugins.generic_pool;
245
247
  "google-cloud-pubsub": tracer.plugins.google_cloud_pubsub;
246
248
  "google-cloud-vertexai": tracer.plugins.google_cloud_vertexai;
@@ -269,6 +271,7 @@ interface Plugins {
269
271
  "mysql2": tracer.plugins.mysql2;
270
272
  "net": tracer.plugins.net;
271
273
  "next": tracer.plugins.next;
274
+ "nyc": tracer.plugins.nyc;
272
275
  "openai": tracer.plugins.openai;
273
276
  "opensearch": tracer.plugins.opensearch;
274
277
  "oracledb": tracer.plugins.oracledb;
@@ -286,7 +289,9 @@ interface Plugins {
286
289
  "tedious": tracer.plugins.tedious;
287
290
  "undici": tracer.plugins.undici;
288
291
  "vitest": tracer.plugins.vitest;
292
+ "web": tracer.plugins.web;
289
293
  "winston": tracer.plugins.winston;
294
+ "ws": tracer.plugins.ws;
290
295
  }
291
296
 
292
297
  declare namespace tracer {
@@ -687,6 +692,7 @@ declare namespace tracer {
687
692
  /**
688
693
  * Whether to enable the feature flagging provider.
689
694
  * Requires Remote Config to be properly configured.
695
+ * Can be configured via DD_EXPERIMENTAL_FLAGGING_PROVIDER_ENABLED environment variable.
690
696
  *
691
697
  * @default false
692
698
  */
@@ -694,6 +700,7 @@ declare namespace tracer {
694
700
  /**
695
701
  * Timeout in milliseconds for OpenFeature provider initialization.
696
702
  * If configuration is not received within this time, initialization fails.
703
+ * Can be configured via DD_EXPERIMENTAL_FLAGGING_PROVIDER_INITIALIZATION_TIMEOUT_MS environment variable.
697
704
  *
698
705
  * @default 30000
699
706
  */
@@ -1924,6 +1931,16 @@ declare namespace tracer {
1924
1931
  */
1925
1932
  interface fetch extends HttpClient {}
1926
1933
 
1934
+ /**
1935
+ * This plugin patches the [find-my-way](https://github.com/delvedor/find-my-way) router.
1936
+ */
1937
+ interface find_my_way extends Integration {}
1938
+
1939
+ /**
1940
+ * This plugin automatically instruments Node.js core fs operations.
1941
+ */
1942
+ interface fs extends Instrumentation {}
1943
+
1927
1944
  /**
1928
1945
  * This plugin patches the [generic-pool](https://github.com/coopernurse/node-pool)
1929
1946
  * module to bind the callbacks the the caller context.
@@ -2355,6 +2372,11 @@ declare namespace tracer {
2355
2372
  };
2356
2373
  }
2357
2374
 
2375
+ /**
2376
+ * This plugin integrates with [nyc](https://github.com/istanbuljs/nyc) for CI visibility.
2377
+ */
2378
+ interface nyc extends Integration {}
2379
+
2358
2380
  /**
2359
2381
  * This plugin automatically instruments the
2360
2382
  * [openai](https://platform.openai.com/docs/api-reference?lang=node.js) module.
@@ -2541,6 +2563,32 @@ declare namespace tracer {
2541
2563
  */
2542
2564
  interface vitest extends Integration {}
2543
2565
 
2566
+ /**
2567
+ * This plugin implements shared web request instrumentation helpers.
2568
+ */
2569
+ interface web extends HttpServer {
2570
+ /**
2571
+ * Custom filter function used to decide whether a URL/path should be instrumented.
2572
+ * Takes precedence over allowlist/blocklist.
2573
+ */
2574
+ filter?: (urlOrPath: string) => boolean;
2575
+
2576
+ /**
2577
+ * Whether (or how) to obfuscate querystring values in `http.url`.
2578
+ *
2579
+ * - `true`: obfuscate all values
2580
+ * - `false`: disable obfuscation
2581
+ * - `string`: regex string used to obfuscate matching values (empty string disables)
2582
+ * - `RegExp`: regex used to obfuscate matching values
2583
+ */
2584
+ queryStringObfuscation?: boolean | string | RegExp;
2585
+
2586
+ /**
2587
+ * Whether to enable resource renaming when the framework route is unavailable.
2588
+ */
2589
+ resourceRenamingEnabled?: boolean;
2590
+ }
2591
+
2544
2592
  /**
2545
2593
  * This plugin patches the [winston](https://github.com/winstonjs/winston)
2546
2594
  * to automatically inject trace identifiers in log records when the
@@ -2548,6 +2596,18 @@ declare namespace tracer {
2548
2596
  * on the tracer.
2549
2597
  */
2550
2598
  interface winston extends Integration {}
2599
+
2600
+ /**
2601
+ * This plugin automatically instruments the
2602
+ * [ws](https://github.com/websockets/ws) module.
2603
+ */
2604
+ interface ws extends Instrumentation {
2605
+ /**
2606
+ * Controls whether websocket messages should be traced.
2607
+ * This is also configurable via `DD_TRACE_WEBSOCKET_MESSAGES_ENABLED`.
2608
+ */
2609
+ traceWebsocketMessagesEnabled?: boolean;
2610
+ }
2551
2611
  }
2552
2612
 
2553
2613
  export namespace opentelemetry {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dd-trace",
3
- "version": "5.83.0",
3
+ "version": "5.84.0",
4
4
  "description": "Datadog APM tracing client for JavaScript",
5
5
  "main": "index.js",
6
6
  "typings": "index.d.ts",
@@ -28,7 +28,7 @@
28
28
  "test:debugger": "mocha packages/dd-trace/test/debugger/**/*.spec.js",
29
29
  "test:debugger:ci": "nyc --no-clean --include \"packages/dd-trace/src/debugger/**/*.js\" -- npm run test:debugger",
30
30
  "test:eslint-rules": "node eslint-rules/*.test.mjs",
31
- "test:trace:core": "node scripts/mocha-parallel-files.js --expose-gc --timeout 30000 -- \"packages/dd-trace/test/*.spec.js\" \"packages/dd-trace/test/{ci-visibility,config,crashtracking,datastreams,encode,exporters,msgpack,opentelemetry,opentracing,payload-tagging,plugins,remote_config,service-naming,standalone,telemetry,external-logger}/**/*.spec.js\"",
31
+ "test:trace:core": "node scripts/mocha-parallel-files.js --expose-gc --timeout 30000 -- \"packages/dd-trace/test/*.spec.js\" \"packages/dd-trace/test/{agent,ci-visibility,config,crashtracking,datastreams,encode,exporters,msgpack,opentelemetry,opentracing,payload-tagging,plugins,remote_config,service-naming,standalone,telemetry,external-logger}/**/*.spec.js\"",
32
32
  "test:trace:core:ci": "nyc --no-clean --include \"packages/dd-trace/src/**/*.js\" -- npm run test:trace:core",
33
33
  "test:trace:guardrails": "mocha packages/dd-trace/test/guardrails/**/*.spec.js",
34
34
  "test:trace:guardrails:ci": "nyc --no-clean --include \"packages/dd-trace/src/guardrails/**/*.js\" -- npm run test:trace:guardrails",
@@ -137,7 +137,7 @@
137
137
  "@datadog/native-appsec": "10.3.0",
138
138
  "@datadog/native-iast-taint-tracking": "4.1.0",
139
139
  "@datadog/native-metrics": "3.1.1",
140
- "@datadog/openfeature-node-server": "^0.2.0",
140
+ "@datadog/openfeature-node-server": "^0.3.3",
141
141
  "@datadog/pprof": "5.13.2",
142
142
  "@datadog/wasm-js-rewriter": "5.0.1",
143
143
  "@opentelemetry/api": ">=1.0.0 <1.10.0",
@@ -24,4 +24,10 @@ module.exports = {
24
24
 
25
25
  return esquery.traverse(ast, selector, visitor)
26
26
  },
27
+
28
+ query: (ast, query) => {
29
+ esquery ??= require('../../../../../vendor/dist/esquery').default
30
+
31
+ return esquery.query(ast, query)
32
+ },
27
33
  }
@@ -127,6 +127,7 @@ function fromFunctionQuery (functionQuery) {
127
127
  if (className) {
128
128
  queries.push(
129
129
  `[id.name="${className}"]`,
130
+ `[id.name="${className}"] > ClassExpression`,
130
131
  `[id.name="${className}"] > ClassBody > [key.name="${methodName}"] > [async]`,
131
132
  `[id.name="${className}"] > ClassExpression > ClassBody > [key.name="${methodName}"] > [async]`
132
133
  )
@@ -1,6 +1,6 @@
1
1
  'use strict'
2
2
 
3
- const { parse } = require('./compiler')
3
+ const { parse, query } = require('./compiler')
4
4
 
5
5
  const tracingChannelPredicate = (node) => (
6
6
  node.specifiers?.[0]?.local?.name === 'tr_ch_apm_tracingChannel' ||
@@ -106,34 +106,91 @@ function traceInstanceMethod (state, node, program) {
106
106
  }
107
107
 
108
108
  function wrap (state, node) {
109
- const { channelName, operator, functionQuery: { index = -1 } } = state
109
+ const { channelName, operator } = state
110
+
111
+ if (operator === 'traceCallback') return wrapCallback(state, node)
112
+
110
113
  const async = operator === 'tracePromise' ? 'async' : ''
111
114
  const channelVariable = 'tr_ch_apm$' + channelName.replaceAll(':', '_')
112
- const tracedArgs = operator === 'traceCallback'
113
- ? `__apm$original_args.splice(${index}, 1, arguments[${index >= 0 ? index : `arguments.length + ${index}`}])`
114
- : '__apm$original_args'
115
- const traceParams = operator === 'traceCallback'
116
- ? `__apm$traced, ${index}`
117
- : '__apm$traced'
118
115
  const wrapper = parse(`
119
116
  function wrapper () {
120
- const __apm$original_args = arguments;
121
- const __apm$traced = ${async} function () {
117
+ const __apm$traced = ${async} () => {
122
118
  const __apm$wrapped = () => {};
123
- const __apm$traced_args = ${tracedArgs};
124
- return __apm$wrapped.apply(this, __apm$traced_args);
119
+ return __apm$wrapped.apply(this, arguments);
125
120
  };
126
- if (!${channelVariable}.hasSubscribers) return __apm$traced.apply(this, arguments);
127
- return ${channelVariable}.${operator}(${traceParams}, {
121
+ if (!${channelVariable}.hasSubscribers) return __apm$traced();
122
+ return ${channelVariable}.${operator}(__apm$traced, {
123
+ arguments,
124
+ self: this,
125
+ moduleVersion: "1.0.0"
126
+ });
127
+ }
128
+ `).body[0].body // Extract only block statement of function body.
129
+
130
+ // Replace the right-hand side assignment of `const __apm$wrapped = () => {}`.
131
+ query(wrapper, '[id.name=__apm$wrapped]')[0].init = node
132
+
133
+ return wrapper
134
+ }
135
+
136
+ function wrapCallback (state, node) {
137
+ const { channelName, functionQuery: { index = -1 } } = state
138
+ const channelVariable = 'tr_ch_apm$' + channelName.replaceAll(':', '_')
139
+ const wrapper = parse(`
140
+ function wrapper () {
141
+ const __apm$cb = Array.prototype.at.call(arguments, ${index});
142
+ const __apm$ctx = {
128
143
  arguments,
129
144
  self: this,
130
145
  moduleVersion: "1.0.0"
131
- }, this, ...arguments);
146
+ };
147
+ const __apm$traced = () => {
148
+ const __apm$wrapped = () => {};
149
+ return __apm$wrapped.apply(this, arguments);
150
+ };
151
+
152
+ if (!${channelVariable}.start.hasSubscribers) return __apm$traced();
153
+
154
+ function __apm$wrappedCb(err, res) {
155
+ if (err) {
156
+ __apm$ctx.error = err;
157
+ ${channelVariable}.error.publish(__apm$ctx);
158
+ } else {
159
+ __apm$ctx.result = res;
160
+ }
161
+
162
+ ${channelVariable}.asyncStart.runStores(__apm$ctx, () => {
163
+ try {
164
+ if (__apm$cb) {
165
+ return __apm$cb.apply(this, arguments);
166
+ }
167
+ } finally {
168
+ ${channelVariable}.asyncEnd.publish(__apm$ctx);
169
+ }
170
+ });
171
+ }
172
+
173
+ if (typeof __apm$cb !== 'function') {
174
+ return __apm$traced();
175
+ }
176
+ Array.prototype.splice.call(arguments, ${index}, 1, __apm$wrappedCb);
177
+
178
+ return ${channelVariable}.start.runStores(__apm$ctx, () => {
179
+ try {
180
+ return __apm$traced();
181
+ } catch (err) {
182
+ __apm$ctx.error = err;
183
+ ${channelVariable}.error.publish(__apm$ctx);
184
+ throw err;
185
+ } finally {
186
+ ${channelVariable}.end.publish(__apm$ctx);
187
+ }
188
+ });
132
189
  }
133
190
  `).body[0].body // Extract only block statement of function body.
134
191
 
135
192
  // Replace the right-hand side assignment of `const __apm$wrapped = () => {}`.
136
- wrapper.body[1].declarations[0].init.body.body[0].declarations[0].init = node
193
+ query(wrapper, '[id.name=__apm$wrapped]')[0].init = node
137
194
 
138
195
  return wrapper
139
196
  }
@@ -91,6 +91,7 @@ const wrappedWorkers = new WeakSet()
91
91
  const testSuiteMockedFiles = new Map()
92
92
  const testsToBeRetried = new Set()
93
93
  const testSuiteAbsolutePathsWithFastCheck = new Set()
94
+ const testSuiteJestObjects = new Map()
94
95
 
95
96
  const BREAKPOINT_HIT_GRACE_PERIOD_MS = 200
96
97
  const ATR_RETRY_SUPPRESSION_FLAG = '_ddDisableAtrRetry'
@@ -237,6 +238,28 @@ function getWrappedEnvironment (BaseEnvironment, jestVersion) {
237
238
  }
238
239
  }
239
240
 
241
+ /**
242
+ * Jest mock state issue during test retries
243
+ *
244
+ * Problem:
245
+ * - Jest tracks mock function calls using internal state (call count, call arguments, etc.)
246
+ * - When a test is retried, the mock state is not automatically reset
247
+ * - This causes assertions like `toHaveBeenCalledTimes(1)` to fail because the call count
248
+ * accumulates across retries
249
+ *
250
+ * The solution is to clear all mocks before each retry attempt.
251
+ */
252
+ resetMockState () {
253
+ try {
254
+ const jestObject = testSuiteJestObjects.get(this.testSuiteAbsolutePath)
255
+ if (jestObject?.clearAllMocks) {
256
+ jestObject.clearAllMocks()
257
+ }
258
+ } catch (e) {
259
+ log.warn('Error resetting mock state', e)
260
+ }
261
+ }
262
+
240
263
  // This function returns an array if the known tests are valid and null otherwise.
241
264
  getKnownTestsForSuite (suiteKnownTests) {
242
265
  // `suiteKnownTests` is `this.testEnvironmentOptions._ddKnownTests`,
@@ -339,8 +362,9 @@ function getWrappedEnvironment (BaseEnvironment, jestVersion) {
339
362
  if (event.name === 'test_start') {
340
363
  const testName = getJestTestName(event.test, this.getShouldStripSeedFromTestName())
341
364
  if (testsToBeRetried.has(testName)) {
342
- // This is needed because we're trying tests with the same name
365
+ // This is needed because we're retrying tests with the same name
343
366
  this.resetSnapshotState()
367
+ this.resetMockState()
344
368
  }
345
369
 
346
370
  let isNewTest = false
@@ -864,7 +888,6 @@ function getCliWrapper (isNewJestVersion) {
864
888
 
865
889
  const {
866
890
  results: {
867
- success,
868
891
  coverageMap,
869
892
  numFailedTestSuites,
870
893
  numFailedTests,
@@ -883,53 +906,6 @@ function getCliWrapper (isNewJestVersion) {
883
906
  // ignore errors
884
907
  }
885
908
  }
886
- let status, error
887
-
888
- if (success) {
889
- status = numTotalTests === 0 && numTotalTestSuites === 0 ? 'skip' : 'pass'
890
- } else {
891
- status = 'fail'
892
- error = new Error(`Failed test suites: ${numFailedTestSuites}. Failed tests: ${numFailedTests}`)
893
- }
894
- let timeoutId
895
-
896
- // Pass the resolve callback to defer it to DC listener
897
- const flushPromise = new Promise((resolve) => {
898
- onDone = () => {
899
- clearTimeout(timeoutId)
900
- resolve()
901
- }
902
- })
903
-
904
- const timeoutPromise = new Promise((resolve) => {
905
- timeoutId = setTimeout(() => {
906
- resolve('timeout')
907
- }, FLUSH_TIMEOUT).unref()
908
- })
909
-
910
- testSessionFinishCh.publish({
911
- status,
912
- isSuitesSkipped,
913
- isSuitesSkippingEnabled,
914
- isCodeCoverageEnabled,
915
- testCodeCoverageLinesTotal,
916
- numSkippedSuites,
917
- hasUnskippableSuites,
918
- hasForcedToRunSuites,
919
- error,
920
- isEarlyFlakeDetectionEnabled,
921
- isEarlyFlakeDetectionFaulty,
922
- isTestManagementTestsEnabled,
923
- onDone
924
- })
925
-
926
- const waitingResult = await Promise.race([flushPromise, timeoutPromise])
927
-
928
- if (waitingResult === 'timeout') {
929
- log.error('Timeout waiting for the tracer to flush')
930
- }
931
-
932
- numSkippedSuites = 0
933
909
 
934
910
  /**
935
911
  * If Early Flake Detection (EFD) is enabled the logic is as follows:
@@ -938,7 +914,6 @@ function getCliWrapper (isNewJestVersion) {
938
914
  * The rationale behind is the following: you may still be able to block your CI pipeline by gating
939
915
  * on flakiness (the test will be considered flaky), but you may choose to unblock the pipeline too.
940
916
  */
941
-
942
917
  if (isEarlyFlakeDetectionEnabled) {
943
918
  let numFailedTestsToIgnore = 0
944
919
  for (const testStatuses of newTestsTestStatuses.values()) {
@@ -996,6 +971,55 @@ function getCliWrapper (isNewJestVersion) {
996
971
  }
997
972
  }
998
973
 
974
+ // Determine session status after EFD and quarantine checks have potentially modified success
975
+ let status, error
976
+ if (result.results.success) {
977
+ status = numTotalTests === 0 && numTotalTestSuites === 0 ? 'skip' : 'pass'
978
+ } else {
979
+ status = 'fail'
980
+ error = new Error(`Failed test suites: ${numFailedTestSuites}. Failed tests: ${numFailedTests}`)
981
+ }
982
+
983
+ let timeoutId
984
+
985
+ // Pass the resolve callback to defer it to DC listener
986
+ const flushPromise = new Promise((resolve) => {
987
+ onDone = () => {
988
+ clearTimeout(timeoutId)
989
+ resolve()
990
+ }
991
+ })
992
+
993
+ const timeoutPromise = new Promise((resolve) => {
994
+ timeoutId = setTimeout(() => {
995
+ resolve('timeout')
996
+ }, FLUSH_TIMEOUT).unref()
997
+ })
998
+
999
+ testSessionFinishCh.publish({
1000
+ status,
1001
+ isSuitesSkipped,
1002
+ isSuitesSkippingEnabled,
1003
+ isCodeCoverageEnabled,
1004
+ testCodeCoverageLinesTotal,
1005
+ numSkippedSuites,
1006
+ hasUnskippableSuites,
1007
+ hasForcedToRunSuites,
1008
+ error,
1009
+ isEarlyFlakeDetectionEnabled,
1010
+ isEarlyFlakeDetectionFaulty,
1011
+ isTestManagementTestsEnabled,
1012
+ onDone
1013
+ })
1014
+
1015
+ const waitingResult = await Promise.race([flushPromise, timeoutPromise])
1016
+
1017
+ if (waitingResult === 'timeout') {
1018
+ log.error('Timeout waiting for the tracer to flush')
1019
+ }
1020
+
1021
+ numSkippedSuites = 0
1022
+
999
1023
  return result
1000
1024
  }, {
1001
1025
  replaceGetter: true
@@ -1148,9 +1172,19 @@ function jestAdapterWrapper (jestAdapter, jestVersion) {
1148
1172
  })
1149
1173
  }
1150
1174
  testSuiteFinishCh.publish({ status, errorMessage, testSuiteAbsolutePath: environment.testSuiteAbsolutePath })
1175
+
1176
+ // Cleanup per-suite state to avoid memory leaks
1177
+ testSuiteMockedFiles.delete(environment.testSuiteAbsolutePath)
1178
+ testSuiteJestObjects.delete(environment.testSuiteAbsolutePath)
1179
+
1151
1180
  return suiteResults
1152
1181
  }).catch(error => {
1153
1182
  testSuiteFinishCh.publish({ status: 'fail', error, testSuiteAbsolutePath: environment.testSuiteAbsolutePath })
1183
+
1184
+ // Cleanup per-suite state to avoid memory leaks
1185
+ testSuiteMockedFiles.delete(environment.testSuiteAbsolutePath)
1186
+ testSuiteJestObjects.delete(environment.testSuiteAbsolutePath)
1187
+
1154
1188
  throw error
1155
1189
  })
1156
1190
  })
@@ -1312,6 +1346,11 @@ addHook({
1312
1346
  const result = _createJestObjectFor.apply(this, arguments)
1313
1347
  const suiteFilePath = this._testPath || from
1314
1348
 
1349
+ // Store the jest object so we can access it later for resetting mock state
1350
+ if (suiteFilePath) {
1351
+ testSuiteJestObjects.set(suiteFilePath, result)
1352
+ }
1353
+
1315
1354
  shimmer.wrap(result, 'mock', mock => function (moduleName) {
1316
1355
  // If the library is mocked with `jest.mock`, we don't want to bypass jest's own require engine
1317
1356
  if (LIBRARIES_BYPASSING_JEST_REQUIRE_ENGINE.has(moduleName)) {
@@ -484,10 +484,11 @@ function dispatcherRunWrapper (run) {
484
484
 
485
485
  function dispatcherRunWrapperNew (run) {
486
486
  return function (testGroups) {
487
- // Filter out disabled tests from testGroups before they get scheduled
487
+ // Filter out disabled tests from testGroups before they get scheduled,
488
+ // unless they have attemptToFix (in which case they should still run and be retried)
488
489
  if (isTestManagementTestsEnabled) {
489
490
  testGroups.forEach(group => {
490
- group.tests = group.tests.filter(test => !test._ddIsDisabled)
491
+ group.tests = group.tests.filter(test => !test._ddIsDisabled || test._ddIsAttemptToFix)
491
492
  })
492
493
  // Remove empty groups
493
494
  testGroups = testGroups.filter(group => group.tests.length > 0)
@@ -911,14 +912,16 @@ addHook({
911
912
  const fileSuitesWithManagedTestsToProjects = new Map()
912
913
  for (const test of allTests) {
913
914
  const testProperties = getTestProperties(test)
914
- // Disabled tests are skipped and not retried
915
+ // Disabled tests are skipped unless they have attemptToFix
915
916
  if (testProperties.disabled) {
916
917
  test._ddIsDisabled = true
917
- test.expectedStatus = 'skipped'
918
- // setting test.expectedStatus to 'skipped' does not work for every case,
919
- // so we need to filter out disabled tests in dispatcherRunWrapperNew,
920
- // so they don't get to the workers
921
- continue
918
+ if (!testProperties.attemptToFix) {
919
+ test.expectedStatus = 'skipped'
920
+ // setting test.expectedStatus to 'skipped' does not work for every case,
921
+ // so we need to filter out disabled tests in dispatcherRunWrapperNew,
922
+ // so they don't get to the workers
923
+ continue
924
+ }
922
925
  }
923
926
  if (testProperties.quarantined) {
924
927
  test._ddIsQuarantined = true
@@ -947,6 +950,7 @@ addHook({
947
950
  (test) => test._ddIsAttemptToFix,
948
951
  [
949
952
  (test) => test._ddIsQuarantined && '_ddIsQuarantined',
953
+ (test) => test._ddIsDisabled && '_ddIsDisabled',
950
954
  '_ddIsAttemptToFix',
951
955
  '_ddIsAttemptToFixRetry'
952
956
  ],
@@ -5,48 +5,48 @@ const { storage } = require('../../datadog-core')
5
5
  const { getEnvironmentVariable, getValueFromEnvSources } = require('../../dd-trace/src/config/helper')
6
6
 
7
7
  const {
8
- TEST_SKIP_REASON,
9
- TEST_STATUS,
10
- TEST_SOURCE_START,
8
+ addIntelligentTestRunnerSpanTags,
11
9
  finishAllTraceSpans,
12
- getTestSuitePath,
10
+ getTestEndLine,
13
11
  getTestSuiteCommonTags,
14
- addIntelligentTestRunnerSpanTags,
15
- TEST_ITR_UNSKIPPABLE,
16
- TEST_ITR_FORCED_RUN,
17
- TEST_CODE_OWNERS,
12
+ getTestSuitePath,
13
+ isModifiedTest,
14
+ CUCUMBER_IS_PARALLEL,
18
15
  ITR_CORRELATION_ID,
19
- TEST_SOURCE_FILE,
20
- TEST_EARLY_FLAKE_ENABLED,
16
+ TEST_BROWSER_DRIVER,
17
+ TEST_CODE_OWNERS,
21
18
  TEST_EARLY_FLAKE_ABORT_REASON,
19
+ TEST_EARLY_FLAKE_ENABLED,
20
+ TEST_HAS_FAILED_ALL_RETRIES,
21
+ TEST_IS_MODIFIED,
22
22
  TEST_IS_NEW,
23
23
  TEST_IS_RETRY,
24
- CUCUMBER_IS_PARALLEL,
25
- TEST_RETRY_REASON,
24
+ TEST_IS_RUM_ACTIVE,
25
+ TEST_ITR_FORCED_RUN,
26
+ TEST_ITR_UNSKIPPABLE,
27
+ TEST_MANAGEMENT_ATTEMPT_TO_FIX_PASSED,
26
28
  TEST_MANAGEMENT_ENABLED,
27
- TEST_MANAGEMENT_IS_QUARANTINED,
28
- TEST_MANAGEMENT_IS_DISABLED,
29
29
  TEST_MANAGEMENT_IS_ATTEMPT_TO_FIX,
30
- TEST_HAS_FAILED_ALL_RETRIES,
31
- TEST_MANAGEMENT_ATTEMPT_TO_FIX_PASSED,
30
+ TEST_MANAGEMENT_IS_DISABLED,
31
+ TEST_MANAGEMENT_IS_QUARANTINED,
32
32
  TEST_RETRY_REASON_TYPES,
33
- TEST_IS_MODIFIED,
34
- isModifiedTest,
35
- getTestEndLine
33
+ TEST_RETRY_REASON,
34
+ TEST_SKIP_REASON,
35
+ TEST_SOURCE_FILE,
36
+ TEST_SOURCE_START,
37
+ TEST_STATUS,
36
38
  } = require('../../dd-trace/src/plugins/util/test')
37
39
  const { RESOURCE_NAME } = require('../../../ext/tags')
38
40
  const { COMPONENT, ERROR_MESSAGE } = require('../../dd-trace/src/constants')
39
41
  const {
42
+ TELEMETRY_CODE_COVERAGE_EMPTY,
43
+ TELEMETRY_CODE_COVERAGE_FINISHED,
44
+ TELEMETRY_CODE_COVERAGE_NUM_FILES,
45
+ TELEMETRY_CODE_COVERAGE_STARTED,
40
46
  TELEMETRY_EVENT_CREATED,
41
47
  TELEMETRY_EVENT_FINISHED,
42
- TELEMETRY_CODE_COVERAGE_STARTED,
43
- TELEMETRY_CODE_COVERAGE_FINISHED,
44
48
  TELEMETRY_ITR_FORCED_TO_RUN,
45
- TELEMETRY_CODE_COVERAGE_EMPTY,
46
49
  TELEMETRY_ITR_UNSKIPPABLE,
47
- TELEMETRY_CODE_COVERAGE_NUM_FILES,
48
- TEST_IS_RUM_ACTIVE,
49
- TEST_BROWSER_DRIVER,
50
50
  TELEMETRY_TEST_SESSION
51
51
  } = require('../../dd-trace/src/ci-visibility/telemetry')
52
52
 
@@ -362,18 +362,19 @@ class CucumberPlugin extends CiPlugin {
362
362
  }
363
363
  }
364
364
 
365
+ const spanTags = span.context()._tags
366
+ const telemetryTags = {
367
+ hasCodeOwners: !!spanTags[TEST_CODE_OWNERS],
368
+ isNew,
369
+ isRum: spanTags[TEST_IS_RUM_ACTIVE] === 'true',
370
+ browserDriver: spanTags[TEST_BROWSER_DRIVER]
371
+ }
365
372
  span.finish()
366
373
  if (!isStep) {
367
- const spanTags = span.context()._tags
368
374
  this.telemetry.ciVisEvent(
369
375
  TELEMETRY_EVENT_FINISHED,
370
376
  'test',
371
- {
372
- hasCodeOwners: !!spanTags[TEST_CODE_OWNERS],
373
- isNew,
374
- isRum: spanTags[TEST_IS_RUM_ACTIVE] === 'true',
375
- browserDriver: spanTags[TEST_BROWSER_DRIVER]
376
- }
377
+ telemetryTags
377
378
  )
378
379
  finishAllTraceSpans(span)
379
380
  // If it's a worker, flushing is cheap, as it's just sending data to the main process