dd-trace 5.22.0 → 5.23.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 +2 -0
- package/index.d.ts +20 -8
- package/package.json +9 -3
- package/packages/datadog-instrumentations/src/cucumber.js +290 -53
- package/packages/datadog-instrumentations/src/jest.js +3 -1
- package/packages/datadog-instrumentations/src/kafkajs.js +67 -31
- package/packages/datadog-instrumentations/src/microgateway-core.js +3 -1
- package/packages/datadog-instrumentations/src/mocha/main.js +139 -54
- package/packages/datadog-instrumentations/src/mocha/utils.js +35 -15
- package/packages/datadog-instrumentations/src/mocha/worker.js +29 -1
- package/packages/datadog-instrumentations/src/openai.js +4 -2
- package/packages/datadog-instrumentations/src/pg.js +59 -4
- package/packages/datadog-instrumentations/src/vitest.js +184 -9
- package/packages/datadog-plugin-amqplib/src/consumer.js +1 -3
- package/packages/datadog-plugin-aws-sdk/src/base.js +33 -0
- package/packages/datadog-plugin-aws-sdk/src/services/kinesis.js +1 -1
- package/packages/datadog-plugin-aws-sdk/src/services/sns.js +2 -0
- package/packages/datadog-plugin-aws-sdk/src/services/sqs.js +1 -1
- package/packages/datadog-plugin-cucumber/src/index.js +24 -1
- package/packages/datadog-plugin-cypress/src/cypress-plugin.js +36 -6
- package/packages/datadog-plugin-cypress/src/support.js +4 -1
- package/packages/datadog-plugin-http/src/client.js +1 -42
- package/packages/datadog-plugin-http2/src/client.js +1 -26
- package/packages/datadog-plugin-jest/src/index.js +17 -1
- package/packages/datadog-plugin-kafkajs/src/batch-consumer.js +20 -0
- package/packages/datadog-plugin-kafkajs/src/consumer.js +1 -2
- package/packages/datadog-plugin-kafkajs/src/index.js +3 -1
- package/packages/datadog-plugin-mocha/src/index.js +18 -0
- package/packages/datadog-plugin-openai/src/index.js +27 -18
- package/packages/datadog-plugin-playwright/src/index.js +9 -0
- package/packages/datadog-plugin-rhea/src/consumer.js +1 -3
- package/packages/datadog-plugin-vitest/src/index.js +68 -3
- package/packages/dd-trace/src/appsec/addresses.js +3 -1
- package/packages/dd-trace/src/appsec/channels.js +4 -2
- package/packages/dd-trace/src/appsec/rasp/index.js +103 -0
- package/packages/dd-trace/src/appsec/rasp/sql_injection.js +86 -0
- package/packages/dd-trace/src/appsec/rasp/ssrf.js +37 -0
- package/packages/dd-trace/src/appsec/rasp/utils.js +63 -0
- package/packages/dd-trace/src/appsec/remote_config/capabilities.js +2 -0
- package/packages/dd-trace/src/appsec/remote_config/index.js +16 -7
- package/packages/dd-trace/src/appsec/remote_config/manager.js +89 -51
- package/packages/dd-trace/src/appsec/waf/waf_context_wrapper.js +33 -14
- package/packages/dd-trace/src/appsec/waf/waf_manager.js +2 -1
- package/packages/dd-trace/src/ci-visibility/exporters/agentless/writer.js +4 -0
- package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +13 -0
- package/packages/dd-trace/src/config.js +61 -10
- package/packages/dd-trace/src/constants.js +11 -1
- package/packages/dd-trace/src/data_streams_context.js +3 -0
- package/packages/dd-trace/src/datastreams/fnv.js +23 -0
- package/packages/dd-trace/src/datastreams/pathway.js +12 -5
- package/packages/dd-trace/src/datastreams/processor.js +35 -0
- package/packages/dd-trace/src/datastreams/schemas/schema.js +8 -0
- package/packages/dd-trace/src/datastreams/schemas/schema_builder.js +125 -0
- package/packages/dd-trace/src/datastreams/schemas/schema_sampler.js +29 -0
- package/packages/dd-trace/src/debugger/devtools_client/config.js +24 -0
- package/packages/dd-trace/src/debugger/devtools_client/index.js +57 -0
- package/packages/dd-trace/src/debugger/devtools_client/inspector_promises_polyfill.js +23 -0
- package/packages/dd-trace/src/debugger/devtools_client/remote_config.js +164 -0
- package/packages/dd-trace/src/debugger/devtools_client/send.js +28 -0
- package/packages/dd-trace/src/debugger/devtools_client/session.js +7 -0
- package/packages/dd-trace/src/debugger/devtools_client/state.js +47 -0
- package/packages/dd-trace/src/debugger/devtools_client/status.js +109 -0
- package/packages/dd-trace/src/debugger/index.js +92 -0
- package/packages/dd-trace/src/encode/agentless-ci-visibility.js +29 -2
- package/packages/dd-trace/src/exporters/common/request.js +1 -1
- package/packages/dd-trace/src/payload-tagging/config/aws.json +30 -0
- package/packages/dd-trace/src/payload-tagging/config/index.js +30 -0
- package/packages/dd-trace/src/payload-tagging/index.js +93 -0
- package/packages/dd-trace/src/payload-tagging/tagging.js +83 -0
- package/packages/dd-trace/src/plugin_manager.js +11 -10
- package/packages/dd-trace/src/plugins/ci_plugin.js +33 -8
- package/packages/dd-trace/src/plugins/util/env.js +5 -2
- package/packages/dd-trace/src/plugins/util/test.js +26 -2
- package/packages/dd-trace/src/profiling/config.js +5 -0
- package/packages/dd-trace/src/profiling/exporters/agent.js +1 -1
- package/packages/dd-trace/src/profiling/profilers/event_plugins/dns.js +13 -0
- package/packages/dd-trace/src/profiling/profilers/event_plugins/dns_lookup.js +16 -0
- package/packages/dd-trace/src/profiling/profilers/event_plugins/dns_lookupservice.js +16 -0
- package/packages/dd-trace/src/profiling/profilers/event_plugins/dns_resolve.js +24 -0
- package/packages/dd-trace/src/profiling/profilers/event_plugins/dns_reverse.js +16 -0
- package/packages/dd-trace/src/profiling/profilers/event_plugins/event.js +48 -0
- package/packages/dd-trace/src/profiling/profilers/event_plugins/net.js +24 -0
- package/packages/dd-trace/src/profiling/profilers/events.js +108 -32
- package/packages/dd-trace/src/profiling/profilers/shared.js +5 -0
- package/packages/dd-trace/src/profiling/profilers/wall.js +9 -3
- package/packages/dd-trace/src/profiling/ssi-heuristics.js +10 -2
- package/packages/dd-trace/src/proxy.js +10 -3
- package/packages/dd-trace/src/span_stats.js +4 -2
- package/packages/dd-trace/src/appsec/rasp.js +0 -176
|
@@ -53,14 +53,15 @@ function wrapQuery (query) {
|
|
|
53
53
|
}
|
|
54
54
|
|
|
55
55
|
return asyncResource.runInAsyncScope(() => {
|
|
56
|
+
const abortController = new AbortController()
|
|
57
|
+
|
|
56
58
|
startCh.publish({
|
|
57
59
|
params: this.connectionParameters,
|
|
58
60
|
query: pgQuery,
|
|
59
|
-
processId
|
|
61
|
+
processId,
|
|
62
|
+
abortController
|
|
60
63
|
})
|
|
61
64
|
|
|
62
|
-
arguments[0] = pgQuery
|
|
63
|
-
|
|
64
65
|
const finish = asyncResource.bind(function (error) {
|
|
65
66
|
if (error) {
|
|
66
67
|
errorCh.publish(error)
|
|
@@ -68,6 +69,43 @@ function wrapQuery (query) {
|
|
|
68
69
|
finishCh.publish()
|
|
69
70
|
})
|
|
70
71
|
|
|
72
|
+
if (abortController.signal.aborted) {
|
|
73
|
+
const error = abortController.signal.reason || new Error('Aborted')
|
|
74
|
+
|
|
75
|
+
// eslint-disable-next-line max-len
|
|
76
|
+
// Based on: https://github.com/brianc/node-postgres/blob/54eb0fa216aaccd727765641e7d1cf5da2bc483d/packages/pg/lib/client.js#L510
|
|
77
|
+
const reusingQuery = typeof pgQuery.submit === 'function'
|
|
78
|
+
const callback = arguments[arguments.length - 1]
|
|
79
|
+
|
|
80
|
+
finish(error)
|
|
81
|
+
|
|
82
|
+
if (reusingQuery) {
|
|
83
|
+
if (!pgQuery.callback && typeof callback === 'function') {
|
|
84
|
+
pgQuery.callback = callback
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (pgQuery.callback) {
|
|
88
|
+
pgQuery.callback(error)
|
|
89
|
+
} else {
|
|
90
|
+
process.nextTick(() => {
|
|
91
|
+
pgQuery.emit('error', error)
|
|
92
|
+
})
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return pgQuery
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (typeof callback === 'function') {
|
|
99
|
+
callback(error)
|
|
100
|
+
|
|
101
|
+
return
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return Promise.reject(error)
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
arguments[0] = pgQuery
|
|
108
|
+
|
|
71
109
|
const retval = query.apply(this, arguments)
|
|
72
110
|
const queryQueue = this.queryQueue || this._queryQueue
|
|
73
111
|
const activeQuery = this.activeQuery || this._activeQuery
|
|
@@ -112,8 +150,11 @@ function wrapPoolQuery (query) {
|
|
|
112
150
|
const pgQuery = arguments[0] !== null && typeof arguments[0] === 'object' ? arguments[0] : { text: arguments[0] }
|
|
113
151
|
|
|
114
152
|
return asyncResource.runInAsyncScope(() => {
|
|
153
|
+
const abortController = new AbortController()
|
|
154
|
+
|
|
115
155
|
startPoolQueryCh.publish({
|
|
116
|
-
query: pgQuery
|
|
156
|
+
query: pgQuery,
|
|
157
|
+
abortController
|
|
117
158
|
})
|
|
118
159
|
|
|
119
160
|
const finish = asyncResource.bind(function () {
|
|
@@ -121,6 +162,20 @@ function wrapPoolQuery (query) {
|
|
|
121
162
|
})
|
|
122
163
|
|
|
123
164
|
const cb = arguments[arguments.length - 1]
|
|
165
|
+
|
|
166
|
+
if (abortController.signal.aborted) {
|
|
167
|
+
const error = abortController.signal.reason || new Error('Aborted')
|
|
168
|
+
finish()
|
|
169
|
+
|
|
170
|
+
if (typeof cb === 'function') {
|
|
171
|
+
cb(error)
|
|
172
|
+
|
|
173
|
+
return
|
|
174
|
+
} else {
|
|
175
|
+
return Promise.reject(error)
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
124
179
|
if (typeof cb === 'function') {
|
|
125
180
|
arguments[arguments.length - 1] = shimmer.wrapFunction(cb, cb => function () {
|
|
126
181
|
finish()
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
const { addHook, channel, AsyncResource } = require('./helpers/instrument')
|
|
2
2
|
const shimmer = require('../../datadog-shimmer')
|
|
3
|
+
const log = require('../../dd-trace/src/log')
|
|
3
4
|
|
|
4
5
|
// test hooks
|
|
5
6
|
const testStartCh = channel('ci:vitest:test:start')
|
|
@@ -7,6 +8,7 @@ const testFinishTimeCh = channel('ci:vitest:test:finish-time')
|
|
|
7
8
|
const testPassCh = channel('ci:vitest:test:pass')
|
|
8
9
|
const testErrorCh = channel('ci:vitest:test:error')
|
|
9
10
|
const testSkipCh = channel('ci:vitest:test:skip')
|
|
11
|
+
const isNewTestCh = channel('ci:vitest:test:is-new')
|
|
10
12
|
|
|
11
13
|
// test suite hooks
|
|
12
14
|
const testSuiteStartCh = channel('ci:vitest:test-suite:start')
|
|
@@ -17,9 +19,13 @@ const testSuiteErrorCh = channel('ci:vitest:test-suite:error')
|
|
|
17
19
|
const testSessionStartCh = channel('ci:vitest:session:start')
|
|
18
20
|
const testSessionFinishCh = channel('ci:vitest:session:finish')
|
|
19
21
|
const libraryConfigurationCh = channel('ci:vitest:library-configuration')
|
|
22
|
+
const knownTestsCh = channel('ci:vitest:known-tests')
|
|
23
|
+
const isEarlyFlakeDetectionFaultyCh = channel('ci:vitest:is-early-flake-detection-faulty')
|
|
20
24
|
|
|
21
25
|
const taskToAsync = new WeakMap()
|
|
22
|
-
|
|
26
|
+
const taskToStatuses = new WeakMap()
|
|
27
|
+
const newTasks = new WeakSet()
|
|
28
|
+
const switchedStatuses = new WeakSet()
|
|
23
29
|
const sessionAsyncResource = new AsyncResource('bound-anonymous-fn')
|
|
24
30
|
|
|
25
31
|
function isReporterPackage (vitestPackage) {
|
|
@@ -108,20 +114,61 @@ function getSortWrapper (sort) {
|
|
|
108
114
|
// will not work. This will be a known limitation.
|
|
109
115
|
let isFlakyTestRetriesEnabled = false
|
|
110
116
|
let flakyTestRetriesCount = 0
|
|
117
|
+
let isEarlyFlakeDetectionEnabled = false
|
|
118
|
+
let earlyFlakeDetectionNumRetries = 0
|
|
119
|
+
let isEarlyFlakeDetectionFaulty = false
|
|
120
|
+
let knownTests = {}
|
|
111
121
|
|
|
112
122
|
try {
|
|
113
123
|
const { err, libraryConfig } = await getChannelPromise(libraryConfigurationCh)
|
|
114
124
|
if (!err) {
|
|
115
125
|
isFlakyTestRetriesEnabled = libraryConfig.isFlakyTestRetriesEnabled
|
|
116
126
|
flakyTestRetriesCount = libraryConfig.flakyTestRetriesCount
|
|
127
|
+
isEarlyFlakeDetectionEnabled = libraryConfig.isEarlyFlakeDetectionEnabled
|
|
128
|
+
earlyFlakeDetectionNumRetries = libraryConfig.earlyFlakeDetectionNumRetries
|
|
117
129
|
}
|
|
118
130
|
} catch (e) {
|
|
119
131
|
isFlakyTestRetriesEnabled = false
|
|
132
|
+
isEarlyFlakeDetectionEnabled = false
|
|
120
133
|
}
|
|
134
|
+
|
|
121
135
|
if (isFlakyTestRetriesEnabled && !this.ctx.config.retry && flakyTestRetriesCount > 0) {
|
|
122
136
|
this.ctx.config.retry = flakyTestRetriesCount
|
|
123
137
|
}
|
|
124
138
|
|
|
139
|
+
if (isEarlyFlakeDetectionEnabled) {
|
|
140
|
+
const knownTestsResponse = await getChannelPromise(knownTestsCh)
|
|
141
|
+
if (!knownTestsResponse.err) {
|
|
142
|
+
knownTests = knownTestsResponse.knownTests
|
|
143
|
+
const testFilepaths = await this.ctx.getTestFilepaths()
|
|
144
|
+
|
|
145
|
+
isEarlyFlakeDetectionFaultyCh.publish({
|
|
146
|
+
knownTests: knownTests.vitest || {},
|
|
147
|
+
testFilepaths,
|
|
148
|
+
onDone: (isFaulty) => {
|
|
149
|
+
isEarlyFlakeDetectionFaulty = isFaulty
|
|
150
|
+
}
|
|
151
|
+
})
|
|
152
|
+
if (isEarlyFlakeDetectionFaulty) {
|
|
153
|
+
isEarlyFlakeDetectionEnabled = false
|
|
154
|
+
log.warn('Early flake detection is disabled because the number of new tests is too high.')
|
|
155
|
+
} else {
|
|
156
|
+
// TODO: use this to pass session and module IDs to the worker, instead of polluting process.env
|
|
157
|
+
// Note: setting this.ctx.config.provide directly does not work because it's cached
|
|
158
|
+
try {
|
|
159
|
+
const workspaceProject = this.ctx.getCoreWorkspaceProject()
|
|
160
|
+
workspaceProject._provided._ddKnownTests = knownTests.vitest
|
|
161
|
+
workspaceProject._provided._ddIsEarlyFlakeDetectionEnabled = isEarlyFlakeDetectionEnabled
|
|
162
|
+
workspaceProject._provided._ddEarlyFlakeDetectionNumRetries = earlyFlakeDetectionNumRetries
|
|
163
|
+
} catch (e) {
|
|
164
|
+
log.warn('Could not send known tests to workers so Early Flake Detection will not work.')
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
} else {
|
|
168
|
+
isEarlyFlakeDetectionEnabled = false
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
125
172
|
let testCodeCoverageLinesTotal
|
|
126
173
|
|
|
127
174
|
if (this.ctx.coverageProvider?.generateCoverage) {
|
|
@@ -154,6 +201,8 @@ function getSortWrapper (sort) {
|
|
|
154
201
|
status: getSessionStatus(this.state),
|
|
155
202
|
testCodeCoverageLinesTotal,
|
|
156
203
|
error,
|
|
204
|
+
isEarlyFlakeDetectionEnabled,
|
|
205
|
+
isEarlyFlakeDetectionFaulty,
|
|
157
206
|
onFinish
|
|
158
207
|
})
|
|
159
208
|
})
|
|
@@ -188,12 +237,83 @@ addHook({
|
|
|
188
237
|
file: 'dist/runners.js'
|
|
189
238
|
}, (vitestPackage) => {
|
|
190
239
|
const { VitestTestRunner } = vitestPackage
|
|
240
|
+
|
|
241
|
+
// `onBeforeRunTask` is run before any repetition or attempt is run
|
|
242
|
+
shimmer.wrap(VitestTestRunner.prototype, 'onBeforeRunTask', onBeforeRunTask => async function (task) {
|
|
243
|
+
const testName = getTestName(task)
|
|
244
|
+
try {
|
|
245
|
+
const {
|
|
246
|
+
_ddKnownTests: knownTests,
|
|
247
|
+
_ddIsEarlyFlakeDetectionEnabled: isEarlyFlakeDetectionEnabled,
|
|
248
|
+
_ddEarlyFlakeDetectionNumRetries: numRepeats
|
|
249
|
+
} = globalThis.__vitest_worker__.providedContext
|
|
250
|
+
|
|
251
|
+
if (isEarlyFlakeDetectionEnabled) {
|
|
252
|
+
isNewTestCh.publish({
|
|
253
|
+
knownTests,
|
|
254
|
+
testSuiteAbsolutePath: task.file.filepath,
|
|
255
|
+
testName,
|
|
256
|
+
onDone: (isNew) => {
|
|
257
|
+
if (isNew) {
|
|
258
|
+
task.repeats = numRepeats
|
|
259
|
+
newTasks.add(task)
|
|
260
|
+
taskToStatuses.set(task, [])
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
})
|
|
264
|
+
}
|
|
265
|
+
} catch (e) {
|
|
266
|
+
log.error('Vitest workers could not parse known tests, so Early Flake Detection will not work.')
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
return onBeforeRunTask.apply(this, arguments)
|
|
270
|
+
})
|
|
271
|
+
|
|
272
|
+
// `onAfterRunTask` is run after all repetitions or attempts are run
|
|
273
|
+
shimmer.wrap(VitestTestRunner.prototype, 'onAfterRunTask', onAfterRunTask => async function (task) {
|
|
274
|
+
const {
|
|
275
|
+
_ddIsEarlyFlakeDetectionEnabled: isEarlyFlakeDetectionEnabled
|
|
276
|
+
} = globalThis.__vitest_worker__.providedContext
|
|
277
|
+
|
|
278
|
+
if (isEarlyFlakeDetectionEnabled && taskToStatuses.has(task)) {
|
|
279
|
+
const statuses = taskToStatuses.get(task)
|
|
280
|
+
// If the test has passed at least once, we consider it passed
|
|
281
|
+
if (statuses.includes('pass')) {
|
|
282
|
+
if (task.result.state === 'fail') {
|
|
283
|
+
switchedStatuses.add(task)
|
|
284
|
+
}
|
|
285
|
+
task.result.state = 'pass'
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
return onAfterRunTask.apply(this, arguments)
|
|
290
|
+
})
|
|
291
|
+
|
|
191
292
|
// test start (only tests that are not marked as skip or todo)
|
|
293
|
+
// `onBeforeTryTask` is run for every repetition and attempt of the test
|
|
192
294
|
shimmer.wrap(VitestTestRunner.prototype, 'onBeforeTryTask', onBeforeTryTask => async function (task, retryInfo) {
|
|
193
295
|
if (!testStartCh.hasSubscribers) {
|
|
194
296
|
return onBeforeTryTask.apply(this, arguments)
|
|
195
297
|
}
|
|
196
|
-
const
|
|
298
|
+
const testName = getTestName(task)
|
|
299
|
+
let isNew = false
|
|
300
|
+
let isEarlyFlakeDetectionEnabled = false
|
|
301
|
+
|
|
302
|
+
try {
|
|
303
|
+
const {
|
|
304
|
+
_ddIsEarlyFlakeDetectionEnabled
|
|
305
|
+
} = globalThis.__vitest_worker__.providedContext
|
|
306
|
+
|
|
307
|
+
isEarlyFlakeDetectionEnabled = _ddIsEarlyFlakeDetectionEnabled
|
|
308
|
+
|
|
309
|
+
if (isEarlyFlakeDetectionEnabled) {
|
|
310
|
+
isNew = newTasks.has(task)
|
|
311
|
+
}
|
|
312
|
+
} catch (e) {
|
|
313
|
+
log.error('Vitest workers could not parse known tests, so Early Flake Detection will not work.')
|
|
314
|
+
}
|
|
315
|
+
const { retry: numAttempt, repeats: numRepetition } = retryInfo
|
|
316
|
+
|
|
197
317
|
// We finish the previous test here because we know it has failed already
|
|
198
318
|
if (numAttempt > 0) {
|
|
199
319
|
const asyncResource = taskToAsync.get(task)
|
|
@@ -205,14 +325,58 @@ addHook({
|
|
|
205
325
|
}
|
|
206
326
|
}
|
|
207
327
|
|
|
328
|
+
const lastExecutionStatus = task.result.state
|
|
329
|
+
|
|
330
|
+
// These clauses handle task.repeats, whether EFD is enabled or not
|
|
331
|
+
// The only thing that EFD does is to forcefully pass the test if it has passed at least once
|
|
332
|
+
if (numRepetition > 0 && numRepetition < task.repeats) { // it may or may have not failed
|
|
333
|
+
// Here we finish the earlier iteration,
|
|
334
|
+
// as long as it's not the _last_ iteration (which will be finished normally)
|
|
335
|
+
|
|
336
|
+
// TODO: check test duration (not to repeat if it's too slow)
|
|
337
|
+
const asyncResource = taskToAsync.get(task)
|
|
338
|
+
if (asyncResource) {
|
|
339
|
+
if (lastExecutionStatus === 'fail') {
|
|
340
|
+
const testError = task.result?.errors?.[0]
|
|
341
|
+
asyncResource.runInAsyncScope(() => {
|
|
342
|
+
testErrorCh.publish({ error: testError })
|
|
343
|
+
})
|
|
344
|
+
} else {
|
|
345
|
+
asyncResource.runInAsyncScope(() => {
|
|
346
|
+
testPassCh.publish({ task })
|
|
347
|
+
})
|
|
348
|
+
}
|
|
349
|
+
if (isEarlyFlakeDetectionEnabled) {
|
|
350
|
+
const statuses = taskToStatuses.get(task)
|
|
351
|
+
statuses.push(lastExecutionStatus)
|
|
352
|
+
// If we don't "reset" the result.state to "pass", once a repetition fails,
|
|
353
|
+
// vitest will always consider the test as failed, so we can't read the actual status
|
|
354
|
+
task.result.state = 'pass'
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
} else if (numRepetition === task.repeats) {
|
|
358
|
+
const asyncResource = taskToAsync.get(task)
|
|
359
|
+
if (lastExecutionStatus === 'fail') {
|
|
360
|
+
const testError = task.result?.errors?.[0]
|
|
361
|
+
asyncResource.runInAsyncScope(() => {
|
|
362
|
+
testErrorCh.publish({ error: testError })
|
|
363
|
+
})
|
|
364
|
+
} else {
|
|
365
|
+
asyncResource.runInAsyncScope(() => {
|
|
366
|
+
testPassCh.publish({ task })
|
|
367
|
+
})
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
|
|
208
371
|
const asyncResource = new AsyncResource('bound-anonymous-fn')
|
|
209
372
|
taskToAsync.set(task, asyncResource)
|
|
210
373
|
|
|
211
374
|
asyncResource.runInAsyncScope(() => {
|
|
212
375
|
testStartCh.publish({
|
|
213
|
-
testName
|
|
376
|
+
testName,
|
|
214
377
|
testSuiteAbsolutePath: task.file.filepath,
|
|
215
|
-
isRetry: numAttempt > 0
|
|
378
|
+
isRetry: numAttempt > 0 || numRepetition > 0,
|
|
379
|
+
isNew
|
|
216
380
|
})
|
|
217
381
|
})
|
|
218
382
|
return onBeforeTryTask.apply(this, arguments)
|
|
@@ -230,7 +394,7 @@ addHook({
|
|
|
230
394
|
const asyncResource = taskToAsync.get(task)
|
|
231
395
|
|
|
232
396
|
if (asyncResource) {
|
|
233
|
-
// We don't finish here because the test might fail in a later hook
|
|
397
|
+
// We don't finish here because the test might fail in a later hook (afterEach)
|
|
234
398
|
asyncResource.runInAsyncScope(() => {
|
|
235
399
|
testFinishTimeCh.publish({ status, task })
|
|
236
400
|
})
|
|
@@ -270,7 +434,16 @@ addHook({
|
|
|
270
434
|
|
|
271
435
|
addHook({
|
|
272
436
|
name: 'vitest',
|
|
273
|
-
versions: ['>=2.0
|
|
437
|
+
versions: ['>=2.1.0'],
|
|
438
|
+
filePattern: 'dist/chunks/RandomSequencer.*'
|
|
439
|
+
}, (randomSequencerPackage) => {
|
|
440
|
+
shimmer.wrap(randomSequencerPackage.B.prototype, 'sort', getSortWrapper)
|
|
441
|
+
return randomSequencerPackage
|
|
442
|
+
})
|
|
443
|
+
|
|
444
|
+
addHook({
|
|
445
|
+
name: 'vitest',
|
|
446
|
+
versions: ['>=2.0.5 <2.1.0'],
|
|
274
447
|
filePattern: 'dist/chunks/index.*'
|
|
275
448
|
}, (vitestPackage) => {
|
|
276
449
|
if (isReporterPackageNewest(vitestPackage)) {
|
|
@@ -323,19 +496,21 @@ addHook({
|
|
|
323
496
|
testTasks.forEach(task => {
|
|
324
497
|
const testAsyncResource = taskToAsync.get(task)
|
|
325
498
|
const { result } = task
|
|
499
|
+
// We have to trick vitest into thinking that the test has passed
|
|
500
|
+
// but we want to report it as failed if it did fail
|
|
501
|
+
const isSwitchedStatus = switchedStatuses.has(task)
|
|
326
502
|
|
|
327
503
|
if (result) {
|
|
328
504
|
const { state, duration, errors } = result
|
|
329
505
|
if (state === 'skip') { // programmatic skip
|
|
330
506
|
testSkipCh.publish({ testName: getTestName(task), testSuiteAbsolutePath: task.file.filepath })
|
|
331
|
-
} else if (state === 'pass') {
|
|
507
|
+
} else if (state === 'pass' && !isSwitchedStatus) {
|
|
332
508
|
if (testAsyncResource) {
|
|
333
509
|
testAsyncResource.runInAsyncScope(() => {
|
|
334
510
|
testPassCh.publish({ task })
|
|
335
511
|
})
|
|
336
512
|
}
|
|
337
|
-
} else if (state === 'fail') {
|
|
338
|
-
// If it's failing, we have no accurate finish time, so we have to use `duration`
|
|
513
|
+
} else if (state === 'fail' || isSwitchedStatus) {
|
|
339
514
|
let testError
|
|
340
515
|
|
|
341
516
|
if (errors?.length) {
|
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
const { TEXT_MAP } = require('../../../ext/formats')
|
|
4
4
|
const ConsumerPlugin = require('../../dd-trace/src/plugins/consumer')
|
|
5
5
|
const { getAmqpMessageSize } = require('../../dd-trace/src/datastreams/processor')
|
|
6
|
-
const { DsmPathwayCodec } = require('../../dd-trace/src/datastreams/pathway')
|
|
7
6
|
const { getResourceName } = require('./util')
|
|
8
7
|
|
|
9
8
|
class AmqplibConsumerPlugin extends ConsumerPlugin {
|
|
@@ -30,8 +29,7 @@ class AmqplibConsumerPlugin extends ConsumerPlugin {
|
|
|
30
29
|
})
|
|
31
30
|
|
|
32
31
|
if (
|
|
33
|
-
this.config.dsmEnabled && message?.properties?.headers
|
|
34
|
-
DsmPathwayCodec.contextExists(message.properties.headers)
|
|
32
|
+
this.config.dsmEnabled && message?.properties?.headers
|
|
35
33
|
) {
|
|
36
34
|
const payloadSize = getAmqpMessageSize({ headers: message.properties.headers, content: message.content })
|
|
37
35
|
const queue = fields.queue ? fields.queue : fields.routingKey
|
|
@@ -5,9 +5,11 @@ const ClientPlugin = require('../../dd-trace/src/plugins/client')
|
|
|
5
5
|
const { storage } = require('../../datadog-core')
|
|
6
6
|
const { isTrue } = require('../../dd-trace/src/util')
|
|
7
7
|
const coalesce = require('koalas')
|
|
8
|
+
const { tagsFromRequest, tagsFromResponse } = require('../../dd-trace/src/payload-tagging')
|
|
8
9
|
|
|
9
10
|
class BaseAwsSdkPlugin extends ClientPlugin {
|
|
10
11
|
static get id () { return 'aws' }
|
|
12
|
+
static get isPayloadReporter () { return false }
|
|
11
13
|
|
|
12
14
|
get serviceIdentifier () {
|
|
13
15
|
const id = this.constructor.id.toLowerCase()
|
|
@@ -20,6 +22,14 @@ class BaseAwsSdkPlugin extends ClientPlugin {
|
|
|
20
22
|
return id
|
|
21
23
|
}
|
|
22
24
|
|
|
25
|
+
get cloudTaggingConfig () {
|
|
26
|
+
return this._tracerConfig.cloudPayloadTagging
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
get payloadTaggingRules () {
|
|
30
|
+
return this.cloudTaggingConfig.rules.aws?.[this.constructor.id]
|
|
31
|
+
}
|
|
32
|
+
|
|
23
33
|
constructor (...args) {
|
|
24
34
|
super(...args)
|
|
25
35
|
|
|
@@ -51,6 +61,12 @@ class BaseAwsSdkPlugin extends ClientPlugin {
|
|
|
51
61
|
|
|
52
62
|
this.requestInject(span, request)
|
|
53
63
|
|
|
64
|
+
if (this.constructor.isPayloadReporter && this.cloudTaggingConfig.requestsEnabled) {
|
|
65
|
+
const maxDepth = this.cloudTaggingConfig.maxDepth
|
|
66
|
+
const requestTags = tagsFromRequest(this.payloadTaggingRules, request.params, { maxDepth })
|
|
67
|
+
span.addTags(requestTags)
|
|
68
|
+
}
|
|
69
|
+
|
|
54
70
|
const store = storage.getStore()
|
|
55
71
|
|
|
56
72
|
this.enter(span, store)
|
|
@@ -116,6 +132,7 @@ class BaseAwsSdkPlugin extends ClientPlugin {
|
|
|
116
132
|
const params = response.request.params
|
|
117
133
|
const operation = response.request.operation
|
|
118
134
|
const extraTags = this.generateTags(params, operation, response) || {}
|
|
135
|
+
|
|
119
136
|
const tags = Object.assign({
|
|
120
137
|
'aws.response.request_id': response.requestId,
|
|
121
138
|
'resource.name': operation,
|
|
@@ -123,6 +140,22 @@ class BaseAwsSdkPlugin extends ClientPlugin {
|
|
|
123
140
|
}, extraTags)
|
|
124
141
|
|
|
125
142
|
span.addTags(tags)
|
|
143
|
+
|
|
144
|
+
if (this.constructor.isPayloadReporter && this.cloudTaggingConfig.responsesEnabled) {
|
|
145
|
+
const maxDepth = this.cloudTaggingConfig.maxDepth
|
|
146
|
+
const responseBody = this.extractResponseBody(response)
|
|
147
|
+
const responseTags = tagsFromResponse(this.payloadTaggingRules, responseBody, { maxDepth })
|
|
148
|
+
span.addTags(responseTags)
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
extractResponseBody (response) {
|
|
153
|
+
if (response.hasOwnProperty('data')) {
|
|
154
|
+
return response.data
|
|
155
|
+
}
|
|
156
|
+
return Object.fromEntries(
|
|
157
|
+
Object.entries(response).filter(([key]) => !['request', 'requestId', 'error', '$metadata'].includes(key))
|
|
158
|
+
)
|
|
126
159
|
}
|
|
127
160
|
|
|
128
161
|
generateTags () {
|
|
@@ -113,7 +113,7 @@ class Kinesis extends BaseAwsSdkPlugin {
|
|
|
113
113
|
const parsedAttributes = JSON.parse(Buffer.from(record.Data).toString())
|
|
114
114
|
|
|
115
115
|
if (
|
|
116
|
-
parsedAttributes?._datadog && streamName
|
|
116
|
+
parsedAttributes?._datadog && streamName
|
|
117
117
|
) {
|
|
118
118
|
const payloadSize = getSizeOrZero(record.Data)
|
|
119
119
|
this.tracer.decodeDataStreamsContext(parsedAttributes._datadog)
|
|
@@ -7,6 +7,7 @@ const BaseAwsSdkPlugin = require('../base')
|
|
|
7
7
|
class Sns extends BaseAwsSdkPlugin {
|
|
8
8
|
static get id () { return 'sns' }
|
|
9
9
|
static get peerServicePrecursors () { return ['topicname'] }
|
|
10
|
+
static get isPayloadReporter () { return true }
|
|
10
11
|
|
|
11
12
|
generateTags (params, operation, response) {
|
|
12
13
|
if (!params) return {}
|
|
@@ -20,6 +21,7 @@ class Sns extends BaseAwsSdkPlugin {
|
|
|
20
21
|
|
|
21
22
|
// Get the topic name from the last part of the ARN
|
|
22
23
|
const topicName = arnParts[arnParts.length - 1]
|
|
24
|
+
|
|
23
25
|
return {
|
|
24
26
|
'resource.name': `${operation} ${params.TopicArn || response.data.TopicArn}`,
|
|
25
27
|
'aws.sns.topic_arn': TopicArn,
|
|
@@ -194,7 +194,7 @@ class Sqs extends BaseAwsSdkPlugin {
|
|
|
194
194
|
parsedAttributes = this.parseDatadogAttributes(message.MessageAttributes._datadog)
|
|
195
195
|
}
|
|
196
196
|
}
|
|
197
|
-
if (parsedAttributes
|
|
197
|
+
if (parsedAttributes) {
|
|
198
198
|
const payloadSize = getHeadersSize({
|
|
199
199
|
Body: message.Body,
|
|
200
200
|
MessageAttributes: message.MessageAttributes
|
|
@@ -17,6 +17,7 @@ const {
|
|
|
17
17
|
ITR_CORRELATION_ID,
|
|
18
18
|
TEST_SOURCE_FILE,
|
|
19
19
|
TEST_EARLY_FLAKE_ENABLED,
|
|
20
|
+
TEST_EARLY_FLAKE_ABORT_REASON,
|
|
20
21
|
TEST_IS_NEW,
|
|
21
22
|
TEST_IS_RETRY,
|
|
22
23
|
TEST_SUITE_ID,
|
|
@@ -79,6 +80,7 @@ class CucumberPlugin extends CiPlugin {
|
|
|
79
80
|
hasUnskippableSuites,
|
|
80
81
|
hasForcedToRunSuites,
|
|
81
82
|
isEarlyFlakeDetectionEnabled,
|
|
83
|
+
isEarlyFlakeDetectionFaulty,
|
|
82
84
|
isParallel
|
|
83
85
|
}) => {
|
|
84
86
|
const { isSuitesSkippingEnabled, isCodeCoverageEnabled } = this.libraryConfig || {}
|
|
@@ -99,6 +101,9 @@ class CucumberPlugin extends CiPlugin {
|
|
|
99
101
|
if (isEarlyFlakeDetectionEnabled) {
|
|
100
102
|
this.testSessionSpan.setTag(TEST_EARLY_FLAKE_ENABLED, 'true')
|
|
101
103
|
}
|
|
104
|
+
if (isEarlyFlakeDetectionFaulty) {
|
|
105
|
+
this.testSessionSpan.setTag(TEST_EARLY_FLAKE_ABORT_REASON, 'faulty')
|
|
106
|
+
}
|
|
102
107
|
if (isParallel) {
|
|
103
108
|
this.testSessionSpan.setTag(CUCUMBER_IS_PARALLEL, 'true')
|
|
104
109
|
}
|
|
@@ -116,7 +121,15 @@ class CucumberPlugin extends CiPlugin {
|
|
|
116
121
|
this.tracer._exporter.flush()
|
|
117
122
|
})
|
|
118
123
|
|
|
119
|
-
this.addSub('ci:cucumber:test-suite:start', ({
|
|
124
|
+
this.addSub('ci:cucumber:test-suite:start', ({
|
|
125
|
+
testFileAbsolutePath,
|
|
126
|
+
isUnskippable,
|
|
127
|
+
isForcedToRun,
|
|
128
|
+
itrCorrelationId
|
|
129
|
+
}) => {
|
|
130
|
+
const testSuitePath = getTestSuitePath(testFileAbsolutePath, process.cwd())
|
|
131
|
+
const testSourceFile = getTestSuitePath(testFileAbsolutePath, this.repositoryRoot)
|
|
132
|
+
|
|
120
133
|
const testSuiteMetadata = getTestSuiteCommonTags(
|
|
121
134
|
this.command,
|
|
122
135
|
this.frameworkVersion,
|
|
@@ -134,6 +147,16 @@ class CucumberPlugin extends CiPlugin {
|
|
|
134
147
|
if (itrCorrelationId) {
|
|
135
148
|
testSuiteMetadata[ITR_CORRELATION_ID] = itrCorrelationId
|
|
136
149
|
}
|
|
150
|
+
if (testSourceFile) {
|
|
151
|
+
testSuiteMetadata[TEST_SOURCE_FILE] = testSourceFile
|
|
152
|
+
testSuiteMetadata[TEST_SOURCE_START] = 1
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const codeOwners = this.getCodeOwners(testSuiteMetadata)
|
|
156
|
+
if (codeOwners) {
|
|
157
|
+
testSuiteMetadata[TEST_CODE_OWNERS] = codeOwners
|
|
158
|
+
}
|
|
159
|
+
|
|
137
160
|
const testSuiteSpan = this.tracer.startSpan('cucumber.test_suite', {
|
|
138
161
|
childOf: this.testModuleSpan,
|
|
139
162
|
tags: {
|
|
@@ -28,7 +28,10 @@ const {
|
|
|
28
28
|
TEST_SOURCE_FILE,
|
|
29
29
|
TEST_IS_NEW,
|
|
30
30
|
TEST_IS_RETRY,
|
|
31
|
-
TEST_EARLY_FLAKE_ENABLED
|
|
31
|
+
TEST_EARLY_FLAKE_ENABLED,
|
|
32
|
+
getTestSessionName,
|
|
33
|
+
TEST_SESSION_NAME,
|
|
34
|
+
TEST_LEVEL_EVENT_TYPES
|
|
32
35
|
} = require('../../dd-trace/src/plugins/util/test')
|
|
33
36
|
const { isMarkedAsUnskippable } = require('../../datadog-plugin-jest/src/util')
|
|
34
37
|
const { ORIGIN_KEY, COMPONENT } = require('../../dd-trace/src/constants')
|
|
@@ -245,10 +248,22 @@ class CypressPlugin {
|
|
|
245
248
|
return this.libraryConfigurationPromise
|
|
246
249
|
}
|
|
247
250
|
|
|
248
|
-
getTestSuiteSpan (
|
|
251
|
+
getTestSuiteSpan ({ testSuite, testSuiteAbsolutePath }) {
|
|
249
252
|
const testSuiteSpanMetadata =
|
|
250
|
-
getTestSuiteCommonTags(this.command, this.frameworkVersion,
|
|
253
|
+
getTestSuiteCommonTags(this.command, this.frameworkVersion, testSuite, TEST_FRAMEWORK_NAME)
|
|
254
|
+
|
|
251
255
|
this.ciVisEvent(TELEMETRY_EVENT_CREATED, 'suite')
|
|
256
|
+
|
|
257
|
+
if (testSuiteAbsolutePath) {
|
|
258
|
+
const testSourceFile = getTestSuitePath(testSuiteAbsolutePath, this.repositoryRoot)
|
|
259
|
+
testSuiteSpanMetadata[TEST_SOURCE_FILE] = testSourceFile
|
|
260
|
+
testSuiteSpanMetadata[TEST_SOURCE_START] = 1
|
|
261
|
+
const codeOwners = this.getTestCodeOwners({ testSuite, testSourceFile })
|
|
262
|
+
if (codeOwners) {
|
|
263
|
+
testSuiteSpanMetadata[TEST_CODE_OWNERS] = codeOwners
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
252
267
|
return this.tracer.startSpan(`${TEST_FRAMEWORK_NAME}.test_suite`, {
|
|
253
268
|
childOf: this.testModuleSpan,
|
|
254
269
|
tags: {
|
|
@@ -387,6 +402,18 @@ class CypressPlugin {
|
|
|
387
402
|
testSessionSpanMetadata[TEST_EARLY_FLAKE_ENABLED] = 'true'
|
|
388
403
|
}
|
|
389
404
|
|
|
405
|
+
const testSessionName = getTestSessionName(this.tracer._tracer._config, this.command, this.testEnvironmentMetadata)
|
|
406
|
+
|
|
407
|
+
if (this.tracer._tracer._exporter?.setMetadataTags) {
|
|
408
|
+
const metadataTags = {}
|
|
409
|
+
for (const testLevel of TEST_LEVEL_EVENT_TYPES) {
|
|
410
|
+
metadataTags[testLevel] = {
|
|
411
|
+
[TEST_SESSION_NAME]: testSessionName
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
this.tracer._tracer._exporter.setMetadataTags(metadataTags)
|
|
415
|
+
}
|
|
416
|
+
|
|
390
417
|
this.testSessionSpan = this.tracer.startSpan(`${TEST_FRAMEWORK_NAME}.test_session`, {
|
|
391
418
|
childOf,
|
|
392
419
|
tags: {
|
|
@@ -473,7 +500,10 @@ class CypressPlugin {
|
|
|
473
500
|
// dd:testSuiteStart hasn't been triggered for whatever reason
|
|
474
501
|
// We will create the test suite span on the spot if that's the case
|
|
475
502
|
log.warn('There was an error creating the test suite event.')
|
|
476
|
-
this.testSuiteSpan = this.getTestSuiteSpan(
|
|
503
|
+
this.testSuiteSpan = this.getTestSuiteSpan({
|
|
504
|
+
testSuite: spec.relative,
|
|
505
|
+
testSuiteAbsolutePath: spec.absolute
|
|
506
|
+
})
|
|
477
507
|
}
|
|
478
508
|
|
|
479
509
|
// Get tests that didn't go through `dd:afterEach`
|
|
@@ -584,7 +614,7 @@ class CypressPlugin {
|
|
|
584
614
|
|
|
585
615
|
getTasks () {
|
|
586
616
|
return {
|
|
587
|
-
'dd:testSuiteStart': (testSuite) => {
|
|
617
|
+
'dd:testSuiteStart': ({ testSuite, testSuiteAbsolutePath }) => {
|
|
588
618
|
const suitePayload = {
|
|
589
619
|
isEarlyFlakeDetectionEnabled: this.isEarlyFlakeDetectionEnabled,
|
|
590
620
|
knownTestsForSuite: this.knownTestsByTestSuite?.[testSuite] || [],
|
|
@@ -594,7 +624,7 @@ class CypressPlugin {
|
|
|
594
624
|
if (this.testSuiteSpan) {
|
|
595
625
|
return suitePayload
|
|
596
626
|
}
|
|
597
|
-
this.testSuiteSpan = this.getTestSuiteSpan(testSuite)
|
|
627
|
+
this.testSuiteSpan = this.getTestSuiteSpan({ testSuite, testSuiteAbsolutePath })
|
|
598
628
|
return suitePayload
|
|
599
629
|
},
|
|
600
630
|
'dd:beforeEach': (test) => {
|
|
@@ -61,7 +61,10 @@ beforeEach(function () {
|
|
|
61
61
|
})
|
|
62
62
|
|
|
63
63
|
before(function () {
|
|
64
|
-
cy.task('dd:testSuiteStart',
|
|
64
|
+
cy.task('dd:testSuiteStart', {
|
|
65
|
+
testSuite: Cypress.mocha.getRootSuite().file,
|
|
66
|
+
testSuiteAbsolutePath: Cypress.spec && Cypress.spec.absolute
|
|
67
|
+
}).then((suiteConfig) => {
|
|
65
68
|
if (suiteConfig) {
|
|
66
69
|
isEarlyFlakeDetectionEnabled = suiteConfig.isEarlyFlakeDetectionEnabled
|
|
67
70
|
knownTestsForSuite = suiteConfig.knownTestsForSuite
|