dd-trace 4.38.1 → 4.40.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/LICENSE-3rdparty.csv +0 -3
- package/README.md +8 -18
- package/ci/init.js +7 -0
- package/ext/exporters.d.ts +1 -0
- package/ext/exporters.js +2 -1
- package/ext/tags.d.ts +1 -0
- package/ext/tags.js +1 -0
- package/index.d.ts +18 -3
- package/initialize.mjs +52 -0
- package/package.json +9 -12
- package/packages/datadog-instrumentations/src/amqplib.js +5 -2
- package/packages/datadog-instrumentations/src/apollo-server-core.js +0 -1
- package/packages/datadog-instrumentations/src/apollo-server.js +0 -1
- package/packages/datadog-instrumentations/src/body-parser.js +0 -1
- package/packages/datadog-instrumentations/src/check_require_cache.js +67 -5
- package/packages/datadog-instrumentations/src/cookie-parser.js +0 -1
- package/packages/datadog-instrumentations/src/express.js +0 -1
- package/packages/datadog-instrumentations/src/graphql.js +0 -2
- package/packages/datadog-instrumentations/src/helpers/hooks.js +1 -0
- package/packages/datadog-instrumentations/src/helpers/register.js +5 -2
- package/packages/datadog-instrumentations/src/http/server.js +0 -1
- package/packages/datadog-instrumentations/src/jest.js +6 -3
- package/packages/datadog-instrumentations/src/mocha/common.js +48 -0
- package/packages/datadog-instrumentations/src/mocha/main.js +487 -0
- package/packages/datadog-instrumentations/src/mocha/utils.js +306 -0
- package/packages/datadog-instrumentations/src/mocha/worker.js +51 -0
- package/packages/datadog-instrumentations/src/mocha.js +4 -673
- package/packages/datadog-instrumentations/src/openai.js +188 -17
- package/packages/datadog-instrumentations/src/playwright.js +4 -3
- package/packages/datadog-instrumentations/src/router.js +1 -1
- package/packages/datadog-instrumentations/src/selenium.js +13 -6
- package/packages/datadog-plugin-graphql/src/resolve.js +4 -0
- package/packages/datadog-plugin-mocha/src/index.js +82 -8
- package/packages/datadog-plugin-next/src/index.js +1 -2
- package/packages/datadog-plugin-openai/src/index.js +219 -73
- package/packages/dd-trace/src/appsec/addresses.js +4 -2
- package/packages/dd-trace/src/appsec/blocking.js +19 -25
- package/packages/dd-trace/src/appsec/channels.js +2 -1
- package/packages/dd-trace/src/appsec/graphql.js +10 -3
- package/packages/dd-trace/src/appsec/index.js +11 -4
- package/packages/dd-trace/src/appsec/rasp.js +35 -0
- package/packages/dd-trace/src/appsec/remote_config/capabilities.js +2 -1
- package/packages/dd-trace/src/appsec/remote_config/index.js +1 -0
- package/packages/dd-trace/src/appsec/rule_manager.js +15 -25
- package/packages/dd-trace/src/appsec/sdk/user_blocking.js +2 -5
- package/packages/dd-trace/src/appsec/waf/waf_context_wrapper.js +3 -1
- package/packages/dd-trace/src/ci-visibility/exporters/test-worker/index.js +5 -1
- package/packages/dd-trace/src/config.js +97 -22
- package/packages/dd-trace/src/constants.js +2 -0
- package/packages/dd-trace/src/encode/0.4.js +47 -8
- package/packages/dd-trace/src/exporter.js +1 -0
- package/packages/dd-trace/src/flare/file.js +44 -0
- package/packages/dd-trace/src/flare/index.js +98 -0
- package/packages/dd-trace/src/log/channels.js +54 -29
- package/packages/dd-trace/src/log/writer.js +7 -49
- package/packages/dd-trace/src/opentelemetry/span.js +8 -0
- package/packages/dd-trace/src/opentracing/propagation/text_map.js +57 -12
- package/packages/dd-trace/src/plugins/index.js +1 -0
- package/packages/dd-trace/src/plugins/util/ip_extractor.js +1 -1
- package/packages/dd-trace/src/plugins/util/test.js +6 -0
- package/packages/dd-trace/src/priority_sampler.js +8 -4
- package/packages/dd-trace/src/profiler.js +2 -1
- package/packages/dd-trace/src/profiling/config.js +1 -0
- package/packages/dd-trace/src/profiling/profiler.js +1 -1
- package/packages/dd-trace/src/profiling/{ssi-telemetry.js → ssi-heuristics.js} +64 -36
- package/packages/dd-trace/src/profiling/ssi-telemetry-mock-profiler.js +4 -9
- package/packages/dd-trace/src/proxy.js +49 -15
- package/packages/dd-trace/src/ritm.js +13 -1
- package/packages/dd-trace/src/sampling_rule.js +2 -1
- package/packages/dd-trace/src/startup-log.js +19 -15
- package/packages/dd-trace/src/telemetry/index.js +6 -2
- package/packages/dd-trace/src/tracer.js +3 -0
- package/packages/dd-trace/src/plugins/util/ip_blocklist.js +0 -51
|
@@ -0,0 +1,487 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { createCoverageMap } = require('istanbul-lib-coverage')
|
|
4
|
+
const { addHook, channel, AsyncResource } = require('../helpers/instrument')
|
|
5
|
+
const shimmer = require('../../../datadog-shimmer')
|
|
6
|
+
const { isMarkedAsUnskippable } = require('../../../datadog-plugin-jest/src/util')
|
|
7
|
+
const log = require('../../../dd-trace/src/log')
|
|
8
|
+
const {
|
|
9
|
+
getTestSuitePath,
|
|
10
|
+
MOCHA_WORKER_TRACE_PAYLOAD_CODE,
|
|
11
|
+
fromCoverageMapToCoverage,
|
|
12
|
+
getCoveredFilenamesFromCoverage,
|
|
13
|
+
mergeCoverage,
|
|
14
|
+
resetCoverage
|
|
15
|
+
} = require('../../../dd-trace/src/plugins/util/test')
|
|
16
|
+
|
|
17
|
+
const {
|
|
18
|
+
isNewTest,
|
|
19
|
+
retryTest,
|
|
20
|
+
getSuitesByTestFile,
|
|
21
|
+
runnableWrapper,
|
|
22
|
+
getOnTestHandler,
|
|
23
|
+
getOnTestEndHandler,
|
|
24
|
+
getOnHookEndHandler,
|
|
25
|
+
getOnFailHandler,
|
|
26
|
+
getOnPendingHandler,
|
|
27
|
+
testFileToSuiteAr
|
|
28
|
+
} = require('./utils')
|
|
29
|
+
require('./common')
|
|
30
|
+
|
|
31
|
+
const testSessionAsyncResource = new AsyncResource('bound-anonymous-fn')
|
|
32
|
+
const patched = new WeakSet()
|
|
33
|
+
const newTests = {}
|
|
34
|
+
let suitesToSkip = []
|
|
35
|
+
const unskippableSuites = []
|
|
36
|
+
let isSuitesSkipped = false
|
|
37
|
+
let skippedSuites = []
|
|
38
|
+
let isEarlyFlakeDetectionEnabled = false
|
|
39
|
+
let isSuitesSkippingEnabled = false
|
|
40
|
+
let earlyFlakeDetectionNumRetries = 0
|
|
41
|
+
let knownTests = []
|
|
42
|
+
let itrCorrelationId = ''
|
|
43
|
+
let isForcedToRun = false
|
|
44
|
+
|
|
45
|
+
// We'll preserve the original coverage here
|
|
46
|
+
const originalCoverageMap = createCoverageMap()
|
|
47
|
+
|
|
48
|
+
// test channels
|
|
49
|
+
const testStartCh = channel('ci:mocha:test:start')
|
|
50
|
+
|
|
51
|
+
// test suite channels
|
|
52
|
+
const testSuiteStartCh = channel('ci:mocha:test-suite:start')
|
|
53
|
+
const testSuiteFinishCh = channel('ci:mocha:test-suite:finish')
|
|
54
|
+
const testSuiteErrorCh = channel('ci:mocha:test-suite:error')
|
|
55
|
+
const testSuiteCodeCoverageCh = channel('ci:mocha:test-suite:code-coverage')
|
|
56
|
+
|
|
57
|
+
// session channels
|
|
58
|
+
const libraryConfigurationCh = channel('ci:mocha:library-configuration')
|
|
59
|
+
const knownTestsCh = channel('ci:mocha:known-tests')
|
|
60
|
+
const skippableSuitesCh = channel('ci:mocha:test-suite:skippable')
|
|
61
|
+
const workerReportTraceCh = channel('ci:mocha:worker-report:trace')
|
|
62
|
+
const testSessionStartCh = channel('ci:mocha:session:start')
|
|
63
|
+
const testSessionFinishCh = channel('ci:mocha:session:finish')
|
|
64
|
+
const itrSkippedSuitesCh = channel('ci:mocha:itr:skipped-suites')
|
|
65
|
+
|
|
66
|
+
function getFilteredSuites (originalSuites) {
|
|
67
|
+
return originalSuites.reduce((acc, suite) => {
|
|
68
|
+
const testPath = getTestSuitePath(suite.file, process.cwd())
|
|
69
|
+
const shouldSkip = suitesToSkip.includes(testPath)
|
|
70
|
+
const isUnskippable = unskippableSuites.includes(suite.file)
|
|
71
|
+
if (shouldSkip && !isUnskippable) {
|
|
72
|
+
acc.skippedSuites.add(testPath)
|
|
73
|
+
} else {
|
|
74
|
+
acc.suitesToRun.push(suite)
|
|
75
|
+
}
|
|
76
|
+
return acc
|
|
77
|
+
}, { suitesToRun: [], skippedSuites: new Set() })
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function getOnStartHandler (isParallel, frameworkVersion) {
|
|
81
|
+
return testSessionAsyncResource.bind(function () {
|
|
82
|
+
const processArgv = process.argv.slice(2).join(' ')
|
|
83
|
+
const command = `mocha ${processArgv}`
|
|
84
|
+
testSessionStartCh.publish({ command, frameworkVersion })
|
|
85
|
+
if (!isParallel && skippedSuites.length) {
|
|
86
|
+
itrSkippedSuitesCh.publish({ skippedSuites, frameworkVersion })
|
|
87
|
+
}
|
|
88
|
+
})
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function getOnEndHandler (isParallel) {
|
|
92
|
+
return testSessionAsyncResource.bind(function () {
|
|
93
|
+
let status = 'pass'
|
|
94
|
+
let error
|
|
95
|
+
if (this.stats) {
|
|
96
|
+
status = this.stats.failures === 0 ? 'pass' : 'fail'
|
|
97
|
+
if (this.stats.tests === 0) {
|
|
98
|
+
status = 'skip'
|
|
99
|
+
}
|
|
100
|
+
} else if (this.failures !== 0) {
|
|
101
|
+
status = 'fail'
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (!isParallel && isEarlyFlakeDetectionEnabled) {
|
|
105
|
+
/**
|
|
106
|
+
* If Early Flake Detection (EFD) is enabled the logic is as follows:
|
|
107
|
+
* - If all attempts for a test are failing, the test has failed and we will let the test process fail.
|
|
108
|
+
* - If just a single attempt passes, we will prevent the test process from failing.
|
|
109
|
+
* The rationale behind is the following: you may still be able to block your CI pipeline by gating
|
|
110
|
+
* on flakiness (the test will be considered flaky), but you may choose to unblock the pipeline too.
|
|
111
|
+
*/
|
|
112
|
+
for (const tests of Object.values(newTests)) {
|
|
113
|
+
const failingNewTests = tests.filter(test => test.isFailed())
|
|
114
|
+
const areAllNewTestsFailing = failingNewTests.length === tests.length
|
|
115
|
+
if (failingNewTests.length && !areAllNewTestsFailing) {
|
|
116
|
+
this.stats.failures -= failingNewTests.length
|
|
117
|
+
this.failures -= failingNewTests.length
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (status === 'fail') {
|
|
123
|
+
error = new Error(`Failed tests: ${this.failures}.`)
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
testFileToSuiteAr.clear()
|
|
127
|
+
|
|
128
|
+
let testCodeCoverageLinesTotal
|
|
129
|
+
if (global.__coverage__) {
|
|
130
|
+
try {
|
|
131
|
+
testCodeCoverageLinesTotal = originalCoverageMap.getCoverageSummary().lines.pct
|
|
132
|
+
} catch (e) {
|
|
133
|
+
// ignore errors
|
|
134
|
+
}
|
|
135
|
+
// restore the original coverage
|
|
136
|
+
global.__coverage__ = fromCoverageMapToCoverage(originalCoverageMap)
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
testSessionFinishCh.publish({
|
|
140
|
+
status,
|
|
141
|
+
isSuitesSkipped,
|
|
142
|
+
testCodeCoverageLinesTotal,
|
|
143
|
+
numSkippedSuites: skippedSuites.length,
|
|
144
|
+
hasForcedToRunSuites: isForcedToRun,
|
|
145
|
+
hasUnskippableSuites: !!unskippableSuites.length,
|
|
146
|
+
error,
|
|
147
|
+
isEarlyFlakeDetectionEnabled,
|
|
148
|
+
isParallel
|
|
149
|
+
})
|
|
150
|
+
})
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// In this hook we delay the execution with options.delay to grab library configuration,
|
|
154
|
+
// skippable and known tests.
|
|
155
|
+
// It is called but skipped in parallel mode.
|
|
156
|
+
addHook({
|
|
157
|
+
name: 'mocha',
|
|
158
|
+
versions: ['>=5.2.0'],
|
|
159
|
+
file: 'lib/mocha.js'
|
|
160
|
+
}, (Mocha) => {
|
|
161
|
+
const mochaRunAsyncResource = new AsyncResource('bound-anonymous-fn')
|
|
162
|
+
shimmer.wrap(Mocha.prototype, 'run', run => function () {
|
|
163
|
+
// Workers do not need to request any data, just run the tests
|
|
164
|
+
if (!testStartCh.hasSubscribers || process.env.MOCHA_WORKER_ID || this.options.parallel) {
|
|
165
|
+
return run.apply(this, arguments)
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// `options.delay` does not work in parallel mode, so ITR and EFD can't work.
|
|
169
|
+
// TODO: use `lib/cli/run-helpers.js#runMocha` to get the data in parallel mode.
|
|
170
|
+
this.options.delay = true
|
|
171
|
+
|
|
172
|
+
const runner = run.apply(this, arguments)
|
|
173
|
+
|
|
174
|
+
this.files.forEach(path => {
|
|
175
|
+
const isUnskippable = isMarkedAsUnskippable({ path })
|
|
176
|
+
if (isUnskippable) {
|
|
177
|
+
unskippableSuites.push(path)
|
|
178
|
+
}
|
|
179
|
+
})
|
|
180
|
+
|
|
181
|
+
const onReceivedSkippableSuites = ({ err, skippableSuites, itrCorrelationId: responseItrCorrelationId }) => {
|
|
182
|
+
if (err) {
|
|
183
|
+
suitesToSkip = []
|
|
184
|
+
} else {
|
|
185
|
+
suitesToSkip = skippableSuites
|
|
186
|
+
itrCorrelationId = responseItrCorrelationId
|
|
187
|
+
}
|
|
188
|
+
// We remove the suites that we skip through ITR
|
|
189
|
+
const filteredSuites = getFilteredSuites(runner.suite.suites)
|
|
190
|
+
const { suitesToRun } = filteredSuites
|
|
191
|
+
|
|
192
|
+
isSuitesSkipped = suitesToRun.length !== runner.suite.suites.length
|
|
193
|
+
|
|
194
|
+
log.debug(
|
|
195
|
+
() => `${suitesToRun.length} out of ${runner.suite.suites.length} suites are going to run.`
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
runner.suite.suites = suitesToRun
|
|
199
|
+
|
|
200
|
+
skippedSuites = Array.from(filteredSuites.skippedSuites)
|
|
201
|
+
|
|
202
|
+
global.run()
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const onReceivedKnownTests = ({ err, knownTests: receivedKnownTests }) => {
|
|
206
|
+
if (err) {
|
|
207
|
+
knownTests = []
|
|
208
|
+
isEarlyFlakeDetectionEnabled = false
|
|
209
|
+
} else {
|
|
210
|
+
knownTests = receivedKnownTests
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
if (isSuitesSkippingEnabled) {
|
|
214
|
+
skippableSuitesCh.publish({
|
|
215
|
+
onDone: mochaRunAsyncResource.bind(onReceivedSkippableSuites)
|
|
216
|
+
})
|
|
217
|
+
} else {
|
|
218
|
+
global.run()
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
const onReceivedConfiguration = ({ err, libraryConfig }) => {
|
|
223
|
+
if (err || !skippableSuitesCh.hasSubscribers || !knownTestsCh.hasSubscribers) {
|
|
224
|
+
return global.run()
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
isEarlyFlakeDetectionEnabled = libraryConfig.isEarlyFlakeDetectionEnabled
|
|
228
|
+
isSuitesSkippingEnabled = libraryConfig.isSuitesSkippingEnabled
|
|
229
|
+
earlyFlakeDetectionNumRetries = libraryConfig.earlyFlakeDetectionNumRetries
|
|
230
|
+
|
|
231
|
+
if (isEarlyFlakeDetectionEnabled) {
|
|
232
|
+
knownTestsCh.publish({
|
|
233
|
+
onDone: mochaRunAsyncResource.bind(onReceivedKnownTests)
|
|
234
|
+
})
|
|
235
|
+
} else if (isSuitesSkippingEnabled) {
|
|
236
|
+
skippableSuitesCh.publish({
|
|
237
|
+
onDone: mochaRunAsyncResource.bind(onReceivedSkippableSuites)
|
|
238
|
+
})
|
|
239
|
+
} else {
|
|
240
|
+
global.run()
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
mochaRunAsyncResource.runInAsyncScope(() => {
|
|
245
|
+
libraryConfigurationCh.publish({
|
|
246
|
+
onDone: mochaRunAsyncResource.bind(onReceivedConfiguration)
|
|
247
|
+
})
|
|
248
|
+
})
|
|
249
|
+
|
|
250
|
+
return runner
|
|
251
|
+
})
|
|
252
|
+
return Mocha
|
|
253
|
+
})
|
|
254
|
+
|
|
255
|
+
// Only used to set `mocha.options.delay` to true in serial mode. When the mocha CLI is used,
|
|
256
|
+
// setting options.delay in Mocha#run is not enough to delay the execution.
|
|
257
|
+
// TODO: modify this hook to grab the data in parallel mode, so that ITR and EFD can work.
|
|
258
|
+
addHook({
|
|
259
|
+
name: 'mocha',
|
|
260
|
+
versions: ['>=5.2.0'],
|
|
261
|
+
file: 'lib/cli/run-helpers.js'
|
|
262
|
+
}, (run) => {
|
|
263
|
+
shimmer.wrap(run, 'runMocha', runMocha => async function () {
|
|
264
|
+
if (!testStartCh.hasSubscribers) {
|
|
265
|
+
return runMocha.apply(this, arguments)
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
const mocha = arguments[0]
|
|
269
|
+
/**
|
|
270
|
+
* This attaches `run` to the global context, which we'll call after
|
|
271
|
+
* our configuration and skippable suites requests
|
|
272
|
+
*/
|
|
273
|
+
if (!mocha.options.parallel) {
|
|
274
|
+
mocha.options.delay = true
|
|
275
|
+
}
|
|
276
|
+
return runMocha.apply(this, arguments)
|
|
277
|
+
})
|
|
278
|
+
return run
|
|
279
|
+
})
|
|
280
|
+
|
|
281
|
+
// Only used in serial mode (no --parallel flag is passed)
|
|
282
|
+
// This hook is used to generate session, module, suite and test events
|
|
283
|
+
addHook({
|
|
284
|
+
name: 'mocha',
|
|
285
|
+
versions: ['>=5.2.0'],
|
|
286
|
+
file: 'lib/runner.js'
|
|
287
|
+
}, function (Runner, frameworkVersion) {
|
|
288
|
+
if (patched.has(Runner)) return Runner
|
|
289
|
+
|
|
290
|
+
patched.add(Runner)
|
|
291
|
+
|
|
292
|
+
shimmer.wrap(Runner.prototype, 'runTests', runTests => function (suite, fn) {
|
|
293
|
+
if (isEarlyFlakeDetectionEnabled) {
|
|
294
|
+
// by the time we reach `this.on('test')`, it is too late. We need to add retries here
|
|
295
|
+
suite.tests.forEach(test => {
|
|
296
|
+
if (!test.isPending() && isNewTest(test, knownTests)) {
|
|
297
|
+
test._ddIsNew = true
|
|
298
|
+
retryTest(test, earlyFlakeDetectionNumRetries)
|
|
299
|
+
}
|
|
300
|
+
})
|
|
301
|
+
}
|
|
302
|
+
return runTests.apply(this, arguments)
|
|
303
|
+
})
|
|
304
|
+
|
|
305
|
+
shimmer.wrap(Runner.prototype, 'run', run => function () {
|
|
306
|
+
if (!testStartCh.hasSubscribers) {
|
|
307
|
+
return run.apply(this, arguments)
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
const { suitesByTestFile, numSuitesByTestFile } = getSuitesByTestFile(this.suite)
|
|
311
|
+
|
|
312
|
+
this.once('start', getOnStartHandler(false, frameworkVersion))
|
|
313
|
+
|
|
314
|
+
this.once('end', getOnEndHandler(false))
|
|
315
|
+
|
|
316
|
+
this.on('test', getOnTestHandler(true, newTests))
|
|
317
|
+
|
|
318
|
+
this.on('test end', getOnTestEndHandler())
|
|
319
|
+
|
|
320
|
+
// If the hook passes, 'hook end' will be emitted. Otherwise, 'fail' will be emitted
|
|
321
|
+
this.on('hook end', getOnHookEndHandler())
|
|
322
|
+
|
|
323
|
+
this.on('fail', getOnFailHandler(true))
|
|
324
|
+
|
|
325
|
+
this.on('pending', getOnPendingHandler())
|
|
326
|
+
|
|
327
|
+
this.on('suite', function (suite) {
|
|
328
|
+
if (suite.root || !suite.tests.length) {
|
|
329
|
+
return
|
|
330
|
+
}
|
|
331
|
+
let asyncResource = testFileToSuiteAr.get(suite.file)
|
|
332
|
+
if (!asyncResource) {
|
|
333
|
+
asyncResource = new AsyncResource('bound-anonymous-fn')
|
|
334
|
+
testFileToSuiteAr.set(suite.file, asyncResource)
|
|
335
|
+
const isUnskippable = unskippableSuites.includes(suite.file)
|
|
336
|
+
isForcedToRun = isUnskippable && suitesToSkip.includes(getTestSuitePath(suite.file, process.cwd()))
|
|
337
|
+
asyncResource.runInAsyncScope(() => {
|
|
338
|
+
testSuiteStartCh.publish({
|
|
339
|
+
testSuiteAbsolutePath: suite.file,
|
|
340
|
+
isUnskippable,
|
|
341
|
+
isForcedToRun,
|
|
342
|
+
itrCorrelationId
|
|
343
|
+
})
|
|
344
|
+
})
|
|
345
|
+
}
|
|
346
|
+
})
|
|
347
|
+
|
|
348
|
+
this.on('suite end', function (suite) {
|
|
349
|
+
if (suite.root) {
|
|
350
|
+
return
|
|
351
|
+
}
|
|
352
|
+
const suitesInTestFile = suitesByTestFile[suite.file]
|
|
353
|
+
|
|
354
|
+
const isLastSuite = --numSuitesByTestFile[suite.file] === 0
|
|
355
|
+
if (!isLastSuite) {
|
|
356
|
+
return
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
let status = 'pass'
|
|
360
|
+
if (suitesInTestFile.every(suite => suite.pending)) {
|
|
361
|
+
status = 'skip'
|
|
362
|
+
} else {
|
|
363
|
+
// has to check every test in the test file
|
|
364
|
+
suitesInTestFile.forEach(suite => {
|
|
365
|
+
suite.eachTest(test => {
|
|
366
|
+
if (test.state === 'failed' || test.timedOut) {
|
|
367
|
+
status = 'fail'
|
|
368
|
+
}
|
|
369
|
+
})
|
|
370
|
+
})
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
if (global.__coverage__) {
|
|
374
|
+
const coverageFiles = getCoveredFilenamesFromCoverage(global.__coverage__)
|
|
375
|
+
|
|
376
|
+
testSuiteCodeCoverageCh.publish({
|
|
377
|
+
coverageFiles,
|
|
378
|
+
suiteFile: suite.file
|
|
379
|
+
})
|
|
380
|
+
// We need to reset coverage to get a code coverage per suite
|
|
381
|
+
// Before that, we preserve the original coverage
|
|
382
|
+
mergeCoverage(global.__coverage__, originalCoverageMap)
|
|
383
|
+
resetCoverage(global.__coverage__)
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
const asyncResource = testFileToSuiteAr.get(suite.file)
|
|
387
|
+
asyncResource.runInAsyncScope(() => {
|
|
388
|
+
testSuiteFinishCh.publish(status)
|
|
389
|
+
})
|
|
390
|
+
})
|
|
391
|
+
|
|
392
|
+
return run.apply(this, arguments)
|
|
393
|
+
})
|
|
394
|
+
|
|
395
|
+
return Runner
|
|
396
|
+
})
|
|
397
|
+
|
|
398
|
+
// Used both in serial and parallel mode, and by both the main process and the workers
|
|
399
|
+
// Used to set the correct async resource to the test.
|
|
400
|
+
addHook({
|
|
401
|
+
name: 'mocha',
|
|
402
|
+
versions: ['>=5.2.0'],
|
|
403
|
+
file: 'lib/runnable.js'
|
|
404
|
+
}, runnableWrapper)
|
|
405
|
+
|
|
406
|
+
// Only used in parallel mode (--parallel flag is passed)
|
|
407
|
+
// Used to generate suite events and receive test payloads from workers
|
|
408
|
+
addHook({
|
|
409
|
+
name: 'workerpool',
|
|
410
|
+
// mocha@8.0.0 added parallel support and uses workerpool for it
|
|
411
|
+
// The version they use is 6.0.0:
|
|
412
|
+
// https://github.com/mochajs/mocha/blob/612fa31228c695f16173ac675f40ccdf26b4cfb5/package.json#L75
|
|
413
|
+
versions: ['>=6.0.0'],
|
|
414
|
+
file: 'src/WorkerHandler.js'
|
|
415
|
+
}, (workerHandlerPackage) => {
|
|
416
|
+
shimmer.wrap(workerHandlerPackage.prototype, 'exec', exec => function (message, [testSuiteAbsolutePath]) {
|
|
417
|
+
if (!testStartCh.hasSubscribers) {
|
|
418
|
+
return exec.apply(this, arguments)
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
this.worker.on('message', function (message) {
|
|
422
|
+
if (Array.isArray(message)) {
|
|
423
|
+
const [messageCode, payload] = message
|
|
424
|
+
if (messageCode === MOCHA_WORKER_TRACE_PAYLOAD_CODE) {
|
|
425
|
+
testSessionAsyncResource.runInAsyncScope(() => {
|
|
426
|
+
workerReportTraceCh.publish(payload)
|
|
427
|
+
})
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
})
|
|
431
|
+
|
|
432
|
+
const testSuiteAsyncResource = new AsyncResource('bound-anonymous-fn')
|
|
433
|
+
testSuiteAsyncResource.runInAsyncScope(() => {
|
|
434
|
+
testSuiteStartCh.publish({
|
|
435
|
+
testSuiteAbsolutePath
|
|
436
|
+
})
|
|
437
|
+
})
|
|
438
|
+
|
|
439
|
+
try {
|
|
440
|
+
const promise = exec.apply(this, arguments)
|
|
441
|
+
promise.then(
|
|
442
|
+
(result) => {
|
|
443
|
+
const status = result.failureCount === 0 ? 'pass' : 'fail'
|
|
444
|
+
testSuiteAsyncResource.runInAsyncScope(() => {
|
|
445
|
+
testSuiteFinishCh.publish(status)
|
|
446
|
+
})
|
|
447
|
+
},
|
|
448
|
+
(err) => {
|
|
449
|
+
testSuiteAsyncResource.runInAsyncScope(() => {
|
|
450
|
+
testSuiteErrorCh.publish(err)
|
|
451
|
+
testSuiteFinishCh.publish('fail')
|
|
452
|
+
})
|
|
453
|
+
}
|
|
454
|
+
)
|
|
455
|
+
return promise
|
|
456
|
+
} catch (err) {
|
|
457
|
+
testSuiteAsyncResource.runInAsyncScope(() => {
|
|
458
|
+
testSuiteErrorCh.publish(err)
|
|
459
|
+
testSuiteFinishCh.publish('fail')
|
|
460
|
+
})
|
|
461
|
+
throw err
|
|
462
|
+
}
|
|
463
|
+
})
|
|
464
|
+
|
|
465
|
+
return workerHandlerPackage
|
|
466
|
+
})
|
|
467
|
+
|
|
468
|
+
// Only used in parallel mode (--parallel flag is passed)
|
|
469
|
+
// Used to start and finish test session and test module
|
|
470
|
+
addHook({
|
|
471
|
+
name: 'mocha',
|
|
472
|
+
versions: ['>=5.2.0'],
|
|
473
|
+
file: 'lib/nodejs/parallel-buffered-runner.js'
|
|
474
|
+
}, (ParallelBufferedRunner, frameworkVersion) => {
|
|
475
|
+
shimmer.wrap(ParallelBufferedRunner.prototype, 'run', run => function () {
|
|
476
|
+
if (!testStartCh.hasSubscribers) {
|
|
477
|
+
return run.apply(this, arguments)
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
this.once('start', getOnStartHandler(true, frameworkVersion))
|
|
481
|
+
this.once('end', getOnEndHandler(true))
|
|
482
|
+
|
|
483
|
+
return run.apply(this, arguments)
|
|
484
|
+
})
|
|
485
|
+
|
|
486
|
+
return ParallelBufferedRunner
|
|
487
|
+
})
|