dd-trace 6.0.0-pre-2b43d31 → 6.0.0-pre-efcd0a3

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dd-trace",
3
- "version": "6.0.0-pre-2b43d31",
3
+ "version": "6.0.0-pre-efcd0a3",
4
4
  "description": "Datadog APM tracing client for JavaScript",
5
5
  "main": "index.js",
6
6
  "typings": "index.d.ts",
@@ -69,7 +69,7 @@
69
69
  "node": ">=18"
70
70
  },
71
71
  "dependencies": {
72
- "@datadog/native-appsec": "7.1.0",
72
+ "@datadog/native-appsec": "7.1.1",
73
73
  "@datadog/native-iast-rewriter": "2.3.0",
74
74
  "@datadog/native-iast-taint-tracking": "1.7.0",
75
75
  "@datadog/native-metrics": "^2.0.0",
@@ -44,11 +44,14 @@ const knownTestsCh = channel('ci:jest:known-tests')
44
44
 
45
45
  const itrSkippedSuitesCh = channel('ci:jest:itr:skipped-suites')
46
46
 
47
+ // Message sent by jest's main process to workers to run a test suite (=test file)
48
+ // https://github.com/jestjs/jest/blob/1d682f21c7a35da4d3ab3a1436a357b980ebd0fa/packages/jest-worker/src/types.ts#L37
49
+ const CHILD_MESSAGE_CALL = 1
47
50
  // Maximum time we'll wait for the tracer to flush
48
51
  const FLUSH_TIMEOUT = 10000
49
52
 
50
53
  let skippableSuites = []
51
- let knownTests = []
54
+ let knownTests = {}
52
55
  let isCodeCoverageEnabled = false
53
56
  let isSuitesSkippingEnabled = false
54
57
  let isUserCodeCoverageEnabled = false
@@ -73,6 +76,7 @@ const specStatusToTestStatus = {
73
76
  const asyncResources = new WeakMap()
74
77
  const originalTestFns = new WeakMap()
75
78
  const retriedTestsToNumAttempts = new Map()
79
+ const newTestsTestStatuses = new Map()
76
80
 
77
81
  // based on https://github.com/facebook/jest/blob/main/packages/jest-circus/src/formatNodeAssertErrors.ts#L41
78
82
  function formatJestError (errors) {
@@ -101,6 +105,13 @@ function getTestEnvironmentOptions (config) {
101
105
  return {}
102
106
  }
103
107
 
108
+ function getEfdStats (testStatuses) {
109
+ return testStatuses.reduce((acc, testStatus) => {
110
+ acc[testStatus]++
111
+ return acc
112
+ }, { pass: 0, fail: 0 })
113
+ }
114
+
104
115
  function getWrappedEnvironment (BaseEnvironment, jestVersion) {
105
116
  return class DatadogEnvironment extends BaseEnvironment {
106
117
  constructor (config, context) {
@@ -123,9 +134,12 @@ function getWrappedEnvironment (BaseEnvironment, jestVersion) {
123
134
  this.isEarlyFlakeDetectionEnabled = this.testEnvironmentOptions._ddIsEarlyFlakeDetectionEnabled
124
135
 
125
136
  if (this.isEarlyFlakeDetectionEnabled) {
137
+ const hasKnownTests = !!knownTests.jest
126
138
  earlyFlakeDetectionNumRetries = this.testEnvironmentOptions._ddEarlyFlakeDetectionNumRetries
127
139
  try {
128
- this.knownTestsForThisSuite = this.getKnownTestsForSuite(this.testEnvironmentOptions._ddKnownTests)
140
+ this.knownTestsForThisSuite = hasKnownTests
141
+ ? (knownTests.jest[this.testSuite] || [])
142
+ : this.getKnownTestsForSuite(this.testEnvironmentOptions._ddKnownTests)
129
143
  } catch (e) {
130
144
  // If there has been an error parsing the tests, we'll disable Early Flake Deteciton
131
145
  this.isEarlyFlakeDetectionEnabled = false
@@ -145,7 +159,7 @@ function getWrappedEnvironment (BaseEnvironment, jestVersion) {
145
159
  if (typeof knownTestsForSuite === 'string') {
146
160
  knownTestsForSuite = JSON.parse(knownTestsForSuite)
147
161
  }
148
- return knownTestsForSuite.jest?.[this.testSuite] || []
162
+ return knownTestsForSuite
149
163
  }
150
164
 
151
165
  // Add the `add_test` event we don't have the test object yet, so
@@ -242,6 +256,19 @@ function getWrappedEnvironment (BaseEnvironment, jestVersion) {
242
256
  })
243
257
  // restore in case it is retried
244
258
  event.test.fn = originalTestFns.get(event.test)
259
+ // We'll store the test statuses of the retries
260
+ if (this.isEarlyFlakeDetectionEnabled) {
261
+ const testName = getJestTestName(event.test)
262
+ const originalTestName = removeEfdStringFromTestName(testName)
263
+ const isNewTest = retriedTestsToNumAttempts.has(originalTestName)
264
+ if (isNewTest) {
265
+ if (newTestsTestStatuses.has(originalTestName)) {
266
+ newTestsTestStatuses.get(originalTestName).push(status)
267
+ } else {
268
+ newTestsTestStatuses.set(originalTestName, [status])
269
+ }
270
+ }
271
+ }
245
272
  })
246
273
  }
247
274
  if (event.name === 'test_skip' || event.name === 'test_todo') {
@@ -508,6 +535,28 @@ function cliWrapper (cli, jestVersion) {
508
535
 
509
536
  numSkippedSuites = 0
510
537
 
538
+ /**
539
+ * If Early Flake Detection (EFD) is enabled the logic is as follows:
540
+ * - If all attempts for a test are failing, the test has failed and we will let the test process fail.
541
+ * - If just a single attempt passes, we will prevent the test process from failing.
542
+ * The rationale behind is the following: you may still be able to block your CI pipeline by gating
543
+ * on flakiness (the test will be considered flaky), but you may choose to unblock the pipeline too.
544
+ */
545
+
546
+ if (isEarlyFlakeDetectionEnabled) {
547
+ let numFailedTestsToIgnore = 0
548
+ for (const testStatuses of newTestsTestStatuses.values()) {
549
+ const { pass, fail } = getEfdStats(testStatuses)
550
+ if (pass > 0) { // as long as one passes, we'll consider the test passed
551
+ numFailedTestsToIgnore += fail
552
+ }
553
+ }
554
+ // If every test that failed was an EFD retry, we'll consider the suite passed
555
+ if (numFailedTestsToIgnore !== 0 && result.results.numFailedTests === numFailedTestsToIgnore) {
556
+ result.results.success = true
557
+ }
558
+ }
559
+
511
560
  return result
512
561
  })
513
562
 
@@ -619,7 +668,6 @@ function configureTestEnvironment (readConfigsResult) {
619
668
  // because `jestAdapterWrapper` runs in a different process. We have to go through `testEnvironmentOptions`
620
669
  configs.forEach(config => {
621
670
  config.testEnvironmentOptions._ddTestCodeCoverageEnabled = isCodeCoverageEnabled
622
- config.testEnvironmentOptions._ddKnownTests = knownTests
623
671
  })
624
672
 
625
673
  isUserCodeCoverageEnabled = !!readConfigsResult.globalConfig.collectCoverage
@@ -795,6 +843,38 @@ addHook({
795
843
  file: 'build/workers/ChildProcessWorker.js'
796
844
  }, (childProcessWorker) => {
797
845
  const ChildProcessWorker = childProcessWorker.default
846
+ shimmer.wrap(ChildProcessWorker.prototype, 'send', send => function (request) {
847
+ if (!isEarlyFlakeDetectionEnabled) {
848
+ return send.apply(this, arguments)
849
+ }
850
+ const [type] = request
851
+ // eslint-disable-next-line
852
+ // https://github.com/jestjs/jest/blob/1d682f21c7a35da4d3ab3a1436a357b980ebd0fa/packages/jest-worker/src/workers/ChildProcessWorker.ts#L424
853
+ if (type === CHILD_MESSAGE_CALL) {
854
+ // This is the message that the main process sends to the worker to run a test suite (=test file).
855
+ // In here we modify the config.testEnvironmentOptions to include the known tests for the suite.
856
+ // This way the suite only knows about the tests that are part of it.
857
+ const args = request[request.length - 1]
858
+ if (args.length > 1) {
859
+ return send.apply(this, arguments)
860
+ }
861
+ if (!args[0]?.config) {
862
+ return send.apply(this, arguments)
863
+ }
864
+ const [{ globalConfig, config, path: testSuiteAbsolutePath }] = args
865
+ const testSuite = getTestSuitePath(testSuiteAbsolutePath, globalConfig.rootDir || process.cwd())
866
+ const suiteKnownTests = knownTests.jest?.[testSuite] || []
867
+ args[0].config = {
868
+ ...config,
869
+ testEnvironmentOptions: {
870
+ ...config.testEnvironmentOptions,
871
+ _ddKnownTests: suiteKnownTests
872
+ }
873
+ }
874
+ }
875
+
876
+ return send.apply(this, arguments)
877
+ })
798
878
  shimmer.wrap(ChildProcessWorker.prototype, '_onMessage', _onMessage => function () {
799
879
  const [code, data] = arguments[0]
800
880
  if (code === JEST_WORKER_TRACE_PAYLOAD_CODE) { // datadog trace payload
@@ -21,7 +21,7 @@ function finish (err) {
21
21
  finishChannel.publish(undefined)
22
22
  }
23
23
 
24
- addHook({ name: 'oracledb', versions: ['5'] }, oracledb => {
24
+ addHook({ name: 'oracledb', versions: ['>=5'] }, oracledb => {
25
25
  shimmer.wrap(oracledb.Connection.prototype, 'execute', execute => {
26
26
  return function wrappedExecute (dbQuery, ...args) {
27
27
  if (!startChannel.hasSubscribers) {
@@ -20,11 +20,15 @@ const EXCLUDED_LOCATIONS = getNodeModulesPaths(
20
20
  'pusher/lib/utils.js',
21
21
  'redlock/dist/cjs',
22
22
  'sqreen/lib/package-reader/index.js',
23
- 'ws/lib/websocket-server.js'
23
+ 'ws/lib/websocket-server.js',
24
+ 'google-gax/build/src/grpc.js',
25
+ 'cookie-signature/index.js'
24
26
  )
25
27
 
26
28
  const EXCLUDED_PATHS_FROM_STACK = [
27
- path.join('node_modules', 'object-hash', path.sep)
29
+ path.join('node_modules', 'object-hash', path.sep),
30
+ path.join('node_modules', 'aws-sdk', 'lib', 'util.js'),
31
+ path.join('node_modules', 'keygrip', path.sep)
28
32
  ]
29
33
  class WeakHashAnalyzer extends Analyzer {
30
34
  constructor () {
@@ -27,6 +27,7 @@ const qsRegex = '(?:p(?:ass)?w(?:or)?d|pass(?:_?phrase)?|secret|(?:api_?|private
27
27
  const defaultWafObfuscatorKeyRegex = '(?i)(?:p(?:ass)?w(?:or)?d|pass(?:_?phrase)?|secret|(?:api_?|private_?|public_?)key)|token|consumer_?(?:id|key|secret)|sign(?:ed|ature)|bearer|authorization'
28
28
  // eslint-disable-next-line max-len
29
29
  const defaultWafObfuscatorValueRegex = '(?i)(?:p(?:ass)?w(?:or)?d|pass(?:_?phrase)?|secret|(?:api_?|private_?|public_?|access_?|secret_?)key(?:_?id)?|token|consumer_?(?:id|key|secret)|sign(?:ed|ature)?|auth(?:entication|orization)?)(?:\\s*=[^;]|"\\s*:\\s*"[^"]+")|bearer\\s+[a-z0-9\\._\\-]+|token:[a-z0-9]{13}|gh[opsu]_[0-9a-zA-Z]{36}|ey[I-L][\\w=-]+\\.ey[I-L][\\w=-]+(?:\\.[\\w.+\\/=-]+)?|[\\-]{5}BEGIN[a-z\\s]+PRIVATE\\sKEY[\\-]{5}[^\\-]+[\\-]{5}END[a-z\\s]+PRIVATE\\sKEY|ssh-rsa\\s*[a-z0-9\\/\\.+]{100,}'
30
+ const runtimeId = uuid()
30
31
 
31
32
  function maybeFile (filepath) {
32
33
  if (!filepath) return
@@ -291,7 +292,7 @@ class Config {
291
292
  service: this.service,
292
293
  env: this.env,
293
294
  version: this.version,
294
- 'runtime-id': uuid()
295
+ 'runtime-id': runtimeId
295
296
  })
296
297
 
297
298
  if (this.isCiVisibility) {
@@ -823,6 +824,7 @@ class Config {
823
824
  : undefined
824
825
 
825
826
  tagger.add(tags, options.tracing_tags)
827
+ if (Object.keys(tags).length) tags['runtime-id'] = runtimeId
826
828
 
827
829
  this._setUnit(opts, 'sampleRate', options.tracing_sampling_rate)
828
830
  this._setBoolean(opts, 'logInjection', options.log_injection_enabled)