2024-04-19 11:02:43 +02:00
|
|
|
//@ts-check
|
|
|
|
|
2021-09-13 14:36:25 +02:00
|
|
|
const os = require('os')
|
2019-09-10 19:11:55 +02:00
|
|
|
const path = require('path')
|
|
|
|
const _glob = require('glob')
|
2023-10-17 21:31:19 +02:00
|
|
|
const { existsSync } = require('fs')
|
|
|
|
const fsp = require('fs/promises')
|
2021-08-06 17:20:05 +02:00
|
|
|
const nodeFetch = require('node-fetch')
|
|
|
|
const vercelFetch = require('@vercel/fetch')
|
2024-04-19 11:02:43 +02:00
|
|
|
// @ts-expect-error
|
2021-08-06 17:20:05 +02:00
|
|
|
const fetch = vercelFetch(nodeFetch)
|
2019-09-10 19:11:55 +02:00
|
|
|
const { promisify } = require('util')
|
|
|
|
const { Sema } = require('async-sema')
|
|
|
|
const { spawn, exec: execOrig } = require('child_process')
|
2021-09-13 14:36:25 +02:00
|
|
|
const { createNextInstall } = require('./test/lib/create-next-install')
|
2019-09-10 19:11:55 +02:00
|
|
|
const glob = promisify(_glob)
|
|
|
|
const exec = promisify(execOrig)
|
2023-10-12 15:57:09 +02:00
|
|
|
const core = require('@actions/core')
|
2023-11-29 04:22:45 +01:00
|
|
|
const { getTestFilter } = require('./test/get-test-filter')
|
|
|
|
|
|
|
|
let argv = require('yargs/yargs')(process.argv.slice(2))
|
|
|
|
.string('type')
|
|
|
|
.string('test-pattern')
|
|
|
|
.boolean('timings')
|
|
|
|
.boolean('write-timings')
|
2024-04-01 22:15:43 +02:00
|
|
|
.number('retries')
|
2023-11-29 04:22:45 +01:00
|
|
|
.boolean('debug')
|
|
|
|
.string('g')
|
|
|
|
.alias('g', 'group')
|
|
|
|
.number('c')
|
2024-04-19 11:02:43 +02:00
|
|
|
.boolean('related')
|
|
|
|
.alias('r', 'related')
|
2023-11-29 04:22:45 +01:00
|
|
|
.alias('c', 'concurrency').argv
|
2019-09-10 19:11:55 +02:00
|
|
|
|
2023-09-22 23:37:48 +02:00
|
|
|
function escapeRegexp(str) {
|
|
|
|
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2023-10-10 20:26:40 +02:00
|
|
|
* @typedef {{ file: string, excludedCases: string[] }} TestFile
|
2023-09-22 23:37:48 +02:00
|
|
|
*/
|
|
|
|
|
2023-08-02 14:31:52 +02:00
|
|
|
const GROUP = process.env.CI ? '##[group]' : ''
|
|
|
|
const ENDGROUP = process.env.CI ? '##[endgroup]' : ''
|
|
|
|
|
2023-11-29 04:22:45 +01:00
|
|
|
const externalTestsFilter = getTestFilter()
|
|
|
|
|
2020-03-04 09:54:49 +01:00
|
|
|
const timings = []
|
2021-09-13 14:36:25 +02:00
|
|
|
const DEFAULT_NUM_RETRIES = os.platform() === 'win32' ? 2 : 1
|
2019-10-28 18:24:29 +01:00
|
|
|
const DEFAULT_CONCURRENCY = 2
|
2020-03-04 09:54:49 +01:00
|
|
|
const RESULTS_EXT = `.results.json`
|
|
|
|
const isTestJob = !!process.env.NEXT_TEST_JOB
|
2023-02-14 22:30:34 +01:00
|
|
|
// Check env to see if test should continue even if some of test fails
|
2023-02-08 02:51:54 +01:00
|
|
|
const shouldContinueTestsOnError = !!process.env.NEXT_TEST_CONTINUE_ON_ERROR
|
2023-10-31 22:26:17 +01:00
|
|
|
// Check env to load a list of test paths to skip retry. This is to be used in conjunction with NEXT_TEST_CONTINUE_ON_ERROR,
|
2023-02-14 22:30:34 +01:00
|
|
|
// When try to run all of the tests regardless of pass / fail and want to skip retrying `known` failed tests.
|
|
|
|
// manifest should be a json file with an array of test paths.
|
|
|
|
const skipRetryTestManifest = process.env.NEXT_TEST_SKIP_RETRY_MANIFEST
|
|
|
|
? require(process.env.NEXT_TEST_SKIP_RETRY_MANIFEST)
|
|
|
|
: []
|
2021-09-28 17:15:04 +02:00
|
|
|
const TIMINGS_API = `https://api.github.com/gists/4500dd89ae2f5d70d9aaceb191f528d1`
|
|
|
|
const TIMINGS_API_HEADERS = {
|
|
|
|
Accept: 'application/vnd.github.v3+json',
|
2023-01-04 00:36:59 +01:00
|
|
|
...(process.env.TEST_TIMINGS_TOKEN
|
|
|
|
? {
|
|
|
|
Authorization: `Bearer ${process.env.TEST_TIMINGS_TOKEN}`,
|
|
|
|
}
|
|
|
|
: {}),
|
2021-09-28 17:15:04 +02:00
|
|
|
}
|
2019-09-10 19:11:55 +02:00
|
|
|
|
2021-08-24 14:52:45 +02:00
|
|
|
const testFilters = {
|
2023-08-01 02:04:45 +02:00
|
|
|
development: new RegExp(
|
2023-09-20 02:34:38 +02:00
|
|
|
'^(test/(development|e2e)|packages/.*/src/.*)/.*\\.test\\.(js|jsx|ts|tsx)$'
|
2023-08-01 02:04:45 +02:00
|
|
|
),
|
|
|
|
production: new RegExp(
|
|
|
|
'^(test/(production|e2e))/.*\\.test\\.(js|jsx|ts|tsx)$'
|
|
|
|
),
|
|
|
|
unit: new RegExp(
|
|
|
|
'^test/unit|packages/.*/src/.*/.*\\.test\\.(js|jsx|ts|tsx)$'
|
|
|
|
),
|
2023-02-27 11:54:24 +01:00
|
|
|
examples: 'examples/',
|
2023-08-01 02:04:45 +02:00
|
|
|
integration: 'test/integration/',
|
|
|
|
e2e: 'test/e2e/',
|
2021-08-24 14:52:45 +02:00
|
|
|
}
|
|
|
|
|
2022-12-16 09:58:04 +01:00
|
|
|
const mockTrace = () => ({
|
|
|
|
traceAsyncFn: (fn) => fn(mockTrace()),
|
Speed up createNext test suite isolation (#64909)
## What?
Before: 25.71s
![CleanShot 2024-04-23 at 12 19
42@2x](https://github.com/vercel/next.js/assets/6324199/3a0ebb81-ac55-4b0c-8bfc-9a61ce138e6f)
After: 11.05s (-57%)
![CleanShot 2024-04-23 at 12 16
35@2x](https://github.com/vercel/next.js/assets/6324199/d7b6cd4c-d1e4-4dc2-a423-20b539186d25)
## How?
Currently the system for isolation looks like this:
- Copy `packages` folder to an isolated directory
- Run `pnpm pack` for all folders in `packages`
- Collect the pack files, add them as `dependencies` in package.json of
the isolated application
- Run `pnpm install`
Because the `next-swc` (Turbopack + SWC, yes we still need to rename the
package) binary file is quite large in development (900MB+) it means we
have to copy, then zip (pnpm pack), then unzip (pnpm install) that
binary, which takes about 3+ seconds in each step.
The change in this PR is to skip the copy/zip/unzip completely by
providing the folder path for the binary directly to Next.js, as it's a
binary we don't need the special isolation for this, it's already
standalone so running it directly allows us to skip 14,6 seconds of work
that is required for each isolated test in development.
This will likely have little effect on CI times as we already do some
tricks like only running the packing at the start of the CI process.
Only thing that could be better and is probably worth doing is adopting
this change for the time it saves for unzipping, that's almost 4 seconds
per test still.
<!-- Thanks for opening a PR! Your contribution is much appreciated.
To make sure your PR is handled as smoothly as possible we request that
you follow the checklist sections below.
Choose the right checklist for the change(s) that you're making:
## For Contributors
### Improving Documentation
- Run `pnpm prettier-fix` to fix formatting issues before opening the
PR.
- Read the Docs Contribution Guide to ensure your contribution follows
the docs guidelines:
https://nextjs.org/docs/community/contribution-guide
### Adding or Updating Examples
- The "examples guidelines" are followed from our contributing doc
https://github.com/vercel/next.js/blob/canary/contributing/examples/adding-examples.md
- Make sure the linting passes by running `pnpm build && pnpm lint`. See
https://github.com/vercel/next.js/blob/canary/contributing/repository/linting.md
### Fixing a bug
- Related issues linked using `fixes #number`
- Tests added. See:
https://github.com/vercel/next.js/blob/canary/contributing/core/testing.md#writing-tests-for-nextjs
- Errors have a helpful link attached, see
https://github.com/vercel/next.js/blob/canary/contributing.md
### Adding a feature
- Implements an existing feature request or RFC. Make sure the feature
request has been accepted for implementation before opening a PR. (A
discussion must be opened, see
https://github.com/vercel/next.js/discussions/new?category=ideas)
- Related issues/discussions are linked using `fixes #number`
- e2e tests added
(https://github.com/vercel/next.js/blob/canary/contributing/core/testing.md#writing-tests-for-nextjs)
- Documentation added
- Telemetry added. In case of a feature if it's used or not.
- Errors have a helpful link attached, see
https://github.com/vercel/next.js/blob/canary/contributing.md
## For Maintainers
- Minimal description (aim for explaining to someone not on the team to
understand the PR)
- When linking to a Slack thread, you might want to share details of the
conclusion
- Link both the Linear (Fixes NEXT-xxx) and the GitHub issues
- Add review comments if necessary to explain to the reviewer the logic
behind a change
### What?
### Why?
### How?
Closes NEXT-
Fixes #
-->
Closes NEXT-3200
---------
Co-authored-by: JJ Kasper <jj@jjsweb.site>
2024-04-23 21:12:59 +02:00
|
|
|
traceFn: (fn) => fn(mockTrace()),
|
2022-12-16 09:58:04 +01:00
|
|
|
traceChild: () => mockTrace(),
|
|
|
|
})
|
|
|
|
|
2020-11-10 18:25:50 +01:00
|
|
|
// which types we have configured to run separate
|
2021-08-24 14:52:45 +02:00
|
|
|
const configuredTestTypes = Object.values(testFilters)
|
2023-10-12 15:57:09 +02:00
|
|
|
const errorsPerTests = new Map()
|
|
|
|
|
|
|
|
async function maybeLogSummary() {
|
|
|
|
if (process.env.CI && errorsPerTests.size > 0) {
|
|
|
|
const outputTemplate = `
|
|
|
|
${Array.from(errorsPerTests.entries())
|
|
|
|
.map(([test, output]) => {
|
|
|
|
return `
|
|
|
|
<details>
|
|
|
|
<summary>${test}</summary>
|
|
|
|
|
|
|
|
\`\`\`
|
|
|
|
${output}
|
|
|
|
\`\`\`
|
|
|
|
|
|
|
|
</details>
|
|
|
|
`
|
|
|
|
})
|
|
|
|
.join('\n')}`
|
|
|
|
|
|
|
|
await core.summary
|
|
|
|
.addHeading('Tests failures')
|
|
|
|
.addTable([
|
|
|
|
[
|
|
|
|
{
|
|
|
|
data: 'Test suite',
|
|
|
|
header: true,
|
|
|
|
},
|
|
|
|
],
|
|
|
|
...Array.from(errorsPerTests.entries()).map(([test]) => {
|
|
|
|
return [
|
|
|
|
`<a href="https://github.com/vercel/next.js/blob/canary/${test}">${test}</a>`,
|
|
|
|
]
|
|
|
|
}),
|
|
|
|
])
|
|
|
|
.addRaw(outputTemplate)
|
|
|
|
.write()
|
|
|
|
}
|
|
|
|
}
|
2020-11-10 18:25:50 +01:00
|
|
|
|
2024-01-17 17:09:57 +01:00
|
|
|
let exiting = false
|
|
|
|
|
2021-09-13 14:36:25 +02:00
|
|
|
const cleanUpAndExit = async (code) => {
|
2024-01-17 17:09:57 +01:00
|
|
|
if (exiting) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
exiting = true
|
|
|
|
console.log(`exiting with code ${code}`)
|
|
|
|
|
2021-09-13 14:36:25 +02:00
|
|
|
if (process.env.NEXT_TEST_STARTER) {
|
2023-10-17 21:31:19 +02:00
|
|
|
await fsp.rm(process.env.NEXT_TEST_STARTER, {
|
|
|
|
recursive: true,
|
|
|
|
force: true,
|
|
|
|
})
|
2021-09-13 14:36:25 +02:00
|
|
|
}
|
2023-04-13 08:23:59 +02:00
|
|
|
if (process.env.NEXT_TEST_TEMP_REPO) {
|
2023-10-17 21:31:19 +02:00
|
|
|
await fsp.rm(process.env.NEXT_TEST_TEMP_REPO, {
|
|
|
|
recursive: true,
|
|
|
|
force: true,
|
|
|
|
})
|
2023-04-13 08:23:59 +02:00
|
|
|
}
|
2023-10-12 15:57:09 +02:00
|
|
|
if (process.env.CI) {
|
|
|
|
await maybeLogSummary()
|
|
|
|
}
|
2024-01-17 17:09:57 +01:00
|
|
|
process.exit(code)
|
2021-09-13 14:36:25 +02:00
|
|
|
}
|
|
|
|
|
2023-09-22 23:37:48 +02:00
|
|
|
const isMatchingPattern = (pattern, file) => {
|
2023-08-01 02:04:45 +02:00
|
|
|
if (pattern instanceof RegExp) {
|
2023-09-22 23:37:48 +02:00
|
|
|
return pattern.test(file)
|
2023-08-01 02:04:45 +02:00
|
|
|
} else {
|
2023-09-22 23:37:48 +02:00
|
|
|
return file.startsWith(pattern)
|
2023-08-01 02:04:45 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-09-28 17:15:04 +02:00
|
|
|
async function getTestTimings() {
|
2022-12-31 09:12:42 +01:00
|
|
|
let timingsRes
|
|
|
|
|
|
|
|
const doFetch = () =>
|
|
|
|
fetch(TIMINGS_API, {
|
|
|
|
headers: {
|
|
|
|
...TIMINGS_API_HEADERS,
|
|
|
|
},
|
|
|
|
})
|
|
|
|
timingsRes = await doFetch()
|
|
|
|
|
|
|
|
if (timingsRes.status === 403) {
|
|
|
|
const delay = 15
|
|
|
|
console.log(`Got 403 response waiting ${delay} seconds before retry`)
|
|
|
|
await new Promise((resolve) => setTimeout(resolve, delay * 1000))
|
|
|
|
timingsRes = await doFetch()
|
|
|
|
}
|
2021-09-28 17:15:04 +02:00
|
|
|
|
|
|
|
if (!timingsRes.ok) {
|
|
|
|
throw new Error(`request status: ${timingsRes.status}`)
|
|
|
|
}
|
|
|
|
const timingsData = await timingsRes.json()
|
|
|
|
return JSON.parse(timingsData.files['test-timings.json'].content)
|
|
|
|
}
|
|
|
|
|
2021-03-16 22:08:35 +01:00
|
|
|
async function main() {
|
2023-11-29 04:22:45 +01:00
|
|
|
// Ensure we have the arguments awaited from yargs.
|
|
|
|
argv = await argv
|
|
|
|
|
|
|
|
const options = {
|
|
|
|
concurrency: argv.concurrency || DEFAULT_CONCURRENCY,
|
|
|
|
debug: argv.debug ?? false,
|
|
|
|
timings: argv.timings ?? false,
|
|
|
|
writeTimings: argv.writeTimings ?? false,
|
|
|
|
group: argv.group ?? false,
|
|
|
|
testPattern: argv.testPattern ?? false,
|
|
|
|
type: argv.type ?? false,
|
2024-04-19 11:02:43 +02:00
|
|
|
related: argv.related ?? false,
|
2024-04-01 22:15:43 +02:00
|
|
|
retries: argv.retries ?? DEFAULT_NUM_RETRIES,
|
2023-11-29 04:22:45 +01:00
|
|
|
}
|
2024-04-01 22:15:43 +02:00
|
|
|
let numRetries = options.retries
|
2023-11-29 04:22:45 +01:00
|
|
|
const hideOutput = !options.debug
|
|
|
|
|
2020-11-10 18:25:50 +01:00
|
|
|
let filterTestsBy
|
|
|
|
|
2023-11-29 04:22:45 +01:00
|
|
|
switch (options.type) {
|
2021-08-24 14:52:45 +02:00
|
|
|
case 'unit': {
|
|
|
|
numRetries = 0
|
|
|
|
filterTestsBy = testFilters.unit
|
2020-11-10 18:25:50 +01:00
|
|
|
break
|
2021-08-24 14:52:45 +02:00
|
|
|
}
|
2023-08-01 02:04:45 +02:00
|
|
|
case 'all': {
|
|
|
|
filterTestsBy = 'none'
|
2021-09-13 14:36:25 +02:00
|
|
|
break
|
|
|
|
}
|
2023-08-01 02:04:45 +02:00
|
|
|
default: {
|
2023-11-29 04:22:45 +01:00
|
|
|
filterTestsBy = testFilters[options.type]
|
2023-02-27 11:54:24 +01:00
|
|
|
break
|
|
|
|
}
|
2020-11-10 18:25:50 +01:00
|
|
|
}
|
|
|
|
|
2023-11-29 04:22:45 +01:00
|
|
|
console.log('Running tests with concurrency:', options.concurrency)
|
2021-09-13 14:36:25 +02:00
|
|
|
|
2023-09-22 23:37:48 +02:00
|
|
|
/** @type TestFile[] */
|
2024-04-19 11:02:43 +02:00
|
|
|
let tests = argv._.filter((arg) =>
|
|
|
|
arg.toString().match(/\.test\.(js|ts|tsx)/)
|
|
|
|
).map((file) => ({ file: file.toString(), excludedCases: [] }))
|
2020-01-23 18:37:01 +01:00
|
|
|
let prevTimings
|
2019-09-10 19:11:55 +02:00
|
|
|
|
|
|
|
if (tests.length === 0) {
|
2024-04-19 11:02:43 +02:00
|
|
|
/** @type {RegExp | undefined} */
|
2023-05-28 06:02:31 +02:00
|
|
|
let testPatternRegex
|
|
|
|
|
2024-04-19 11:02:43 +02:00
|
|
|
if (options.testPattern && typeof options.testPattern === 'string') {
|
2023-11-29 04:22:45 +01:00
|
|
|
testPatternRegex = new RegExp(options.testPattern)
|
2023-05-28 06:02:31 +02:00
|
|
|
}
|
|
|
|
|
2024-04-19 11:02:43 +02:00
|
|
|
if (options.related) {
|
|
|
|
const { getRelatedTests } = await import('./scripts/run-related-test.mjs')
|
|
|
|
const tests = await getRelatedTests()
|
|
|
|
if (tests.length)
|
|
|
|
testPatternRegex = new RegExp(tests.map(escapeRegexp).join('|'))
|
|
|
|
|
|
|
|
if (testPatternRegex) {
|
|
|
|
console.log('Running related tests:', testPatternRegex.toString())
|
|
|
|
} else {
|
2024-04-19 12:19:26 +02:00
|
|
|
console.log('No matching related tests, exiting.')
|
|
|
|
process.exit(0)
|
2024-04-19 11:02:43 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-11-10 18:25:50 +01:00
|
|
|
tests = (
|
2021-08-24 14:52:45 +02:00
|
|
|
await glob('**/*.test.{js,ts,tsx}', {
|
2020-11-10 18:25:50 +01:00
|
|
|
nodir: true,
|
2023-07-28 15:54:15 +02:00
|
|
|
cwd: __dirname,
|
|
|
|
ignore: '**/node_modules/**',
|
2020-11-10 18:25:50 +01:00
|
|
|
})
|
2023-09-22 23:37:48 +02:00
|
|
|
)
|
|
|
|
.filter((file) => {
|
|
|
|
if (testPatternRegex) {
|
|
|
|
return testPatternRegex.test(file)
|
2023-08-01 02:04:45 +02:00
|
|
|
}
|
2023-09-22 23:37:48 +02:00
|
|
|
if (filterTestsBy) {
|
|
|
|
// only include the specified type
|
|
|
|
if (filterTestsBy === 'none') {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
return isMatchingPattern(filterTestsBy, file)
|
|
|
|
}
|
|
|
|
// include all except the separately configured types
|
|
|
|
return !configuredTestTypes.some((type) =>
|
|
|
|
isMatchingPattern(type, file)
|
|
|
|
)
|
|
|
|
})
|
|
|
|
.map((file) => ({
|
|
|
|
file,
|
2023-10-10 20:26:40 +02:00
|
|
|
excludedCases: [],
|
2023-09-22 23:37:48 +02:00
|
|
|
}))
|
2023-05-28 06:02:31 +02:00
|
|
|
}
|
2020-01-23 18:37:01 +01:00
|
|
|
|
2023-11-29 04:22:45 +01:00
|
|
|
if (options.timings && options.group) {
|
2023-05-28 06:02:31 +02:00
|
|
|
console.log('Fetching previous timings data')
|
|
|
|
try {
|
|
|
|
const timingsFile = path.join(process.cwd(), 'test-timings.json')
|
2020-01-27 21:07:31 +01:00
|
|
|
try {
|
2023-10-17 21:31:19 +02:00
|
|
|
prevTimings = JSON.parse(await fsp.readFile(timingsFile, 'utf8'))
|
2023-05-28 06:02:31 +02:00
|
|
|
console.log('Loaded test timings from disk successfully')
|
|
|
|
} catch (_) {
|
|
|
|
console.error('failed to load from disk', _)
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!prevTimings) {
|
|
|
|
prevTimings = await getTestTimings()
|
|
|
|
console.log('Fetched previous timings data successfully')
|
|
|
|
|
2023-11-29 04:22:45 +01:00
|
|
|
if (options.writeTimings) {
|
2023-10-17 21:31:19 +02:00
|
|
|
await fsp.writeFile(timingsFile, JSON.stringify(prevTimings))
|
2023-05-28 06:02:31 +02:00
|
|
|
console.log('Wrote previous timings data to', timingsFile)
|
|
|
|
await cleanUpAndExit(0)
|
2020-01-23 18:37:01 +01:00
|
|
|
}
|
|
|
|
}
|
2023-05-28 06:02:31 +02:00
|
|
|
} catch (err) {
|
|
|
|
console.log(`Failed to fetch timings data`, err)
|
|
|
|
await cleanUpAndExit(1)
|
2020-01-23 18:37:01 +01:00
|
|
|
}
|
2019-09-10 19:11:55 +02:00
|
|
|
}
|
|
|
|
|
2023-05-23 10:55:33 +02:00
|
|
|
// If there are external manifest contains list of tests, apply it to the test lists.
|
2023-11-29 04:22:45 +01:00
|
|
|
if (externalTestsFilter) {
|
|
|
|
tests = externalTestsFilter(tests)
|
2023-05-23 10:55:33 +02:00
|
|
|
}
|
|
|
|
|
2023-09-22 23:37:48 +02:00
|
|
|
let testSet = new Set()
|
|
|
|
tests = tests
|
|
|
|
.map((test) => {
|
|
|
|
test.file = test.file.replace(/\\/g, '/').replace(/\/test$/, '')
|
|
|
|
return test
|
|
|
|
})
|
|
|
|
.filter((test) => {
|
|
|
|
if (testSet.has(test.file)) return false
|
|
|
|
testSet.add(test.file)
|
|
|
|
return true
|
|
|
|
})
|
2019-09-10 19:11:55 +02:00
|
|
|
|
2024-04-19 11:02:43 +02:00
|
|
|
if (options.group && typeof options.group === 'string') {
|
2023-11-29 04:22:45 +01:00
|
|
|
const groupParts = options.group.split('/')
|
2019-09-10 19:11:55 +02:00
|
|
|
const groupPos = parseInt(groupParts[0], 10)
|
|
|
|
const groupTotal = parseInt(groupParts[1], 10)
|
|
|
|
|
2020-01-23 18:37:01 +01:00
|
|
|
if (prevTimings) {
|
2024-04-19 11:02:43 +02:00
|
|
|
/** @type {TestFile[][]} */
|
2020-01-23 18:37:01 +01:00
|
|
|
const groups = [[]]
|
|
|
|
const groupTimes = [0]
|
|
|
|
|
2023-09-22 23:37:48 +02:00
|
|
|
for (const test of tests) {
|
2020-01-23 18:37:01 +01:00
|
|
|
let smallestGroup = groupTimes[0]
|
|
|
|
let smallestGroupIdx = 0
|
|
|
|
|
2020-11-10 18:25:50 +01:00
|
|
|
// get the smallest group time to add current one to
|
2020-01-23 18:37:01 +01:00
|
|
|
for (let i = 1; i < groupTotal; i++) {
|
|
|
|
if (!groups[i]) {
|
|
|
|
groups[i] = []
|
|
|
|
groupTimes[i] = 0
|
|
|
|
}
|
|
|
|
|
|
|
|
const time = groupTimes[i]
|
|
|
|
if (time < smallestGroup) {
|
|
|
|
smallestGroup = time
|
|
|
|
smallestGroupIdx = i
|
|
|
|
}
|
|
|
|
}
|
2023-09-22 23:37:48 +02:00
|
|
|
groups[smallestGroupIdx].push(test)
|
|
|
|
groupTimes[smallestGroupIdx] += prevTimings[test.file] || 1
|
2020-01-23 18:37:01 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
const curGroupIdx = groupPos - 1
|
2023-09-22 23:37:48 +02:00
|
|
|
tests = groups[curGroupIdx]
|
2020-01-23 18:37:01 +01:00
|
|
|
|
|
|
|
console.log(
|
|
|
|
'Current group previous accumulated times:',
|
|
|
|
Math.round(groupTimes[curGroupIdx]) + 's'
|
|
|
|
)
|
|
|
|
} else {
|
2023-09-22 23:37:48 +02:00
|
|
|
const numPerGroup = Math.ceil(tests.length / groupTotal)
|
2021-10-11 21:23:33 +02:00
|
|
|
let offset = (groupPos - 1) * numPerGroup
|
2023-09-22 23:37:48 +02:00
|
|
|
tests = tests.slice(offset, offset + numPerGroup)
|
2023-05-28 06:02:31 +02:00
|
|
|
console.log('Splitting without timings')
|
2020-01-23 18:37:01 +01:00
|
|
|
}
|
|
|
|
}
|
2021-09-13 14:36:25 +02:00
|
|
|
|
2024-03-19 10:26:14 +01:00
|
|
|
if (!tests) {
|
|
|
|
tests = []
|
|
|
|
}
|
|
|
|
|
2023-09-22 23:37:48 +02:00
|
|
|
if (tests.length === 0) {
|
2023-11-29 04:22:45 +01:00
|
|
|
console.log('No tests found for', options.type, 'exiting..')
|
2021-09-13 14:36:25 +02:00
|
|
|
}
|
|
|
|
|
2023-08-02 14:31:52 +02:00
|
|
|
console.log(`${GROUP}Running tests:
|
2023-09-22 23:37:48 +02:00
|
|
|
${tests.map((t) => t.file).join('\n')}
|
2023-08-02 14:31:52 +02:00
|
|
|
${ENDGROUP}`)
|
2023-09-22 23:37:48 +02:00
|
|
|
console.log(`total: ${tests.length}`)
|
2019-11-25 23:50:46 +01:00
|
|
|
|
2023-09-22 23:37:48 +02:00
|
|
|
const hasIsolatedTests = tests.some((test) => {
|
2021-09-13 14:36:25 +02:00
|
|
|
return configuredTestTypes.some(
|
2023-09-22 23:37:48 +02:00
|
|
|
(type) =>
|
|
|
|
type !== testFilters.unit && test.file.startsWith(`test/${type}`)
|
2021-09-13 14:36:25 +02:00
|
|
|
)
|
|
|
|
})
|
|
|
|
|
2022-05-24 00:37:21 +02:00
|
|
|
if (
|
2022-12-31 09:12:42 +01:00
|
|
|
process.platform !== 'win32' &&
|
2022-05-24 00:37:21 +02:00
|
|
|
process.env.NEXT_TEST_MODE !== 'deploy' &&
|
2023-11-29 04:22:45 +01:00
|
|
|
((options.type && options.type !== 'unit') || hasIsolatedTests)
|
2022-05-24 00:37:21 +02:00
|
|
|
) {
|
2021-09-13 14:36:25 +02:00
|
|
|
// for isolated next tests: e2e, dev, prod we create
|
|
|
|
// a starter Next.js install to re-use to speed up tests
|
|
|
|
// to avoid having to run yarn each time
|
2023-08-02 14:31:52 +02:00
|
|
|
console.log(`${GROUP}Creating Next.js install for isolated tests`)
|
2022-04-01 00:35:00 +02:00
|
|
|
const reactVersion = process.env.NEXT_TEST_REACT_VERSION || 'latest'
|
2023-04-13 08:23:59 +02:00
|
|
|
const { installDir, pkgPaths, tmpRepoDir } = await createNextInstall({
|
2022-12-16 09:58:04 +01:00
|
|
|
parentSpan: mockTrace(),
|
|
|
|
dependencies: {
|
|
|
|
react: reactVersion,
|
|
|
|
'react-dom': reactVersion,
|
|
|
|
},
|
2023-04-13 08:23:59 +02:00
|
|
|
keepRepoDir: true,
|
2021-09-13 14:36:25 +02:00
|
|
|
})
|
2023-04-13 08:23:59 +02:00
|
|
|
|
|
|
|
const serializedPkgPaths = []
|
|
|
|
|
|
|
|
for (const key of pkgPaths.keys()) {
|
|
|
|
serializedPkgPaths.push([key, pkgPaths.get(key)])
|
|
|
|
}
|
|
|
|
process.env.NEXT_TEST_PKG_PATHS = JSON.stringify(serializedPkgPaths)
|
|
|
|
process.env.NEXT_TEST_TEMP_REPO = tmpRepoDir
|
|
|
|
process.env.NEXT_TEST_STARTER = installDir
|
2023-08-02 14:31:52 +02:00
|
|
|
console.log(`${ENDGROUP}`)
|
2021-09-13 14:36:25 +02:00
|
|
|
}
|
|
|
|
|
2023-11-29 04:22:45 +01:00
|
|
|
const sema = new Sema(options.concurrency, { capacity: tests.length })
|
2023-09-22 23:37:48 +02:00
|
|
|
const outputSema = new Sema(1, { capacity: tests.length })
|
2022-05-29 06:35:16 +02:00
|
|
|
const children = new Set()
|
2019-09-10 19:11:55 +02:00
|
|
|
const jestPath = path.join(
|
2022-05-29 06:35:16 +02:00
|
|
|
__dirname,
|
|
|
|
'node_modules',
|
|
|
|
'.bin',
|
|
|
|
`jest${process.platform === 'win32' ? '.CMD' : ''}`
|
2019-09-10 19:11:55 +02:00
|
|
|
)
|
2023-09-21 17:07:00 +02:00
|
|
|
let firstError = true
|
|
|
|
let killed = false
|
2019-09-10 19:11:55 +02:00
|
|
|
|
2023-09-22 23:37:48 +02:00
|
|
|
const runTest = (/** @type {TestFile} */ test, isFinalRun, isRetry) =>
|
2019-09-10 19:11:55 +02:00
|
|
|
new Promise((resolve, reject) => {
|
2019-11-25 23:50:46 +01:00
|
|
|
const start = new Date().getTime()
|
2021-07-29 17:35:13 +02:00
|
|
|
let outputChunks = []
|
2022-09-29 23:45:10 +02:00
|
|
|
|
|
|
|
const shouldRecordTestWithReplay = process.env.RECORD_REPLAY && isRetry
|
|
|
|
|
2023-09-22 23:37:48 +02:00
|
|
|
const args = [
|
|
|
|
...(shouldRecordTestWithReplay
|
|
|
|
? [`--config=jest.replay.config.js`]
|
|
|
|
: []),
|
2024-01-11 10:23:20 +01:00
|
|
|
...(process.env.CI ? ['--ci'] : []),
|
2023-09-22 23:37:48 +02:00
|
|
|
'--runInBand',
|
|
|
|
'--forceExit',
|
|
|
|
'--verbose',
|
|
|
|
'--silent',
|
|
|
|
...(isTestJob
|
|
|
|
? ['--json', `--outputFile=${test.file}${RESULTS_EXT}`]
|
|
|
|
: []),
|
|
|
|
test.file,
|
2023-10-10 20:26:40 +02:00
|
|
|
...(test.excludedCases.length === 0
|
2023-09-22 23:37:48 +02:00
|
|
|
? []
|
|
|
|
: [
|
|
|
|
'--testNamePattern',
|
2023-11-09 21:43:35 +01:00
|
|
|
`^(?!(?:${test.excludedCases.map(escapeRegexp).join('|')})$).`,
|
2023-09-22 23:37:48 +02:00
|
|
|
]),
|
|
|
|
]
|
|
|
|
const env = {
|
|
|
|
IS_RETRY: isRetry ? 'true' : undefined,
|
|
|
|
RECORD_REPLAY: shouldRecordTestWithReplay,
|
|
|
|
// run tests in headless mode by default
|
|
|
|
HEADLESS: 'true',
|
|
|
|
TRACE_PLAYWRIGHT: 'true',
|
|
|
|
NEXT_TELEMETRY_DISABLED: '1',
|
|
|
|
// unset CI env so CI behavior is only explicitly
|
|
|
|
// tested when enabled
|
|
|
|
CI: '',
|
|
|
|
CIRCLECI: '',
|
|
|
|
GITHUB_ACTIONS: '',
|
|
|
|
CONTINUOUS_INTEGRATION: '',
|
|
|
|
RUN_ID: '',
|
|
|
|
BUILD_NUMBER: '',
|
|
|
|
// Format the output of junit report to include the test name
|
|
|
|
// For the debugging purpose to compare actual run list to the generated reports
|
|
|
|
// [NOTE]: This won't affect if junit reporter is not enabled
|
2024-04-19 11:02:43 +02:00
|
|
|
// @ts-expect-error .replaceAll() does exist. Follow-up why TS is not recognizing it
|
2023-09-22 23:37:48 +02:00
|
|
|
JEST_JUNIT_OUTPUT_NAME: test.file.replaceAll('/', '_'),
|
|
|
|
// Specify suite name for the test to avoid unexpected merging across different env / grouped tests
|
|
|
|
// This is not individual suites name (corresponding 'describe'), top level suite name which have redundant names by default
|
|
|
|
// [NOTE]: This won't affect if junit reporter is not enabled
|
|
|
|
JEST_SUITE_NAME: [
|
|
|
|
`${process.env.NEXT_TEST_MODE ?? 'default'}`,
|
2023-11-29 04:22:45 +01:00
|
|
|
options.group,
|
|
|
|
options.type,
|
2023-09-22 23:37:48 +02:00
|
|
|
test.file,
|
|
|
|
]
|
|
|
|
.filter(Boolean)
|
|
|
|
.join(':'),
|
|
|
|
...(isFinalRun
|
|
|
|
? {
|
|
|
|
// Events can be finicky in CI. This switches to a more
|
|
|
|
// reliable polling method.
|
|
|
|
// CHOKIDAR_USEPOLLING: 'true',
|
|
|
|
// CHOKIDAR_INTERVAL: 500,
|
|
|
|
// WATCHPACK_POLLING: 500,
|
|
|
|
}
|
|
|
|
: {}),
|
|
|
|
}
|
|
|
|
|
2022-07-27 18:41:42 +02:00
|
|
|
const handleOutput = (type) => (chunk) => {
|
2023-05-28 06:02:31 +02:00
|
|
|
if (hideOutput) {
|
2022-07-27 18:41:42 +02:00
|
|
|
outputChunks.push({ type, chunk })
|
2021-09-13 14:36:25 +02:00
|
|
|
} else {
|
2023-08-02 14:31:52 +02:00
|
|
|
process.stdout.write(chunk)
|
2021-09-13 14:36:25 +02:00
|
|
|
}
|
|
|
|
}
|
2023-09-22 23:37:48 +02:00
|
|
|
const stdout = handleOutput('stdout')
|
|
|
|
stdout(
|
|
|
|
[
|
|
|
|
...Object.entries(env).map((e) => `${e[0]}=${e[1]}`),
|
|
|
|
jestPath,
|
|
|
|
...args.map((a) => `'${a}'`),
|
2023-09-26 16:58:34 +02:00
|
|
|
].join(' ') + '\n'
|
2023-09-22 23:37:48 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
const child = spawn(jestPath, args, {
|
|
|
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
|
|
env: {
|
|
|
|
...process.env,
|
|
|
|
...env,
|
|
|
|
},
|
|
|
|
})
|
|
|
|
child.stdout.on('data', stdout)
|
2022-07-27 18:41:42 +02:00
|
|
|
child.stderr.on('data', handleOutput('stderr'))
|
2021-09-13 14:36:25 +02:00
|
|
|
|
2019-09-17 19:01:46 +02:00
|
|
|
children.add(child)
|
2021-09-13 14:36:25 +02:00
|
|
|
|
2021-12-21 19:52:07 +01:00
|
|
|
child.on('exit', async (code, signal) => {
|
2019-09-10 19:11:55 +02:00
|
|
|
children.delete(child)
|
2023-12-11 17:21:46 +01:00
|
|
|
const isChildExitWithNonZero = code !== 0 || signal !== null
|
|
|
|
if (isChildExitWithNonZero) {
|
2022-08-16 16:14:37 +02:00
|
|
|
if (hideOutput) {
|
2023-08-02 14:31:52 +02:00
|
|
|
await outputSema.acquire()
|
2023-09-21 17:07:00 +02:00
|
|
|
const isExpanded =
|
|
|
|
firstError && !killed && !shouldContinueTestsOnError
|
|
|
|
if (isExpanded) {
|
|
|
|
firstError = false
|
2023-09-22 23:37:48 +02:00
|
|
|
process.stdout.write(`❌ ${test.file} output:\n`)
|
2023-09-21 17:07:00 +02:00
|
|
|
} else if (killed) {
|
2023-09-22 23:37:48 +02:00
|
|
|
process.stdout.write(`${GROUP}${test.file} output (killed)\n`)
|
2023-09-21 17:07:00 +02:00
|
|
|
} else {
|
2023-09-22 23:37:48 +02:00
|
|
|
process.stdout.write(`${GROUP}❌ ${test.file} output\n`)
|
2023-09-21 17:07:00 +02:00
|
|
|
}
|
2023-10-12 15:57:09 +02:00
|
|
|
|
|
|
|
let output = ''
|
2021-09-07 17:02:29 +02:00
|
|
|
// limit out to last 64kb so that we don't
|
|
|
|
// run out of log room in CI
|
2023-08-02 14:31:52 +02:00
|
|
|
for (const { chunk } of outputChunks) {
|
|
|
|
process.stdout.write(chunk)
|
2023-10-12 15:57:09 +02:00
|
|
|
output += chunk.toString()
|
2023-08-02 14:31:52 +02:00
|
|
|
}
|
2023-10-12 15:57:09 +02:00
|
|
|
|
|
|
|
if (process.env.CI && !killed) {
|
|
|
|
errorsPerTests.set(test.file, output)
|
|
|
|
}
|
|
|
|
|
2023-09-21 17:07:00 +02:00
|
|
|
if (isExpanded) {
|
2023-09-22 23:37:48 +02:00
|
|
|
process.stdout.write(`end of ${test.file} output\n`)
|
2023-09-21 17:07:00 +02:00
|
|
|
} else {
|
2023-09-22 23:37:48 +02:00
|
|
|
process.stdout.write(`end of ${test.file} output\n${ENDGROUP}\n`)
|
2023-09-21 17:07:00 +02:00
|
|
|
}
|
2023-08-02 14:31:52 +02:00
|
|
|
outputSema.release()
|
2021-08-24 14:52:45 +02:00
|
|
|
}
|
2023-05-28 06:02:31 +02:00
|
|
|
const err = new Error(
|
|
|
|
code ? `failed with code: ${code}` : `failed with signal: ${signal}`
|
2021-12-21 19:52:07 +01:00
|
|
|
)
|
2024-04-19 11:02:43 +02:00
|
|
|
// @ts-expect-error
|
2023-08-02 14:31:52 +02:00
|
|
|
err.output = outputChunks
|
|
|
|
.map(({ chunk }) => chunk.toString())
|
|
|
|
.join('')
|
2023-05-28 06:02:31 +02:00
|
|
|
|
|
|
|
return reject(err)
|
2021-07-29 17:35:13 +02:00
|
|
|
}
|
2023-12-11 17:21:46 +01:00
|
|
|
|
|
|
|
// If environment is CI and if this test execution is failed after retry, preserve test traces
|
|
|
|
// to upload into github actions artifacts for debugging purpose
|
|
|
|
const shouldPreserveTracesOutput =
|
2023-12-13 00:18:47 +01:00
|
|
|
(process.env.CI && isRetry && isChildExitWithNonZero) ||
|
|
|
|
process.env.PRESERVE_TRACES_OUTPUT
|
2023-12-11 17:21:46 +01:00
|
|
|
if (!shouldPreserveTracesOutput) {
|
|
|
|
await fsp
|
|
|
|
.rm(
|
|
|
|
path.join(
|
|
|
|
__dirname,
|
|
|
|
'test/traces',
|
|
|
|
path
|
|
|
|
.relative(path.join(__dirname, 'test'), test.file)
|
|
|
|
.replace(/\//g, '-')
|
|
|
|
),
|
|
|
|
{ recursive: true, force: true }
|
|
|
|
)
|
|
|
|
.catch(() => {})
|
|
|
|
}
|
|
|
|
|
2019-11-25 23:50:46 +01:00
|
|
|
resolve(new Date().getTime() - start)
|
2019-09-17 19:01:46 +02:00
|
|
|
})
|
2019-09-10 19:11:55 +02:00
|
|
|
})
|
|
|
|
|
2021-10-11 21:23:33 +02:00
|
|
|
const directorySemas = new Map()
|
|
|
|
|
2023-02-15 06:56:08 +01:00
|
|
|
const originalRetries = numRetries
|
2019-09-10 19:11:55 +02:00
|
|
|
await Promise.all(
|
2023-09-22 23:37:48 +02:00
|
|
|
tests.map(async (test) => {
|
|
|
|
const dirName = path.dirname(test.file)
|
2021-10-11 21:23:33 +02:00
|
|
|
let dirSema = directorySemas.get(dirName)
|
2023-05-28 06:02:31 +02:00
|
|
|
|
|
|
|
// we only restrict 1 test per directory for
|
|
|
|
// legacy integration tests
|
2023-09-22 23:37:48 +02:00
|
|
|
if (test.file.startsWith('test/integration') && dirSema === undefined) {
|
2021-10-11 21:23:33 +02:00
|
|
|
directorySemas.set(dirName, (dirSema = new Sema(1)))
|
2023-05-28 06:02:31 +02:00
|
|
|
}
|
|
|
|
if (dirSema) await dirSema.acquire()
|
|
|
|
|
2019-09-10 19:11:55 +02:00
|
|
|
await sema.acquire()
|
|
|
|
let passed = false
|
|
|
|
|
2023-02-15 06:56:08 +01:00
|
|
|
const shouldSkipRetries = skipRetryTestManifest.find((t) =>
|
2023-09-22 23:37:48 +02:00
|
|
|
t.includes(test.file)
|
2023-02-15 06:56:08 +01:00
|
|
|
)
|
|
|
|
const numRetries = shouldSkipRetries ? 0 : originalRetries
|
|
|
|
if (shouldSkipRetries) {
|
2023-09-22 23:37:48 +02:00
|
|
|
console.log(
|
|
|
|
`Skipping retry for ${test.file} due to skipRetryTestManifest`
|
|
|
|
)
|
2023-02-15 06:56:08 +01:00
|
|
|
}
|
|
|
|
|
2021-08-24 14:52:45 +02:00
|
|
|
for (let i = 0; i < numRetries + 1; i++) {
|
2019-09-10 19:11:55 +02:00
|
|
|
try {
|
2023-09-22 23:37:48 +02:00
|
|
|
console.log(`Starting ${test.file} retry ${i}/${numRetries}`)
|
2023-02-15 06:56:08 +01:00
|
|
|
const time = await runTest(
|
|
|
|
test,
|
|
|
|
shouldSkipRetries || i === numRetries,
|
|
|
|
shouldSkipRetries || i > 0
|
|
|
|
)
|
2019-11-25 23:50:46 +01:00
|
|
|
timings.push({
|
2023-09-22 23:37:48 +02:00
|
|
|
file: test.file,
|
2019-11-25 23:50:46 +01:00
|
|
|
time,
|
|
|
|
})
|
2019-09-10 19:11:55 +02:00
|
|
|
passed = true
|
2021-07-29 17:35:13 +02:00
|
|
|
console.log(
|
2023-09-22 23:37:48 +02:00
|
|
|
`Finished ${test.file} on retry ${i}/${numRetries} in ${
|
|
|
|
time / 1000
|
|
|
|
}s`
|
2021-07-29 17:35:13 +02:00
|
|
|
)
|
2019-09-10 19:11:55 +02:00
|
|
|
break
|
|
|
|
} catch (err) {
|
2023-06-07 00:37:05 +02:00
|
|
|
if (i < numRetries) {
|
2019-09-10 19:11:55 +02:00
|
|
|
try {
|
2023-09-22 23:37:48 +02:00
|
|
|
let testDir = path.dirname(path.join(__dirname, test.file))
|
2022-11-16 22:16:35 +01:00
|
|
|
|
|
|
|
// if test is nested in a test folder traverse up a dir to ensure
|
|
|
|
// we clean up relevant test files
|
|
|
|
if (testDir.endsWith('/test') || testDir.endsWith('\\test')) {
|
|
|
|
testDir = path.join(testDir, '../')
|
|
|
|
}
|
2020-01-23 18:37:01 +01:00
|
|
|
console.log('Cleaning test files at', testDir)
|
|
|
|
await exec(`git clean -fdx "${testDir}"`)
|
|
|
|
await exec(`git checkout "${testDir}"`)
|
2019-09-10 19:11:55 +02:00
|
|
|
} catch (err) {}
|
2021-12-21 19:52:07 +01:00
|
|
|
} else {
|
2023-09-22 23:37:48 +02:00
|
|
|
console.error(`${test.file} failed due to ${err}`)
|
2019-09-10 19:11:55 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2023-02-17 00:44:38 +01:00
|
|
|
|
2019-09-10 19:11:55 +02:00
|
|
|
if (!passed) {
|
2023-09-22 23:37:48 +02:00
|
|
|
console.error(
|
|
|
|
`${test.file} failed to pass within ${numRetries} retries`
|
|
|
|
)
|
2020-03-04 09:54:49 +01:00
|
|
|
|
2023-02-08 02:51:54 +01:00
|
|
|
if (!shouldContinueTestsOnError) {
|
2023-09-21 17:07:00 +02:00
|
|
|
killed = true
|
2023-05-28 06:02:31 +02:00
|
|
|
children.forEach((child) => child.kill())
|
2023-02-08 02:51:54 +01:00
|
|
|
cleanUpAndExit(1)
|
|
|
|
} else {
|
|
|
|
console.log(
|
2023-09-22 23:37:48 +02:00
|
|
|
`CONTINUE_ON_ERROR enabled, continuing tests after ${test.file} failed`
|
2023-02-08 02:51:54 +01:00
|
|
|
)
|
|
|
|
}
|
2019-09-10 19:11:55 +02:00
|
|
|
}
|
2023-02-17 00:44:38 +01:00
|
|
|
|
|
|
|
// Emit test output if test failed or if we're continuing tests on error
|
|
|
|
if ((!passed || shouldContinueTestsOnError) && isTestJob) {
|
|
|
|
try {
|
2023-10-17 21:31:19 +02:00
|
|
|
const testsOutput = await fsp.readFile(
|
2023-09-22 23:37:48 +02:00
|
|
|
`${test.file}${RESULTS_EXT}`,
|
|
|
|
'utf8'
|
|
|
|
)
|
2023-08-25 01:12:23 +02:00
|
|
|
const obj = JSON.parse(testsOutput)
|
|
|
|
obj.processEnv = {
|
|
|
|
NEXT_TEST_MODE: process.env.NEXT_TEST_MODE,
|
|
|
|
HEADLESS: process.env.HEADLESS,
|
|
|
|
}
|
2023-08-02 14:31:52 +02:00
|
|
|
await outputSema.acquire()
|
|
|
|
if (GROUP) console.log(`${GROUP}Result as JSON for tooling`)
|
2023-02-17 00:44:38 +01:00
|
|
|
console.log(
|
|
|
|
`--test output start--`,
|
2023-08-25 01:12:23 +02:00
|
|
|
JSON.stringify(obj),
|
2023-02-17 00:44:38 +01:00
|
|
|
`--test output end--`
|
|
|
|
)
|
2023-08-02 14:31:52 +02:00
|
|
|
if (ENDGROUP) console.log(ENDGROUP)
|
|
|
|
outputSema.release()
|
2023-02-17 00:44:38 +01:00
|
|
|
} catch (err) {
|
|
|
|
console.log(`Failed to load test output`, err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-09-10 19:11:55 +02:00
|
|
|
sema.release()
|
2023-05-28 06:02:31 +02:00
|
|
|
if (dirSema) dirSema.release()
|
2019-09-10 19:11:55 +02:00
|
|
|
})
|
|
|
|
)
|
2019-11-25 23:50:46 +01:00
|
|
|
|
2023-11-29 04:22:45 +01:00
|
|
|
if (options.timings) {
|
2020-01-27 21:07:31 +01:00
|
|
|
const curTimings = {}
|
|
|
|
// let junitData = `<testsuites name="jest tests">`
|
2019-11-25 23:50:46 +01:00
|
|
|
/*
|
|
|
|
<testsuite name="/__tests__/bar.test.js" tests="1" errors="0" failures="0" skipped="0" timestamp="2017-10-10T21:56:49" time="0.323">
|
|
|
|
<testcase classname="bar-should be bar" name="bar-should be bar" time="0.004">
|
|
|
|
</testcase>
|
|
|
|
</testsuite>
|
|
|
|
*/
|
|
|
|
|
|
|
|
for (const timing of timings) {
|
|
|
|
const timeInSeconds = timing.time / 1000
|
2020-01-27 21:07:31 +01:00
|
|
|
curTimings[timing.file] = timeInSeconds
|
|
|
|
|
|
|
|
// junitData += `
|
|
|
|
// <testsuite name="${timing.file}" file="${
|
|
|
|
// timing.file
|
|
|
|
// }" tests="1" errors="0" failures="0" skipped="0" timestamp="${new Date().toJSON()}" time="${timeInSeconds}">
|
|
|
|
// <testcase classname="tests suite should pass" name="${
|
|
|
|
// timing.file
|
|
|
|
// }" time="${timeInSeconds}"></testcase>
|
|
|
|
// </testsuite>
|
|
|
|
// `
|
2019-11-25 23:50:46 +01:00
|
|
|
}
|
2020-01-27 21:07:31 +01:00
|
|
|
// junitData += `</testsuites>`
|
|
|
|
// console.log('output timing data to junit.xml')
|
|
|
|
|
2021-09-28 17:15:04 +02:00
|
|
|
if (prevTimings && process.env.TEST_TIMINGS_TOKEN) {
|
2020-01-27 21:07:31 +01:00
|
|
|
try {
|
2021-09-28 17:15:04 +02:00
|
|
|
const newTimings = {
|
|
|
|
...(await getTestTimings()),
|
|
|
|
...curTimings,
|
|
|
|
}
|
|
|
|
|
|
|
|
for (const test of Object.keys(newTimings)) {
|
2023-10-17 21:31:19 +02:00
|
|
|
if (!existsSync(path.join(__dirname, test))) {
|
2021-09-28 17:15:04 +02:00
|
|
|
console.log('removing stale timing', test)
|
|
|
|
delete newTimings[test]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-01-27 21:07:31 +01:00
|
|
|
const timingsRes = await fetch(TIMINGS_API, {
|
2021-09-28 17:15:04 +02:00
|
|
|
method: 'PATCH',
|
2020-01-27 21:07:31 +01:00
|
|
|
headers: {
|
2021-09-28 17:15:04 +02:00
|
|
|
...TIMINGS_API_HEADERS,
|
2020-01-27 21:07:31 +01:00
|
|
|
},
|
2020-04-21 22:11:04 +02:00
|
|
|
body: JSON.stringify({
|
2021-09-28 17:15:04 +02:00
|
|
|
files: {
|
|
|
|
'test-timings.json': {
|
|
|
|
content: JSON.stringify(newTimings),
|
|
|
|
},
|
|
|
|
},
|
2020-04-21 22:11:04 +02:00
|
|
|
}),
|
2020-01-27 21:07:31 +01:00
|
|
|
})
|
2019-11-25 23:50:46 +01:00
|
|
|
|
2020-01-27 21:07:31 +01:00
|
|
|
if (!timingsRes.ok) {
|
|
|
|
throw new Error(`request status: ${timingsRes.status}`)
|
|
|
|
}
|
2023-03-23 19:53:05 +01:00
|
|
|
const result = await timingsRes.json()
|
2020-01-27 21:07:31 +01:00
|
|
|
console.log(
|
2023-03-23 19:53:05 +01:00
|
|
|
`Sent updated timings successfully. API URL: "${result?.url}" HTML URL: "${result?.html_url}"`
|
2020-01-27 21:07:31 +01:00
|
|
|
)
|
|
|
|
} catch (err) {
|
|
|
|
console.log('Failed to update timings data', err)
|
|
|
|
}
|
|
|
|
}
|
2019-11-25 23:50:46 +01:00
|
|
|
}
|
2021-03-16 22:08:35 +01:00
|
|
|
}
|
|
|
|
|
2023-04-13 08:23:59 +02:00
|
|
|
main()
|
|
|
|
.then(() => cleanUpAndExit(0))
|
|
|
|
.catch((err) => {
|
|
|
|
console.error(err)
|
|
|
|
cleanUpAndExit(1)
|
|
|
|
})
|