dd-trace 5.92.0 → 5.94.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/package.json +15 -11
- package/packages/datadog-instrumentations/src/helpers/bundler-register.js +23 -0
- package/packages/datadog-instrumentations/src/jest.js +118 -32
- package/packages/datadog-instrumentations/src/mocha/main.js +6 -0
- package/packages/datadog-instrumentations/src/mocha/utils.js +89 -5
- package/packages/datadog-instrumentations/src/playwright.js +10 -0
- package/packages/datadog-instrumentations/src/vitest.js +119 -0
- package/packages/datadog-plugin-aws-sdk/src/base.js +5 -0
- package/packages/datadog-plugin-cypress/src/cypress-plugin.js +12 -0
- package/packages/datadog-plugin-jest/src/index.js +6 -0
- package/packages/datadog-plugin-mocha/src/index.js +11 -0
- package/packages/datadog-plugin-playwright/src/index.js +9 -0
- package/packages/datadog-plugin-vitest/src/index.js +9 -0
- package/packages/datadog-webpack/index.js +187 -0
- package/packages/datadog-webpack/src/loader.js +27 -0
- package/packages/datadog-webpack/src/log.js +32 -0
- package/packages/dd-trace/src/ci-visibility/early-flake-detection/get-known-tests.js +103 -32
- package/packages/dd-trace/src/ci-visibility/exporters/test-worker/writer.js +10 -21
- package/packages/dd-trace/src/config/supported-configurations.json +2 -2
- package/packages/dd-trace/src/crashtracking/index.js +7 -1
- package/packages/dd-trace/src/exporters/common/docker.js +1 -0
- package/packages/dd-trace/src/exporters/common/request.js +26 -17
- package/packages/dd-trace/src/opentracing/span.js +5 -0
- package/packages/dd-trace/src/plugin_manager.js +10 -7
- package/packages/dd-trace/src/plugins/util/test.js +76 -0
- package/packages/dd-trace/src/priority_sampler.js +6 -3
- package/packages/dd-trace/src/profiling/profiler.js +78 -47
- package/packages/dd-trace/src/profiling/profilers/wall.js +35 -28
- package/packages/dd-trace/src/proxy.js +4 -3
- package/packages/dd-trace/src/tracer_metadata.js +10 -1
- package/webpack.js +3 -0
|
@@ -6,6 +6,9 @@ const log = require('../../dd-trace/src/log')
|
|
|
6
6
|
const {
|
|
7
7
|
VITEST_WORKER_TRACE_PAYLOAD_CODE,
|
|
8
8
|
VITEST_WORKER_LOGS_PAYLOAD_CODE,
|
|
9
|
+
DYNAMIC_NAME_RE,
|
|
10
|
+
collectDynamicNamesFromTraces,
|
|
11
|
+
logDynamicNamesWarning,
|
|
9
12
|
} = require('../../dd-trace/src/plugins/util/test')
|
|
10
13
|
const { addHook, channel } = require('./helpers/instrument')
|
|
11
14
|
|
|
@@ -20,6 +23,7 @@ const isAttemptToFixCh = channel('ci:vitest:test:is-attempt-to-fix')
|
|
|
20
23
|
const isDisabledCh = channel('ci:vitest:test:is-disabled')
|
|
21
24
|
const isQuarantinedCh = channel('ci:vitest:test:is-quarantined')
|
|
22
25
|
const isModifiedCh = channel('ci:vitest:test:is-modified')
|
|
26
|
+
const testFnCh = channel('ci:vitest:test:fn')
|
|
23
27
|
|
|
24
28
|
// test suite hooks
|
|
25
29
|
const testSuiteStartCh = channel('ci:vitest:test-suite:start')
|
|
@@ -41,7 +45,10 @@ const codeCoverageReportCh = channel('ci:vitest:coverage-report')
|
|
|
41
45
|
|
|
42
46
|
const taskToCtx = new WeakMap()
|
|
43
47
|
const taskToStatuses = new WeakMap()
|
|
48
|
+
const originalHookFns = new WeakMap()
|
|
44
49
|
const newTasks = new WeakSet()
|
|
50
|
+
const dynamicNameTasks = new WeakSet()
|
|
51
|
+
const newTestsWithDynamicNames = new Set()
|
|
45
52
|
const disabledTasks = new WeakSet()
|
|
46
53
|
const quarantinedTasks = new WeakSet()
|
|
47
54
|
const attemptToFixTasks = new WeakSet()
|
|
@@ -58,6 +65,9 @@ let isEarlyFlakeDetectionFaulty = false
|
|
|
58
65
|
let isKnownTestsEnabled = false
|
|
59
66
|
let isTestManagementTestsEnabled = false
|
|
60
67
|
let isImpactedTestsEnabled = false
|
|
68
|
+
let vitestGetFn = null
|
|
69
|
+
let vitestSetFn = null
|
|
70
|
+
let vitestGetHooks = null
|
|
61
71
|
let testManagementAttemptToFixRetries = 0
|
|
62
72
|
let isDiEnabled = false
|
|
63
73
|
let testCodeCoverageLinesTotal
|
|
@@ -241,6 +251,37 @@ function getTestName (task) {
|
|
|
241
251
|
return testName
|
|
242
252
|
}
|
|
243
253
|
|
|
254
|
+
/**
|
|
255
|
+
* Wraps a function so it runs inside the current test span context.
|
|
256
|
+
* @param {object} task
|
|
257
|
+
* @param {Function} fn
|
|
258
|
+
* @returns {Function}
|
|
259
|
+
*/
|
|
260
|
+
function wrapTestScopedFn (task, fn) {
|
|
261
|
+
return shimmer.wrapFunction(fn, fn => function () {
|
|
262
|
+
return testFnCh.runStores(taskToCtx.get(task), () => fn.apply(this, arguments))
|
|
263
|
+
})
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Wraps a `beforeEach` cleanup callback so it inherits the test span context.
|
|
268
|
+
* Vitest allows `beforeEach` to return a cleanup function, including via a promise.
|
|
269
|
+
* @param {object} task
|
|
270
|
+
* @param {unknown} result
|
|
271
|
+
* @returns {unknown}
|
|
272
|
+
*/
|
|
273
|
+
function wrapBeforeEachCleanupResult (task, result) {
|
|
274
|
+
if (typeof result === 'function') {
|
|
275
|
+
return wrapTestScopedFn(task, result)
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
if (result && typeof result.then === 'function') {
|
|
279
|
+
return result.then(cleanupFn => wrapBeforeEachCleanupResult(task, cleanupFn))
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
return result
|
|
283
|
+
}
|
|
284
|
+
|
|
244
285
|
function getSortWrapper (sort, frameworkVersion) {
|
|
245
286
|
return async function () {
|
|
246
287
|
if (!testSessionFinishCh.hasSubscribers) {
|
|
@@ -435,6 +476,8 @@ function getFinishWrapper (exitOrClose) {
|
|
|
435
476
|
onFinish,
|
|
436
477
|
})
|
|
437
478
|
|
|
479
|
+
logDynamicNamesWarning(newTestsWithDynamicNames)
|
|
480
|
+
|
|
438
481
|
await flushPromise
|
|
439
482
|
|
|
440
483
|
// If coverage was generated, publish coverage report channel for upload
|
|
@@ -495,6 +538,7 @@ function threadHandler (thread) {
|
|
|
495
538
|
workerProcess.on('message', (message) => {
|
|
496
539
|
if (message.__tinypool_worker_message__ && message.data) {
|
|
497
540
|
if (message.interprocessCode === VITEST_WORKER_TRACE_PAYLOAD_CODE) {
|
|
541
|
+
collectDynamicNamesFromTraces(message.data, newTestsWithDynamicNames)
|
|
498
542
|
workerReportTraceCh.publish(message.data)
|
|
499
543
|
} else if (message.interprocessCode === VITEST_WORKER_LOGS_PAYLOAD_CODE) {
|
|
500
544
|
workerReportLogsCh.publish(message.data)
|
|
@@ -536,6 +580,7 @@ function getWrappedOn (on) {
|
|
|
536
580
|
if (message.type !== 'Buffer' && Array.isArray(message)) {
|
|
537
581
|
const [interprocessCode, data] = message
|
|
538
582
|
if (interprocessCode === VITEST_WORKER_TRACE_PAYLOAD_CODE) {
|
|
583
|
+
collectDynamicNamesFromTraces(data, newTestsWithDynamicNames)
|
|
539
584
|
workerReportTraceCh.publish(data)
|
|
540
585
|
} else if (interprocessCode === VITEST_WORKER_LOGS_PAYLOAD_CODE) {
|
|
541
586
|
workerReportLogsCh.publish(data)
|
|
@@ -659,6 +704,9 @@ function wrapVitestTestRunner (VitestTestRunner) {
|
|
|
659
704
|
}
|
|
660
705
|
newTasks.add(task)
|
|
661
706
|
taskToStatuses.set(task, [])
|
|
707
|
+
if (DYNAMIC_NAME_RE.test(testName)) {
|
|
708
|
+
dynamicNameTasks.add(task)
|
|
709
|
+
}
|
|
662
710
|
}
|
|
663
711
|
},
|
|
664
712
|
})
|
|
@@ -828,6 +876,7 @@ function wrapVitestTestRunner (VitestTestRunner) {
|
|
|
828
876
|
isRetryReasonEfd,
|
|
829
877
|
isRetryReasonAttemptToFix: isRetryReasonAttemptToFix && numRepetition > 0,
|
|
830
878
|
isNew,
|
|
879
|
+
hasDynamicName: dynamicNameTasks.has(task),
|
|
831
880
|
mightHitProbe: isDiEnabled && numAttempt > 0,
|
|
832
881
|
isAttemptToFix: attemptToFixTasks.has(task),
|
|
833
882
|
isDisabled: disabledTasks.has(task),
|
|
@@ -838,6 +887,47 @@ function wrapVitestTestRunner (VitestTestRunner) {
|
|
|
838
887
|
taskToCtx.set(task, ctx)
|
|
839
888
|
|
|
840
889
|
testStartCh.runStores(ctx, () => {})
|
|
890
|
+
|
|
891
|
+
// Wrap the test function so it runs inside the test span context.
|
|
892
|
+
// Without this, HTTP requests during test execution become orphaned root spans.
|
|
893
|
+
if (vitestGetFn && vitestSetFn) {
|
|
894
|
+
const originalFn = vitestGetFn(task)
|
|
895
|
+
if (originalFn && !originalFn.__ddTraceWrapped) {
|
|
896
|
+
const wrappedFn = wrapTestScopedFn(task, originalFn)
|
|
897
|
+
wrappedFn.__ddTraceWrapped = true
|
|
898
|
+
vitestSetFn(task, wrappedFn)
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
// Wrap beforeEach/afterEach hooks so they also run inside the test span context.
|
|
903
|
+
// In vitest 4+, hooks are in a WeakMap accessed via getHooks(). In older versions, they're on suite.hooks.
|
|
904
|
+
let currentSuite = task.suite
|
|
905
|
+
while (currentSuite) {
|
|
906
|
+
const hooks = vitestGetHooks ? vitestGetHooks(currentSuite) : currentSuite.hooks
|
|
907
|
+
if (hooks) {
|
|
908
|
+
for (const hookType of ['beforeEach', 'afterEach']) {
|
|
909
|
+
const hookArray = hooks[hookType]
|
|
910
|
+
if (!hookArray) continue
|
|
911
|
+
for (let i = 0; i < hookArray.length; i++) {
|
|
912
|
+
const currentFn = hookArray[i]
|
|
913
|
+
const originalFn = originalHookFns.get(currentFn) || currentFn
|
|
914
|
+
const wrappedFn = shimmer.wrapFunction(originalFn, fn => function () {
|
|
915
|
+
const result = testFnCh.runStores(taskToCtx.get(task), () => fn.apply(this, arguments))
|
|
916
|
+
|
|
917
|
+
if (hookType === 'beforeEach') {
|
|
918
|
+
return wrapBeforeEachCleanupResult(task, result)
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
return result
|
|
922
|
+
})
|
|
923
|
+
originalHookFns.set(wrappedFn, originalFn)
|
|
924
|
+
hookArray[i] = wrappedFn
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
currentSuite = currentSuite.suite
|
|
929
|
+
}
|
|
930
|
+
|
|
841
931
|
return onBeforeTryTask.apply(this, arguments)
|
|
842
932
|
})
|
|
843
933
|
|
|
@@ -886,6 +976,20 @@ function wrapVitestTestRunner (VitestTestRunner) {
|
|
|
886
976
|
})
|
|
887
977
|
}
|
|
888
978
|
|
|
979
|
+
function captureRunnerFunctions (pkg) {
|
|
980
|
+
if (vitestGetFn) return
|
|
981
|
+
const getFnExport = findExportByName(pkg, 'getFn')
|
|
982
|
+
const setFnExport = findExportByName(pkg, 'setFn')
|
|
983
|
+
if (getFnExport && setFnExport) {
|
|
984
|
+
vitestGetFn = getFnExport.value
|
|
985
|
+
vitestSetFn = setFnExport.value
|
|
986
|
+
}
|
|
987
|
+
const getHooksExport = findExportByName(pkg, 'getHooks')
|
|
988
|
+
if (getHooksExport) {
|
|
989
|
+
vitestGetHooks = getHooksExport.value
|
|
990
|
+
}
|
|
991
|
+
}
|
|
992
|
+
|
|
889
993
|
addHook({
|
|
890
994
|
name: 'vitest',
|
|
891
995
|
versions: ['>=4.0.0'],
|
|
@@ -896,11 +1000,26 @@ addHook({
|
|
|
896
1000
|
return testPackage
|
|
897
1001
|
}
|
|
898
1002
|
|
|
1003
|
+
captureRunnerFunctions(testPackage)
|
|
899
1004
|
wrapVitestTestRunner(testRunner.value)
|
|
900
1005
|
|
|
901
1006
|
return testPackage
|
|
902
1007
|
})
|
|
903
1008
|
|
|
1009
|
+
addHook({
|
|
1010
|
+
name: '@vitest/runner',
|
|
1011
|
+
versions: ['>=1.6.0'],
|
|
1012
|
+
}, (runnerModule) => {
|
|
1013
|
+
if (!vitestGetFn && runnerModule.getFn && runnerModule.setFn) {
|
|
1014
|
+
vitestGetFn = runnerModule.getFn
|
|
1015
|
+
vitestSetFn = runnerModule.setFn
|
|
1016
|
+
}
|
|
1017
|
+
if (!vitestGetHooks && runnerModule.getHooks) {
|
|
1018
|
+
vitestGetHooks = runnerModule.getHooks
|
|
1019
|
+
}
|
|
1020
|
+
return runnerModule
|
|
1021
|
+
})
|
|
1022
|
+
|
|
904
1023
|
addHook({
|
|
905
1024
|
name: 'vitest',
|
|
906
1025
|
versions: ['>=1.6.0 <4.0.0'],
|
|
@@ -154,6 +154,11 @@ class BaseAwsSdkPlugin extends ClientPlugin {
|
|
|
154
154
|
})
|
|
155
155
|
|
|
156
156
|
this.finish(ctx)
|
|
157
|
+
|
|
158
|
+
if (IS_SERVERLESS) {
|
|
159
|
+
const peerStore = storage('peerServerless').getStore()
|
|
160
|
+
if (peerStore) delete peerStore.peerHostname
|
|
161
|
+
}
|
|
157
162
|
})
|
|
158
163
|
|
|
159
164
|
this.addBind(`apm:aws:response:start:${this.serviceIdentifier}`, ctx => {
|
|
@@ -49,6 +49,9 @@ const {
|
|
|
49
49
|
getSessionRequestErrorTags,
|
|
50
50
|
DD_CI_LIBRARY_CONFIGURATION_ERROR,
|
|
51
51
|
TEST_IS_MODIFIED,
|
|
52
|
+
TEST_HAS_DYNAMIC_NAME,
|
|
53
|
+
DYNAMIC_NAME_RE,
|
|
54
|
+
logDynamicNamesWarning,
|
|
52
55
|
getPullRequestBaseBranch,
|
|
53
56
|
} = require('../../dd-trace/src/plugins/util/test')
|
|
54
57
|
const { isMarkedAsUnskippable } = require('../../datadog-plugin-jest/src/util')
|
|
@@ -261,6 +264,7 @@ class CypressPlugin {
|
|
|
261
264
|
testManagementAttemptToFixRetries = 0
|
|
262
265
|
isImpactedTestsEnabled = false
|
|
263
266
|
modifiedFiles = []
|
|
267
|
+
newTestsWithDynamicNames = new Set()
|
|
264
268
|
|
|
265
269
|
constructor () {
|
|
266
270
|
const {
|
|
@@ -675,6 +679,8 @@ class CypressPlugin {
|
|
|
675
679
|
this.testSessionSpan.setTag(TEST_MANAGEMENT_ENABLED, 'true')
|
|
676
680
|
}
|
|
677
681
|
|
|
682
|
+
logDynamicNamesWarning(this.newTestsWithDynamicNames)
|
|
683
|
+
|
|
678
684
|
this.testModuleSpan.finish()
|
|
679
685
|
this.ciVisEvent(TELEMETRY_EVENT_FINISHED, 'module')
|
|
680
686
|
this.testSessionSpan.finish()
|
|
@@ -990,6 +996,12 @@ class CypressPlugin {
|
|
|
990
996
|
this.activeTestSpan.setTag(TEST_IS_RETRY, 'true')
|
|
991
997
|
this.activeTestSpan.setTag(TEST_RETRY_REASON, TEST_RETRY_REASON_TYPES.efd)
|
|
992
998
|
}
|
|
999
|
+
if (DYNAMIC_NAME_RE.test(testName)) {
|
|
1000
|
+
this.activeTestSpan.setTag(TEST_HAS_DYNAMIC_NAME, 'true')
|
|
1001
|
+
if (testStatuses.length === 1) {
|
|
1002
|
+
this.newTestsWithDynamicNames.add(`${testSuite} › ${testName}`)
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
993
1005
|
}
|
|
994
1006
|
if (isModified) {
|
|
995
1007
|
this.activeTestSpan.setTag(TEST_IS_MODIFIED, 'true')
|
|
@@ -23,6 +23,7 @@ const {
|
|
|
23
23
|
TEST_SOURCE_FILE,
|
|
24
24
|
TEST_IS_NEW,
|
|
25
25
|
TEST_IS_RETRY,
|
|
26
|
+
TEST_HAS_DYNAMIC_NAME,
|
|
26
27
|
TEST_EARLY_FLAKE_ENABLED,
|
|
27
28
|
TEST_EARLY_FLAKE_ABORT_REASON,
|
|
28
29
|
JEST_DISPLAY_NAME,
|
|
@@ -501,6 +502,7 @@ class JestPlugin extends CiPlugin {
|
|
|
501
502
|
isDisabled,
|
|
502
503
|
isQuarantined,
|
|
503
504
|
isModified,
|
|
505
|
+
hasDynamicName,
|
|
504
506
|
testSuiteAbsolutePath,
|
|
505
507
|
} = test
|
|
506
508
|
|
|
@@ -549,6 +551,10 @@ class JestPlugin extends CiPlugin {
|
|
|
549
551
|
if (isNew) {
|
|
550
552
|
extraTags[TEST_IS_NEW] = 'true'
|
|
551
553
|
}
|
|
554
|
+
|
|
555
|
+
if (hasDynamicName) {
|
|
556
|
+
extraTags[TEST_HAS_DYNAMIC_NAME] = 'true'
|
|
557
|
+
}
|
|
552
558
|
const testSuiteSpan = this.testSuiteSpanPerTestSuiteAbsolutePath.get(testSuiteAbsolutePath) || this.testSuiteSpan
|
|
553
559
|
|
|
554
560
|
return super.startTestSpan(name, suite, testSuiteSpan, extraTags)
|
|
@@ -32,6 +32,8 @@ const {
|
|
|
32
32
|
TEST_MANAGEMENT_ATTEMPT_TO_FIX_PASSED,
|
|
33
33
|
TEST_RETRY_REASON_TYPES,
|
|
34
34
|
TEST_IS_MODIFIED,
|
|
35
|
+
TEST_FINAL_STATUS,
|
|
36
|
+
TEST_HAS_DYNAMIC_NAME,
|
|
35
37
|
isModifiedTest,
|
|
36
38
|
} = require('../../dd-trace/src/plugins/util/test')
|
|
37
39
|
const { COMPONENT } = require('../../dd-trace/src/constants')
|
|
@@ -213,9 +215,13 @@ class MochaPlugin extends CiPlugin {
|
|
|
213
215
|
attemptToFixFailed,
|
|
214
216
|
isAttemptToFixRetry,
|
|
215
217
|
isAtrRetry,
|
|
218
|
+
finalStatus,
|
|
216
219
|
}) => {
|
|
217
220
|
if (span) {
|
|
218
221
|
span.setTag(TEST_STATUS, status)
|
|
222
|
+
if (finalStatus) {
|
|
223
|
+
span.setTag(TEST_FINAL_STATUS, finalStatus)
|
|
224
|
+
}
|
|
219
225
|
if (hasBeenRetried) {
|
|
220
226
|
span.setTag(TEST_IS_RETRY, 'true')
|
|
221
227
|
if (isAtrRetry) {
|
|
@@ -422,6 +428,7 @@ class MochaPlugin extends CiPlugin {
|
|
|
422
428
|
isDisabled,
|
|
423
429
|
isQuarantined,
|
|
424
430
|
isModified,
|
|
431
|
+
hasDynamicName,
|
|
425
432
|
} = testInfo
|
|
426
433
|
|
|
427
434
|
const extraTags = {}
|
|
@@ -473,6 +480,10 @@ class MochaPlugin extends CiPlugin {
|
|
|
473
480
|
}
|
|
474
481
|
}
|
|
475
482
|
|
|
483
|
+
if (hasDynamicName) {
|
|
484
|
+
extraTags[TEST_HAS_DYNAMIC_NAME] = 'true'
|
|
485
|
+
}
|
|
486
|
+
|
|
476
487
|
return super.startTestSpan(testName, testSuite, testSuiteSpan, extraTags)
|
|
477
488
|
}
|
|
478
489
|
}
|
|
@@ -38,6 +38,8 @@ const {
|
|
|
38
38
|
TEST_STATUS,
|
|
39
39
|
TEST_SUITE_ID,
|
|
40
40
|
TEST_SUITE,
|
|
41
|
+
TEST_HAS_DYNAMIC_NAME,
|
|
42
|
+
DYNAMIC_NAME_RE,
|
|
41
43
|
} = require('../../dd-trace/src/plugins/util/test')
|
|
42
44
|
const { RESOURCE_NAME } = require('../../../ext/tags')
|
|
43
45
|
const { COMPONENT } = require('../../dd-trace/src/constants')
|
|
@@ -221,6 +223,9 @@ class PlaywrightPlugin extends CiPlugin {
|
|
|
221
223
|
trace_id: id(span.trace_id),
|
|
222
224
|
parent_id: id(span.parent_id),
|
|
223
225
|
}
|
|
226
|
+
if (span.meta[TEST_IS_NEW] === 'true' && DYNAMIC_NAME_RE.test(span.meta[TEST_NAME] || '')) {
|
|
227
|
+
formattedSpan.meta[TEST_HAS_DYNAMIC_NAME] = 'true'
|
|
228
|
+
}
|
|
224
229
|
if (span.name === 'playwright.test') {
|
|
225
230
|
// TODO: remove this comment
|
|
226
231
|
// TODO: Let's pass rootDir, repositoryRoot, command, session id and module id as env vars
|
|
@@ -303,6 +308,7 @@ class PlaywrightPlugin extends CiPlugin {
|
|
|
303
308
|
error,
|
|
304
309
|
extraTags,
|
|
305
310
|
isNew,
|
|
311
|
+
hasDynamicName,
|
|
306
312
|
isEfdRetry,
|
|
307
313
|
isRetry,
|
|
308
314
|
isAttemptToFix,
|
|
@@ -335,6 +341,9 @@ class PlaywrightPlugin extends CiPlugin {
|
|
|
335
341
|
span.setTag(TEST_RETRY_REASON, TEST_RETRY_REASON_TYPES.efd)
|
|
336
342
|
}
|
|
337
343
|
}
|
|
344
|
+
if (hasDynamicName) {
|
|
345
|
+
span.setTag(TEST_HAS_DYNAMIC_NAME, 'true')
|
|
346
|
+
}
|
|
338
347
|
if (isRetry) {
|
|
339
348
|
span.setTag(TEST_IS_RETRY, 'true')
|
|
340
349
|
if (isAtrRetry) {
|
|
@@ -33,6 +33,7 @@ const {
|
|
|
33
33
|
TEST_RETRY_REASON_TYPES,
|
|
34
34
|
isModifiedTest,
|
|
35
35
|
TEST_IS_MODIFIED,
|
|
36
|
+
TEST_HAS_DYNAMIC_NAME,
|
|
36
37
|
} = require('../../dd-trace/src/plugins/util/test')
|
|
37
38
|
const { COMPONENT } = require('../../dd-trace/src/constants')
|
|
38
39
|
const {
|
|
@@ -117,6 +118,7 @@ class VitestPlugin extends CiPlugin {
|
|
|
117
118
|
testSuiteAbsolutePath,
|
|
118
119
|
isRetry,
|
|
119
120
|
isNew,
|
|
121
|
+
hasDynamicName,
|
|
120
122
|
isAttemptToFix,
|
|
121
123
|
isQuarantined,
|
|
122
124
|
isDisabled,
|
|
@@ -148,6 +150,9 @@ class VitestPlugin extends CiPlugin {
|
|
|
148
150
|
if (isNew) {
|
|
149
151
|
extraTags[TEST_IS_NEW] = 'true'
|
|
150
152
|
}
|
|
153
|
+
if (hasDynamicName) {
|
|
154
|
+
extraTags[TEST_HAS_DYNAMIC_NAME] = 'true'
|
|
155
|
+
}
|
|
151
156
|
if (isAttemptToFix) {
|
|
152
157
|
extraTags[TEST_MANAGEMENT_IS_ATTEMPT_TO_FIX] = 'true'
|
|
153
158
|
}
|
|
@@ -180,6 +185,10 @@ class VitestPlugin extends CiPlugin {
|
|
|
180
185
|
return ctx.currentStore
|
|
181
186
|
})
|
|
182
187
|
|
|
188
|
+
this.addBind('ci:vitest:test:fn', (ctx) => {
|
|
189
|
+
return ctx.currentStore
|
|
190
|
+
})
|
|
191
|
+
|
|
183
192
|
this.addBind('ci:vitest:test:finish-time', (ctx) => {
|
|
184
193
|
const { status, task, attemptToFixPassed, attemptToFixFailed } = ctx
|
|
185
194
|
const span = ctx.currentStore?.span
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { execSync } = require('node:child_process')
|
|
4
|
+
const fs = require('node:fs')
|
|
5
|
+
|
|
6
|
+
const instrumentations = require('../datadog-instrumentations/src/helpers/instrumentations')
|
|
7
|
+
const extractPackageAndModulePath = require('../datadog-instrumentations/src/helpers/extract-package-and-module-path')
|
|
8
|
+
const hooks = require('../datadog-instrumentations/src/helpers/hooks')
|
|
9
|
+
const { isESMFile } = require('../datadog-esbuild/src/utils')
|
|
10
|
+
const log = require('./src/log')
|
|
11
|
+
|
|
12
|
+
const PLUGIN_NAME = 'DatadogWebpackPlugin'
|
|
13
|
+
|
|
14
|
+
for (const hook of Object.values(hooks)) {
|
|
15
|
+
if (hook !== null && typeof hook === 'object') {
|
|
16
|
+
hook.fn()
|
|
17
|
+
} else {
|
|
18
|
+
hook()
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const modulesOfInterest = new Set()
|
|
23
|
+
|
|
24
|
+
for (const instrumentation of Object.values(instrumentations)) {
|
|
25
|
+
for (const entry of instrumentation) {
|
|
26
|
+
if (entry.file) {
|
|
27
|
+
modulesOfInterest.add(`${entry.name}/${entry.file}`) // e.g. "redis/my/file.js"
|
|
28
|
+
} else {
|
|
29
|
+
modulesOfInterest.add(entry.name) // e.g. "redis"
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* @returns {{ repositoryURL: string | null, commitSHA: string | null }}
|
|
36
|
+
*/
|
|
37
|
+
function getGitMetadata () {
|
|
38
|
+
const gitMetadata = {
|
|
39
|
+
repositoryURL: null,
|
|
40
|
+
commitSHA: null,
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
try {
|
|
44
|
+
gitMetadata.repositoryURL = execSync('git config --get remote.origin.url', {
|
|
45
|
+
encoding: 'utf8',
|
|
46
|
+
stdio: ['pipe', 'pipe', 'ignore'],
|
|
47
|
+
cwd: process.cwd(),
|
|
48
|
+
}).trim()
|
|
49
|
+
} catch (e) {
|
|
50
|
+
log.warn('failed to get git repository URL:', e.message)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
try {
|
|
54
|
+
gitMetadata.commitSHA = execSync('git rev-parse HEAD', {
|
|
55
|
+
encoding: 'utf8',
|
|
56
|
+
stdio: ['pipe', 'pipe', 'ignore'],
|
|
57
|
+
cwd: process.cwd(),
|
|
58
|
+
}).trim()
|
|
59
|
+
} catch (e) {
|
|
60
|
+
log.warn('failed to get git commit SHA:', e.message)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return gitMetadata
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
class DatadogWebpackPlugin {
|
|
67
|
+
/**
|
|
68
|
+
* @param {object} compiler
|
|
69
|
+
*/
|
|
70
|
+
apply (compiler) {
|
|
71
|
+
// optimization.minimize is not yet set when apply() is called in webpack 5.54.0+
|
|
72
|
+
// (applyWebpackOptionsDefaults runs after plugins), so we defer the check to the
|
|
73
|
+
// environment hook which fires synchronously after defaults are applied.
|
|
74
|
+
compiler.hooks.environment.tap(PLUGIN_NAME, () => {
|
|
75
|
+
if (compiler.options.optimization?.minimize) {
|
|
76
|
+
throw new Error(
|
|
77
|
+
'optimization.minimize is not compatible with DatadogWebpackPlugin and will break dd-trace ' +
|
|
78
|
+
'instrumentation. Disable optimization.minimize when using this plugin.'
|
|
79
|
+
)
|
|
80
|
+
}
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
const gitMetadata = getGitMetadata()
|
|
84
|
+
if (gitMetadata.repositoryURL || gitMetadata.commitSHA) {
|
|
85
|
+
const banner =
|
|
86
|
+
'if (typeof process === \'object\' && process !== null &&\n' +
|
|
87
|
+
' process.env !== null && typeof process.env === \'object\') {\n' +
|
|
88
|
+
(gitMetadata.repositoryURL
|
|
89
|
+
? ` process.env.DD_GIT_REPOSITORY_URL = ${JSON.stringify(gitMetadata.repositoryURL)};\n`
|
|
90
|
+
: '') +
|
|
91
|
+
(gitMetadata.commitSHA
|
|
92
|
+
? ` process.env.DD_GIT_COMMIT_SHA = ${JSON.stringify(gitMetadata.commitSHA)};\n`
|
|
93
|
+
: '') +
|
|
94
|
+
'}\n'
|
|
95
|
+
|
|
96
|
+
compiler.hooks.thisCompilation.tap(PLUGIN_NAME, (compilation) => {
|
|
97
|
+
compilation.hooks.processAssets.tap(
|
|
98
|
+
{ name: PLUGIN_NAME, stage: -2000 },
|
|
99
|
+
() => {
|
|
100
|
+
for (const chunk of compilation.chunks) {
|
|
101
|
+
if (!chunk.canBeInitial()) continue
|
|
102
|
+
for (const filename of chunk.files) {
|
|
103
|
+
if (!filename.endsWith('.js') && !filename.endsWith('.mjs')) continue
|
|
104
|
+
compilation.updateAsset(filename, (old) => {
|
|
105
|
+
const content = banner + old.source()
|
|
106
|
+
return {
|
|
107
|
+
source () { return content },
|
|
108
|
+
size () { return Buffer.byteLength(content, 'utf8') },
|
|
109
|
+
map () { return old.map() },
|
|
110
|
+
sourceAndMap () { return { source: content, map: old.map() } },
|
|
111
|
+
updateHash (hash) { hash.update(content) },
|
|
112
|
+
}
|
|
113
|
+
})
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
)
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
log.debug(
|
|
121
|
+
'Automatically injected git metadata (DD_GIT_REPOSITORY_URL: %s, DD_GIT_COMMIT_SHA: %s)',
|
|
122
|
+
gitMetadata.repositoryURL || 'not available',
|
|
123
|
+
gitMetadata.commitSHA || 'not available'
|
|
124
|
+
)
|
|
125
|
+
} else {
|
|
126
|
+
log.warn('No git metadata available - skipping injection')
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
compiler.hooks.normalModuleFactory.tap(PLUGIN_NAME, (nmf) => {
|
|
130
|
+
nmf.hooks.afterResolve.tap(PLUGIN_NAME, (resolveData) => {
|
|
131
|
+
const { createData } = resolveData
|
|
132
|
+
const resource = createData?.resource
|
|
133
|
+
if (!resource || !resource.includes('node_modules')) {
|
|
134
|
+
return
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const normalizedResource = resource.replaceAll('\\', '/')
|
|
138
|
+
const { pkg, path: modulePath, pkgJson } = extractPackageAndModulePath(normalizedResource)
|
|
139
|
+
if (!pkg) {
|
|
140
|
+
return
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const request = resolveData.request
|
|
144
|
+
|
|
145
|
+
if (!modulesOfInterest.has(request) && !modulesOfInterest.has(`${pkg}/${modulePath}`)) {
|
|
146
|
+
return
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (!pkgJson) {
|
|
150
|
+
return
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
let packageJson
|
|
154
|
+
try {
|
|
155
|
+
packageJson = JSON.parse(fs.readFileSync(pkgJson).toString())
|
|
156
|
+
} catch (e) {
|
|
157
|
+
if (e.code === 'ENOENT') {
|
|
158
|
+
log.debug(
|
|
159
|
+
'Skipping `package.json` lookup for %s. The package may be vendored.',
|
|
160
|
+
pkg
|
|
161
|
+
)
|
|
162
|
+
return
|
|
163
|
+
}
|
|
164
|
+
throw e
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if (isESMFile(normalizedResource, pkgJson, packageJson)) {
|
|
168
|
+
log.warn('Skipping ESM module (ESM support is not available in the webpack plugin): %s', resource)
|
|
169
|
+
return
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const version = packageJson.version
|
|
173
|
+
const pkgPath = request === pkg ? pkg : `${pkg}/${modulePath}`
|
|
174
|
+
|
|
175
|
+
createData.loaders = createData.loaders || []
|
|
176
|
+
createData.loaders.unshift({
|
|
177
|
+
loader: require.resolve('./src/loader'),
|
|
178
|
+
options: { pkg, version, path: pkgPath },
|
|
179
|
+
})
|
|
180
|
+
|
|
181
|
+
log.debug('LOAD: %s@%s, pkg "%s"', pkg, version, pkgPath)
|
|
182
|
+
})
|
|
183
|
+
})
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
module.exports = DatadogWebpackPlugin
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const CHANNEL = 'dd-trace:bundler:load'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Webpack loader that appends a dc-polyfill channel publish to a CJS module.
|
|
7
|
+
* Called for each module-of-interest identified by DatadogWebpackPlugin.
|
|
8
|
+
*
|
|
9
|
+
* @param {string} source
|
|
10
|
+
* @returns {string}
|
|
11
|
+
*/
|
|
12
|
+
module.exports = function loader (source) {
|
|
13
|
+
this.cacheable(false)
|
|
14
|
+
const { pkg, version, path: pkgPath } = this.getOptions()
|
|
15
|
+
|
|
16
|
+
return (
|
|
17
|
+
source +
|
|
18
|
+
'\n;{\n' +
|
|
19
|
+
' const __dd_dc = require(\'dc-polyfill\');\n' +
|
|
20
|
+
` const __dd_ch = __dd_dc.channel('${CHANNEL}');\n` +
|
|
21
|
+
' const __dd_mod = module.exports;\n' +
|
|
22
|
+
` const __dd_payload = { module: __dd_mod, version: '${version}', package: '${pkg}', path: '${pkgPath}' };\n` +
|
|
23
|
+
' __dd_ch.publish(__dd_payload);\n' +
|
|
24
|
+
' module.exports = __dd_payload.module;\n' +
|
|
25
|
+
'}\n'
|
|
26
|
+
)
|
|
27
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { format } = require('util')
|
|
4
|
+
|
|
5
|
+
// eslint-disable-next-line eslint-rules/eslint-process-env
|
|
6
|
+
const DD_TRACE_DEBUG = (process.env.DD_TRACE_DEBUG || '').trim().toLowerCase()
|
|
7
|
+
const DEBUG = DD_TRACE_DEBUG === 'true' || DD_TRACE_DEBUG === '1'
|
|
8
|
+
|
|
9
|
+
const noop = () => {}
|
|
10
|
+
|
|
11
|
+
const formatWithLogPrefix = (prefix, str, ...args) => {
|
|
12
|
+
if (typeof str === 'string') {
|
|
13
|
+
return format(`${prefix} ${str}`, ...args)
|
|
14
|
+
}
|
|
15
|
+
return format(prefix, str, ...args)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
module.exports = DEBUG
|
|
19
|
+
? {
|
|
20
|
+
debug (...args) {
|
|
21
|
+
// eslint-disable-next-line no-console
|
|
22
|
+
console.log(formatWithLogPrefix('[dd-trace/webpack]', ...args))
|
|
23
|
+
},
|
|
24
|
+
warn (...args) {
|
|
25
|
+
// eslint-disable-next-line no-console
|
|
26
|
+
console.warn(formatWithLogPrefix('[dd-trace/webpack] Warning:', ...args))
|
|
27
|
+
},
|
|
28
|
+
}
|
|
29
|
+
: {
|
|
30
|
+
debug: noop,
|
|
31
|
+
warn: noop,
|
|
32
|
+
}
|