dd-trace
Version:
Datadog APM tracing client for JavaScript
170 lines (145 loc) • 5.26 kB
JavaScript
const { readFileSync } = require('fs')
const { parse } = require('../../../vendor/dist/jest-docblock')
const { getTestSuitePath } = require('../../dd-trace/src/plugins/util/test')
const log = require('../../dd-trace/src/log')
/**
* There are two ways to call `test.each` in `jest`:
* 1. With an array of arrays: https://jestjs.io/docs/api#1-testeachtablename-fn-timeout
* 2. With a tagged template literal: https://jestjs.io/docs/api#2-testeachtablename-fn-timeout
* This function distinguishes between the two and returns the test parameters in different formats:
* 1. An array of arrays with the different parameters to the test, e.g.
* [[1, 2, 3], [2, 3, 5]]
* 2. An array of objects, e.g.
* [{ a: 1, b: 2, expected: 3 }, { a: 2, b: 3, expected: 5}]
*/
function getFormattedJestTestParameters (testParameters) {
if (!testParameters || !testParameters.length) {
return
}
const [parameterArray, ...parameterValues] = testParameters
if (parameterValues.length === 0) { // Way 1.
return parameterArray
}
// Way 2.
const parameterKeys = parameterArray[0].split('|').map(key => key.trim())
const formattedParameters = []
let lastFormattedParameter = {}
for (let index = 0; index < parameterValues.length; index++) {
const parameterIndex = index % parameterKeys.length
if (parameterIndex === 0) {
lastFormattedParameter = {}
formattedParameters.push(lastFormattedParameter)
}
const key = parameterKeys[parameterIndex]
lastFormattedParameter[key] = parameterValues[index]
}
return formattedParameters
}
// Support for `@fast-check/jest`: this library modifies the test name to include the seed
// A test name that keeps changing breaks some Test Optimization's features.
const SEED_SUFFIX_RE = /\s*\(with seed=-?\d+\)\s*$/i
// https://github.com/facebook/jest/blob/3e38157ad5f23fb7d24669d24fae8ded06a7ab75/packages/jest-circus/src/utils.ts#L396
function getJestTestName (test, shouldStripSeed = false) {
const titles = []
let parent = test
do {
titles.unshift(parent.name)
} while ((parent = parent.parent))
titles.shift() // remove TOP_DESCRIBE_BLOCK_NAME
const testName = titles.join(' ')
if (shouldStripSeed) {
return testName.replace(SEED_SUFFIX_RE, '')
}
return testName
}
const globalDocblockRegExp = /^\s*(\/\*\*?(.|\r?\n)*?\*\/)/
const MAX_COMMENTS_CHECKED = 10
function isMarkedAsUnskippable (test) {
let testSource
try {
testSource = readFileSync(test.path, 'utf8')
} catch {
return false
}
const re = globalDocblockRegExp
re.lastIndex = 0
let commentsChecked = 0
while (testSource.length) {
const match = re.exec(testSource)
if (!match) break
const comment = match[1]
let docblocks
try {
docblocks = parse(comment)
} catch {
// Skip unparsable comment and continue scanning
if (commentsChecked++ >= MAX_COMMENTS_CHECKED) {
return false
}
continue
}
if (docblocks?.datadog) {
try {
// @ts-expect-error The datadog type is defined by us and may only be a string.
return JSON.parse(docblocks.datadog).unskippable
} catch {
// If the @datadog block comment is present but malformed, we'll run the suite
log.warn('@datadog block comment is malformed.')
return true
}
}
if (commentsChecked++ >= MAX_COMMENTS_CHECKED) {
return false
}
// To stop as soon as no doc blocks are found, slice the source. That way the
// regexp works by using the `^` anchor. Without it, it would continue
// scanning the rest of the file.
testSource = testSource.slice(match[0].length)
}
return false
}
function getJestSuitesToRun (skippableSuites, originalTests, rootDir) {
const unskippableSuites = {}
const forcedToRunSuites = {}
const skippedSuites = []
const suitesToRun = []
for (const test of originalTests) {
const relativePath = getTestSuitePath(test.path, rootDir)
const shouldBeSkipped = skippableSuites.includes(relativePath)
if (isMarkedAsUnskippable(test)) {
suitesToRun.push(test)
unskippableSuites[relativePath] = true
if (shouldBeSkipped) {
forcedToRunSuites[relativePath] = true
}
continue
}
if (shouldBeSkipped) {
skippedSuites.push(relativePath)
} else {
suitesToRun.push(test)
}
}
const hasUnskippableSuites = Object.keys(unskippableSuites).length > 0
const hasForcedToRunSuites = Object.keys(forcedToRunSuites).length > 0
if (originalTests.length) {
// The config object is shared by all tests, so we can just take the first one
const [test] = originalTests
if (test?.context?.config?.testEnvironmentOptions) {
if (hasUnskippableSuites) {
test.context.config.testEnvironmentOptions._ddUnskippable = JSON.stringify(unskippableSuites)
}
if (hasForcedToRunSuites) {
test.context.config.testEnvironmentOptions._ddForcedToRun = JSON.stringify(forcedToRunSuites)
}
}
}
return {
skippedSuites,
suitesToRun,
hasUnskippableSuites,
hasForcedToRunSuites
}
}
module.exports = { getFormattedJestTestParameters, getJestTestName, getJestSuitesToRun, isMarkedAsUnskippable }