Skip to content

Commit 11c9294

Browse files
[test optimization] Fix @jest/transform not to modify testEnvironmentOptions's shape (#7718)
1 parent 4b126a0 commit 11c9294

File tree

9 files changed

+195
-36
lines changed

9 files changed

+195
-36
lines changed

.github/CODEOWNERS

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@
100100
/integration-tests/cucumber/ @DataDog/ci-app-libraries
101101
/integration-tests/cypress/ @DataDog/ci-app-libraries
102102
/integration-tests/playwright/ @DataDog/ci-app-libraries
103-
/integration-tests/jest/jest.spec.js @DataDog/ci-app-libraries
103+
/integration-tests/jest/ @DataDog/ci-app-libraries
104104
/integration-tests/mocha/mocha.spec.js @DataDog/ci-app-libraries
105105
/integration-tests/playwright/playwright.spec.js @DataDog/ci-app-libraries
106106
/integration-tests/cucumber/cucumber.spec.js @DataDog/ci-app-libraries
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
'use strict'
2+
3+
const path = require('path')
4+
5+
const { sharedOptions } = require('./transform-config-identity/shared-options')
6+
7+
module.exports = {
8+
rootDir: path.join(__dirname, '..'),
9+
cache: false,
10+
collectCoverage: true,
11+
testEnvironment: 'node',
12+
testRunner: 'jest-circus/runner',
13+
testEnvironmentOptions: sharedOptions,
14+
testMatch: ['**/jest/transform-config-identity/**/*.test.ts'],
15+
transform: {
16+
'^.+\\.[jt]sx?$': '<rootDir>/jest/transform-config-identity/custom-transformer.js',
17+
},
18+
}

integration-tests/jest/jest.spec.js

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,10 +107,13 @@ describe(`jest@${JEST_VERSION} commonJS`, () => {
107107
useSandbox([
108108
`jest@${JEST_VERSION}`,
109109
`jest-jasmine2@${JEST_VERSION}`,
110+
`babel-jest@${JEST_VERSION}`,
110111
// jest-environment-jsdom is included in older versions of jest
111112
JEST_VERSION === 'latest' ? `jest-environment-jsdom@${JEST_VERSION}` : '',
112113
// jest-circus is not included in older versions of jest
113114
JEST_VERSION !== 'latest' ? `jest-circus@${JEST_VERSION}` : '',
115+
'@babel/core',
116+
'@babel/preset-typescript',
114117
'@happy-dom/jest-environment',
115118
'office-addin-mock',
116119
'winston',
@@ -1376,6 +1379,45 @@ describe(`jest@${JEST_VERSION} commonJS`, () => {
13761379
})
13771380
})
13781381

1382+
it('preserves custom testEnvironmentOptions for coverage transforms', async function () {
1383+
// This repro exists because one of our hooks modified `testEnvironmentOptions`,
1384+
// which can cause downstream transform errors.
1385+
this.timeout(60_000)
1386+
1387+
let outputWithTracer = ''
1388+
const command = 'node ./node_modules/jest/bin/jest --config ./jest/dd-trace-transform-repro.config.js --coverage'
1389+
const eventsPromise = receiver
1390+
.gatherPayloadsMaxTimeout(({ url }) => url.endsWith('/api/v2/citestcycle'), (payloads) => {
1391+
const events = payloads.flatMap(({ payload }) => payload.events)
1392+
const testEvents = events.filter(event => event.type === 'test')
1393+
1394+
assert.ok(testEvents.length > 0)
1395+
})
1396+
1397+
childProcess = exec(
1398+
command,
1399+
{
1400+
cwd,
1401+
env: getCiVisAgentlessConfig(receiver.port),
1402+
}
1403+
)
1404+
1405+
childProcess.stdout?.on('data', (chunk) => {
1406+
outputWithTracer += chunk.toString()
1407+
})
1408+
childProcess.stderr?.on('data', (chunk) => {
1409+
outputWithTracer += chunk.toString()
1410+
})
1411+
1412+
const [[exitCode]] = await Promise.all([
1413+
once(childProcess, 'exit'),
1414+
eventsPromise,
1415+
])
1416+
1417+
assert.strictEqual(exitCode, 0, outputWithTracer)
1418+
assert.doesNotMatch(outputWithTracer, /testEnvironmentOptions prototype was lost/)
1419+
})
1420+
13791421
context('intelligent test runner', () => {
13801422
context('if the agent is not event platform proxy compatible', () => {
13811423
it('does not do any intelligent test runner request', (done) => {
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
'use strict'
2+
3+
const { createTransformer } = require('babel-jest')
4+
5+
const { SecretOptions } = require('./shared-options')
6+
7+
const babelJestTransformer = createTransformer({
8+
presets: ['@babel/preset-typescript'],
9+
})
10+
11+
module.exports = {
12+
process (sourceText, sourcePath, configOrTransformOptions, legacyTransformOptions) {
13+
// Jest <=24 passes the project config as the third argument. Newer versions
14+
// pass a transform options object with the config nested inside `config`.
15+
const jestConfig = legacyTransformOptions
16+
? configOrTransformOptions
17+
: configOrTransformOptions?.config ?? configOrTransformOptions
18+
19+
const preservesTestEnvironmentOptionsPrototype =
20+
jestConfig?.testEnvironmentOptions instanceof SecretOptions
21+
22+
if (!preservesTestEnvironmentOptionsPrototype) {
23+
throw new Error('testEnvironmentOptions prototype was lost before Babel transform')
24+
}
25+
26+
return babelJestTransformer.process.apply(babelJestTransformer, arguments)
27+
},
28+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
const loadedFixture = require('./fixture.ts')
2+
3+
describe('transform config identity repro', () => {
4+
it('runs with coverage', () => {
5+
expect(loadedFixture()).toBe('ok')
6+
})
7+
})
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
const fixtureValue = (): string => 'ok'
2+
3+
module.exports = fixtureValue
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
'use strict'
2+
3+
class SecretOptions {}
4+
5+
const sharedOptions = new SecretOptions()
6+
7+
module.exports = {
8+
SecretOptions,
9+
sharedOptions,
10+
}

packages/datadog-instrumentations/src/jest.js

Lines changed: 83 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1562,49 +1562,97 @@ function jestConfigSyncWrapper (jestConfig) {
15621562
})
15631563
}
15641564

1565+
const DD_TEST_ENVIRONMENT_OPTION_KEYS = [
1566+
'_ddTestModuleId',
1567+
'_ddTestSessionId',
1568+
'_ddTestCommand',
1569+
'_ddTestSessionName',
1570+
'_ddForcedToRun',
1571+
'_ddUnskippable',
1572+
'_ddItrCorrelationId',
1573+
'_ddKnownTests',
1574+
'_ddIsEarlyFlakeDetectionEnabled',
1575+
'_ddEarlyFlakeDetectionSlowTestRetries',
1576+
'_ddRepositoryRoot',
1577+
'_ddIsFlakyTestRetriesEnabled',
1578+
'_ddFlakyTestRetriesCount',
1579+
'_ddIsDiEnabled',
1580+
'_ddIsKnownTestsEnabled',
1581+
'_ddIsTestManagementTestsEnabled',
1582+
'_ddTestManagementTests',
1583+
'_ddTestManagementAttemptToFixRetries',
1584+
'_ddModifiedFiles',
1585+
]
1586+
1587+
function removeDatadogTestEnvironmentOptions (testEnvironmentOptions) {
1588+
const removedEntries = []
1589+
1590+
for (const key of DD_TEST_ENVIRONMENT_OPTION_KEYS) {
1591+
if (!Object.hasOwn(testEnvironmentOptions, key)) {
1592+
continue
1593+
}
1594+
1595+
removedEntries.push([key, testEnvironmentOptions[key]])
1596+
delete testEnvironmentOptions[key]
1597+
}
1598+
1599+
return function restoreDatadogTestEnvironmentOptions () {
1600+
for (const [key, value] of removedEntries) {
1601+
testEnvironmentOptions[key] = value
1602+
}
1603+
}
1604+
}
1605+
1606+
/**
1607+
* Wrap `createScriptTransformer` to temporarily hide Datadog-specific
1608+
* `testEnvironmentOptions` keys while Jest builds its transform config.
1609+
*
1610+
* @param {Function} createScriptTransformer
1611+
* @returns {Function}
1612+
*/
1613+
function wrapCreateScriptTransformer (createScriptTransformer) {
1614+
return function (config) {
1615+
const testEnvironmentOptions = config?.testEnvironmentOptions
1616+
1617+
if (!testEnvironmentOptions) {
1618+
return createScriptTransformer.apply(this, arguments)
1619+
}
1620+
1621+
const restoreTestEnvironmentOptions = removeDatadogTestEnvironmentOptions(testEnvironmentOptions)
1622+
1623+
try {
1624+
const result = createScriptTransformer.apply(this, arguments)
1625+
1626+
if (result?.then) {
1627+
return result.finally(restoreTestEnvironmentOptions)
1628+
}
1629+
1630+
restoreTestEnvironmentOptions()
1631+
return result
1632+
} catch (e) {
1633+
restoreTestEnvironmentOptions()
1634+
throw e
1635+
}
1636+
}
1637+
}
1638+
15651639
addHook({
15661640
name: '@jest/transform',
1567-
versions: ['>=24.8.0'],
1641+
versions: ['>=24.8.0 <30.0.0'],
15681642
file: 'build/ScriptTransformer.js',
15691643
}, transformPackage => {
1570-
const originalCreateScriptTransformer = transformPackage.createScriptTransformer
1571-
1572-
// `createScriptTransformer` is an async function
1573-
transformPackage.createScriptTransformer = function (config) {
1574-
const { testEnvironmentOptions, ...restOfConfig } = config
1575-
const {
1576-
_ddTestModuleId,
1577-
_ddTestSessionId,
1578-
_ddTestCommand,
1579-
_ddTestSessionName,
1580-
_ddForcedToRun,
1581-
_ddUnskippable,
1582-
_ddItrCorrelationId,
1583-
_ddKnownTests,
1584-
_ddIsEarlyFlakeDetectionEnabled,
1585-
_ddEarlyFlakeDetectionSlowTestRetries,
1586-
_ddRepositoryRoot,
1587-
_ddIsFlakyTestRetriesEnabled,
1588-
_ddFlakyTestRetriesCount,
1589-
_ddIsDiEnabled,
1590-
_ddIsKnownTestsEnabled,
1591-
_ddIsTestManagementTestsEnabled,
1592-
_ddTestManagementTests,
1593-
_ddTestManagementAttemptToFixRetries,
1594-
_ddModifiedFiles,
1595-
...restOfTestEnvironmentOptions
1596-
} = testEnvironmentOptions
1597-
1598-
restOfConfig.testEnvironmentOptions = restOfTestEnvironmentOptions
1599-
1600-
arguments[0] = restOfConfig
1601-
1602-
return originalCreateScriptTransformer.apply(this, arguments)
1603-
}
1644+
transformPackage.createScriptTransformer = wrapCreateScriptTransformer(transformPackage.createScriptTransformer)
16041645

16051646
return transformPackage
16061647
})
16071648

1649+
addHook({
1650+
name: '@jest/transform',
1651+
versions: ['>=30.0.0'],
1652+
}, transformPackage => {
1653+
return shimmer.wrap(transformPackage, 'createScriptTransformer', wrapCreateScriptTransformer, { replaceGetter: true })
1654+
})
1655+
16081656
/**
16091657
* Hook to remove the test paths (test suite) that are part of `skippableSuites`
16101658
*/

packages/dd-trace/test/plugins/versions/package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
"license": "BSD-3-Clause",
55
"private": true,
66
"dependencies": {
7+
"@babel/core": "7.29.0",
8+
"@babel/preset-typescript": "7.28.5",
79
"@ai-sdk/openai": "3.0.12",
810
"@anthropic-ai/sdk": "0.73.0",
911
"@apollo/gateway": "2.12.2",
@@ -84,6 +86,7 @@
8486
"avsc": "5.7.9",
8587
"aws-sdk": "2.1693.0",
8688
"axios": "1.13.2",
89+
"babel-jest": "30.2.0",
8790
"azure-functions-core-tools": "4.6.0",
8891
"bluebird": "3.7.2",
8992
"body-parser": "2.2.2",

0 commit comments

Comments
 (0)