dd-trace 3.47.0 → 3.48.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/README.md +1 -32
- package/ci/init.js +1 -4
- package/index.d.ts +21 -0
- package/package.json +6 -5
- package/packages/datadog-instrumentations/src/amqplib.js +1 -1
- package/packages/datadog-instrumentations/src/child_process.js +150 -0
- package/packages/datadog-instrumentations/src/cucumber.js +12 -12
- package/packages/datadog-instrumentations/src/express.js +20 -0
- package/packages/datadog-instrumentations/src/grpc/client.js +56 -36
- package/packages/datadog-instrumentations/src/helpers/hooks.js +2 -2
- package/packages/datadog-instrumentations/src/jest.js +147 -10
- package/packages/datadog-instrumentations/src/mocha.js +3 -3
- package/packages/datadog-instrumentations/src/mongoose.js +23 -10
- package/packages/datadog-instrumentations/src/next.js +17 -3
- package/packages/datadog-instrumentations/src/playwright.js +41 -9
- package/packages/datadog-plugin-amqplib/src/consumer.js +10 -1
- package/packages/datadog-plugin-amqplib/src/producer.js +14 -1
- package/packages/datadog-plugin-aws-sdk/src/services/kinesis.js +107 -1
- package/packages/datadog-plugin-child_process/src/index.js +91 -0
- package/packages/datadog-plugin-child_process/src/scrub-cmd-params.js +125 -0
- package/packages/datadog-plugin-cucumber/src/index.js +16 -11
- package/packages/datadog-plugin-cypress/src/plugin.js +25 -12
- package/packages/datadog-plugin-grpc/src/client.js +16 -2
- package/packages/datadog-plugin-http/src/client.js +1 -1
- package/packages/datadog-plugin-jest/src/index.js +47 -6
- package/packages/datadog-plugin-mocha/src/index.js +14 -5
- package/packages/datadog-plugin-playwright/src/index.js +19 -5
- package/packages/datadog-plugin-rhea/src/consumer.js +11 -1
- package/packages/datadog-plugin-rhea/src/producer.js +11 -0
- package/packages/dd-trace/src/appsec/addresses.js +2 -0
- package/packages/dd-trace/src/appsec/api_security_sampler.js +16 -3
- package/packages/dd-trace/src/appsec/channels.js +2 -1
- package/packages/dd-trace/src/appsec/iast/analyzers/command-injection-analyzer.js +1 -1
- package/packages/dd-trace/src/appsec/iast/analyzers/sql-injection-analyzer.js +7 -28
- package/packages/dd-trace/src/appsec/iast/analyzers/vulnerability-analyzer.js +10 -6
- package/packages/dd-trace/src/appsec/iast/iast-plugin.js +4 -1
- package/packages/dd-trace/src/appsec/index.js +17 -2
- package/packages/dd-trace/src/appsec/rule_manager.js +2 -2
- package/packages/dd-trace/src/ci-visibility/early-flake-detection/get-known-tests.js +83 -0
- package/packages/dd-trace/src/ci-visibility/exporters/agent-proxy/index.js +25 -6
- package/packages/dd-trace/src/ci-visibility/exporters/agentless/index.js +2 -0
- package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +83 -41
- package/packages/dd-trace/src/ci-visibility/exporters/git/git_metadata.js +30 -8
- package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +7 -1
- package/packages/dd-trace/src/ci-visibility/{intelligent-test-runner/get-itr-configuration.js → requests/get-library-configuration.js} +18 -6
- package/packages/dd-trace/src/config.js +22 -9
- package/packages/dd-trace/src/datastreams/processor.js +6 -0
- package/packages/dd-trace/src/datastreams/writer.js +2 -5
- package/packages/dd-trace/src/dogstatsd.js +3 -5
- package/packages/dd-trace/src/encode/agentless-ci-visibility.js +5 -3
- package/packages/dd-trace/src/exporters/common/request.js +21 -3
- package/packages/dd-trace/src/format.js +25 -1
- package/packages/dd-trace/src/noop/span.js +1 -0
- package/packages/dd-trace/src/opentelemetry/span.js +9 -2
- package/packages/dd-trace/src/opentracing/span.js +38 -0
- package/packages/dd-trace/src/opentracing/span_context.js +12 -6
- package/packages/dd-trace/src/opentracing/tracer.js +2 -1
- package/packages/dd-trace/src/plugins/ci_plugin.js +24 -8
- package/packages/dd-trace/src/plugins/index.js +1 -0
- package/packages/dd-trace/src/plugins/util/git.js +6 -0
- package/packages/dd-trace/src/plugins/util/test.js +36 -7
- package/packages/dd-trace/src/profiling/config.js +22 -22
- package/packages/dd-trace/src/proxy.js +31 -23
- package/packages/dd-trace/src/span_processor.js +5 -1
- package/packages/dd-trace/src/telemetry/index.js +3 -0
- package/packages/datadog-instrumentations/src/child-process.js +0 -29
- package/packages/dd-trace/src/plugins/util/exec.js +0 -34
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const TracingPlugin = require('../../dd-trace/src/plugins/tracing')
|
|
4
|
+
const scrubChildProcessCmd = require('./scrub-cmd-params')
|
|
5
|
+
|
|
6
|
+
const MAX_ARG_SIZE = 4096 // 4kB
|
|
7
|
+
|
|
8
|
+
function truncateCommand (cmdFields) {
|
|
9
|
+
let size = cmdFields[0].length
|
|
10
|
+
let truncated = false
|
|
11
|
+
for (let i = 1; i < cmdFields.length; i++) {
|
|
12
|
+
if (size >= MAX_ARG_SIZE) {
|
|
13
|
+
truncated = true
|
|
14
|
+
cmdFields[i] = ''
|
|
15
|
+
continue
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const argLen = cmdFields[i].length
|
|
19
|
+
if (size < MAX_ARG_SIZE && size + argLen > MAX_ARG_SIZE) {
|
|
20
|
+
cmdFields[i] = cmdFields[i].substring(0, 2)
|
|
21
|
+
truncated = true
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
size += argLen
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return truncated
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
class ChildProcessPlugin extends TracingPlugin {
|
|
31
|
+
static get id () { return 'child_process' }
|
|
32
|
+
static get prefix () { return 'tracing:datadog:child_process:execution' }
|
|
33
|
+
|
|
34
|
+
get tracer () {
|
|
35
|
+
return this._tracer
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
start ({ command, shell }) {
|
|
39
|
+
if (typeof command !== 'string') {
|
|
40
|
+
return
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const cmdFields = scrubChildProcessCmd(command)
|
|
44
|
+
const truncated = truncateCommand(cmdFields)
|
|
45
|
+
const property = (shell === true) ? 'cmd.shell' : 'cmd.exec'
|
|
46
|
+
|
|
47
|
+
const meta = {
|
|
48
|
+
'component': 'subprocess',
|
|
49
|
+
[property]: (shell === true) ? cmdFields.join(' ') : JSON.stringify(cmdFields)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (truncated) {
|
|
53
|
+
meta['cmd.truncated'] = `${truncated}`
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
this.startSpan('command_execution', {
|
|
57
|
+
service: this.config.service,
|
|
58
|
+
resource: (shell === true) ? 'sh' : cmdFields[0],
|
|
59
|
+
type: 'system',
|
|
60
|
+
meta
|
|
61
|
+
})
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
end ({ result, error }) {
|
|
65
|
+
let exitCode
|
|
66
|
+
|
|
67
|
+
if (result !== undefined) {
|
|
68
|
+
exitCode = result?.status || 0
|
|
69
|
+
} else if (error !== undefined) {
|
|
70
|
+
exitCode = error?.status || error?.code || 0
|
|
71
|
+
} else {
|
|
72
|
+
// TracingChannels call start, end synchronously. Later when the promise is resolved then asyncStart asyncEnd.
|
|
73
|
+
// Therefore in the case of calling end with neither result nor error means that they will come in the asyncEnd.
|
|
74
|
+
return
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
this.activeSpan?.setTag('cmd.exit_code', `${exitCode}`)
|
|
78
|
+
this.activeSpan?.finish()
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
error (error) {
|
|
82
|
+
this.addError(error)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
asyncEnd ({ result }) {
|
|
86
|
+
this.activeSpan?.setTag('cmd.exit_code', `${result}`)
|
|
87
|
+
this.activeSpan?.finish()
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
module.exports = ChildProcessPlugin
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const shellParser = require('shell-quote/parse')
|
|
4
|
+
|
|
5
|
+
const ALLOWED_ENV_VARIABLES = ['LD_PRELOAD', 'LD_LIBRARY_PATH', 'PATH']
|
|
6
|
+
const PROCESS_DENYLIST = ['md5']
|
|
7
|
+
|
|
8
|
+
const VARNAMES_REGEX = /\$([\w\d_]*)(?:[^\w\d_]|$)/gmi
|
|
9
|
+
// eslint-disable-next-line max-len
|
|
10
|
+
const PARAM_PATTERN = '^-{0,2}(?:p(?:ass(?:w(?:or)?d)?)?|api_?key|secret|a(?:ccess|uth)_token|mysql_pwd|credentials|(?:stripe)?token)$'
|
|
11
|
+
const regexParam = new RegExp(PARAM_PATTERN, 'i')
|
|
12
|
+
const ENV_PATTERN = '^(\\w+=\\w+;)*\\w+=\\w+;?$'
|
|
13
|
+
const envvarRegex = new RegExp(ENV_PATTERN)
|
|
14
|
+
const REDACTED = '?'
|
|
15
|
+
|
|
16
|
+
function extractVarNames (expression) {
|
|
17
|
+
const varNames = new Set()
|
|
18
|
+
let match
|
|
19
|
+
|
|
20
|
+
while ((match = VARNAMES_REGEX.exec(expression))) {
|
|
21
|
+
varNames.add(match[1])
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const varNamesObject = {}
|
|
25
|
+
for (const varName of varNames.keys()) {
|
|
26
|
+
varNamesObject[varName] = `$${varName}`
|
|
27
|
+
}
|
|
28
|
+
return varNamesObject
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function getTokensByExpression (expressionTokens) {
|
|
32
|
+
const expressionListTokens = []
|
|
33
|
+
let wipExpressionTokens = []
|
|
34
|
+
let isNewExpression = true
|
|
35
|
+
|
|
36
|
+
expressionTokens.forEach(token => {
|
|
37
|
+
if (isNewExpression) {
|
|
38
|
+
expressionListTokens.push(wipExpressionTokens)
|
|
39
|
+
isNewExpression = false
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
wipExpressionTokens.push(token)
|
|
43
|
+
|
|
44
|
+
if (token.op) {
|
|
45
|
+
wipExpressionTokens = []
|
|
46
|
+
isNewExpression = true
|
|
47
|
+
}
|
|
48
|
+
})
|
|
49
|
+
return expressionListTokens
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function scrubChildProcessCmd (expression) {
|
|
53
|
+
const varNames = extractVarNames(expression)
|
|
54
|
+
const expressionTokens = shellParser(expression, varNames)
|
|
55
|
+
|
|
56
|
+
const expressionListTokens = getTokensByExpression(expressionTokens)
|
|
57
|
+
|
|
58
|
+
const result = []
|
|
59
|
+
expressionListTokens.forEach((expressionTokens) => {
|
|
60
|
+
let foundBinary = false
|
|
61
|
+
for (let index = 0; index < expressionTokens.length; index++) {
|
|
62
|
+
const token = expressionTokens[index]
|
|
63
|
+
|
|
64
|
+
if (typeof token === 'object') {
|
|
65
|
+
if (token.pattern) {
|
|
66
|
+
result.push(token.pattern)
|
|
67
|
+
} else if (token.op) {
|
|
68
|
+
result.push(token.op)
|
|
69
|
+
} else if (token.comment) {
|
|
70
|
+
result.push(`#${token.comment}`)
|
|
71
|
+
}
|
|
72
|
+
} else if (!foundBinary) {
|
|
73
|
+
if (envvarRegex.test(token)) {
|
|
74
|
+
const envSplit = token.split('=')
|
|
75
|
+
|
|
76
|
+
if (!ALLOWED_ENV_VARIABLES.includes(envSplit[0])) {
|
|
77
|
+
envSplit[1] = REDACTED
|
|
78
|
+
|
|
79
|
+
const newToken = envSplit.join('=')
|
|
80
|
+
expressionTokens[index] = newToken
|
|
81
|
+
|
|
82
|
+
result.push(newToken)
|
|
83
|
+
} else {
|
|
84
|
+
result.push(token)
|
|
85
|
+
}
|
|
86
|
+
} else {
|
|
87
|
+
foundBinary = true
|
|
88
|
+
result.push(token)
|
|
89
|
+
|
|
90
|
+
if (PROCESS_DENYLIST.includes(token)) {
|
|
91
|
+
for (index++; index < expressionTokens.length; index++) {
|
|
92
|
+
const token = expressionTokens[index]
|
|
93
|
+
|
|
94
|
+
if (token.op) {
|
|
95
|
+
result.push(token.op)
|
|
96
|
+
} else {
|
|
97
|
+
expressionTokens[index] = REDACTED
|
|
98
|
+
result.push(REDACTED)
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
break
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
} else {
|
|
105
|
+
const paramKeyValue = token.split('=')
|
|
106
|
+
const paramKey = paramKeyValue[0]
|
|
107
|
+
|
|
108
|
+
if (regexParam.test(paramKey)) {
|
|
109
|
+
if (paramKeyValue.length === 1) {
|
|
110
|
+
expressionTokens[index + 1] = REDACTED
|
|
111
|
+
result.push(token)
|
|
112
|
+
} else {
|
|
113
|
+
result.push(`${paramKey}=${REDACTED}`)
|
|
114
|
+
}
|
|
115
|
+
} else {
|
|
116
|
+
result.push(token)
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
return result
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
module.exports = scrubChildProcessCmd
|
|
@@ -14,7 +14,8 @@ const {
|
|
|
14
14
|
TEST_ITR_UNSKIPPABLE,
|
|
15
15
|
TEST_ITR_FORCED_RUN,
|
|
16
16
|
TEST_CODE_OWNERS,
|
|
17
|
-
ITR_CORRELATION_ID
|
|
17
|
+
ITR_CORRELATION_ID,
|
|
18
|
+
TEST_SOURCE_FILE
|
|
18
19
|
} = require('../../dd-trace/src/plugins/util/test')
|
|
19
20
|
const { RESOURCE_NAME } = require('../../../ext/tags')
|
|
20
21
|
const { COMPONENT, ERROR_MESSAGE } = require('../../dd-trace/src/constants')
|
|
@@ -47,7 +48,7 @@ class CucumberPlugin extends CiPlugin {
|
|
|
47
48
|
hasUnskippableSuites,
|
|
48
49
|
hasForcedToRunSuites
|
|
49
50
|
}) => {
|
|
50
|
-
const { isSuitesSkippingEnabled, isCodeCoverageEnabled } = this.
|
|
51
|
+
const { isSuitesSkippingEnabled, isCodeCoverageEnabled } = this.libraryConfig || {}
|
|
51
52
|
addIntelligentTestRunnerSpanTags(
|
|
52
53
|
this.testSessionSpan,
|
|
53
54
|
this.testModuleSpan,
|
|
@@ -71,7 +72,7 @@ class CucumberPlugin extends CiPlugin {
|
|
|
71
72
|
this.telemetry.ciVisEvent(TELEMETRY_EVENT_FINISHED, 'session')
|
|
72
73
|
finishAllTraceSpans(this.testSessionSpan)
|
|
73
74
|
|
|
74
|
-
this.
|
|
75
|
+
this.libraryConfig = null
|
|
75
76
|
this.tracer._exporter.flush()
|
|
76
77
|
})
|
|
77
78
|
|
|
@@ -102,7 +103,7 @@ class CucumberPlugin extends CiPlugin {
|
|
|
102
103
|
}
|
|
103
104
|
})
|
|
104
105
|
this.telemetry.ciVisEvent(TELEMETRY_EVENT_CREATED, 'suite')
|
|
105
|
-
if (this.
|
|
106
|
+
if (this.libraryConfig?.isCodeCoverageEnabled) {
|
|
106
107
|
this.telemetry.ciVisEvent(TELEMETRY_CODE_COVERAGE_STARTED, 'suite', { library: 'istanbul' })
|
|
107
108
|
}
|
|
108
109
|
})
|
|
@@ -114,7 +115,7 @@ class CucumberPlugin extends CiPlugin {
|
|
|
114
115
|
})
|
|
115
116
|
|
|
116
117
|
this.addSub('ci:cucumber:test-suite:code-coverage', ({ coverageFiles, suiteFile }) => {
|
|
117
|
-
if (!this.
|
|
118
|
+
if (!this.libraryConfig?.isCodeCoverageEnabled) {
|
|
118
119
|
return
|
|
119
120
|
}
|
|
120
121
|
if (!coverageFiles.length) {
|
|
@@ -122,7 +123,7 @@ class CucumberPlugin extends CiPlugin {
|
|
|
122
123
|
}
|
|
123
124
|
|
|
124
125
|
const relativeCoverageFiles = [...coverageFiles, suiteFile]
|
|
125
|
-
.map(filename => getTestSuitePath(filename, this.
|
|
126
|
+
.map(filename => getTestSuitePath(filename, this.repositoryRoot))
|
|
126
127
|
|
|
127
128
|
this.telemetry.distribution(TELEMETRY_CODE_COVERAGE_NUM_FILES, {}, relativeCoverageFiles.length)
|
|
128
129
|
|
|
@@ -136,10 +137,11 @@ class CucumberPlugin extends CiPlugin {
|
|
|
136
137
|
this.telemetry.ciVisEvent(TELEMETRY_CODE_COVERAGE_FINISHED, 'suite', { library: 'istanbul' })
|
|
137
138
|
})
|
|
138
139
|
|
|
139
|
-
this.addSub('ci:cucumber:test:start', ({ testName,
|
|
140
|
+
this.addSub('ci:cucumber:test:start', ({ testName, testFileAbsolutePath, testSourceLine }) => {
|
|
140
141
|
const store = storage.getStore()
|
|
141
|
-
const testSuite = getTestSuitePath(
|
|
142
|
-
const
|
|
142
|
+
const testSuite = getTestSuitePath(testFileAbsolutePath, this.sourceRoot)
|
|
143
|
+
const testSourceFile = getTestSuitePath(testFileAbsolutePath, this.repositoryRoot)
|
|
144
|
+
const testSpan = this.startTestSpan(testName, testSuite, testSourceFile, testSourceLine)
|
|
143
145
|
|
|
144
146
|
this.enter(testSpan, store)
|
|
145
147
|
})
|
|
@@ -191,12 +193,15 @@ class CucumberPlugin extends CiPlugin {
|
|
|
191
193
|
})
|
|
192
194
|
}
|
|
193
195
|
|
|
194
|
-
startTestSpan (testName, testSuite, testSourceLine) {
|
|
196
|
+
startTestSpan (testName, testSuite, testSourceFile, testSourceLine) {
|
|
195
197
|
return super.startTestSpan(
|
|
196
198
|
testName,
|
|
197
199
|
testSuite,
|
|
198
200
|
this.testSuiteSpan,
|
|
199
|
-
{
|
|
201
|
+
{
|
|
202
|
+
[TEST_SOURCE_START]: testSourceLine,
|
|
203
|
+
[TEST_SOURCE_FILE]: testSourceFile
|
|
204
|
+
}
|
|
200
205
|
)
|
|
201
206
|
}
|
|
202
207
|
}
|
|
@@ -24,7 +24,8 @@ const {
|
|
|
24
24
|
TEST_SKIPPED_BY_ITR,
|
|
25
25
|
TEST_ITR_UNSKIPPABLE,
|
|
26
26
|
TEST_ITR_FORCED_RUN,
|
|
27
|
-
ITR_CORRELATION_ID
|
|
27
|
+
ITR_CORRELATION_ID,
|
|
28
|
+
TEST_SOURCE_FILE
|
|
28
29
|
} = require('../../dd-trace/src/plugins/util/test')
|
|
29
30
|
const { ORIGIN_KEY, COMPONENT } = require('../../dd-trace/src/constants')
|
|
30
31
|
const log = require('../../dd-trace/src/log')
|
|
@@ -45,7 +46,8 @@ const {
|
|
|
45
46
|
GIT_REPOSITORY_URL,
|
|
46
47
|
GIT_COMMIT_SHA,
|
|
47
48
|
GIT_BRANCH,
|
|
48
|
-
CI_PROVIDER_NAME
|
|
49
|
+
CI_PROVIDER_NAME,
|
|
50
|
+
CI_WORKSPACE_PATH
|
|
49
51
|
} = require('../../dd-trace/src/plugins/util/tags')
|
|
50
52
|
const {
|
|
51
53
|
OS_VERSION,
|
|
@@ -119,14 +121,14 @@ function getSuiteStatus (suiteStats) {
|
|
|
119
121
|
return 'pass'
|
|
120
122
|
}
|
|
121
123
|
|
|
122
|
-
function
|
|
124
|
+
function getLibraryConfiguration (tracer, testConfiguration) {
|
|
123
125
|
return new Promise(resolve => {
|
|
124
|
-
if (!tracer._tracer._exporter
|
|
126
|
+
if (!tracer._tracer._exporter?.getLibraryConfiguration) {
|
|
125
127
|
return resolve({ err: new Error('CI Visibility was not initialized correctly') })
|
|
126
128
|
}
|
|
127
129
|
|
|
128
|
-
tracer._tracer._exporter.
|
|
129
|
-
resolve({ err,
|
|
130
|
+
tracer._tracer._exporter.getLibraryConfiguration(testConfiguration, (err, libraryConfig) => {
|
|
131
|
+
resolve({ err, libraryConfig })
|
|
130
132
|
})
|
|
131
133
|
})
|
|
132
134
|
}
|
|
@@ -136,7 +138,7 @@ function getSkippableTests (isSuitesSkippingEnabled, tracer, testConfiguration)
|
|
|
136
138
|
return Promise.resolve({ skippableTests: [] })
|
|
137
139
|
}
|
|
138
140
|
return new Promise(resolve => {
|
|
139
|
-
if (!tracer._tracer._exporter
|
|
141
|
+
if (!tracer._tracer._exporter?.getLibraryConfiguration) {
|
|
140
142
|
return resolve({ err: new Error('CI Visibility was not initialized correctly') })
|
|
141
143
|
}
|
|
142
144
|
tracer._tracer._exporter.getSkippableSuites(testConfiguration, (err, skippableTests, correlationId) => {
|
|
@@ -186,7 +188,8 @@ module.exports = (on, config) => {
|
|
|
186
188
|
[RUNTIME_NAME]: runtimeName,
|
|
187
189
|
[RUNTIME_VERSION]: runtimeVersion,
|
|
188
190
|
[GIT_BRANCH]: branch,
|
|
189
|
-
[CI_PROVIDER_NAME]: ciProviderName
|
|
191
|
+
[CI_PROVIDER_NAME]: ciProviderName,
|
|
192
|
+
[CI_WORKSPACE_PATH]: repositoryRoot
|
|
190
193
|
} = testEnvironmentMetadata
|
|
191
194
|
|
|
192
195
|
const isUnsupportedCIProvider = !ciProviderName
|
|
@@ -205,7 +208,7 @@ module.exports = (on, config) => {
|
|
|
205
208
|
testLevel: 'test'
|
|
206
209
|
}
|
|
207
210
|
|
|
208
|
-
const codeOwnersEntries = getCodeOwnersFileEntries()
|
|
211
|
+
const codeOwnersEntries = getCodeOwnersFileEntries(repositoryRoot)
|
|
209
212
|
|
|
210
213
|
let activeSpan = null
|
|
211
214
|
let testSessionSpan = null
|
|
@@ -284,12 +287,12 @@ module.exports = (on, config) => {
|
|
|
284
287
|
}
|
|
285
288
|
|
|
286
289
|
on('before:run', (details) => {
|
|
287
|
-
return
|
|
290
|
+
return getLibraryConfiguration(tracer, testConfiguration).then(({ err, libraryConfig }) => {
|
|
288
291
|
if (err) {
|
|
289
292
|
log.error(err)
|
|
290
293
|
} else {
|
|
291
|
-
isSuitesSkippingEnabled =
|
|
292
|
-
isCodeCoverageEnabled =
|
|
294
|
+
isSuitesSkippingEnabled = libraryConfig.isSuitesSkippingEnabled
|
|
295
|
+
isCodeCoverageEnabled = libraryConfig.isCodeCoverageEnabled
|
|
293
296
|
}
|
|
294
297
|
|
|
295
298
|
return getSkippableTests(isSuitesSkippingEnabled, tracer, testConfiguration)
|
|
@@ -359,6 +362,11 @@ module.exports = (on, config) => {
|
|
|
359
362
|
cypressTestName === test.name && spec.relative === test.suite
|
|
360
363
|
)
|
|
361
364
|
const skippedTestSpan = getTestSpan(cypressTestName, spec.relative)
|
|
365
|
+
if (spec.absolute && repositoryRoot) {
|
|
366
|
+
skippedTestSpan.setTag(TEST_SOURCE_FILE, getTestSuitePath(spec.absolute, repositoryRoot))
|
|
367
|
+
} else {
|
|
368
|
+
skippedTestSpan.setTag(TEST_SOURCE_FILE, spec.relative)
|
|
369
|
+
}
|
|
362
370
|
skippedTestSpan.setTag(TEST_STATUS, 'skip')
|
|
363
371
|
if (isSkippedByItr) {
|
|
364
372
|
skippedTestSpan.setTag(TEST_SKIPPED_BY_ITR, 'true')
|
|
@@ -390,6 +398,11 @@ module.exports = (on, config) => {
|
|
|
390
398
|
if (itrCorrelationId) {
|
|
391
399
|
finishedTest.testSpan.setTag(ITR_CORRELATION_ID, itrCorrelationId)
|
|
392
400
|
}
|
|
401
|
+
if (spec.absolute && repositoryRoot) {
|
|
402
|
+
finishedTest.testSpan.setTag(TEST_SOURCE_FILE, getTestSuitePath(spec.absolute, repositoryRoot))
|
|
403
|
+
} else {
|
|
404
|
+
finishedTest.testSpan.setTag(TEST_SOURCE_FILE, spec.relative)
|
|
405
|
+
}
|
|
393
406
|
finishedTest.testSpan.finish(finishedTest.finishTime)
|
|
394
407
|
})
|
|
395
408
|
|
|
@@ -41,7 +41,6 @@ class GrpcClientPlugin extends ClientPlugin {
|
|
|
41
41
|
'grpc.status.code': 0
|
|
42
42
|
}
|
|
43
43
|
}, false)
|
|
44
|
-
|
|
45
44
|
// needed as precursor for peer.service
|
|
46
45
|
if (method.service && method.package) {
|
|
47
46
|
span.setTag('rpc.service', method.package + '.' + method.service)
|
|
@@ -68,7 +67,7 @@ class GrpcClientPlugin extends ClientPlugin {
|
|
|
68
67
|
this.addError(error, span)
|
|
69
68
|
}
|
|
70
69
|
|
|
71
|
-
finish ({ span, result }) {
|
|
70
|
+
finish ({ span, result, peer }) {
|
|
72
71
|
if (!span) return
|
|
73
72
|
|
|
74
73
|
const { code, metadata } = result || {}
|
|
@@ -80,6 +79,21 @@ class GrpcClientPlugin extends ClientPlugin {
|
|
|
80
79
|
addMetadataTags(span, metadata, metadataFilter, 'response')
|
|
81
80
|
}
|
|
82
81
|
|
|
82
|
+
if (peer) {
|
|
83
|
+
// The only scheme we want to support here is ipv[46]:port, although
|
|
84
|
+
// more are supported by the library
|
|
85
|
+
// https://github.com/grpc/grpc/blob/v1.60.0/doc/naming.md
|
|
86
|
+
const parts = peer.split(':')
|
|
87
|
+
if (parts[parts.length - 1].match(/^\d+/)) {
|
|
88
|
+
const port = parts[parts.length - 1]
|
|
89
|
+
const ip = parts.slice(0, -1).join(':')
|
|
90
|
+
span.setTag('network.destination.ip', ip)
|
|
91
|
+
span.setTag('network.destination.port', port)
|
|
92
|
+
} else {
|
|
93
|
+
span.setTag('network.destination.ip', peer)
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
83
97
|
this.tagPeerService(span)
|
|
84
98
|
span.finish()
|
|
85
99
|
}
|
|
@@ -122,7 +122,7 @@ class HttpClientPlugin extends ClientPlugin {
|
|
|
122
122
|
// conditions for no error:
|
|
123
123
|
// 1. not using a custom agent instance with custom timeout specified
|
|
124
124
|
// 2. no invocation of `req.setTimeout`
|
|
125
|
-
if (!args.options.agent?.options
|
|
125
|
+
if (!args.options.agent?.options?.timeout && !customRequestTimeout) return
|
|
126
126
|
|
|
127
127
|
span.setTag('error', 1)
|
|
128
128
|
}
|
|
@@ -14,7 +14,12 @@ const {
|
|
|
14
14
|
TEST_ITR_UNSKIPPABLE,
|
|
15
15
|
TEST_ITR_FORCED_RUN,
|
|
16
16
|
TEST_CODE_OWNERS,
|
|
17
|
-
ITR_CORRELATION_ID
|
|
17
|
+
ITR_CORRELATION_ID,
|
|
18
|
+
TEST_SOURCE_FILE,
|
|
19
|
+
getTestSuitePath,
|
|
20
|
+
TEST_IS_NEW,
|
|
21
|
+
TEST_EARLY_FLAKE_IS_RETRY,
|
|
22
|
+
TEST_EARLY_FLAKE_IS_ENABLED
|
|
18
23
|
} = require('../../dd-trace/src/plugins/util/test')
|
|
19
24
|
const { COMPONENT } = require('../../dd-trace/src/constants')
|
|
20
25
|
const id = require('../../dd-trace/src/id')
|
|
@@ -81,7 +86,9 @@ class JestPlugin extends CiPlugin {
|
|
|
81
86
|
numSkippedSuites,
|
|
82
87
|
hasUnskippableSuites,
|
|
83
88
|
hasForcedToRunSuites,
|
|
84
|
-
error
|
|
89
|
+
error,
|
|
90
|
+
isEarlyFlakeDetectionEnabled,
|
|
91
|
+
onDone
|
|
85
92
|
}) => {
|
|
86
93
|
this.testSessionSpan.setTag(TEST_STATUS, status)
|
|
87
94
|
this.testModuleSpan.setTag(TEST_STATUS, status)
|
|
@@ -106,23 +113,34 @@ class JestPlugin extends CiPlugin {
|
|
|
106
113
|
}
|
|
107
114
|
)
|
|
108
115
|
|
|
116
|
+
if (isEarlyFlakeDetectionEnabled) {
|
|
117
|
+
this.testSessionSpan.setTag(TEST_EARLY_FLAKE_IS_ENABLED, 'true')
|
|
118
|
+
}
|
|
119
|
+
|
|
109
120
|
this.testModuleSpan.finish()
|
|
110
121
|
this.telemetry.ciVisEvent(TELEMETRY_EVENT_FINISHED, 'module')
|
|
111
122
|
this.testSessionSpan.finish()
|
|
112
123
|
this.telemetry.ciVisEvent(TELEMETRY_EVENT_FINISHED, 'session')
|
|
113
124
|
finishAllTraceSpans(this.testSessionSpan)
|
|
114
|
-
|
|
125
|
+
|
|
126
|
+
this.tracer._exporter.flush(() => {
|
|
127
|
+
if (onDone) {
|
|
128
|
+
onDone()
|
|
129
|
+
}
|
|
130
|
+
})
|
|
115
131
|
})
|
|
116
132
|
|
|
117
133
|
// Test suites can be run in a different process from jest's main one.
|
|
118
134
|
// This subscriber changes the configuration objects from jest to inject the trace id
|
|
119
|
-
// of the test session to the processes that run the test suites.
|
|
135
|
+
// of the test session to the processes that run the test suites, and other data.
|
|
120
136
|
this.addSub('ci:jest:session:configuration', configs => {
|
|
121
137
|
configs.forEach(config => {
|
|
122
138
|
config._ddTestSessionId = this.testSessionSpan.context().toTraceId()
|
|
123
139
|
config._ddTestModuleId = this.testModuleSpan.context().toSpanId()
|
|
124
140
|
config._ddTestCommand = this.testSessionSpan.context()._tags[TEST_COMMAND]
|
|
125
141
|
config._ddItrCorrelationId = this.itrCorrelationId
|
|
142
|
+
config._ddIsEarlyFlakeDetectionEnabled = !!this.libraryConfig?.isEarlyFlakeDetectionEnabled
|
|
143
|
+
config._ddEarlyFlakeDetectionNumRetries = this.libraryConfig?.earlyFlakeDetectionNumRetries ?? 0
|
|
126
144
|
})
|
|
127
145
|
})
|
|
128
146
|
|
|
@@ -223,7 +241,7 @@ class JestPlugin extends CiPlugin {
|
|
|
223
241
|
})
|
|
224
242
|
|
|
225
243
|
/**
|
|
226
|
-
* This can't use `this.
|
|
244
|
+
* This can't use `this.libraryConfig` like `ci:mocha:test-suite:code-coverage`
|
|
227
245
|
* because this subscription happens in a different process from the one
|
|
228
246
|
* fetching the ITR config.
|
|
229
247
|
*/
|
|
@@ -286,7 +304,17 @@ class JestPlugin extends CiPlugin {
|
|
|
286
304
|
}
|
|
287
305
|
|
|
288
306
|
startTestSpan (test) {
|
|
289
|
-
const {
|
|
307
|
+
const {
|
|
308
|
+
suite,
|
|
309
|
+
name,
|
|
310
|
+
runner,
|
|
311
|
+
testParameters,
|
|
312
|
+
frameworkVersion,
|
|
313
|
+
testStartLine,
|
|
314
|
+
testFileAbsolutePath,
|
|
315
|
+
isNew,
|
|
316
|
+
isEfdRetry
|
|
317
|
+
} = test
|
|
290
318
|
|
|
291
319
|
const extraTags = {
|
|
292
320
|
[JEST_TEST_RUNNER]: runner,
|
|
@@ -296,6 +324,19 @@ class JestPlugin extends CiPlugin {
|
|
|
296
324
|
if (testStartLine) {
|
|
297
325
|
extraTags[TEST_SOURCE_START] = testStartLine
|
|
298
326
|
}
|
|
327
|
+
if (testFileAbsolutePath) {
|
|
328
|
+
extraTags[TEST_SOURCE_FILE] = getTestSuitePath(testFileAbsolutePath, this.repositoryRoot)
|
|
329
|
+
} else {
|
|
330
|
+
// If for whatever we don't have the full path, we'll set the source file to the suite name
|
|
331
|
+
extraTags[TEST_SOURCE_FILE] = suite
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
if (isNew) {
|
|
335
|
+
extraTags[TEST_IS_NEW] = 'true'
|
|
336
|
+
if (isEfdRetry) {
|
|
337
|
+
extraTags[TEST_EARLY_FLAKE_IS_RETRY] = 'true'
|
|
338
|
+
}
|
|
339
|
+
}
|
|
299
340
|
|
|
300
341
|
return super.startTestSpan(name, suite, this.testSuiteSpan, extraTags)
|
|
301
342
|
}
|
|
@@ -15,7 +15,8 @@ const {
|
|
|
15
15
|
TEST_ITR_UNSKIPPABLE,
|
|
16
16
|
TEST_ITR_FORCED_RUN,
|
|
17
17
|
TEST_CODE_OWNERS,
|
|
18
|
-
ITR_CORRELATION_ID
|
|
18
|
+
ITR_CORRELATION_ID,
|
|
19
|
+
TEST_SOURCE_FILE
|
|
19
20
|
} = require('../../dd-trace/src/plugins/util/test')
|
|
20
21
|
const { COMPONENT } = require('../../dd-trace/src/constants')
|
|
21
22
|
const {
|
|
@@ -42,7 +43,7 @@ class MochaPlugin extends CiPlugin {
|
|
|
42
43
|
this.sourceRoot = process.cwd()
|
|
43
44
|
|
|
44
45
|
this.addSub('ci:mocha:test-suite:code-coverage', ({ coverageFiles, suiteFile }) => {
|
|
45
|
-
if (!this.
|
|
46
|
+
if (!this.libraryConfig?.isCodeCoverageEnabled) {
|
|
46
47
|
return
|
|
47
48
|
}
|
|
48
49
|
const testSuiteSpan = this._testSuites.get(suiteFile)
|
|
@@ -98,7 +99,7 @@ class MochaPlugin extends CiPlugin {
|
|
|
98
99
|
}
|
|
99
100
|
})
|
|
100
101
|
this.telemetry.ciVisEvent(TELEMETRY_EVENT_CREATED, 'suite')
|
|
101
|
-
if (this.
|
|
102
|
+
if (this.libraryConfig?.isCodeCoverageEnabled) {
|
|
102
103
|
this.telemetry.ciVisEvent(TELEMETRY_CODE_COVERAGE_STARTED, 'suite', { library: 'istanbul' })
|
|
103
104
|
}
|
|
104
105
|
if (itrCorrelationId) {
|
|
@@ -192,7 +193,7 @@ class MochaPlugin extends CiPlugin {
|
|
|
192
193
|
error
|
|
193
194
|
}) => {
|
|
194
195
|
if (this.testSessionSpan) {
|
|
195
|
-
const { isSuitesSkippingEnabled, isCodeCoverageEnabled } = this.
|
|
196
|
+
const { isSuitesSkippingEnabled, isCodeCoverageEnabled } = this.libraryConfig || {}
|
|
196
197
|
this.testSessionSpan.setTag(TEST_STATUS, status)
|
|
197
198
|
this.testModuleSpan.setTag(TEST_STATUS, status)
|
|
198
199
|
|
|
@@ -222,7 +223,7 @@ class MochaPlugin extends CiPlugin {
|
|
|
222
223
|
this.telemetry.ciVisEvent(TELEMETRY_EVENT_FINISHED, 'session')
|
|
223
224
|
finishAllTraceSpans(this.testSessionSpan)
|
|
224
225
|
}
|
|
225
|
-
this.
|
|
226
|
+
this.libraryConfig = null
|
|
226
227
|
this.tracer._exporter.flush()
|
|
227
228
|
})
|
|
228
229
|
}
|
|
@@ -244,6 +245,14 @@ class MochaPlugin extends CiPlugin {
|
|
|
244
245
|
const testSuite = getTestSuitePath(testSuiteAbsolutePath, this.sourceRoot)
|
|
245
246
|
const testSuiteSpan = this._testSuites.get(testSuiteAbsolutePath)
|
|
246
247
|
|
|
248
|
+
const testSourceFile = getTestSuitePath(testSuiteAbsolutePath, this.repositoryRoot)
|
|
249
|
+
|
|
250
|
+
if (testSourceFile) {
|
|
251
|
+
extraTags[TEST_SOURCE_FILE] = testSourceFile
|
|
252
|
+
} else {
|
|
253
|
+
extraTags[TEST_SOURCE_FILE] = testSuite
|
|
254
|
+
}
|
|
255
|
+
|
|
247
256
|
return super.startTestSpan(testName, testSuite, testSuiteSpan, extraTags)
|
|
248
257
|
}
|
|
249
258
|
}
|
|
@@ -9,7 +9,9 @@ const {
|
|
|
9
9
|
getTestSuitePath,
|
|
10
10
|
getTestSuiteCommonTags,
|
|
11
11
|
TEST_SOURCE_START,
|
|
12
|
-
TEST_CODE_OWNERS
|
|
12
|
+
TEST_CODE_OWNERS,
|
|
13
|
+
TEST_SOURCE_FILE,
|
|
14
|
+
TEST_CONFIGURATION_BROWSER_NAME
|
|
13
15
|
} = require('../../dd-trace/src/plugins/util/test')
|
|
14
16
|
const { RESOURCE_NAME } = require('../../../ext/tags')
|
|
15
17
|
const { COMPONENT } = require('../../dd-trace/src/constants')
|
|
@@ -76,10 +78,11 @@ class PlaywrightPlugin extends CiPlugin {
|
|
|
76
78
|
this.telemetry.ciVisEvent(TELEMETRY_EVENT_FINISHED, 'suite')
|
|
77
79
|
})
|
|
78
80
|
|
|
79
|
-
this.addSub('ci:playwright:test:start', ({ testName, testSuiteAbsolutePath, testSourceLine }) => {
|
|
81
|
+
this.addSub('ci:playwright:test:start', ({ testName, testSuiteAbsolutePath, testSourceLine, browserName }) => {
|
|
80
82
|
const store = storage.getStore()
|
|
81
83
|
const testSuite = getTestSuitePath(testSuiteAbsolutePath, this.rootDir)
|
|
82
|
-
const
|
|
84
|
+
const testSourceFile = getTestSuitePath(testSuiteAbsolutePath, this.repositoryRoot)
|
|
85
|
+
const span = this.startTestSpan(testName, testSuite, testSourceFile, testSourceLine, browserName)
|
|
83
86
|
|
|
84
87
|
this.enter(span, store)
|
|
85
88
|
})
|
|
@@ -126,9 +129,20 @@ class PlaywrightPlugin extends CiPlugin {
|
|
|
126
129
|
})
|
|
127
130
|
}
|
|
128
131
|
|
|
129
|
-
startTestSpan (testName, testSuite, testSourceLine) {
|
|
132
|
+
startTestSpan (testName, testSuite, testSourceFile, testSourceLine, browserName) {
|
|
130
133
|
const testSuiteSpan = this._testSuites.get(testSuite)
|
|
131
|
-
|
|
134
|
+
|
|
135
|
+
const extraTags = {
|
|
136
|
+
[TEST_SOURCE_START]: testSourceLine
|
|
137
|
+
}
|
|
138
|
+
if (testSourceFile) {
|
|
139
|
+
extraTags[TEST_SOURCE_FILE] = testSourceFile || testSuite
|
|
140
|
+
}
|
|
141
|
+
if (browserName) {
|
|
142
|
+
extraTags[TEST_CONFIGURATION_BROWSER_NAME] = browserName
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return super.startTestSpan(testName, testSuite, testSuiteSpan, extraTags)
|
|
132
146
|
}
|
|
133
147
|
}
|
|
134
148
|
|