dd-trace 5.17.0 → 5.18.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 +1 -0
- package/ext/exporters.d.ts +1 -1
- package/index.d.ts +47 -1
- package/init.js +40 -1
- package/initialize.mjs +8 -5
- package/package.json +24 -20
- package/packages/datadog-core/src/storage/index.js +1 -10
- package/packages/datadog-esbuild/index.js +5 -1
- package/packages/datadog-instrumentations/src/aws-sdk.js +2 -1
- package/packages/datadog-instrumentations/src/cucumber.js +76 -34
- package/packages/datadog-instrumentations/src/helpers/hook.js +8 -3
- package/packages/datadog-instrumentations/src/helpers/hooks.js +2 -0
- package/packages/datadog-instrumentations/src/helpers/instrument.js +4 -3
- package/packages/datadog-instrumentations/src/helpers/register.js +56 -5
- package/packages/datadog-instrumentations/src/mocha/main.js +12 -1
- package/packages/datadog-instrumentations/src/mocha/utils.js +58 -14
- package/packages/datadog-instrumentations/src/mocha/worker.js +1 -0
- package/packages/datadog-instrumentations/src/playwright.js +1 -1
- package/packages/datadog-instrumentations/src/vitest.js +303 -0
- package/packages/datadog-plugin-aws-sdk/src/base.js +8 -1
- package/packages/datadog-plugin-aws-sdk/src/services/kinesis.js +9 -3
- package/packages/datadog-plugin-aws-sdk/src/services/sns.js +6 -1
- package/packages/datadog-plugin-aws-sdk/src/services/sqs.js +23 -5
- package/packages/datadog-plugin-child_process/src/index.js +1 -1
- package/packages/datadog-plugin-cucumber/src/index.js +24 -1
- package/packages/datadog-plugin-mocha/src/index.js +25 -4
- package/packages/datadog-plugin-openai/src/index.js +52 -30
- package/packages/datadog-plugin-openai/src/token-estimator.js +20 -0
- package/packages/datadog-plugin-vitest/src/index.js +156 -0
- package/packages/dd-trace/src/appsec/iast/path-line.js +2 -19
- package/packages/dd-trace/src/appsec/iast/vulnerability-reporter.js +4 -0
- package/packages/dd-trace/src/appsec/index.js +1 -1
- package/packages/dd-trace/src/appsec/rasp.js +32 -5
- package/packages/dd-trace/src/appsec/recommended.json +208 -3
- package/packages/dd-trace/src/appsec/reporter.js +64 -20
- package/packages/dd-trace/src/appsec/sdk/track_event.js +3 -0
- package/packages/dd-trace/src/appsec/stack_trace.js +90 -0
- package/packages/dd-trace/src/appsec/standalone.js +130 -0
- package/packages/dd-trace/src/appsec/telemetry.js +33 -1
- package/packages/dd-trace/src/appsec/waf/index.js +2 -2
- package/packages/dd-trace/src/appsec/waf/waf_context_wrapper.js +2 -2
- package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +4 -2
- package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +4 -2
- package/packages/dd-trace/src/config.js +110 -40
- package/packages/dd-trace/src/constants.js +3 -1
- package/packages/dd-trace/src/datastreams/processor.js +2 -1
- package/packages/dd-trace/src/exporters/agent/index.js +2 -2
- package/packages/dd-trace/src/format.js +1 -0
- package/packages/dd-trace/src/opentracing/propagation/text_map.js +12 -0
- package/packages/dd-trace/src/opentracing/span.js +4 -1
- package/packages/dd-trace/src/opentracing/tracer.js +2 -2
- package/packages/dd-trace/src/plugins/ci_plugin.js +7 -0
- package/packages/dd-trace/src/plugins/index.js +2 -0
- package/packages/dd-trace/src/plugins/util/test.js +5 -1
- package/packages/dd-trace/src/priority_sampler.js +2 -5
- package/packages/dd-trace/src/profiling/profiler.js +1 -1
- package/packages/dd-trace/src/proxy.js +3 -1
- package/packages/dd-trace/src/rate_limiter.js +2 -2
- package/packages/dd-trace/src/span_stats.js +4 -3
- package/packages/dd-trace/src/telemetry/init-telemetry.js +75 -0
- package/packages/dd-trace/src/tracer.js +2 -2
- package/packages/dd-trace/src/util.js +6 -1
- package/packages/datadog-core/src/storage/async_hooks.js +0 -49
|
@@ -7,6 +7,7 @@ const Hook = require('./hook')
|
|
|
7
7
|
const requirePackageJson = require('../../../dd-trace/src/require-package-json')
|
|
8
8
|
const log = require('../../../dd-trace/src/log')
|
|
9
9
|
const checkRequireCache = require('../check_require_cache')
|
|
10
|
+
const telemetry = require('../../../dd-trace/src/telemetry/init-telemetry')
|
|
10
11
|
|
|
11
12
|
const {
|
|
12
13
|
DD_TRACE_DISABLED_INSTRUMENTATIONS = '',
|
|
@@ -35,22 +36,38 @@ if (DD_TRACE_DEBUG && DD_TRACE_DEBUG.toLowerCase() !== 'false') {
|
|
|
35
36
|
setImmediate(checkRequireCache.checkForPotentialConflicts)
|
|
36
37
|
}
|
|
37
38
|
|
|
39
|
+
const seenCombo = new Set()
|
|
40
|
+
|
|
38
41
|
// TODO: make this more efficient
|
|
39
42
|
for (const packageName of names) {
|
|
40
43
|
if (disabledInstrumentations.has(packageName)) continue
|
|
41
44
|
|
|
42
|
-
|
|
45
|
+
const hookOptions = {}
|
|
46
|
+
|
|
47
|
+
let hook = hooks[packageName]
|
|
48
|
+
|
|
49
|
+
if (typeof hook === 'object') {
|
|
50
|
+
hookOptions.internals = hook.esmFirst
|
|
51
|
+
hook = hook.fn
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
Hook([packageName], hookOptions, (moduleExports, moduleName, moduleBaseDir, moduleVersion) => {
|
|
43
55
|
moduleName = moduleName.replace(pathSepExpr, '/')
|
|
44
56
|
|
|
45
57
|
// This executes the integration file thus adding its entries to `instrumentations`
|
|
46
|
-
|
|
58
|
+
hook()
|
|
47
59
|
|
|
48
60
|
if (!instrumentations[packageName]) {
|
|
49
61
|
return moduleExports
|
|
50
62
|
}
|
|
51
63
|
|
|
52
|
-
|
|
64
|
+
const namesAndSuccesses = {}
|
|
65
|
+
for (const { name, file, versions, hook, filePattern } of instrumentations[packageName]) {
|
|
66
|
+
let fullFilePattern = filePattern
|
|
53
67
|
const fullFilename = filename(name, file)
|
|
68
|
+
if (fullFilePattern) {
|
|
69
|
+
fullFilePattern = filename(name, fullFilePattern)
|
|
70
|
+
}
|
|
54
71
|
|
|
55
72
|
// Create a WeakMap associated with the hook function so that patches on the same moduleExport only happens once
|
|
56
73
|
// for example by instrumenting both dns and node:dns double the spans would be created
|
|
@@ -58,13 +75,29 @@ for (const packageName of names) {
|
|
|
58
75
|
if (!hook[HOOK_SYMBOL]) {
|
|
59
76
|
hook[HOOK_SYMBOL] = new WeakMap()
|
|
60
77
|
}
|
|
78
|
+
let matchesFile = false
|
|
79
|
+
|
|
80
|
+
matchesFile = moduleName === fullFilename
|
|
61
81
|
|
|
62
|
-
if (
|
|
82
|
+
if (fullFilePattern) {
|
|
83
|
+
// Some libraries include a hash in their filenames when installed,
|
|
84
|
+
// so our instrumentation has to include a '.*' to match them for more than a single version.
|
|
85
|
+
matchesFile = matchesFile || new RegExp(fullFilePattern).test(moduleName)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (matchesFile) {
|
|
63
89
|
const version = moduleVersion || getVersion(moduleBaseDir)
|
|
90
|
+
if (!Object.hasOwnProperty(namesAndSuccesses, name)) {
|
|
91
|
+
namesAndSuccesses[name] = {
|
|
92
|
+
success: false,
|
|
93
|
+
version
|
|
94
|
+
}
|
|
95
|
+
}
|
|
64
96
|
|
|
65
97
|
if (matchVersion(version, versions)) {
|
|
66
98
|
// Check if the hook already has a set moduleExport
|
|
67
99
|
if (hook[HOOK_SYMBOL].has(moduleExports)) {
|
|
100
|
+
namesAndSuccesses[name].success = true
|
|
68
101
|
return moduleExports
|
|
69
102
|
}
|
|
70
103
|
|
|
@@ -76,11 +109,29 @@ for (const packageName of names) {
|
|
|
76
109
|
// Set the moduleExports in the hooks weakmap
|
|
77
110
|
hook[HOOK_SYMBOL].set(moduleExports, name)
|
|
78
111
|
} catch (e) {
|
|
79
|
-
log.
|
|
112
|
+
log.info('Error during ddtrace instrumentation of application, aborting.')
|
|
113
|
+
log.info(e)
|
|
114
|
+
telemetry('error', [
|
|
115
|
+
`error_type:${e.constructor.name}`,
|
|
116
|
+
`integration:${name}`,
|
|
117
|
+
`integration_version:${version}`
|
|
118
|
+
])
|
|
80
119
|
}
|
|
120
|
+
namesAndSuccesses[name].success = true
|
|
81
121
|
}
|
|
82
122
|
}
|
|
83
123
|
}
|
|
124
|
+
for (const name of Object.keys(namesAndSuccesses)) {
|
|
125
|
+
const { success, version } = namesAndSuccesses[name]
|
|
126
|
+
if (!success && !seenCombo.has(`${name}@${version}`)) {
|
|
127
|
+
telemetry('abort.integration', [
|
|
128
|
+
`integration:${name}`,
|
|
129
|
+
`integration_version:${version}`
|
|
130
|
+
])
|
|
131
|
+
log.info(`Found incompatible integration version: ${name}@${version}`)
|
|
132
|
+
seenCombo.add(`${name}@${version}`)
|
|
133
|
+
}
|
|
134
|
+
}
|
|
84
135
|
|
|
85
136
|
return moduleExports
|
|
86
137
|
})
|
|
@@ -21,6 +21,7 @@ const {
|
|
|
21
21
|
runnableWrapper,
|
|
22
22
|
getOnTestHandler,
|
|
23
23
|
getOnTestEndHandler,
|
|
24
|
+
getOnTestRetryHandler,
|
|
24
25
|
getOnHookEndHandler,
|
|
25
26
|
getOnFailHandler,
|
|
26
27
|
getOnPendingHandler,
|
|
@@ -37,10 +38,12 @@ let isSuitesSkipped = false
|
|
|
37
38
|
let skippedSuites = []
|
|
38
39
|
let isEarlyFlakeDetectionEnabled = false
|
|
39
40
|
let isSuitesSkippingEnabled = false
|
|
41
|
+
let isFlakyTestRetriesEnabled = false
|
|
40
42
|
let earlyFlakeDetectionNumRetries = 0
|
|
41
43
|
let knownTests = []
|
|
42
44
|
let itrCorrelationId = ''
|
|
43
45
|
let isForcedToRun = false
|
|
46
|
+
const config = {}
|
|
44
47
|
|
|
45
48
|
// We'll preserve the original coverage here
|
|
46
49
|
const originalCoverageMap = createCoverageMap()
|
|
@@ -227,6 +230,12 @@ addHook({
|
|
|
227
230
|
isEarlyFlakeDetectionEnabled = libraryConfig.isEarlyFlakeDetectionEnabled
|
|
228
231
|
isSuitesSkippingEnabled = libraryConfig.isSuitesSkippingEnabled
|
|
229
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
|
|
230
239
|
|
|
231
240
|
if (isEarlyFlakeDetectionEnabled) {
|
|
232
241
|
knownTestsCh.publish({
|
|
@@ -317,6 +326,8 @@ addHook({
|
|
|
317
326
|
|
|
318
327
|
this.on('test end', getOnTestEndHandler())
|
|
319
328
|
|
|
329
|
+
this.on('retry', getOnTestRetryHandler())
|
|
330
|
+
|
|
320
331
|
// If the hook passes, 'hook end' will be emitted. Otherwise, 'fail' will be emitted
|
|
321
332
|
this.on('hook end', getOnHookEndHandler())
|
|
322
333
|
|
|
@@ -401,7 +412,7 @@ addHook({
|
|
|
401
412
|
name: 'mocha',
|
|
402
413
|
versions: ['>=5.2.0'],
|
|
403
414
|
file: 'lib/runnable.js'
|
|
404
|
-
}, runnableWrapper)
|
|
415
|
+
}, (runnablePackage) => runnableWrapper(runnablePackage, config))
|
|
405
416
|
|
|
406
417
|
// Only used in parallel mode (--parallel flag is passed)
|
|
407
418
|
// Used to generate suite events and receive test payloads from workers
|
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
const {
|
|
4
4
|
getTestSuitePath,
|
|
5
5
|
removeEfdStringFromTestName,
|
|
6
|
-
addEfdStringToTestName
|
|
6
|
+
addEfdStringToTestName,
|
|
7
|
+
NUM_FAILED_TEST_RETRIES
|
|
7
8
|
} = require('../../../dd-trace/src/plugins/util/test')
|
|
8
9
|
const { channel, AsyncResource } = require('../helpers/instrument')
|
|
9
10
|
const shimmer = require('../../../datadog-shimmer')
|
|
@@ -11,6 +12,8 @@ const shimmer = require('../../../datadog-shimmer')
|
|
|
11
12
|
// test channels
|
|
12
13
|
const testStartCh = channel('ci:mocha:test:start')
|
|
13
14
|
const testFinishCh = channel('ci:mocha:test:finish')
|
|
15
|
+
// after a test has failed, we'll publish to this channel
|
|
16
|
+
const testRetryCh = channel('ci:mocha:test:retry')
|
|
14
17
|
const errorCh = channel('ci:mocha:test:error')
|
|
15
18
|
const skipCh = channel('ci:mocha:test:skip')
|
|
16
19
|
|
|
@@ -70,6 +73,10 @@ function isMochaRetry (test) {
|
|
|
70
73
|
return test._currentRetry !== undefined && test._currentRetry !== 0
|
|
71
74
|
}
|
|
72
75
|
|
|
76
|
+
function isLastRetry (test) {
|
|
77
|
+
return test._currentRetry === test._retries
|
|
78
|
+
}
|
|
79
|
+
|
|
73
80
|
function getTestFullName (test) {
|
|
74
81
|
return `mocha.${getTestSuitePath(test.file, process.cwd())}.${removeEfdStringFromTestName(test.fullTitle())}`
|
|
75
82
|
}
|
|
@@ -84,22 +91,34 @@ function getTestStatus (test) {
|
|
|
84
91
|
return 'pass'
|
|
85
92
|
}
|
|
86
93
|
|
|
87
|
-
function
|
|
94
|
+
function getTestToArKey (test) {
|
|
88
95
|
if (!test.fn) {
|
|
89
|
-
return
|
|
96
|
+
return test
|
|
90
97
|
}
|
|
91
98
|
if (!wrappedFunctions.has(test.fn)) {
|
|
92
|
-
return
|
|
99
|
+
return test.fn
|
|
93
100
|
}
|
|
94
101
|
const originalFn = originalFns.get(test.fn)
|
|
95
|
-
return
|
|
102
|
+
return originalFn
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function getTestAsyncResource (test) {
|
|
106
|
+
const key = getTestToArKey(test)
|
|
107
|
+
return testToAr.get(key)
|
|
96
108
|
}
|
|
97
109
|
|
|
98
|
-
function runnableWrapper (RunnablePackage) {
|
|
110
|
+
function runnableWrapper (RunnablePackage, libraryConfig) {
|
|
99
111
|
shimmer.wrap(RunnablePackage.prototype, 'run', run => function () {
|
|
100
112
|
if (!testStartCh.hasSubscribers) {
|
|
101
113
|
return run.apply(this, arguments)
|
|
102
114
|
}
|
|
115
|
+
// Flaky test retries does not work in parallel mode
|
|
116
|
+
if (libraryConfig?.isFlakyTestRetriesEnabled) {
|
|
117
|
+
this.retries(NUM_FAILED_TEST_RETRIES)
|
|
118
|
+
}
|
|
119
|
+
// The reason why the wrapping logic is here is because we need to cover
|
|
120
|
+
// `afterEach` and `beforeEach` hooks as well.
|
|
121
|
+
// It can't be done in `getOnTestHandler` because it's only called for tests.
|
|
103
122
|
const isBeforeEach = this.parent._beforeEach.includes(this)
|
|
104
123
|
const isAfterEach = this.parent._afterEach.includes(this)
|
|
105
124
|
|
|
@@ -135,11 +154,16 @@ function runnableWrapper (RunnablePackage) {
|
|
|
135
154
|
|
|
136
155
|
function getOnTestHandler (isMain, newTests) {
|
|
137
156
|
return function (test) {
|
|
138
|
-
if (isMochaRetry(test)) {
|
|
139
|
-
return
|
|
140
|
-
}
|
|
141
157
|
const testStartLine = testToStartLine.get(test)
|
|
142
158
|
const asyncResource = new AsyncResource('bound-anonymous-fn')
|
|
159
|
+
|
|
160
|
+
// This may be a retry. If this is the case, `test.fn` is already wrapped,
|
|
161
|
+
// so we need to restore it.
|
|
162
|
+
if (wrappedFunctions.has(test.fn)) {
|
|
163
|
+
const originalFn = originalFns.get(test.fn)
|
|
164
|
+
test.fn = originalFn
|
|
165
|
+
wrappedFunctions.delete(test.fn)
|
|
166
|
+
}
|
|
143
167
|
testToAr.set(test.fn, asyncResource)
|
|
144
168
|
|
|
145
169
|
const {
|
|
@@ -186,7 +210,7 @@ function getOnTestEndHandler () {
|
|
|
186
210
|
// if there are afterEach to be run, we don't finish the test yet
|
|
187
211
|
if (asyncResource && !test.parent._afterEach.length) {
|
|
188
212
|
asyncResource.runInAsyncScope(() => {
|
|
189
|
-
testFinishCh.publish(status)
|
|
213
|
+
testFinishCh.publish({ status, hasBeenRetried: isMochaRetry(test) })
|
|
190
214
|
})
|
|
191
215
|
}
|
|
192
216
|
}
|
|
@@ -197,12 +221,17 @@ function getOnHookEndHandler () {
|
|
|
197
221
|
const test = hook.ctx.currentTest
|
|
198
222
|
if (test && hook.parent._afterEach.includes(hook)) { // only if it's an afterEach
|
|
199
223
|
const isLastAfterEach = hook.parent._afterEach.indexOf(hook) === hook.parent._afterEach.length - 1
|
|
224
|
+
if (test._retries > 0 && !isLastRetry(test)) {
|
|
225
|
+
return
|
|
226
|
+
}
|
|
200
227
|
if (isLastAfterEach) {
|
|
201
228
|
const status = getTestStatus(test)
|
|
202
229
|
const asyncResource = getTestAsyncResource(test)
|
|
203
|
-
asyncResource
|
|
204
|
-
|
|
205
|
-
|
|
230
|
+
if (asyncResource) {
|
|
231
|
+
asyncResource.runInAsyncScope(() => {
|
|
232
|
+
testFinishCh.publish({ status, hasBeenRetried: isMochaRetry(test) })
|
|
233
|
+
})
|
|
234
|
+
}
|
|
206
235
|
}
|
|
207
236
|
}
|
|
208
237
|
}
|
|
@@ -226,7 +255,7 @@ function getOnFailHandler (isMain) {
|
|
|
226
255
|
err.message = `${testOrHook.fullTitle()}: ${err.message}`
|
|
227
256
|
errorCh.publish(err)
|
|
228
257
|
// if it's a hook and it has failed, 'test end' will not be called
|
|
229
|
-
testFinishCh.publish('fail')
|
|
258
|
+
testFinishCh.publish({ status: 'fail', hasBeenRetried: isMochaRetry(test) })
|
|
230
259
|
} else {
|
|
231
260
|
errorCh.publish(err)
|
|
232
261
|
}
|
|
@@ -250,6 +279,20 @@ function getOnFailHandler (isMain) {
|
|
|
250
279
|
}
|
|
251
280
|
}
|
|
252
281
|
|
|
282
|
+
function getOnTestRetryHandler () {
|
|
283
|
+
return function (test) {
|
|
284
|
+
const asyncResource = getTestAsyncResource(test)
|
|
285
|
+
if (asyncResource) {
|
|
286
|
+
const isFirstAttempt = test._currentRetry === 0
|
|
287
|
+
asyncResource.runInAsyncScope(() => {
|
|
288
|
+
testRetryCh.publish(isFirstAttempt)
|
|
289
|
+
})
|
|
290
|
+
}
|
|
291
|
+
const key = getTestToArKey(test)
|
|
292
|
+
testToAr.delete(key)
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
253
296
|
function getOnPendingHandler () {
|
|
254
297
|
return function (test) {
|
|
255
298
|
const testStartLine = testToStartLine.get(test)
|
|
@@ -299,6 +342,7 @@ module.exports = {
|
|
|
299
342
|
testToStartLine,
|
|
300
343
|
getOnTestHandler,
|
|
301
344
|
getOnTestEndHandler,
|
|
345
|
+
getOnTestRetryHandler,
|
|
302
346
|
getOnHookEndHandler,
|
|
303
347
|
getOnFailHandler,
|
|
304
348
|
getOnPendingHandler,
|
|
@@ -249,7 +249,7 @@ function testEndHandler (test, annotations, testStatus, error, isTimeout) {
|
|
|
249
249
|
testAsyncResource.runInAsyncScope(() => {
|
|
250
250
|
testFinishCh.publish({
|
|
251
251
|
testStatus,
|
|
252
|
-
steps: testResult
|
|
252
|
+
steps: testResult?.steps || [],
|
|
253
253
|
error,
|
|
254
254
|
extraTags: annotationTags,
|
|
255
255
|
isNew: test._ddIsNew,
|
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
const { addHook, channel, AsyncResource } = require('./helpers/instrument')
|
|
2
|
+
const shimmer = require('../../datadog-shimmer')
|
|
3
|
+
|
|
4
|
+
// test hooks
|
|
5
|
+
const testStartCh = channel('ci:vitest:test:start')
|
|
6
|
+
const testFinishTimeCh = channel('ci:vitest:test:finish-time')
|
|
7
|
+
const testPassCh = channel('ci:vitest:test:pass')
|
|
8
|
+
const testErrorCh = channel('ci:vitest:test:error')
|
|
9
|
+
const testSkipCh = channel('ci:vitest:test:skip')
|
|
10
|
+
|
|
11
|
+
// test suite hooks
|
|
12
|
+
const testSuiteStartCh = channel('ci:vitest:test-suite:start')
|
|
13
|
+
const testSuiteFinishCh = channel('ci:vitest:test-suite:finish')
|
|
14
|
+
const testSuiteErrorCh = channel('ci:vitest:test-suite:error')
|
|
15
|
+
|
|
16
|
+
// test session hooks
|
|
17
|
+
const testSessionStartCh = channel('ci:vitest:session:start')
|
|
18
|
+
const testSessionFinishCh = channel('ci:vitest:session:finish')
|
|
19
|
+
|
|
20
|
+
const taskToAsync = new WeakMap()
|
|
21
|
+
|
|
22
|
+
const sessionAsyncResource = new AsyncResource('bound-anonymous-fn')
|
|
23
|
+
|
|
24
|
+
function isReporterPackage (vitestPackage) {
|
|
25
|
+
return vitestPackage.B?.name === 'BaseSequencer'
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// from 2.0.0
|
|
29
|
+
function isReporterPackageNew (vitestPackage) {
|
|
30
|
+
return vitestPackage.e?.name === 'BaseSequencer'
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function getSessionStatus (state) {
|
|
34
|
+
if (state.getCountOfFailedTests() > 0) {
|
|
35
|
+
return 'fail'
|
|
36
|
+
}
|
|
37
|
+
if (state.pathsSet.size === 0) {
|
|
38
|
+
return 'skip'
|
|
39
|
+
}
|
|
40
|
+
return 'pass'
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// eslint-disable-next-line
|
|
44
|
+
// From https://github.com/vitest-dev/vitest/blob/51c04e2f44d91322b334f8ccbcdb368facc3f8ec/packages/runner/src/run.ts#L243-L250
|
|
45
|
+
function getVitestTestStatus (test, retryCount) {
|
|
46
|
+
if (test.result.state !== 'fail') {
|
|
47
|
+
if (!test.repeats) {
|
|
48
|
+
return 'pass'
|
|
49
|
+
} else if (test.repeats && (test.retry ?? 0) === retryCount) {
|
|
50
|
+
return 'pass'
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return 'fail'
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function getTypeTasks (fileTasks, type = 'test') {
|
|
57
|
+
const typeTasks = []
|
|
58
|
+
|
|
59
|
+
function getTasks (tasks) {
|
|
60
|
+
for (const task of tasks) {
|
|
61
|
+
if (task.type === type) {
|
|
62
|
+
typeTasks.push(task)
|
|
63
|
+
} else if (task.tasks) {
|
|
64
|
+
getTasks(task.tasks)
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
getTasks(fileTasks)
|
|
70
|
+
|
|
71
|
+
return typeTasks
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function getTestName (task) {
|
|
75
|
+
let testName = task.name
|
|
76
|
+
let currentTask = task.suite
|
|
77
|
+
|
|
78
|
+
while (currentTask) {
|
|
79
|
+
if (currentTask.name) {
|
|
80
|
+
testName = `${currentTask.name} ${testName}`
|
|
81
|
+
}
|
|
82
|
+
currentTask = currentTask.suite
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return testName
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function getSortWrapper (sort) {
|
|
89
|
+
return async function () {
|
|
90
|
+
if (!testSessionFinishCh.hasSubscribers) {
|
|
91
|
+
return sort.apply(this, arguments)
|
|
92
|
+
}
|
|
93
|
+
shimmer.wrap(this.ctx, 'exit', exit => async function () {
|
|
94
|
+
let onFinish
|
|
95
|
+
|
|
96
|
+
const flushPromise = new Promise(resolve => {
|
|
97
|
+
onFinish = resolve
|
|
98
|
+
})
|
|
99
|
+
const failedSuites = this.state.getFailedFilepaths()
|
|
100
|
+
let error
|
|
101
|
+
if (failedSuites.length) {
|
|
102
|
+
error = new Error(`Test suites failed: ${failedSuites.length}.`)
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
sessionAsyncResource.runInAsyncScope(() => {
|
|
106
|
+
testSessionFinishCh.publish({
|
|
107
|
+
status: getSessionStatus(this.state),
|
|
108
|
+
onFinish,
|
|
109
|
+
error
|
|
110
|
+
})
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
await flushPromise
|
|
114
|
+
|
|
115
|
+
return exit.apply(this, arguments)
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
return sort.apply(this, arguments)
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
addHook({
|
|
123
|
+
name: 'vitest',
|
|
124
|
+
versions: ['>=1.6.0'],
|
|
125
|
+
file: 'dist/runners.js'
|
|
126
|
+
}, (vitestPackage) => {
|
|
127
|
+
const { VitestTestRunner } = vitestPackage
|
|
128
|
+
// test start (only tests that are not marked as skip or todo)
|
|
129
|
+
shimmer.wrap(VitestTestRunner.prototype, 'onBeforeTryTask', onBeforeTryTask => async function (task) {
|
|
130
|
+
if (!testStartCh.hasSubscribers) {
|
|
131
|
+
return onBeforeTryTask.apply(this, arguments)
|
|
132
|
+
}
|
|
133
|
+
const asyncResource = new AsyncResource('bound-anonymous-fn')
|
|
134
|
+
taskToAsync.set(task, asyncResource)
|
|
135
|
+
|
|
136
|
+
asyncResource.runInAsyncScope(() => {
|
|
137
|
+
testStartCh.publish({ testName: getTestName(task), testSuiteAbsolutePath: task.suite.file.filepath })
|
|
138
|
+
})
|
|
139
|
+
return onBeforeTryTask.apply(this, arguments)
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
// test finish (only passed tests)
|
|
143
|
+
shimmer.wrap(VitestTestRunner.prototype, 'onAfterTryTask', onAfterTryTask =>
|
|
144
|
+
async function (task, { retry: retryCount }) {
|
|
145
|
+
if (!testFinishTimeCh.hasSubscribers) {
|
|
146
|
+
return onAfterTryTask.apply(this, arguments)
|
|
147
|
+
}
|
|
148
|
+
const result = await onAfterTryTask.apply(this, arguments)
|
|
149
|
+
|
|
150
|
+
const status = getVitestTestStatus(task, retryCount)
|
|
151
|
+
const asyncResource = taskToAsync.get(task)
|
|
152
|
+
|
|
153
|
+
if (asyncResource) {
|
|
154
|
+
// We don't finish here because the test might fail in a later hook
|
|
155
|
+
asyncResource.runInAsyncScope(() => {
|
|
156
|
+
testFinishTimeCh.publish({ status, task })
|
|
157
|
+
})
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return result
|
|
161
|
+
})
|
|
162
|
+
|
|
163
|
+
return vitestPackage
|
|
164
|
+
})
|
|
165
|
+
|
|
166
|
+
addHook({
|
|
167
|
+
name: 'vitest',
|
|
168
|
+
versions: ['>=2.0.0'],
|
|
169
|
+
filePattern: 'dist/vendor/index.*'
|
|
170
|
+
}, (vitestPackage) => {
|
|
171
|
+
// there are multiple index* files so we have to check the exported values
|
|
172
|
+
if (isReporterPackageNew(vitestPackage)) {
|
|
173
|
+
shimmer.wrap(vitestPackage.e.prototype, 'sort', getSortWrapper)
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
return vitestPackage
|
|
177
|
+
})
|
|
178
|
+
|
|
179
|
+
addHook({
|
|
180
|
+
name: 'vitest',
|
|
181
|
+
versions: ['>=1.6.0'],
|
|
182
|
+
filePattern: 'dist/vendor/index.*'
|
|
183
|
+
}, (vitestPackage) => {
|
|
184
|
+
// there are multiple index* files so we have to check the exported values
|
|
185
|
+
if (isReporterPackage(vitestPackage)) {
|
|
186
|
+
shimmer.wrap(vitestPackage.B.prototype, 'sort', getSortWrapper)
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
return vitestPackage
|
|
190
|
+
})
|
|
191
|
+
|
|
192
|
+
// Can't specify file because compiled vitest includes hashes in their files
|
|
193
|
+
addHook({
|
|
194
|
+
name: 'vitest',
|
|
195
|
+
versions: ['>=1.6.0'],
|
|
196
|
+
filePattern: 'dist/vendor/cac.*'
|
|
197
|
+
}, (vitestPackage, frameworkVersion) => {
|
|
198
|
+
shimmer.wrap(vitestPackage, 'c', oldCreateCli => function () {
|
|
199
|
+
if (!testSessionStartCh.hasSubscribers) {
|
|
200
|
+
return oldCreateCli.apply(this, arguments)
|
|
201
|
+
}
|
|
202
|
+
sessionAsyncResource.runInAsyncScope(() => {
|
|
203
|
+
const processArgv = process.argv.slice(2).join(' ')
|
|
204
|
+
testSessionStartCh.publish({ command: `vitest ${processArgv}`, frameworkVersion })
|
|
205
|
+
})
|
|
206
|
+
return oldCreateCli.apply(this, arguments)
|
|
207
|
+
})
|
|
208
|
+
|
|
209
|
+
return vitestPackage
|
|
210
|
+
})
|
|
211
|
+
|
|
212
|
+
// test suite start and finish
|
|
213
|
+
// only relevant for workers
|
|
214
|
+
addHook({
|
|
215
|
+
name: '@vitest/runner',
|
|
216
|
+
versions: ['>=1.6.0'],
|
|
217
|
+
file: 'dist/index.js'
|
|
218
|
+
}, vitestPackage => {
|
|
219
|
+
shimmer.wrap(vitestPackage, 'startTests', startTests => async function (testPath) {
|
|
220
|
+
let testSuiteError = null
|
|
221
|
+
if (!testSuiteStartCh.hasSubscribers) {
|
|
222
|
+
return startTests.apply(this, arguments)
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
const testSuiteAsyncResource = new AsyncResource('bound-anonymous-fn')
|
|
226
|
+
testSuiteAsyncResource.runInAsyncScope(() => {
|
|
227
|
+
testSuiteStartCh.publish(testPath[0])
|
|
228
|
+
})
|
|
229
|
+
const startTestsResponse = await startTests.apply(this, arguments)
|
|
230
|
+
|
|
231
|
+
let onFinish = null
|
|
232
|
+
const onFinishPromise = new Promise(resolve => {
|
|
233
|
+
onFinish = resolve
|
|
234
|
+
})
|
|
235
|
+
|
|
236
|
+
const testTasks = getTypeTasks(startTestsResponse[0].tasks)
|
|
237
|
+
|
|
238
|
+
testTasks.forEach(task => {
|
|
239
|
+
const testAsyncResource = taskToAsync.get(task)
|
|
240
|
+
const { result } = task
|
|
241
|
+
|
|
242
|
+
if (result) {
|
|
243
|
+
const { state, duration, errors } = result
|
|
244
|
+
if (state === 'skip') { // programmatic skip
|
|
245
|
+
testSkipCh.publish({ testName: getTestName(task), testSuiteAbsolutePath: task.suite.file.filepath })
|
|
246
|
+
} else if (state === 'pass') {
|
|
247
|
+
if (testAsyncResource) {
|
|
248
|
+
testAsyncResource.runInAsyncScope(() => {
|
|
249
|
+
testPassCh.publish({ task })
|
|
250
|
+
})
|
|
251
|
+
}
|
|
252
|
+
} else if (state === 'fail') {
|
|
253
|
+
// If it's failing, we have no accurate finish time, so we have to use `duration`
|
|
254
|
+
let testError
|
|
255
|
+
|
|
256
|
+
if (errors?.length) {
|
|
257
|
+
testError = errors[0]
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
if (testAsyncResource) {
|
|
261
|
+
testAsyncResource.runInAsyncScope(() => {
|
|
262
|
+
testErrorCh.publish({ duration, error: testError })
|
|
263
|
+
})
|
|
264
|
+
}
|
|
265
|
+
if (errors?.length) {
|
|
266
|
+
testSuiteError = testError // we store the error to bubble it up to the suite
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
} else { // test.skip or test.todo
|
|
270
|
+
testSkipCh.publish({ testName: getTestName(task), testSuiteAbsolutePath: task.suite.file.filepath })
|
|
271
|
+
}
|
|
272
|
+
})
|
|
273
|
+
|
|
274
|
+
const testSuiteResult = startTestsResponse[0].result
|
|
275
|
+
|
|
276
|
+
if (testSuiteResult.errors?.length) { // Errors from root level hooks
|
|
277
|
+
testSuiteError = testSuiteResult.errors[0]
|
|
278
|
+
} else if (testSuiteResult.state === 'fail') { // Errors from `describe` level hooks
|
|
279
|
+
const suiteTasks = getTypeTasks(startTestsResponse[0].tasks, 'suite')
|
|
280
|
+
const failedSuites = suiteTasks.filter(task => task.result?.state === 'fail')
|
|
281
|
+
if (failedSuites.length && failedSuites[0].result?.errors?.length) {
|
|
282
|
+
testSuiteError = failedSuites[0].result.errors[0]
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
if (testSuiteError) {
|
|
287
|
+
testSuiteAsyncResource.runInAsyncScope(() => {
|
|
288
|
+
testSuiteErrorCh.publish({ error: testSuiteError })
|
|
289
|
+
})
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
testSuiteAsyncResource.runInAsyncScope(() => {
|
|
293
|
+
testSuiteFinishCh.publish({ status: testSuiteResult.state, onFinish })
|
|
294
|
+
})
|
|
295
|
+
|
|
296
|
+
// TODO: fix too frequent flushes
|
|
297
|
+
await onFinishPromise
|
|
298
|
+
|
|
299
|
+
return startTestsResponse
|
|
300
|
+
})
|
|
301
|
+
|
|
302
|
+
return vitestPackage
|
|
303
|
+
})
|
|
@@ -64,11 +64,17 @@ class BaseAwsSdkPlugin extends ClientPlugin {
|
|
|
64
64
|
span.setTag('region', region)
|
|
65
65
|
})
|
|
66
66
|
|
|
67
|
-
this.addSub(`apm:aws:request:complete:${this.serviceIdentifier}`, ({ response }) => {
|
|
67
|
+
this.addSub(`apm:aws:request:complete:${this.serviceIdentifier}`, ({ response, cbExists = false }) => {
|
|
68
68
|
const store = storage.getStore()
|
|
69
69
|
if (!store) return
|
|
70
70
|
const { span } = store
|
|
71
71
|
if (!span) return
|
|
72
|
+
// try to extract DSM context from response if no callback exists as extraction normally happens in CB
|
|
73
|
+
if (!cbExists && this.serviceIdentifier === 'sqs') {
|
|
74
|
+
const params = response.request.params
|
|
75
|
+
const operation = response.request.operation
|
|
76
|
+
this.responseExtractDSMContext(operation, params, response.data, span)
|
|
77
|
+
}
|
|
72
78
|
this.addResponseTags(span, response)
|
|
73
79
|
this.finish(span, response, response.error)
|
|
74
80
|
})
|
|
@@ -159,6 +165,7 @@ function normalizeConfig (config, serviceIdentifier) {
|
|
|
159
165
|
|
|
160
166
|
return Object.assign({}, config, specificConfig, {
|
|
161
167
|
splitByAwsService: config.splitByAwsService !== false,
|
|
168
|
+
batchPropagationEnabled: config.batchPropagationEnabled !== false,
|
|
162
169
|
hooks
|
|
163
170
|
})
|
|
164
171
|
}
|
|
@@ -52,7 +52,7 @@ class Kinesis extends BaseAwsSdkPlugin {
|
|
|
52
52
|
|
|
53
53
|
// extract DSM context after as we might not have a parent-child but may have a DSM context
|
|
54
54
|
this.responseExtractDSMContext(
|
|
55
|
-
request.operation, response, span || null, streamName
|
|
55
|
+
request.operation, request.params, response, span || null, { streamName }
|
|
56
56
|
)
|
|
57
57
|
}
|
|
58
58
|
})
|
|
@@ -100,7 +100,8 @@ class Kinesis extends BaseAwsSdkPlugin {
|
|
|
100
100
|
}
|
|
101
101
|
}
|
|
102
102
|
|
|
103
|
-
responseExtractDSMContext (operation, response, span,
|
|
103
|
+
responseExtractDSMContext (operation, params, response, span, kwargs = {}) {
|
|
104
|
+
const { streamName } = kwargs
|
|
104
105
|
if (!this.config.dsmEnabled) return
|
|
105
106
|
if (operation !== 'getRecords') return
|
|
106
107
|
if (!response || !response.Records || !response.Records[0]) return
|
|
@@ -151,7 +152,12 @@ class Kinesis extends BaseAwsSdkPlugin {
|
|
|
151
152
|
case 'putRecords':
|
|
152
153
|
stream = params.StreamArn ? params.StreamArn : (params.StreamName ? params.StreamName : '')
|
|
153
154
|
for (let i = 0; i < params.Records.length; i++) {
|
|
154
|
-
this.injectToMessage(
|
|
155
|
+
this.injectToMessage(
|
|
156
|
+
span,
|
|
157
|
+
params.Records[i],
|
|
158
|
+
stream,
|
|
159
|
+
i === 0 || (this.config.kinesis && this.config.kinesis.batchPropagationEnabled)
|
|
160
|
+
)
|
|
155
161
|
}
|
|
156
162
|
}
|
|
157
163
|
}
|