chore: Upgrade to Vitest 4 and regroup tests for performance (#2618)
This commit is contained in:
+64
-27
@@ -23,23 +23,6 @@ const EXCLUSIONS = [
|
||||
];
|
||||
|
||||
const TEST_DIRECTORY = 'tests';
|
||||
const INCLUSIONS = [`${TEST_DIRECTORY}/**/*.test.ts`];
|
||||
|
||||
// For test performance reasons, tests are split into two groups:
|
||||
//
|
||||
// - `shared`: run non-isolated, so they share one loaded copy of the source
|
||||
// tree instead of each re-importing it.
|
||||
// - `isolated`: given their own module registry per file, because sharing one
|
||||
// would change their behaviour. A file calling `vi.mock()` cannot replace a
|
||||
// module an earlier file already loaded unmocked; a file loading the template
|
||||
// renderer reads browser globals as the renderer loads; and a file needing a
|
||||
// DOM leaves modules holding a `window` that is torn down when it finishes,
|
||||
// which breaks any later file that reaches one of those modules.
|
||||
//
|
||||
// Absent this variable every test file runs isolated, which is both the safe
|
||||
// default and what coverage requires (istanbul only counts a module's top-level
|
||||
// code the first time it runs).
|
||||
const GROUP = process.env.VITEST_GROUP;
|
||||
|
||||
const findTestFiles = (directory: string): string[] => {
|
||||
const files: string[] = [];
|
||||
@@ -54,16 +37,21 @@ const findTestFiles = (directory: string): string[] => {
|
||||
return files;
|
||||
};
|
||||
|
||||
const REQUIRE_ISOLATION_REGEXP =
|
||||
/\bvi\.(do)?mock\(|loadRenderer|stubConnectedHomeAssistant|@vitest-environment\s+jsdom/;
|
||||
// Each test file is read and sorted into one of the projects below, which
|
||||
// explain what these two properties cost.
|
||||
const REQUIRE_ISOLATION_REGEXP = /\bvi\.(do)?mock\(/;
|
||||
const REQUIRE_DOM_REGEXP = /@vitest-environment\s+jsdom/;
|
||||
|
||||
const getGroup = (file: string): string =>
|
||||
REQUIRE_ISOLATION_REGEXP.test(readFileSync(file, 'utf-8')) ? 'isolated' : 'shared';
|
||||
const getGroup = (file: string): string => {
|
||||
const contents = readFileSync(file, 'utf-8');
|
||||
if (REQUIRE_ISOLATION_REGEXP.test(contents)) {
|
||||
return 'isolated';
|
||||
}
|
||||
return REQUIRE_DOM_REGEXP.test(contents) ? 'shared-jsdom' : 'shared-node';
|
||||
};
|
||||
|
||||
const getInclusions = (): string[] =>
|
||||
GROUP
|
||||
? findTestFiles(TEST_DIRECTORY).filter((file) => getGroup(file) === GROUP)
|
||||
: INCLUSIONS;
|
||||
const getInclusions = (group: string): string[] =>
|
||||
findTestFiles(TEST_DIRECTORY).filter((file) => getGroup(file) === group);
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [svgPath()],
|
||||
@@ -75,13 +63,62 @@ export default defineConfig({
|
||||
inline: ['ha-nunjucks', 'ts-py-datetime'],
|
||||
},
|
||||
},
|
||||
include: getInclusions(),
|
||||
|
||||
// Forked child processes start and tear down faster here than worker
|
||||
// threads, which matters when every test file needs a fresh one.
|
||||
pool: 'forks',
|
||||
|
||||
isolate: !GROUP || GROUP === 'isolated',
|
||||
// Importing the source tree costs far more than running the tests in it, so
|
||||
// files are grouped by whether they can share one loaded copy of it. Each
|
||||
// project below is a group that can, or the one that cannot.
|
||||
projects: [
|
||||
{
|
||||
extends: true,
|
||||
test: {
|
||||
// Nothing stops these sharing, so they run against a single loaded
|
||||
// copy of the source tree. Most of the suite is here, and anything
|
||||
// moved out of here pays to import that tree again.
|
||||
name: 'shared-node',
|
||||
include: getInclusions('shared-node'),
|
||||
isolate: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
extends: true,
|
||||
test: {
|
||||
// These share too, but only with each other. A module loaded under
|
||||
// jsdom holds a reference to that DOM, so a worker that moves from a
|
||||
// DOM file to a non-DOM one drops its loaded copy of the source tree
|
||||
// and imports it again. A project's files are spread across workers
|
||||
// without regard to environment, so putting both kinds in one project
|
||||
// leaves every worker alternating between them and reloading as it
|
||||
// goes, and the more workers there are the more often that happens. A
|
||||
// project per environment hands each worker a run of files that all
|
||||
// want the same one. The file-level `@vitest-environment jsdom`
|
||||
// comments are what sort files into here, and are redundant once they
|
||||
// arrive.
|
||||
//
|
||||
// They share one `document` as well, so a file that redefines part of
|
||||
// it must leave the property configurable for the files that follow.
|
||||
name: 'shared-jsdom',
|
||||
include: getInclusions('shared-jsdom'),
|
||||
environment: 'jsdom',
|
||||
isolate: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
extends: true,
|
||||
test: {
|
||||
// `vi.mock()` cannot replace a module that an earlier file already
|
||||
// loaded unmocked, so these cannot share a loaded source tree with
|
||||
// anything, including each other. They get a registry per file and
|
||||
// pay the full import cost.
|
||||
name: 'isolated',
|
||||
include: getInclusions('isolated'),
|
||||
isolate: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
// Hide console writing to keep output clean, usual sources of noise:
|
||||
// - Unnecessary Lit dev-mode warnings.
|
||||
|
||||
Reference in New Issue
Block a user