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
|
@@ -1,674 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
const { addHook, channel, AsyncResource } = require('./helpers/instrument')
|
|
6
|
-
const shimmer = require('../../datadog-shimmer')
|
|
7
|
-
const log = require('../../dd-trace/src/log')
|
|
8
|
-
const {
|
|
9
|
-
getCoveredFilenamesFromCoverage,
|
|
10
|
-
resetCoverage,
|
|
11
|
-
mergeCoverage,
|
|
12
|
-
getTestSuitePath,
|
|
13
|
-
fromCoverageMapToCoverage,
|
|
14
|
-
getCallSites,
|
|
15
|
-
addEfdStringToTestName,
|
|
16
|
-
removeEfdStringFromTestName
|
|
17
|
-
} = require('../../dd-trace/src/plugins/util/test')
|
|
18
|
-
|
|
19
|
-
const testStartCh = channel('ci:mocha:test:start')
|
|
20
|
-
const errorCh = channel('ci:mocha:test:error')
|
|
21
|
-
const skipCh = channel('ci:mocha:test:skip')
|
|
22
|
-
const testFinishCh = channel('ci:mocha:test:finish')
|
|
23
|
-
const parameterizedTestCh = channel('ci:mocha:test:parameterize')
|
|
24
|
-
|
|
25
|
-
const libraryConfigurationCh = channel('ci:mocha:library-configuration')
|
|
26
|
-
const knownTestsCh = channel('ci:mocha:known-tests')
|
|
27
|
-
const skippableSuitesCh = channel('ci:mocha:test-suite:skippable')
|
|
28
|
-
|
|
29
|
-
const testSessionStartCh = channel('ci:mocha:session:start')
|
|
30
|
-
const testSessionFinishCh = channel('ci:mocha:session:finish')
|
|
31
|
-
|
|
32
|
-
const testSuiteStartCh = channel('ci:mocha:test-suite:start')
|
|
33
|
-
const testSuiteFinishCh = channel('ci:mocha:test-suite:finish')
|
|
34
|
-
const testSuiteErrorCh = channel('ci:mocha:test-suite:error')
|
|
35
|
-
const testSuiteCodeCoverageCh = channel('ci:mocha:test-suite:code-coverage')
|
|
36
|
-
|
|
37
|
-
const itrSkippedSuitesCh = channel('ci:mocha:itr:skipped-suites')
|
|
38
|
-
|
|
39
|
-
// TODO: remove when root hooks and fixtures are implemented
|
|
40
|
-
const patched = new WeakSet()
|
|
41
|
-
|
|
42
|
-
const testToAr = new WeakMap()
|
|
43
|
-
const originalFns = new WeakMap()
|
|
44
|
-
const testFileToSuiteAr = new Map()
|
|
45
|
-
const testToStartLine = new WeakMap()
|
|
46
|
-
const newTests = {}
|
|
47
|
-
|
|
48
|
-
// `isWorker` is true if it's a Mocha worker
|
|
49
|
-
let isWorker = false
|
|
50
|
-
|
|
51
|
-
// We'll preserve the original coverage here
|
|
52
|
-
const originalCoverageMap = createCoverageMap()
|
|
53
|
-
|
|
54
|
-
let suitesToSkip = []
|
|
55
|
-
let frameworkVersion
|
|
56
|
-
let isSuitesSkipped = false
|
|
57
|
-
let skippedSuites = []
|
|
58
|
-
const unskippableSuites = []
|
|
59
|
-
let isForcedToRun = false
|
|
60
|
-
let itrCorrelationId = ''
|
|
61
|
-
let isEarlyFlakeDetectionEnabled = false
|
|
62
|
-
let earlyFlakeDetectionNumRetries = 0
|
|
63
|
-
let isSuitesSkippingEnabled = false
|
|
64
|
-
let knownTests = []
|
|
65
|
-
|
|
66
|
-
function getSuitesByTestFile (root) {
|
|
67
|
-
const suitesByTestFile = {}
|
|
68
|
-
function getSuites (suite) {
|
|
69
|
-
if (suite.file) {
|
|
70
|
-
if (suitesByTestFile[suite.file]) {
|
|
71
|
-
suitesByTestFile[suite.file].push(suite)
|
|
72
|
-
} else {
|
|
73
|
-
suitesByTestFile[suite.file] = [suite]
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
suite.suites.forEach(suite => {
|
|
77
|
-
getSuites(suite)
|
|
78
|
-
})
|
|
79
|
-
}
|
|
80
|
-
getSuites(root)
|
|
81
|
-
|
|
82
|
-
const numSuitesByTestFile = Object.keys(suitesByTestFile).reduce((acc, testFile) => {
|
|
83
|
-
acc[testFile] = suitesByTestFile[testFile].length
|
|
84
|
-
return acc
|
|
85
|
-
}, {})
|
|
86
|
-
|
|
87
|
-
return { suitesByTestFile, numSuitesByTestFile }
|
|
1
|
+
if (process.env.MOCHA_WORKER_ID) {
|
|
2
|
+
require('./mocha/worker')
|
|
3
|
+
} else {
|
|
4
|
+
require('./mocha/main')
|
|
88
5
|
}
|
|
89
|
-
|
|
90
|
-
function getTestStatus (test) {
|
|
91
|
-
if (test.isPending()) {
|
|
92
|
-
return 'skip'
|
|
93
|
-
}
|
|
94
|
-
if (test.isFailed() || test.timedOut) {
|
|
95
|
-
return 'fail'
|
|
96
|
-
}
|
|
97
|
-
return 'pass'
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
function isRetry (test) {
|
|
101
|
-
return test._currentRetry !== undefined && test._currentRetry !== 0
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
function getTestFullName (test) {
|
|
105
|
-
return `mocha.${getTestSuitePath(test.file, process.cwd())}.${removeEfdStringFromTestName(test.fullTitle())}`
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
function isNewTest (test) {
|
|
109
|
-
const testSuite = getTestSuitePath(test.file, process.cwd())
|
|
110
|
-
const testName = removeEfdStringFromTestName(test.fullTitle())
|
|
111
|
-
const testsForSuite = knownTests.mocha?.[testSuite] || []
|
|
112
|
-
return !testsForSuite.includes(testName)
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
function retryTest (test) {
|
|
116
|
-
const originalTestName = test.title
|
|
117
|
-
const suite = test.parent
|
|
118
|
-
for (let retryIndex = 0; retryIndex < earlyFlakeDetectionNumRetries; retryIndex++) {
|
|
119
|
-
const clonedTest = test.clone()
|
|
120
|
-
clonedTest.title = addEfdStringToTestName(originalTestName, retryIndex + 1)
|
|
121
|
-
suite.addTest(clonedTest)
|
|
122
|
-
clonedTest._ddIsNew = true
|
|
123
|
-
clonedTest._ddIsEfdRetry = true
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
function getTestAsyncResource (test) {
|
|
128
|
-
if (!test.fn) {
|
|
129
|
-
return testToAr.get(test)
|
|
130
|
-
}
|
|
131
|
-
if (!test.fn.asyncResource) {
|
|
132
|
-
return testToAr.get(test.fn)
|
|
133
|
-
}
|
|
134
|
-
const originalFn = originalFns.get(test.fn)
|
|
135
|
-
return testToAr.get(originalFn)
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
function getFilteredSuites (originalSuites) {
|
|
139
|
-
return originalSuites.reduce((acc, suite) => {
|
|
140
|
-
const testPath = getTestSuitePath(suite.file, process.cwd())
|
|
141
|
-
const shouldSkip = suitesToSkip.includes(testPath)
|
|
142
|
-
const isUnskippable = unskippableSuites.includes(suite.file)
|
|
143
|
-
if (shouldSkip && !isUnskippable) {
|
|
144
|
-
acc.skippedSuites.add(testPath)
|
|
145
|
-
} else {
|
|
146
|
-
acc.suitesToRun.push(suite)
|
|
147
|
-
}
|
|
148
|
-
return acc
|
|
149
|
-
}, { suitesToRun: [], skippedSuites: new Set() })
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
function mochaHook (Runner) {
|
|
153
|
-
if (patched.has(Runner)) return Runner
|
|
154
|
-
|
|
155
|
-
patched.add(Runner)
|
|
156
|
-
|
|
157
|
-
shimmer.wrap(Runner.prototype, 'runTests', runTests => function (suite, fn) {
|
|
158
|
-
if (isEarlyFlakeDetectionEnabled) {
|
|
159
|
-
// by the time we reach `this.on('test')`, it is too late. We need to add retries here
|
|
160
|
-
suite.tests.forEach(test => {
|
|
161
|
-
if (!test.isPending() && isNewTest(test)) {
|
|
162
|
-
test._ddIsNew = true
|
|
163
|
-
retryTest(test)
|
|
164
|
-
}
|
|
165
|
-
})
|
|
166
|
-
}
|
|
167
|
-
return runTests.apply(this, arguments)
|
|
168
|
-
})
|
|
169
|
-
|
|
170
|
-
shimmer.wrap(Runner.prototype, 'run', run => function () {
|
|
171
|
-
if (!testStartCh.hasSubscribers || isWorker) {
|
|
172
|
-
return run.apply(this, arguments)
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
const { suitesByTestFile, numSuitesByTestFile } = getSuitesByTestFile(this.suite)
|
|
176
|
-
|
|
177
|
-
const testRunAsyncResource = new AsyncResource('bound-anonymous-fn')
|
|
178
|
-
|
|
179
|
-
this.once('end', testRunAsyncResource.bind(function () {
|
|
180
|
-
let status = 'pass'
|
|
181
|
-
let error
|
|
182
|
-
if (this.stats) {
|
|
183
|
-
status = this.stats.failures === 0 ? 'pass' : 'fail'
|
|
184
|
-
if (this.stats.tests === 0) {
|
|
185
|
-
status = 'skip'
|
|
186
|
-
}
|
|
187
|
-
} else if (this.failures !== 0) {
|
|
188
|
-
status = 'fail'
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
if (isEarlyFlakeDetectionEnabled) {
|
|
192
|
-
/**
|
|
193
|
-
* If Early Flake Detection (EFD) is enabled the logic is as follows:
|
|
194
|
-
* - If all attempts for a test are failing, the test has failed and we will let the test process fail.
|
|
195
|
-
* - If just a single attempt passes, we will prevent the test process from failing.
|
|
196
|
-
* The rationale behind is the following: you may still be able to block your CI pipeline by gating
|
|
197
|
-
* on flakiness (the test will be considered flaky), but you may choose to unblock the pipeline too.
|
|
198
|
-
*/
|
|
199
|
-
for (const tests of Object.values(newTests)) {
|
|
200
|
-
const failingNewTests = tests.filter(test => test.isFailed())
|
|
201
|
-
const areAllNewTestsFailing = failingNewTests.length === tests.length
|
|
202
|
-
if (failingNewTests.length && !areAllNewTestsFailing) {
|
|
203
|
-
this.stats.failures -= failingNewTests.length
|
|
204
|
-
this.failures -= failingNewTests.length
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
if (status === 'fail') {
|
|
210
|
-
error = new Error(`Failed tests: ${this.failures}.`)
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
testFileToSuiteAr.clear()
|
|
214
|
-
|
|
215
|
-
let testCodeCoverageLinesTotal
|
|
216
|
-
if (global.__coverage__) {
|
|
217
|
-
try {
|
|
218
|
-
testCodeCoverageLinesTotal = originalCoverageMap.getCoverageSummary().lines.pct
|
|
219
|
-
} catch (e) {
|
|
220
|
-
// ignore errors
|
|
221
|
-
}
|
|
222
|
-
// restore the original coverage
|
|
223
|
-
global.__coverage__ = fromCoverageMapToCoverage(originalCoverageMap)
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
testSessionFinishCh.publish({
|
|
227
|
-
status,
|
|
228
|
-
isSuitesSkipped,
|
|
229
|
-
testCodeCoverageLinesTotal,
|
|
230
|
-
numSkippedSuites: skippedSuites.length,
|
|
231
|
-
hasForcedToRunSuites: isForcedToRun,
|
|
232
|
-
hasUnskippableSuites: !!unskippableSuites.length,
|
|
233
|
-
error,
|
|
234
|
-
isEarlyFlakeDetectionEnabled
|
|
235
|
-
})
|
|
236
|
-
}))
|
|
237
|
-
|
|
238
|
-
this.once('start', testRunAsyncResource.bind(function () {
|
|
239
|
-
const processArgv = process.argv.slice(2).join(' ')
|
|
240
|
-
const command = `mocha ${processArgv}`
|
|
241
|
-
testSessionStartCh.publish({ command, frameworkVersion })
|
|
242
|
-
if (skippedSuites.length) {
|
|
243
|
-
itrSkippedSuitesCh.publish({ skippedSuites, frameworkVersion })
|
|
244
|
-
}
|
|
245
|
-
}))
|
|
246
|
-
|
|
247
|
-
this.on('suite', function (suite) {
|
|
248
|
-
if (suite.root || !suite.tests.length) {
|
|
249
|
-
return
|
|
250
|
-
}
|
|
251
|
-
let asyncResource = testFileToSuiteAr.get(suite.file)
|
|
252
|
-
if (!asyncResource) {
|
|
253
|
-
asyncResource = new AsyncResource('bound-anonymous-fn')
|
|
254
|
-
testFileToSuiteAr.set(suite.file, asyncResource)
|
|
255
|
-
const isUnskippable = unskippableSuites.includes(suite.file)
|
|
256
|
-
isForcedToRun = isUnskippable && suitesToSkip.includes(getTestSuitePath(suite.file, process.cwd()))
|
|
257
|
-
asyncResource.runInAsyncScope(() => {
|
|
258
|
-
testSuiteStartCh.publish({
|
|
259
|
-
testSuite: suite.file,
|
|
260
|
-
isUnskippable,
|
|
261
|
-
isForcedToRun,
|
|
262
|
-
itrCorrelationId
|
|
263
|
-
})
|
|
264
|
-
})
|
|
265
|
-
}
|
|
266
|
-
})
|
|
267
|
-
|
|
268
|
-
this.on('suite end', function (suite) {
|
|
269
|
-
if (suite.root) {
|
|
270
|
-
return
|
|
271
|
-
}
|
|
272
|
-
const suitesInTestFile = suitesByTestFile[suite.file]
|
|
273
|
-
|
|
274
|
-
const isLastSuite = --numSuitesByTestFile[suite.file] === 0
|
|
275
|
-
if (!isLastSuite) {
|
|
276
|
-
return
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
let status = 'pass'
|
|
280
|
-
if (suitesInTestFile.every(suite => suite.pending)) {
|
|
281
|
-
status = 'skip'
|
|
282
|
-
} else {
|
|
283
|
-
// has to check every test in the test file
|
|
284
|
-
suitesInTestFile.forEach(suite => {
|
|
285
|
-
suite.eachTest(test => {
|
|
286
|
-
if (test.state === 'failed' || test.timedOut) {
|
|
287
|
-
status = 'fail'
|
|
288
|
-
}
|
|
289
|
-
})
|
|
290
|
-
})
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
if (global.__coverage__) {
|
|
294
|
-
const coverageFiles = getCoveredFilenamesFromCoverage(global.__coverage__)
|
|
295
|
-
|
|
296
|
-
testSuiteCodeCoverageCh.publish({
|
|
297
|
-
coverageFiles,
|
|
298
|
-
suiteFile: suite.file
|
|
299
|
-
})
|
|
300
|
-
// We need to reset coverage to get a code coverage per suite
|
|
301
|
-
// Before that, we preserve the original coverage
|
|
302
|
-
mergeCoverage(global.__coverage__, originalCoverageMap)
|
|
303
|
-
resetCoverage(global.__coverage__)
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
const asyncResource = testFileToSuiteAr.get(suite.file)
|
|
307
|
-
asyncResource.runInAsyncScope(() => {
|
|
308
|
-
testSuiteFinishCh.publish(status)
|
|
309
|
-
})
|
|
310
|
-
})
|
|
311
|
-
|
|
312
|
-
this.on('test', (test) => {
|
|
313
|
-
if (isRetry(test)) {
|
|
314
|
-
return
|
|
315
|
-
}
|
|
316
|
-
const testStartLine = testToStartLine.get(test)
|
|
317
|
-
const asyncResource = new AsyncResource('bound-anonymous-fn')
|
|
318
|
-
testToAr.set(test.fn, asyncResource)
|
|
319
|
-
|
|
320
|
-
const {
|
|
321
|
-
file: testSuiteAbsolutePath,
|
|
322
|
-
title,
|
|
323
|
-
_ddIsNew: isNew,
|
|
324
|
-
_ddIsEfdRetry: isEfdRetry
|
|
325
|
-
} = test
|
|
326
|
-
|
|
327
|
-
const testInfo = {
|
|
328
|
-
testName: test.fullTitle(),
|
|
329
|
-
testSuiteAbsolutePath,
|
|
330
|
-
title,
|
|
331
|
-
isNew,
|
|
332
|
-
isEfdRetry,
|
|
333
|
-
testStartLine
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
// We want to store the result of the new tests
|
|
337
|
-
if (isNew) {
|
|
338
|
-
const testFullName = getTestFullName(test)
|
|
339
|
-
if (newTests[testFullName]) {
|
|
340
|
-
newTests[testFullName].push(test)
|
|
341
|
-
} else {
|
|
342
|
-
newTests[testFullName] = [test]
|
|
343
|
-
}
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
asyncResource.runInAsyncScope(() => {
|
|
347
|
-
testStartCh.publish(testInfo)
|
|
348
|
-
})
|
|
349
|
-
})
|
|
350
|
-
|
|
351
|
-
this.on('test end', (test) => {
|
|
352
|
-
const asyncResource = getTestAsyncResource(test)
|
|
353
|
-
const status = getTestStatus(test)
|
|
354
|
-
|
|
355
|
-
// if there are afterEach to be run, we don't finish the test yet
|
|
356
|
-
if (asyncResource && !test.parent._afterEach.length) {
|
|
357
|
-
asyncResource.runInAsyncScope(() => {
|
|
358
|
-
testFinishCh.publish(status)
|
|
359
|
-
})
|
|
360
|
-
}
|
|
361
|
-
})
|
|
362
|
-
|
|
363
|
-
// If the hook passes, 'hook end' will be emitted. Otherwise, 'fail' will be emitted
|
|
364
|
-
this.on('hook end', (hook) => {
|
|
365
|
-
const test = hook.ctx.currentTest
|
|
366
|
-
if (test && hook.parent._afterEach.includes(hook)) { // only if it's an afterEach
|
|
367
|
-
const isLastAfterEach = hook.parent._afterEach.indexOf(hook) === hook.parent._afterEach.length - 1
|
|
368
|
-
if (isLastAfterEach) {
|
|
369
|
-
const status = getTestStatus(test)
|
|
370
|
-
const asyncResource = getTestAsyncResource(test)
|
|
371
|
-
asyncResource.runInAsyncScope(() => {
|
|
372
|
-
testFinishCh.publish(status)
|
|
373
|
-
})
|
|
374
|
-
}
|
|
375
|
-
}
|
|
376
|
-
})
|
|
377
|
-
|
|
378
|
-
this.on('fail', (testOrHook, err) => {
|
|
379
|
-
const testFile = testOrHook.file
|
|
380
|
-
let test = testOrHook
|
|
381
|
-
const isHook = testOrHook.type === 'hook'
|
|
382
|
-
if (isHook && testOrHook.ctx) {
|
|
383
|
-
test = testOrHook.ctx.currentTest
|
|
384
|
-
}
|
|
385
|
-
let testAsyncResource
|
|
386
|
-
if (test) {
|
|
387
|
-
testAsyncResource = getTestAsyncResource(test)
|
|
388
|
-
}
|
|
389
|
-
if (testAsyncResource) {
|
|
390
|
-
testAsyncResource.runInAsyncScope(() => {
|
|
391
|
-
if (isHook) {
|
|
392
|
-
err.message = `${testOrHook.fullTitle()}: ${err.message}`
|
|
393
|
-
errorCh.publish(err)
|
|
394
|
-
// if it's a hook and it has failed, 'test end' will not be called
|
|
395
|
-
testFinishCh.publish('fail')
|
|
396
|
-
} else {
|
|
397
|
-
errorCh.publish(err)
|
|
398
|
-
}
|
|
399
|
-
})
|
|
400
|
-
}
|
|
401
|
-
const testSuiteAsyncResource = testFileToSuiteAr.get(testFile)
|
|
402
|
-
|
|
403
|
-
if (testSuiteAsyncResource) {
|
|
404
|
-
// we propagate the error to the suite
|
|
405
|
-
const testSuiteError = new Error(
|
|
406
|
-
`"${testOrHook.parent.fullTitle()}" failed with message "${err.message}"`
|
|
407
|
-
)
|
|
408
|
-
testSuiteError.stack = err.stack
|
|
409
|
-
testSuiteAsyncResource.runInAsyncScope(() => {
|
|
410
|
-
testSuiteErrorCh.publish(testSuiteError)
|
|
411
|
-
})
|
|
412
|
-
}
|
|
413
|
-
})
|
|
414
|
-
|
|
415
|
-
this.on('pending', (test) => {
|
|
416
|
-
const testStartLine = testToStartLine.get(test)
|
|
417
|
-
const {
|
|
418
|
-
file: testSuiteAbsolutePath,
|
|
419
|
-
title
|
|
420
|
-
} = test
|
|
421
|
-
|
|
422
|
-
const testInfo = {
|
|
423
|
-
testName: test.fullTitle(),
|
|
424
|
-
testSuiteAbsolutePath,
|
|
425
|
-
title,
|
|
426
|
-
testStartLine
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
const asyncResource = getTestAsyncResource(test)
|
|
430
|
-
if (asyncResource) {
|
|
431
|
-
asyncResource.runInAsyncScope(() => {
|
|
432
|
-
skipCh.publish(testInfo)
|
|
433
|
-
})
|
|
434
|
-
} else {
|
|
435
|
-
// if there is no async resource, the test has been skipped through `test.skip`
|
|
436
|
-
// or the parent suite is skipped
|
|
437
|
-
const skippedTestAsyncResource = new AsyncResource('bound-anonymous-fn')
|
|
438
|
-
if (test.fn) {
|
|
439
|
-
testToAr.set(test.fn, skippedTestAsyncResource)
|
|
440
|
-
} else {
|
|
441
|
-
testToAr.set(test, skippedTestAsyncResource)
|
|
442
|
-
}
|
|
443
|
-
skippedTestAsyncResource.runInAsyncScope(() => {
|
|
444
|
-
skipCh.publish(testInfo)
|
|
445
|
-
})
|
|
446
|
-
}
|
|
447
|
-
})
|
|
448
|
-
|
|
449
|
-
return run.apply(this, arguments)
|
|
450
|
-
})
|
|
451
|
-
|
|
452
|
-
return Runner
|
|
453
|
-
}
|
|
454
|
-
|
|
455
|
-
function mochaEachHook (mochaEach) {
|
|
456
|
-
if (patched.has(mochaEach)) return mochaEach
|
|
457
|
-
|
|
458
|
-
patched.add(mochaEach)
|
|
459
|
-
|
|
460
|
-
return shimmer.wrap(mochaEach, function () {
|
|
461
|
-
const [params] = arguments
|
|
462
|
-
const { it, ...rest } = mochaEach.apply(this, arguments)
|
|
463
|
-
return {
|
|
464
|
-
it: function (title) {
|
|
465
|
-
parameterizedTestCh.publish({ title, params })
|
|
466
|
-
it.apply(this, arguments)
|
|
467
|
-
},
|
|
468
|
-
...rest
|
|
469
|
-
}
|
|
470
|
-
})
|
|
471
|
-
}
|
|
472
|
-
|
|
473
|
-
addHook({
|
|
474
|
-
name: 'mocha',
|
|
475
|
-
versions: ['>=5.2.0'],
|
|
476
|
-
file: 'lib/mocha.js'
|
|
477
|
-
}, (Mocha, mochaVersion) => {
|
|
478
|
-
frameworkVersion = mochaVersion
|
|
479
|
-
const mochaRunAsyncResource = new AsyncResource('bound-anonymous-fn')
|
|
480
|
-
/**
|
|
481
|
-
* Get ITR configuration and skippable suites
|
|
482
|
-
* If ITR is disabled, `onDone` is called immediately on the subscriber
|
|
483
|
-
*/
|
|
484
|
-
shimmer.wrap(Mocha.prototype, 'run', run => function () {
|
|
485
|
-
if (this.options.parallel) {
|
|
486
|
-
log.warn('Unable to initialize CI Visibility because Mocha is running in parallel mode.')
|
|
487
|
-
return run.apply(this, arguments)
|
|
488
|
-
}
|
|
489
|
-
|
|
490
|
-
if (!libraryConfigurationCh.hasSubscribers || this.isWorker) {
|
|
491
|
-
if (this.isWorker) {
|
|
492
|
-
isWorker = true
|
|
493
|
-
}
|
|
494
|
-
return run.apply(this, arguments)
|
|
495
|
-
}
|
|
496
|
-
this.options.delay = true
|
|
497
|
-
|
|
498
|
-
const runner = run.apply(this, arguments)
|
|
499
|
-
|
|
500
|
-
this.files.forEach(path => {
|
|
501
|
-
const isUnskippable = isMarkedAsUnskippable({ path })
|
|
502
|
-
if (isUnskippable) {
|
|
503
|
-
unskippableSuites.push(path)
|
|
504
|
-
}
|
|
505
|
-
})
|
|
506
|
-
|
|
507
|
-
const onReceivedSkippableSuites = ({ err, skippableSuites, itrCorrelationId: responseItrCorrelationId }) => {
|
|
508
|
-
if (err) {
|
|
509
|
-
suitesToSkip = []
|
|
510
|
-
} else {
|
|
511
|
-
suitesToSkip = skippableSuites
|
|
512
|
-
itrCorrelationId = responseItrCorrelationId
|
|
513
|
-
}
|
|
514
|
-
// We remove the suites that we skip through ITR
|
|
515
|
-
const filteredSuites = getFilteredSuites(runner.suite.suites)
|
|
516
|
-
const { suitesToRun } = filteredSuites
|
|
517
|
-
|
|
518
|
-
isSuitesSkipped = suitesToRun.length !== runner.suite.suites.length
|
|
519
|
-
|
|
520
|
-
log.debug(
|
|
521
|
-
() => `${suitesToRun.length} out of ${runner.suite.suites.length} suites are going to run.`
|
|
522
|
-
)
|
|
523
|
-
|
|
524
|
-
runner.suite.suites = suitesToRun
|
|
525
|
-
|
|
526
|
-
skippedSuites = Array.from(filteredSuites.skippedSuites)
|
|
527
|
-
|
|
528
|
-
global.run()
|
|
529
|
-
}
|
|
530
|
-
|
|
531
|
-
const onReceivedKnownTests = ({ err, knownTests: receivedKnownTests }) => {
|
|
532
|
-
if (err) {
|
|
533
|
-
knownTests = []
|
|
534
|
-
isEarlyFlakeDetectionEnabled = false
|
|
535
|
-
} else {
|
|
536
|
-
knownTests = receivedKnownTests
|
|
537
|
-
}
|
|
538
|
-
|
|
539
|
-
if (isSuitesSkippingEnabled) {
|
|
540
|
-
skippableSuitesCh.publish({
|
|
541
|
-
onDone: mochaRunAsyncResource.bind(onReceivedSkippableSuites)
|
|
542
|
-
})
|
|
543
|
-
} else {
|
|
544
|
-
global.run()
|
|
545
|
-
}
|
|
546
|
-
}
|
|
547
|
-
|
|
548
|
-
const onReceivedConfiguration = ({ err, libraryConfig }) => {
|
|
549
|
-
if (err || !skippableSuitesCh.hasSubscribers || !knownTestsCh.hasSubscribers) {
|
|
550
|
-
return global.run()
|
|
551
|
-
}
|
|
552
|
-
|
|
553
|
-
isEarlyFlakeDetectionEnabled = libraryConfig.isEarlyFlakeDetectionEnabled
|
|
554
|
-
isSuitesSkippingEnabled = libraryConfig.isSuitesSkippingEnabled
|
|
555
|
-
earlyFlakeDetectionNumRetries = libraryConfig.earlyFlakeDetectionNumRetries
|
|
556
|
-
|
|
557
|
-
if (isEarlyFlakeDetectionEnabled) {
|
|
558
|
-
knownTestsCh.publish({
|
|
559
|
-
onDone: mochaRunAsyncResource.bind(onReceivedKnownTests)
|
|
560
|
-
})
|
|
561
|
-
} else if (isSuitesSkippingEnabled) {
|
|
562
|
-
skippableSuitesCh.publish({
|
|
563
|
-
onDone: mochaRunAsyncResource.bind(onReceivedSkippableSuites)
|
|
564
|
-
})
|
|
565
|
-
} else {
|
|
566
|
-
global.run()
|
|
567
|
-
}
|
|
568
|
-
}
|
|
569
|
-
|
|
570
|
-
mochaRunAsyncResource.runInAsyncScope(() => {
|
|
571
|
-
libraryConfigurationCh.publish({
|
|
572
|
-
onDone: mochaRunAsyncResource.bind(onReceivedConfiguration)
|
|
573
|
-
})
|
|
574
|
-
})
|
|
575
|
-
return runner
|
|
576
|
-
})
|
|
577
|
-
return Mocha
|
|
578
|
-
})
|
|
579
|
-
|
|
580
|
-
addHook({
|
|
581
|
-
name: 'mocha',
|
|
582
|
-
versions: ['>=5.2.0'],
|
|
583
|
-
file: 'lib/suite.js'
|
|
584
|
-
}, (Suite) => {
|
|
585
|
-
shimmer.wrap(Suite.prototype, 'addTest', addTest => function (test) {
|
|
586
|
-
const callSites = getCallSites()
|
|
587
|
-
let startLine
|
|
588
|
-
const testCallSite = callSites.find(site => site.getFileName() === test.file)
|
|
589
|
-
if (testCallSite) {
|
|
590
|
-
startLine = testCallSite.getLineNumber()
|
|
591
|
-
testToStartLine.set(test, startLine)
|
|
592
|
-
}
|
|
593
|
-
return addTest.apply(this, arguments)
|
|
594
|
-
})
|
|
595
|
-
return Suite
|
|
596
|
-
})
|
|
597
|
-
|
|
598
|
-
addHook({
|
|
599
|
-
name: 'mocha',
|
|
600
|
-
versions: ['>=5.2.0'],
|
|
601
|
-
file: 'lib/runner.js'
|
|
602
|
-
}, mochaHook)
|
|
603
|
-
|
|
604
|
-
addHook({
|
|
605
|
-
name: 'mocha',
|
|
606
|
-
versions: ['>=5.2.0'],
|
|
607
|
-
file: 'lib/cli/run-helpers.js'
|
|
608
|
-
}, (run) => {
|
|
609
|
-
shimmer.wrap(run, 'runMocha', runMocha => async function () {
|
|
610
|
-
if (!testStartCh.hasSubscribers) {
|
|
611
|
-
return runMocha.apply(this, arguments)
|
|
612
|
-
}
|
|
613
|
-
const mocha = arguments[0]
|
|
614
|
-
/**
|
|
615
|
-
* This attaches `run` to the global context, which we'll call after
|
|
616
|
-
* our configuration and skippable suites requests
|
|
617
|
-
*/
|
|
618
|
-
if (!mocha.options.parallel) {
|
|
619
|
-
mocha.options.delay = true
|
|
620
|
-
}
|
|
621
|
-
return runMocha.apply(this, arguments)
|
|
622
|
-
})
|
|
623
|
-
return run
|
|
624
|
-
})
|
|
625
|
-
|
|
626
|
-
addHook({
|
|
627
|
-
name: 'mocha',
|
|
628
|
-
versions: ['>=5.2.0'],
|
|
629
|
-
file: 'lib/runnable.js'
|
|
630
|
-
}, (Runnable) => {
|
|
631
|
-
shimmer.wrap(Runnable.prototype, 'run', run => function () {
|
|
632
|
-
if (!testStartCh.hasSubscribers) {
|
|
633
|
-
return run.apply(this, arguments)
|
|
634
|
-
}
|
|
635
|
-
const isBeforeEach = this.parent._beforeEach.includes(this)
|
|
636
|
-
const isAfterEach = this.parent._afterEach.includes(this)
|
|
637
|
-
|
|
638
|
-
const isTestHook = isBeforeEach || isAfterEach
|
|
639
|
-
|
|
640
|
-
// we restore the original user defined function
|
|
641
|
-
if (this.fn.asyncResource) {
|
|
642
|
-
const originalFn = originalFns.get(this.fn)
|
|
643
|
-
this.fn = originalFn
|
|
644
|
-
}
|
|
645
|
-
|
|
646
|
-
if (isTestHook || this.type === 'test') {
|
|
647
|
-
const test = isTestHook ? this.ctx.currentTest : this
|
|
648
|
-
const asyncResource = getTestAsyncResource(test)
|
|
649
|
-
|
|
650
|
-
if (asyncResource) {
|
|
651
|
-
// we bind the test fn to the correct async resource
|
|
652
|
-
const newFn = asyncResource.bind(this.fn)
|
|
653
|
-
|
|
654
|
-
// we store the original function, not to lose it
|
|
655
|
-
originalFns.set(newFn, this.fn)
|
|
656
|
-
this.fn = newFn
|
|
657
|
-
|
|
658
|
-
// Temporarily keep functionality when .asyncResource is removed from node
|
|
659
|
-
// in https://github.com/nodejs/node/pull/46432
|
|
660
|
-
if (!this.fn.asyncResource) {
|
|
661
|
-
this.fn.asyncResource = asyncResource
|
|
662
|
-
}
|
|
663
|
-
}
|
|
664
|
-
}
|
|
665
|
-
|
|
666
|
-
return run.apply(this, arguments)
|
|
667
|
-
})
|
|
668
|
-
return Runnable
|
|
669
|
-
})
|
|
670
|
-
|
|
671
|
-
addHook({
|
|
672
|
-
name: 'mocha-each',
|
|
673
|
-
versions: ['>=2.0.1']
|
|
674
|
-
}, mochaEachHook)
|