add: unit and integration testing
This commit is contained in:
+22
@@ -0,0 +1,22 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
Copyright Contributors to the Jest project.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
+190
@@ -0,0 +1,190 @@
|
||||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Measures what loading a module costs, for both CJS `require` and ESM
|
||||
* `import`. A generated binary tree of modules is loaded from a single test
|
||||
* file, `jest.resetModules()` between rounds so every round pays full price:
|
||||
* resolution, the mock decision, the transform lookup and the file read all
|
||||
* start cold, as they do in a fresh worker. Every module also requires
|
||||
* `node:path`, so the core-module path is exercised too.
|
||||
*
|
||||
* `transform: {}` keeps Babel out of the measurement.
|
||||
*
|
||||
* Needs `yarn build:js` first — this spawns the built CLI rather than reaching
|
||||
* into `src`, because a `Runtime` cannot be built without a config, an
|
||||
* environment and a haste map around it.
|
||||
*
|
||||
* To start the test, run:
|
||||
* node test.js
|
||||
* node test.js 30 cjs
|
||||
*
|
||||
* A single number here means nothing: absolute timings move with the machine,
|
||||
* the Node version and whatever else is running. To compare two revisions,
|
||||
* swap `packages/jest-runtime/src` between them and rebuild between
|
||||
* measurements, alternating instead of measuring each revision once, so drift
|
||||
* lands on both sides. Read the `modules` column, not `total` — `startup` is
|
||||
* the same fixture with zero rounds, and it dominates a short run.
|
||||
*
|
||||
* Measured on an M-series laptop, Node v26.7.0, 30 rounds, alternating twice
|
||||
* against the per-require work removed in #16376, reading the `modules`
|
||||
* column:
|
||||
*
|
||||
* CJS 1992 ms -> 1893 ms and 2013 ms -> 1886 ms
|
||||
* ESM 3031 ms -> 2937 ms and 3018 ms -> 2962 ms
|
||||
*
|
||||
* about 5-6% of CJS module-loading time and 2-3% of ESM's. Re-runs of the same
|
||||
* pair minutes apart moved by more than that gap, which is why two
|
||||
* alternations are the minimum worth reporting.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const {spawnSync} = require('node:child_process');
|
||||
const os = require('node:os');
|
||||
const path = require('node:path');
|
||||
const fs = require('graceful-fs');
|
||||
|
||||
const NODE_COUNT = 2047; // a complete binary tree, 11 deep
|
||||
const RUNS = 5;
|
||||
|
||||
const JEST_BIN = path.resolve(__dirname, '../../jest-cli/bin/jest.js');
|
||||
|
||||
const iterations = Number(process.argv[2] ?? 30);
|
||||
const onlyMode = process.argv[3];
|
||||
|
||||
function writeFixture(root, mode) {
|
||||
const extension = mode === 'esm' ? '.mjs' : '.js';
|
||||
fs.mkdirSync(path.join(root, 'modules'), {recursive: true});
|
||||
fs.mkdirSync(path.join(root, '__tests__'), {recursive: true});
|
||||
|
||||
const manifest = {
|
||||
jest: {testEnvironment: 'node', testTimeout: 600_000, transform: {}},
|
||||
name: `jest-runtime-benchmark-${mode}`,
|
||||
private: true,
|
||||
version: '0.0.0',
|
||||
};
|
||||
if (mode === 'esm') {
|
||||
manifest.type = 'module';
|
||||
}
|
||||
fs.writeFileSync(
|
||||
path.join(root, 'package.json'),
|
||||
`${JSON.stringify(manifest, null, 2)}\n`,
|
||||
);
|
||||
|
||||
for (let index = 0; index < NODE_COUNT; index++) {
|
||||
const children = [2 * index + 1, 2 * index + 2].filter(
|
||||
child => child < NODE_COUNT,
|
||||
);
|
||||
const sum = children.map(child => ` + m${child}`).join('');
|
||||
const lines =
|
||||
mode === 'esm'
|
||||
? [
|
||||
"import * as path from 'node:path';",
|
||||
...children.map(
|
||||
child => `import m${child} from './m${child}.mjs';`,
|
||||
),
|
||||
`export default ${index} + path.sep.length${sum};`,
|
||||
]
|
||||
: [
|
||||
"const path = require('node:path');",
|
||||
...children.map(
|
||||
child => `const m${child} = require('./m${child}.js');`,
|
||||
),
|
||||
`module.exports = ${index} + path.sep.length${sum};`,
|
||||
];
|
||||
fs.writeFileSync(
|
||||
path.join(root, 'modules', `m${index}${extension}`),
|
||||
`${lines.join('\n')}\n`,
|
||||
);
|
||||
}
|
||||
|
||||
const test =
|
||||
mode === 'esm'
|
||||
? [
|
||||
"import {expect, jest, test} from '@jest/globals';",
|
||||
'const rounds = Number(process.env.BENCHMARK_ROUNDS);',
|
||||
"test('loads the tree', async () => {",
|
||||
' for (let round = 0; round < rounds; round++) {',
|
||||
' jest.resetModules();',
|
||||
" const {default: root} = await import('../modules/m0.mjs');",
|
||||
" expect(typeof root).toBe('number');",
|
||||
' }',
|
||||
'});',
|
||||
]
|
||||
: [
|
||||
'const rounds = Number(process.env.BENCHMARK_ROUNDS);',
|
||||
"test('loads the tree', () => {",
|
||||
' for (let round = 0; round < rounds; round++) {',
|
||||
' jest.resetModules();',
|
||||
" expect(typeof require('../modules/m0.js')).toBe('number');",
|
||||
' }',
|
||||
'});',
|
||||
];
|
||||
fs.writeFileSync(
|
||||
path.join(root, '__tests__', `benchmark.test${extension}`),
|
||||
`${test.join('\n')}\n`,
|
||||
);
|
||||
}
|
||||
|
||||
function median(cwd, mode, rounds) {
|
||||
const samples = [];
|
||||
// The first run is a warmup: it pays for the OS file cache the rest reuse.
|
||||
for (let run = 0; run <= RUNS; run++) {
|
||||
const start = process.hrtime.bigint();
|
||||
const result = spawnSync(
|
||||
process.execPath,
|
||||
[JEST_BIN, '--no-cache', '--runInBand', '--silent'],
|
||||
{
|
||||
cwd,
|
||||
env: {
|
||||
...process.env,
|
||||
BENCHMARK_ROUNDS: String(rounds),
|
||||
...(mode === 'esm'
|
||||
? {NODE_OPTIONS: '--experimental-vm-modules'}
|
||||
: undefined),
|
||||
},
|
||||
stdio: 'pipe',
|
||||
},
|
||||
);
|
||||
if (result.status !== 0) {
|
||||
console.error(result.stderr.toString());
|
||||
throw new Error(`the ${mode} fixture failed to run`);
|
||||
}
|
||||
if (run > 0) {
|
||||
samples.push(Number(process.hrtime.bigint() - start) / 1e6);
|
||||
}
|
||||
}
|
||||
samples.sort((left, right) => left - right);
|
||||
return samples;
|
||||
}
|
||||
|
||||
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'jest-runtime-benchmark-'));
|
||||
|
||||
try {
|
||||
for (const mode of ['cjs', 'esm']) {
|
||||
if (onlyMode && mode !== onlyMode) continue;
|
||||
|
||||
const cwd = path.join(root, mode);
|
||||
writeFixture(cwd, mode);
|
||||
|
||||
const startup = median(cwd, mode, 0);
|
||||
const samples = median(cwd, mode, iterations);
|
||||
const total = samples[Math.floor(samples.length / 2)];
|
||||
const floor = startup[Math.floor(startup.length / 2)];
|
||||
|
||||
console.log(
|
||||
`${mode} total ${total.toFixed(0)} ms startup ${floor.toFixed(
|
||||
0,
|
||||
)} ms modules ${(total - floor).toFixed(0)} ms (samples ${samples
|
||||
.map(sample => sample.toFixed(0))
|
||||
.join(' ')})`,
|
||||
);
|
||||
}
|
||||
} finally {
|
||||
fs.rmSync(root, {force: true, recursive: true});
|
||||
}
|
||||
+135
@@ -0,0 +1,135 @@
|
||||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import {JestEnvironment} from '@jest/environment';
|
||||
import {expect} from '@jest/globals';
|
||||
import {SourceMapRegistry} from '@jest/source-map';
|
||||
import {TestContext, V8CoverageResult} from '@jest/test-result';
|
||||
import {
|
||||
CallerTransformOptions,
|
||||
ScriptTransformer,
|
||||
ShouldInstrumentOptions,
|
||||
shouldInstrument,
|
||||
} from '@jest/transform';
|
||||
import {Config, Global as Global_2} from '@jest/types';
|
||||
import {IHasteMap, IModuleMap} from 'jest-haste-map';
|
||||
import Resolver from 'jest-resolve';
|
||||
|
||||
declare interface EnvironmentGlobals extends Global_2.TestFrameworkGlobals {
|
||||
expect: typeof expect;
|
||||
}
|
||||
|
||||
declare type HasteMapOptions = {
|
||||
console?: Console;
|
||||
maxWorkers: number;
|
||||
resetCache: boolean;
|
||||
watch?: boolean;
|
||||
watchman: boolean;
|
||||
workerThreads?: boolean;
|
||||
};
|
||||
|
||||
declare class Runtime {
|
||||
private readonly fileCache;
|
||||
private readonly _config;
|
||||
private readonly _coverageOptions;
|
||||
private readonly _environment;
|
||||
private readonly mockState;
|
||||
private readonly registries;
|
||||
private readonly testMainModule;
|
||||
private readonly requireBuilder;
|
||||
private readonly executor;
|
||||
private readonly esmLoader;
|
||||
private readonly cjsLoader;
|
||||
private readonly _moduleMocker;
|
||||
private readonly cjsExportsCache;
|
||||
private readonly _testPath;
|
||||
private readonly _resolution;
|
||||
private readonly transformCache;
|
||||
private readonly v8Coverage;
|
||||
private readonly coreModule;
|
||||
private readonly jestGlobals;
|
||||
private readonly testState;
|
||||
private readonly loggedReferenceErrors;
|
||||
constructor(
|
||||
config: Config.ProjectConfig,
|
||||
environment: JestEnvironment,
|
||||
resolver: Resolver,
|
||||
transformer: ScriptTransformer,
|
||||
cacheFS: Map<string, string>,
|
||||
coverageOptions: ShouldInstrumentOptions,
|
||||
testPath: string,
|
||||
globalConfig: Config.GlobalConfig,
|
||||
);
|
||||
static shouldInstrument: typeof shouldInstrument;
|
||||
static createContext(
|
||||
config: Config.ProjectConfig,
|
||||
options: {
|
||||
console?: Console;
|
||||
maxWorkers: number;
|
||||
watch?: boolean;
|
||||
watchman: boolean;
|
||||
},
|
||||
): Promise<TestContext>;
|
||||
static createHasteMap(
|
||||
config: Config.ProjectConfig,
|
||||
options?: HasteMapOptions,
|
||||
): Promise<IHasteMap>;
|
||||
static createResolver(
|
||||
config: Config.ProjectConfig,
|
||||
moduleMap: IModuleMap,
|
||||
): Resolver;
|
||||
unstable_shouldLoadAsEsm(modulePath: string): boolean;
|
||||
unstable_importModule(
|
||||
from: string,
|
||||
moduleName?: string,
|
||||
): Promise<unknown | void>;
|
||||
requireModule<T = unknown>(
|
||||
from: string,
|
||||
moduleName?: string,
|
||||
options?: TransformOptions,
|
||||
isRequireActual?: boolean,
|
||||
): T;
|
||||
requireInternalModule<T = unknown>(from: string, to?: string): T;
|
||||
requireActual<T = unknown>(from: string, moduleName: string): T;
|
||||
requireMock<T = unknown>(from: string, moduleName: string): T;
|
||||
private _requireMockWithId;
|
||||
private installGetBuiltinModule;
|
||||
private _esmMockTarget;
|
||||
private _getFullTransformationOptions;
|
||||
requireModuleOrMock<T = unknown>(from: string, moduleName: string): T;
|
||||
isolateModules(fn: () => void): void;
|
||||
isolateModulesAsync(fn: () => Promise<void>): Promise<void>;
|
||||
resetModules(): void;
|
||||
collectV8Coverage(): Promise<void>;
|
||||
stopCollectingV8Coverage(): Promise<void>;
|
||||
getAllCoverageInfoCopy(): JestEnvironment['global']['__coverage__'];
|
||||
getAllV8CoverageInfoCopy(): V8CoverageResult;
|
||||
getSourceMaps(): SourceMapRegistry;
|
||||
setMock(
|
||||
from: string,
|
||||
moduleName: string,
|
||||
mockFactory: () => unknown,
|
||||
options?: {
|
||||
virtual?: boolean;
|
||||
},
|
||||
): void;
|
||||
private setModuleMock;
|
||||
restoreAllMocks(): void;
|
||||
resetAllMocks(): void;
|
||||
clearAllMocks(): void;
|
||||
enterTestCode(): void;
|
||||
leaveTestCode(): void;
|
||||
teardown(): void;
|
||||
private _generateMock;
|
||||
private _logFormattedReferenceError;
|
||||
setGlobalsForRuntime(globals: EnvironmentGlobals): void;
|
||||
}
|
||||
export default Runtime;
|
||||
|
||||
declare interface TransformOptions extends Required<CallerTransformOptions> {
|
||||
isInternalModule: boolean;
|
||||
}
|
||||
+5254
File diff suppressed because it is too large
Load Diff
+3
@@ -0,0 +1,3 @@
|
||||
import cjsModule from './index.js';
|
||||
|
||||
export default cjsModule.default;
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
{
|
||||
"name": "jest-runtime",
|
||||
"version": "30.5.1",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/jestjs/jest.git",
|
||||
"directory": "packages/jest-runtime"
|
||||
},
|
||||
"license": "MIT",
|
||||
"main": "./build/index.js",
|
||||
"types": "./build/index.d.ts",
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./build/index.d.ts",
|
||||
"require": "./build/index.js",
|
||||
"import": "./build/index.mjs",
|
||||
"default": "./build/index.js"
|
||||
},
|
||||
"./package.json": "./package.json"
|
||||
},
|
||||
"dependencies": {
|
||||
"@jest/environment": "30.5.1",
|
||||
"@jest/fake-timers": "30.5.1",
|
||||
"@jest/globals": "30.5.1",
|
||||
"@jest/source-map": "30.5.0",
|
||||
"@jest/test-result": "30.5.1",
|
||||
"@jest/transform": "30.5.1",
|
||||
"@jest/types": "30.5.1",
|
||||
"@types/node": "*",
|
||||
"chalk": "^4.1.2",
|
||||
"cjs-module-lexer": "^2.2.0",
|
||||
"collect-v8-coverage": "^1.0.2",
|
||||
"es-module-lexer": "^2.1.0",
|
||||
"glob": "^13.0.6",
|
||||
"graceful-fs": "^4.2.11",
|
||||
"jest-haste-map": "30.5.1",
|
||||
"jest-message-util": "30.5.1",
|
||||
"jest-mock": "30.5.1",
|
||||
"jest-regex-util": "30.5.0",
|
||||
"jest-resolve": "30.5.1",
|
||||
"jest-snapshot": "30.5.1",
|
||||
"jest-util": "30.5.1",
|
||||
"slash": "^3.0.0",
|
||||
"strip-bom": "^4.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@jest/test-utils": "30.5.1",
|
||||
"@types/graceful-fs": "^4.1.9",
|
||||
"jest-environment-node": "30.5.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"gitHead": "9ab14feccd6c15fec1334dbcb03a53a079d153d3"
|
||||
}
|
||||
Reference in New Issue
Block a user