dd-trace 4.43.0 → 4.45.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 (67) hide show
  1. package/ext/formats.d.ts +1 -0
  2. package/ext/formats.js +2 -1
  3. package/index.d.ts +2 -1
  4. package/init.js +3 -15
  5. package/package.json +4 -3
  6. package/packages/datadog-instrumentations/src/body-parser.js +14 -2
  7. package/packages/datadog-instrumentations/src/cucumber.js +10 -0
  8. package/packages/datadog-instrumentations/src/helpers/hooks.js +3 -2
  9. package/packages/datadog-instrumentations/src/helpers/register.js +21 -12
  10. package/packages/datadog-instrumentations/src/http/client.js +7 -1
  11. package/packages/datadog-instrumentations/src/http/server.js +50 -13
  12. package/packages/datadog-instrumentations/src/mocha/main.js +111 -78
  13. package/packages/datadog-instrumentations/src/nyc.js +23 -0
  14. package/packages/datadog-instrumentations/src/process.js +29 -0
  15. package/packages/datadog-instrumentations/src/vitest.js +65 -25
  16. package/packages/datadog-plugin-aws-sdk/src/base.js +15 -1
  17. package/packages/datadog-plugin-aws-sdk/src/services/kinesis.js +1 -1
  18. package/packages/datadog-plugin-aws-sdk/src/services/sns.js +1 -1
  19. package/packages/datadog-plugin-aws-sdk/src/services/sqs.js +3 -3
  20. package/packages/datadog-plugin-cucumber/src/index.js +12 -2
  21. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +53 -12
  22. package/packages/datadog-plugin-jest/src/index.js +17 -4
  23. package/packages/datadog-plugin-mocha/src/index.js +25 -6
  24. package/packages/datadog-plugin-nyc/src/index.js +35 -0
  25. package/packages/datadog-plugin-playwright/src/index.js +9 -4
  26. package/packages/datadog-plugin-vitest/src/index.js +32 -5
  27. package/packages/dd-trace/src/appsec/blocking.js +10 -1
  28. package/packages/dd-trace/src/appsec/channels.js +4 -1
  29. package/packages/dd-trace/src/appsec/iast/analyzers/analyzers.js +1 -0
  30. package/packages/dd-trace/src/appsec/iast/analyzers/code-injection-analyzer.js +16 -0
  31. package/packages/dd-trace/src/appsec/iast/analyzers/weak-hash-analyzer.js +2 -0
  32. package/packages/dd-trace/src/appsec/iast/taint-tracking/csi-methods.js +2 -1
  33. package/packages/dd-trace/src/appsec/iast/taint-tracking/taint-tracking-impl.js +11 -0
  34. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/code-injection-sensitive-analyzer.js +25 -0
  35. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-handler.js +2 -0
  36. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-regex.js +2 -2
  37. package/packages/dd-trace/src/appsec/iast/vulnerabilities.js +1 -0
  38. package/packages/dd-trace/src/appsec/index.js +12 -7
  39. package/packages/dd-trace/src/appsec/rasp.js +121 -7
  40. package/packages/dd-trace/src/appsec/recommended.json +220 -2
  41. package/packages/dd-trace/src/ci-visibility/early-flake-detection/get-known-tests.js +40 -1
  42. package/packages/dd-trace/src/ci-visibility/exporters/agentless/coverage-writer.js +2 -4
  43. package/packages/dd-trace/src/ci-visibility/exporters/agentless/writer.js +2 -4
  44. package/packages/dd-trace/src/ci-visibility/exporters/git/git_metadata.js +8 -7
  45. package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +2 -4
  46. package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +2 -4
  47. package/packages/dd-trace/src/ci-visibility/telemetry.js +29 -2
  48. package/packages/dd-trace/src/config.js +158 -153
  49. package/packages/dd-trace/src/data_streams.js +44 -0
  50. package/packages/dd-trace/src/datastreams/pathway.js +4 -2
  51. package/packages/dd-trace/src/log/index.js +32 -0
  52. package/packages/dd-trace/src/opentelemetry/context_manager.js +22 -39
  53. package/packages/dd-trace/src/opentelemetry/span_context.js +2 -2
  54. package/packages/dd-trace/src/opentelemetry/tracer.js +23 -14
  55. package/packages/dd-trace/src/opentelemetry/tracer_provider.js +9 -1
  56. package/packages/dd-trace/src/opentracing/propagation/log.js +1 -1
  57. package/packages/dd-trace/src/opentracing/propagation/text_map.js +60 -0
  58. package/packages/dd-trace/src/opentracing/propagation/text_map_dsm.js +43 -0
  59. package/packages/dd-trace/src/opentracing/span_context.js +1 -0
  60. package/packages/dd-trace/src/opentracing/tracer.js +10 -6
  61. package/packages/dd-trace/src/plugins/ci_plugin.js +11 -4
  62. package/packages/dd-trace/src/plugins/index.js +1 -0
  63. package/packages/dd-trace/src/plugins/plugin.js +12 -1
  64. package/packages/dd-trace/src/plugins/util/git.js +14 -1
  65. package/packages/dd-trace/src/proxy.js +1 -0
  66. package/packages/dd-trace/src/telemetry/index.js +1 -1
  67. package/packages/dd-trace/src/tracer.js +2 -0
package/ext/formats.d.ts CHANGED
@@ -5,6 +5,7 @@ declare const formats: {
5
5
  HTTP_HEADERS: typeof opentracing.FORMAT_HTTP_HEADERS
6
6
  BINARY: typeof opentracing.FORMAT_BINARY
7
7
  LOG: 'log'
8
+ TEXT_MAP_DSM: 'text_map_dsm'
8
9
  }
9
10
 
10
11
  export = formats
package/ext/formats.js CHANGED
@@ -4,5 +4,6 @@ module.exports = {
4
4
  TEXT_MAP: 'text_map',
5
5
  HTTP_HEADERS: 'http_headers',
6
6
  BINARY: 'binary',
7
- LOG: 'log'
7
+ LOG: 'log',
8
+ TEXT_MAP_DSM: 'text_map_dsm'
8
9
  }
package/index.d.ts CHANGED
@@ -1837,9 +1837,10 @@ declare namespace tracer {
1837
1837
  /**
1838
1838
  * Construct a new TracerProvider to register with @opentelemetry/api
1839
1839
  *
1840
+ * @param config Configuration object for the TracerProvider
1840
1841
  * @returns TracerProvider A TracerProvider instance
1841
1842
  */
1842
- new(): TracerProvider;
1843
+ new(config?: Record<string, unknown>): TracerProvider;
1843
1844
 
1844
1845
  /**
1845
1846
  * Returns a Tracer, creating one if one with the given name and version is
package/init.js CHANGED
@@ -2,22 +2,10 @@
2
2
 
3
3
  const path = require('path')
4
4
  const Module = require('module')
5
- const telemetry = require('./packages/dd-trace/src/telemetry/init-telemetry')
6
5
  const semver = require('semver')
7
-
8
- function isTrue (envVar) {
9
- return ['1', 'true', 'True'].includes(envVar)
10
- }
11
-
12
- // eslint-disable-next-line no-console
13
- let log = { info: isTrue(process.env.DD_TRACE_DEBUG) ? console.log : () => {} }
14
- if (semver.satisfies(process.versions.node, '>=16')) {
15
- const Config = require('./packages/dd-trace/src/config')
16
- log = require('./packages/dd-trace/src/log')
17
-
18
- // eslint-disable-next-line no-new
19
- new Config() // we need this to initialize the logger
20
- }
6
+ const log = require('./packages/dd-trace/src/log')
7
+ const { isTrue } = require('./packages/dd-trace/src/util')
8
+ const telemetry = require('./packages/dd-trace/src/telemetry/init-telemetry')
21
9
 
22
10
  let initBailout = false
23
11
  let clobberBailout = false
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dd-trace",
3
- "version": "4.43.0",
3
+ "version": "4.45.0",
4
4
  "description": "Datadog APM tracing client for JavaScript",
5
5
  "main": "index.js",
6
6
  "typings": "index.d.ts",
@@ -33,6 +33,7 @@
33
33
  "test:profiler": "tap \"packages/dd-trace/test/profiling/**/*.spec.js\"",
34
34
  "test:profiler:ci": "npm run test:profiler -- --coverage --nyc-arg=--include=\"packages/dd-trace/src/profiling/**/*.js\"",
35
35
  "test:integration": "mocha --timeout 60000 -r \"packages/dd-trace/test/setup/core.js\" \"integration-tests/*.spec.js\"",
36
+ "test:integration:appsec": "mocha --timeout 60000 -r \"packages/dd-trace/test/setup/core.js\" \"integration-tests/appsec/*.spec.js\"",
36
37
  "test:integration:cucumber": "mocha --timeout 60000 -r \"packages/dd-trace/test/setup/core.js\" \"integration-tests/cucumber/*.spec.js\"",
37
38
  "test:integration:cypress": "mocha --timeout 60000 -r \"packages/dd-trace/test/setup/core.js\" \"integration-tests/cypress/*.spec.js\"",
38
39
  "test:integration:jest": "mocha --timeout 60000 -r \"packages/dd-trace/test/setup/core.js\" \"integration-tests/jest/*.spec.js\"",
@@ -72,8 +73,8 @@
72
73
  },
73
74
  "dependencies": {
74
75
  "@datadog/native-appsec": "8.0.1",
75
- "@datadog/native-iast-rewriter": "2.3.1",
76
- "@datadog/native-iast-taint-tracking": "3.0.0",
76
+ "@datadog/native-iast-rewriter": "2.4.1",
77
+ "@datadog/native-iast-taint-tracking": "3.1.0",
77
78
  "@datadog/native-metrics": "^2.0.0",
78
79
  "@datadog/pprof": "5.3.0",
79
80
  "@datadog/sketches-js": "^2.1.0",
@@ -1,7 +1,7 @@
1
1
  'use strict'
2
2
 
3
3
  const shimmer = require('../../datadog-shimmer')
4
- const { channel, addHook } = require('./helpers/instrument')
4
+ const { channel, addHook, AsyncResource } = require('./helpers/instrument')
5
5
 
6
6
  const bodyParserReadCh = channel('datadog:body-parser:read:finish')
7
7
 
@@ -23,7 +23,19 @@ function publishRequestBodyAndNext (req, res, next) {
23
23
  addHook({
24
24
  name: 'body-parser',
25
25
  file: 'lib/read.js',
26
- versions: ['>=1.4.0']
26
+ versions: ['>=1.4.0 <1.20.0']
27
+ }, read => {
28
+ return shimmer.wrap(read, function (req, res, next) {
29
+ const nextResource = new AsyncResource('bound-anonymous-fn')
30
+ arguments[2] = nextResource.bind(publishRequestBodyAndNext(req, res, next))
31
+ return read.apply(this, arguments)
32
+ })
33
+ })
34
+
35
+ addHook({
36
+ name: 'body-parser',
37
+ file: 'lib/read.js',
38
+ versions: ['>=1.20.0']
27
39
  }, read => {
28
40
  return shimmer.wrap(read, function (req, res, next) {
29
41
  arguments[2] = publishRequestBodyAndNext(req, res, next)
@@ -28,6 +28,8 @@ const workerReportTraceCh = channel('ci:cucumber:worker-report:trace')
28
28
 
29
29
  const itrSkippedSuitesCh = channel('ci:cucumber:itr:skipped-suites')
30
30
 
31
+ const getCodeCoverageCh = channel('ci:nyc:get-coverage')
32
+
31
33
  const {
32
34
  getCoveredFilenamesFromCoverage,
33
35
  resetCoverage,
@@ -356,10 +358,18 @@ function getWrappedStart (start, frameworkVersion, isParallel = false) {
356
358
 
357
359
  const success = await start.apply(this, arguments)
358
360
 
361
+ let untestedCoverage
362
+ if (getCodeCoverageCh.hasSubscribers) {
363
+ untestedCoverage = await getChannelPromise(getCodeCoverageCh)
364
+ }
365
+
359
366
  let testCodeCoverageLinesTotal
360
367
 
361
368
  if (global.__coverage__) {
362
369
  try {
370
+ if (untestedCoverage) {
371
+ originalCoverageMap.merge(fromCoverageMapToCoverage(untestedCoverage))
372
+ }
363
373
  testCodeCoverageLinesTotal = originalCoverageMap.getCoverageSummary().lines.pct
364
374
  } catch (e) {
365
375
  // ignore errors
@@ -72,7 +72,6 @@ module.exports = {
72
72
  'microgateway-core': () => require('../microgateway-core'),
73
73
  mocha: () => require('../mocha'),
74
74
  'mocha-each': () => require('../mocha'),
75
- workerpool: () => require('../mocha'),
76
75
  moleculer: () => require('../moleculer'),
77
76
  mongodb: () => require('../mongodb'),
78
77
  'mongodb-core': () => require('../mongodb-core'),
@@ -89,6 +88,7 @@ module.exports = {
89
88
  'node:http2': () => require('../http2'),
90
89
  'node:https': () => require('../http'),
91
90
  'node:net': () => require('../net'),
91
+ nyc: () => require('../nyc'),
92
92
  oracledb: () => require('../oracledb'),
93
93
  openai: () => require('../openai'),
94
94
  paperplane: () => require('../paperplane'),
@@ -113,5 +113,6 @@ module.exports = {
113
113
  undici: () => require('../undici'),
114
114
  vitest: { esmFirst: true, fn: () => require('../vitest') },
115
115
  when: () => require('../when'),
116
- winston: () => require('../winston')
116
+ winston: () => require('../winston'),
117
+ workerpool: () => require('../mocha')
117
118
  }
@@ -29,6 +29,10 @@ if (!disabledInstrumentations.has('fetch')) {
29
29
  require('../fetch')
30
30
  }
31
31
 
32
+ if (!disabledInstrumentations.has('process')) {
33
+ require('../process')
34
+ }
35
+
32
36
  const HOOK_SYMBOL = Symbol('hookExportsMap')
33
37
 
34
38
  if (DD_TRACE_DEBUG && DD_TRACE_DEBUG.toLowerCase() !== 'false') {
@@ -86,18 +90,22 @@ for (const packageName of names) {
86
90
  }
87
91
 
88
92
  if (matchesFile) {
89
- const version = moduleVersion || getVersion(moduleBaseDir)
93
+ let version = moduleVersion
94
+ try {
95
+ version = version || getVersion(moduleBaseDir)
96
+ } catch (e) {
97
+ log.error(`Error getting version for "${name}": ${e.message}`)
98
+ log.error(e)
99
+ continue
100
+ }
90
101
  if (!Object.hasOwnProperty(namesAndSuccesses, name)) {
91
- namesAndSuccesses[name] = {
92
- success: false,
93
- version
94
- }
102
+ namesAndSuccesses[`${name}@${version}`] = false
95
103
  }
96
104
 
97
105
  if (matchVersion(version, versions)) {
98
106
  // Check if the hook already has a set moduleExport
99
107
  if (hook[HOOK_SYMBOL].has(moduleExports)) {
100
- namesAndSuccesses[name].success = true
108
+ namesAndSuccesses[`${name}@${version}`] = true
101
109
  return moduleExports
102
110
  }
103
111
 
@@ -117,19 +125,20 @@ for (const packageName of names) {
117
125
  `integration_version:${version}`
118
126
  ])
119
127
  }
120
- namesAndSuccesses[name].success = true
128
+ namesAndSuccesses[`${name}@${version}`] = true
121
129
  }
122
130
  }
123
131
  }
124
- for (const name of Object.keys(namesAndSuccesses)) {
125
- const { success, version } = namesAndSuccesses[name]
126
- if (!success && !seenCombo.has(`${name}@${version}`)) {
132
+ for (const nameVersion of Object.keys(namesAndSuccesses)) {
133
+ const [name, version] = nameVersion.split('@')
134
+ const success = namesAndSuccesses[nameVersion]
135
+ if (!success && !seenCombo.has(nameVersion)) {
127
136
  telemetry('abort.integration', [
128
137
  `integration:${name}`,
129
138
  `integration_version:${version}`
130
139
  ])
131
- log.info(`Found incompatible integration version: ${name}@${version}`)
132
- seenCombo.add(`${name}@${version}`)
140
+ log.info(`Found incompatible integration version: ${nameVersion}`)
141
+ seenCombo.add(nameVersion)
133
142
  }
134
143
  }
135
144
 
@@ -43,7 +43,9 @@ function patch (http, methodName) {
43
43
  return request.apply(this, arguments)
44
44
  }
45
45
 
46
- const ctx = { args, http }
46
+ const abortController = new AbortController()
47
+
48
+ const ctx = { args, http, abortController }
47
49
 
48
50
  return startChannel.runStores(ctx, () => {
49
51
  let finished = false
@@ -107,6 +109,10 @@ function patch (http, methodName) {
107
109
  return emit.apply(this, arguments)
108
110
  }
109
111
 
112
+ if (abortController.signal.aborted) {
113
+ req.destroy(abortController.signal.reason || new Error('Aborted'))
114
+ }
115
+
110
116
  return req
111
117
  } catch (e) {
112
118
  ctx.error = e
@@ -12,6 +12,7 @@ const errorServerCh = channel('apm:http:server:request:error')
12
12
  const finishServerCh = channel('apm:http:server:request:finish')
13
13
  const startWriteHeadCh = channel('apm:http:server:response:writeHead:start')
14
14
  const finishSetHeaderCh = channel('datadog:http:server:response:set-header:finish')
15
+ const startSetHeaderCh = channel('datadog:http:server:response:set-header:start')
15
16
 
16
17
  const requestFinishedSet = new WeakSet()
17
18
 
@@ -24,6 +25,12 @@ addHook({ name: httpNames }, http => {
24
25
  shimmer.wrap(http.ServerResponse.prototype, 'writeHead', wrapWriteHead)
25
26
  shimmer.wrap(http.ServerResponse.prototype, 'write', wrapWrite)
26
27
  shimmer.wrap(http.ServerResponse.prototype, 'end', wrapEnd)
28
+ shimmer.wrap(http.ServerResponse.prototype, 'setHeader', wrapSetHeader)
29
+ shimmer.wrap(http.ServerResponse.prototype, 'removeHeader', wrapAppendOrRemoveHeader)
30
+ // Added in node v16.17.0
31
+ if (http.ServerResponse.prototype.appendHeader) {
32
+ shimmer.wrap(http.ServerResponse.prototype, 'appendHeader', wrapAppendOrRemoveHeader)
33
+ }
27
34
  return http
28
35
  })
29
36
 
@@ -65,9 +72,7 @@ function wrapEmit (emit) {
65
72
  // TODO: should this always return true ?
66
73
  return this.listenerCount(eventName) > 0
67
74
  }
68
- if (finishSetHeaderCh.hasSubscribers) {
69
- wrapSetHeader(res)
70
- }
75
+
71
76
  return emit.apply(this, arguments)
72
77
  } catch (err) {
73
78
  errorServerCh.publish(err)
@@ -81,16 +86,6 @@ function wrapEmit (emit) {
81
86
  }
82
87
  }
83
88
 
84
- function wrapSetHeader (res) {
85
- shimmer.wrap(res, 'setHeader', setHeader => {
86
- return function (name, value) {
87
- const setHeaderResult = setHeader.apply(this, arguments)
88
- finishSetHeaderCh.publish({ name, value, res })
89
- return setHeaderResult
90
- }
91
- })
92
- }
93
-
94
89
  function wrapWriteHead (writeHead) {
95
90
  return function wrappedWriteHead (statusCode, reason, obj) {
96
91
  if (!startWriteHeadCh.hasSubscribers) {
@@ -159,6 +154,48 @@ function wrapWrite (write) {
159
154
  }
160
155
  }
161
156
 
157
+ function wrapSetHeader (setHeader) {
158
+ return function wrappedSetHeader (name, value) {
159
+ if (!startSetHeaderCh.hasSubscribers && !finishSetHeaderCh.hasSubscribers) {
160
+ return setHeader.apply(this, arguments)
161
+ }
162
+
163
+ if (startSetHeaderCh.hasSubscribers) {
164
+ const abortController = new AbortController()
165
+ startSetHeaderCh.publish({ res: this, abortController })
166
+
167
+ if (abortController.signal.aborted) {
168
+ return
169
+ }
170
+ }
171
+
172
+ const setHeaderResult = setHeader.apply(this, arguments)
173
+
174
+ if (finishSetHeaderCh.hasSubscribers) {
175
+ finishSetHeaderCh.publish({ name, value, res: this })
176
+ }
177
+
178
+ return setHeaderResult
179
+ }
180
+ }
181
+
182
+ function wrapAppendOrRemoveHeader (originalMethod) {
183
+ return function wrappedAppendOrRemoveHeader () {
184
+ if (!startSetHeaderCh.hasSubscribers) {
185
+ return originalMethod.apply(this, arguments)
186
+ }
187
+
188
+ const abortController = new AbortController()
189
+ startSetHeaderCh.publish({ res: this, abortController })
190
+
191
+ if (abortController.signal.aborted) {
192
+ return this
193
+ }
194
+
195
+ return originalMethod.apply(this, arguments)
196
+ }
197
+ }
198
+
162
199
  function wrapEnd (end) {
163
200
  return function wrappedEnd () {
164
201
  if (!startWriteHeadCh.hasSubscribers) {
@@ -47,6 +47,7 @@ const config = {}
47
47
 
48
48
  // We'll preserve the original coverage here
49
49
  const originalCoverageMap = createCoverageMap()
50
+ let untestedCoverage
50
51
 
51
52
  // test channels
52
53
  const testStartCh = channel('ci:mocha:test:start')
@@ -66,6 +67,8 @@ const testSessionStartCh = channel('ci:mocha:session:start')
66
67
  const testSessionFinishCh = channel('ci:mocha:session:finish')
67
68
  const itrSkippedSuitesCh = channel('ci:mocha:itr:skipped-suites')
68
69
 
70
+ const getCodeCoverageCh = channel('ci:nyc:get-coverage')
71
+
69
72
  function getFilteredSuites (originalSuites) {
70
73
  return originalSuites.reduce((acc, suite) => {
71
74
  const testPath = getTestSuitePath(suite.file, process.cwd())
@@ -131,6 +134,9 @@ function getOnEndHandler (isParallel) {
131
134
  let testCodeCoverageLinesTotal
132
135
  if (global.__coverage__) {
133
136
  try {
137
+ if (untestedCoverage) {
138
+ originalCoverageMap.merge(fromCoverageMapToCoverage(untestedCoverage))
139
+ }
134
140
  testCodeCoverageLinesTotal = originalCoverageMap.getCoverageSummary().lines.pct
135
141
  } catch (e) {
136
142
  // ignore errors
@@ -153,6 +159,83 @@ function getOnEndHandler (isParallel) {
153
159
  })
154
160
  }
155
161
 
162
+ function getExecutionConfiguration (runner, onFinishRequest) {
163
+ const mochaRunAsyncResource = new AsyncResource('bound-anonymous-fn')
164
+
165
+ const onReceivedSkippableSuites = ({ err, skippableSuites, itrCorrelationId: responseItrCorrelationId }) => {
166
+ if (err) {
167
+ suitesToSkip = []
168
+ } else {
169
+ suitesToSkip = skippableSuites
170
+ itrCorrelationId = responseItrCorrelationId
171
+ }
172
+ // We remove the suites that we skip through ITR
173
+ const filteredSuites = getFilteredSuites(runner.suite.suites)
174
+ const { suitesToRun } = filteredSuites
175
+
176
+ isSuitesSkipped = suitesToRun.length !== runner.suite.suites.length
177
+
178
+ log.debug(
179
+ () => `${suitesToRun.length} out of ${runner.suite.suites.length} suites are going to run.`
180
+ )
181
+
182
+ runner.suite.suites = suitesToRun
183
+
184
+ skippedSuites = Array.from(filteredSuites.skippedSuites)
185
+
186
+ onFinishRequest()
187
+ }
188
+
189
+ const onReceivedKnownTests = ({ err, knownTests: receivedKnownTests }) => {
190
+ if (err) {
191
+ knownTests = []
192
+ isEarlyFlakeDetectionEnabled = false
193
+ } else {
194
+ knownTests = receivedKnownTests
195
+ }
196
+
197
+ if (isSuitesSkippingEnabled) {
198
+ skippableSuitesCh.publish({
199
+ onDone: mochaRunAsyncResource.bind(onReceivedSkippableSuites)
200
+ })
201
+ } else {
202
+ onFinishRequest()
203
+ }
204
+ }
205
+
206
+ const onReceivedConfiguration = ({ err, libraryConfig }) => {
207
+ if (err || !skippableSuitesCh.hasSubscribers || !knownTestsCh.hasSubscribers) {
208
+ return onFinishRequest()
209
+ }
210
+
211
+ isEarlyFlakeDetectionEnabled = libraryConfig.isEarlyFlakeDetectionEnabled
212
+ isSuitesSkippingEnabled = libraryConfig.isSuitesSkippingEnabled
213
+ earlyFlakeDetectionNumRetries = libraryConfig.earlyFlakeDetectionNumRetries
214
+ isFlakyTestRetriesEnabled = libraryConfig.isFlakyTestRetriesEnabled
215
+
216
+ config.isEarlyFlakeDetectionEnabled = isEarlyFlakeDetectionEnabled
217
+ config.isSuitesSkippingEnabled = isSuitesSkippingEnabled
218
+ config.earlyFlakeDetectionNumRetries = earlyFlakeDetectionNumRetries
219
+ config.isFlakyTestRetriesEnabled = isFlakyTestRetriesEnabled
220
+
221
+ if (isEarlyFlakeDetectionEnabled) {
222
+ knownTestsCh.publish({
223
+ onDone: mochaRunAsyncResource.bind(onReceivedKnownTests)
224
+ })
225
+ } else if (isSuitesSkippingEnabled) {
226
+ skippableSuitesCh.publish({
227
+ onDone: mochaRunAsyncResource.bind(onReceivedSkippableSuites)
228
+ })
229
+ } else {
230
+ onFinishRequest()
231
+ }
232
+ }
233
+
234
+ libraryConfigurationCh.publish({
235
+ onDone: mochaRunAsyncResource.bind(onReceivedConfiguration)
236
+ })
237
+ }
238
+
156
239
  // In this hook we delay the execution with options.delay to grab library configuration,
157
240
  // skippable and known tests.
158
241
  // It is called but skipped in parallel mode.
@@ -161,7 +244,6 @@ addHook({
161
244
  versions: ['>=5.2.0'],
162
245
  file: 'lib/mocha.js'
163
246
  }, (Mocha) => {
164
- const mochaRunAsyncResource = new AsyncResource('bound-anonymous-fn')
165
247
  shimmer.wrap(Mocha.prototype, 'run', run => function () {
166
248
  // Workers do not need to request any data, just run the tests
167
249
  if (!testStartCh.hasSubscribers || process.env.MOCHA_WORKER_ID || this.options.parallel) {
@@ -181,79 +263,17 @@ addHook({
181
263
  }
182
264
  })
183
265
 
184
- const onReceivedSkippableSuites = ({ err, skippableSuites, itrCorrelationId: responseItrCorrelationId }) => {
185
- if (err) {
186
- suitesToSkip = []
187
- } else {
188
- suitesToSkip = skippableSuites
189
- itrCorrelationId = responseItrCorrelationId
190
- }
191
- // We remove the suites that we skip through ITR
192
- const filteredSuites = getFilteredSuites(runner.suite.suites)
193
- const { suitesToRun } = filteredSuites
194
-
195
- isSuitesSkipped = suitesToRun.length !== runner.suite.suites.length
196
-
197
- log.debug(
198
- () => `${suitesToRun.length} out of ${runner.suite.suites.length} suites are going to run.`
199
- )
200
-
201
- runner.suite.suites = suitesToRun
202
-
203
- skippedSuites = Array.from(filteredSuites.skippedSuites)
204
-
205
- global.run()
206
- }
207
-
208
- const onReceivedKnownTests = ({ err, knownTests: receivedKnownTests }) => {
209
- if (err) {
210
- knownTests = []
211
- isEarlyFlakeDetectionEnabled = false
212
- } else {
213
- knownTests = receivedKnownTests
214
- }
215
-
216
- if (isSuitesSkippingEnabled) {
217
- skippableSuitesCh.publish({
218
- onDone: mochaRunAsyncResource.bind(onReceivedSkippableSuites)
219
- })
220
- } else {
221
- global.run()
222
- }
223
- }
224
-
225
- const onReceivedConfiguration = ({ err, libraryConfig }) => {
226
- if (err || !skippableSuitesCh.hasSubscribers || !knownTestsCh.hasSubscribers) {
227
- return global.run()
228
- }
229
-
230
- isEarlyFlakeDetectionEnabled = libraryConfig.isEarlyFlakeDetectionEnabled
231
- isSuitesSkippingEnabled = libraryConfig.isSuitesSkippingEnabled
232
- earlyFlakeDetectionNumRetries = libraryConfig.earlyFlakeDetectionNumRetries
233
- isFlakyTestRetriesEnabled = libraryConfig.isFlakyTestRetriesEnabled
234
-
235
- config.isEarlyFlakeDetectionEnabled = isEarlyFlakeDetectionEnabled
236
- config.isSuitesSkippingEnabled = isSuitesSkippingEnabled
237
- config.earlyFlakeDetectionNumRetries = earlyFlakeDetectionNumRetries
238
- config.isFlakyTestRetriesEnabled = isFlakyTestRetriesEnabled
239
-
240
- if (isEarlyFlakeDetectionEnabled) {
241
- knownTestsCh.publish({
242
- onDone: mochaRunAsyncResource.bind(onReceivedKnownTests)
243
- })
244
- } else if (isSuitesSkippingEnabled) {
245
- skippableSuitesCh.publish({
246
- onDone: mochaRunAsyncResource.bind(onReceivedSkippableSuites)
266
+ getExecutionConfiguration(runner, () => {
267
+ if (getCodeCoverageCh.hasSubscribers) {
268
+ getCodeCoverageCh.publish({
269
+ onDone: (receivedCodeCoverage) => {
270
+ untestedCoverage = receivedCodeCoverage
271
+ global.run()
272
+ }
247
273
  })
248
274
  } else {
249
275
  global.run()
250
276
  }
251
- }
252
-
253
- mochaRunAsyncResource.runInAsyncScope(() => {
254
- libraryConfigurationCh.publish({
255
- onDone: mochaRunAsyncResource.bind(onReceivedConfiguration)
256
- })
257
277
  })
258
278
 
259
279
  return runner
@@ -395,9 +415,13 @@ addHook({
395
415
  }
396
416
 
397
417
  const asyncResource = testFileToSuiteAr.get(suite.file)
398
- asyncResource.runInAsyncScope(() => {
399
- testSuiteFinishCh.publish(status)
400
- })
418
+ if (asyncResource) {
419
+ asyncResource.runInAsyncScope(() => {
420
+ testSuiteFinishCh.publish(status)
421
+ })
422
+ } else {
423
+ log.warn(() => `No AsyncResource found for suite ${suite.file}`)
424
+ }
401
425
  })
402
426
 
403
427
  return run.apply(this, arguments)
@@ -424,23 +448,29 @@ addHook({
424
448
  versions: ['>=6.0.0'],
425
449
  file: 'src/WorkerHandler.js'
426
450
  }, (workerHandlerPackage) => {
427
- shimmer.wrap(workerHandlerPackage.prototype, 'exec', exec => function (message, [testSuiteAbsolutePath]) {
451
+ shimmer.wrap(workerHandlerPackage.prototype, 'exec', exec => function (_, path) {
428
452
  if (!testStartCh.hasSubscribers) {
429
453
  return exec.apply(this, arguments)
430
454
  }
455
+ if (!path?.length) {
456
+ return exec.apply(this, arguments)
457
+ }
458
+ const [testSuiteAbsolutePath] = path
459
+ const testSuiteAsyncResource = new AsyncResource('bound-anonymous-fn')
431
460
 
432
- this.worker.on('message', function (message) {
461
+ function onMessage (message) {
433
462
  if (Array.isArray(message)) {
434
463
  const [messageCode, payload] = message
435
464
  if (messageCode === MOCHA_WORKER_TRACE_PAYLOAD_CODE) {
436
- testSessionAsyncResource.runInAsyncScope(() => {
465
+ testSuiteAsyncResource.runInAsyncScope(() => {
437
466
  workerReportTraceCh.publish(payload)
438
467
  })
439
468
  }
440
469
  }
441
- })
470
+ }
471
+
472
+ this.worker.on('message', onMessage)
442
473
 
443
- const testSuiteAsyncResource = new AsyncResource('bound-anonymous-fn')
444
474
  testSuiteAsyncResource.runInAsyncScope(() => {
445
475
  testSuiteStartCh.publish({
446
476
  testSuiteAbsolutePath
@@ -455,12 +485,14 @@ addHook({
455
485
  testSuiteAsyncResource.runInAsyncScope(() => {
456
486
  testSuiteFinishCh.publish(status)
457
487
  })
488
+ this.worker.off('message', onMessage)
458
489
  },
459
490
  (err) => {
460
491
  testSuiteAsyncResource.runInAsyncScope(() => {
461
492
  testSuiteErrorCh.publish(err)
462
493
  testSuiteFinishCh.publish('fail')
463
494
  })
495
+ this.worker.off('message', onMessage)
464
496
  }
465
497
  )
466
498
  return promise
@@ -469,6 +501,7 @@ addHook({
469
501
  testSuiteErrorCh.publish(err)
470
502
  testSuiteFinishCh.publish('fail')
471
503
  })
504
+ this.worker.off('message', onMessage)
472
505
  throw err
473
506
  }
474
507
  })
@@ -0,0 +1,23 @@
1
+ const { addHook, channel } = require('./helpers/instrument')
2
+ const shimmer = require('../../datadog-shimmer')
3
+
4
+ const codeCoverageWrapCh = channel('ci:nyc:wrap')
5
+
6
+ addHook({
7
+ name: 'nyc',
8
+ versions: ['>=17']
9
+ }, (nycPackage) => {
10
+ shimmer.wrap(nycPackage.prototype, 'wrap', wrap => async function () {
11
+ // Only relevant if the config `all` is set to true
12
+ try {
13
+ if (JSON.parse(process.env.NYC_CONFIG).all) {
14
+ codeCoverageWrapCh.publish(this)
15
+ }
16
+ } catch (e) {
17
+ // ignore errors
18
+ }
19
+
20
+ return wrap.apply(this, arguments)
21
+ })
22
+ return nycPackage
23
+ })