mirror of
https://github.com/vercel/next.js.git
synced 2026-09-20 02:25:18 +08:00
027205875b
The previous detection mechanism special-cased `canary` which lead to large diffs when backport branches ran CI. We stop special casing `canary` reducing complexity and giving us another flake-detecting attempt when the change is merged. That way we can safely enable flake detection on backport branches without having to test large diffs when PRs are merged targetting backport branches. New backport branches will no longer have flake detecton and new deploy test runs disabled.
218 lines
5.5 KiB
JavaScript
218 lines
5.5 KiB
JavaScript
// @ts-check
|
|
import fs from 'fs/promises'
|
|
import execa from 'execa'
|
|
import { createRequire } from 'module'
|
|
import path from 'path'
|
|
import { getDiffRevision, getGitInfo } from './git-info.mjs'
|
|
|
|
const require = createRequire(import.meta.url)
|
|
const glob = require('glob')
|
|
const {
|
|
getTestFilterFromManifest,
|
|
mergeManifests,
|
|
} = require('../test/get-test-filter')
|
|
|
|
const DEFAULT_DEPLOY_TESTS_MANIFEST_PATH = 'test/deploy-tests-manifest.json'
|
|
const TEST_FILE_REGEX = /^test\/.*?\.test\.(js|ts|tsx)$/
|
|
|
|
function normalizePath(file) {
|
|
return file.replace(/\\/g, '/')
|
|
}
|
|
|
|
function getExcludedCases(suite = {}) {
|
|
return new Set([...(suite.failed ?? []), ...(suite.flakey ?? [])])
|
|
}
|
|
|
|
function getExternalTestsFilterPaths() {
|
|
const externalTestsFilters =
|
|
process.env.NEXT_EXTERNAL_TESTS_FILTERS ??
|
|
DEFAULT_DEPLOY_TESTS_MANIFEST_PATH
|
|
|
|
const manifestPaths = new Map()
|
|
|
|
for (const manifestPath of externalTestsFilters.split(',')) {
|
|
if (!manifestPath.trim()) {
|
|
continue
|
|
}
|
|
|
|
const absolutePath = path.resolve(process.cwd(), manifestPath)
|
|
const repoRelativePath = normalizePath(
|
|
path.relative(process.cwd(), absolutePath)
|
|
)
|
|
|
|
manifestPaths.set(repoRelativePath, {
|
|
absolutePath,
|
|
repoRelativePath,
|
|
})
|
|
}
|
|
|
|
return [...manifestPaths.values()]
|
|
}
|
|
|
|
function getDeployTestsFromManifest(manifest) {
|
|
const testFilter = getTestFilterFromManifest(manifest)
|
|
if (!testFilter) {
|
|
return []
|
|
}
|
|
|
|
const tests = glob
|
|
.sync('test/e2e/**/*.test.{js,ts,tsx}', {
|
|
cwd: process.cwd(),
|
|
ignore: '**/node_modules/**',
|
|
nodir: true,
|
|
})
|
|
.map((file) => ({ file: normalizePath(file), excludedCases: [] }))
|
|
|
|
return testFilter(tests).map((test) => test.file)
|
|
}
|
|
|
|
export function getDeployManifestChangedTests(
|
|
currentManifest,
|
|
previousManifest
|
|
) {
|
|
if (!currentManifest) {
|
|
return []
|
|
}
|
|
|
|
const previousVersion2Manifest = previousManifest ?? {
|
|
version: 2,
|
|
suites: {},
|
|
rules: { include: [], exclude: [] },
|
|
}
|
|
const previousIncludedTests = new Set(
|
|
getDeployTestsFromManifest(previousVersion2Manifest)
|
|
)
|
|
|
|
const changedTests = new Set()
|
|
|
|
for (const file of getDeployTestsFromManifest(currentManifest)) {
|
|
const currentExcludedCases = getExcludedCases(
|
|
currentManifest.suites?.[file]
|
|
)
|
|
const previousExcludedCases = getExcludedCases(
|
|
previousVersion2Manifest.suites?.[file]
|
|
)
|
|
|
|
if (!previousIncludedTests.has(file)) {
|
|
changedTests.add(file)
|
|
continue
|
|
}
|
|
|
|
for (const testCase of previousExcludedCases) {
|
|
if (!currentExcludedCases.has(testCase)) {
|
|
changedTests.add(file)
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
return [...changedTests]
|
|
}
|
|
|
|
/**
|
|
* Detects changed (see {@link getDiffRevision}) tests files.
|
|
* Returns tests separated by test mode (dev/prod), as well as the corresponding commit hash
|
|
* that the current branch is pointing to
|
|
*/
|
|
export default async function getChangedTests() {
|
|
/** @type import('execa').Options */
|
|
const EXECA_OPTS = { shell: true }
|
|
|
|
const { branchName, remoteUrl, commitSha } = await getGitInfo()
|
|
|
|
const diffRevision = await getDiffRevision()
|
|
|
|
const changesResult = await execa(
|
|
`git diff ${diffRevision} --name-only`,
|
|
EXECA_OPTS
|
|
).catch((err) => {
|
|
console.error(err)
|
|
return { stdout: '', stderr: '' }
|
|
})
|
|
console.log(
|
|
{
|
|
branchName,
|
|
remoteUrl,
|
|
commitSha,
|
|
},
|
|
`\ngit diff:\n${changesResult.stderr}\n${changesResult.stdout}`
|
|
)
|
|
const changedFiles = changesResult.stdout.split('\n')
|
|
|
|
// run each test 3 times in each test mode (if E2E) with no-retrying
|
|
// and if any fail it's flakey
|
|
const devTests = new Set()
|
|
const prodTests = new Set()
|
|
const deployTests = new Set()
|
|
|
|
for (let file of changedFiles) {
|
|
// normalize slashes
|
|
file = normalizePath(file)
|
|
const fileExists = await fs
|
|
.access(path.join(process.cwd(), file), fs.constants.F_OK)
|
|
.then(() => true)
|
|
.catch(() => false)
|
|
|
|
if (fileExists && file.match(TEST_FILE_REGEX)) {
|
|
if (file.startsWith('test/e2e/')) {
|
|
devTests.add(file)
|
|
prodTests.add(file)
|
|
deployTests.add(file)
|
|
} else if (file.startsWith('test/production')) {
|
|
prodTests.add(file)
|
|
} else if (file.startsWith('test/development')) {
|
|
devTests.add(file)
|
|
}
|
|
}
|
|
}
|
|
|
|
const externalTestsFilterPaths = getExternalTestsFilterPaths()
|
|
|
|
if (
|
|
externalTestsFilterPaths.some(({ repoRelativePath }) =>
|
|
changedFiles.includes(repoRelativePath)
|
|
)
|
|
) {
|
|
const currentManifest = mergeManifests(
|
|
await Promise.all(
|
|
externalTestsFilterPaths.map(async ({ absolutePath }) =>
|
|
JSON.parse(await fs.readFile(absolutePath, 'utf8'))
|
|
)
|
|
)
|
|
)
|
|
const previousManifest = mergeManifests(
|
|
(
|
|
await Promise.all(
|
|
externalTestsFilterPaths.map(async ({ repoRelativePath }) => {
|
|
const previousManifestOutput = await execa(
|
|
`git show ${diffRevision}:${repoRelativePath}`,
|
|
EXECA_OPTS
|
|
).catch(() => null)
|
|
|
|
return previousManifestOutput?.stdout
|
|
? JSON.parse(previousManifestOutput.stdout)
|
|
: null
|
|
})
|
|
)
|
|
).filter(Boolean)
|
|
)
|
|
|
|
for (const file of getDeployManifestChangedTests(
|
|
currentManifest,
|
|
previousManifest
|
|
)) {
|
|
deployTests.add(file)
|
|
}
|
|
}
|
|
|
|
const detectedTests = {
|
|
devTests: [...devTests],
|
|
prodTests: [...prodTests],
|
|
deployTests: [...deployTests],
|
|
}
|
|
|
|
console.log('Detected tests:', JSON.stringify(detectedTests, null, 2))
|
|
|
|
return { ...detectedTests, commitSha }
|
|
}
|