1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-11-22 01:47:00 +01:00

split unit tests (#16455)

Co-authored-by: legobeat <109787230+legobeat@users.noreply.github.com>
This commit is contained in:
Brad Decker 2023-01-11 13:23:38 -05:00 committed by GitHub
parent 01c0d7823d
commit 64839b6bf7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 580 additions and 179 deletions

View File

@ -16,6 +16,7 @@ executors:
orbs: orbs:
gh: circleci/github-cli@2.0 gh: circleci/github-cli@2.0
codecov: codecov/codecov@3.2.2
workflows: workflows:
test_and_release: test_and_release:
@ -93,9 +94,20 @@ workflows:
- test-e2e-chrome-mv3: - test-e2e-chrome-mv3:
requires: requires:
- prep-build-test-mv3 - prep-build-test-mv3
- test-unit: - test-unit-mocha:
requires: requires:
- prep-deps - prep-deps
- test-unit-jest-main:
requires:
- prep-deps
- test-unit-jest-development:
requires:
- prep-deps
- upload-and-validate-coverage:
requires:
- test-unit-jest-main
- test-unit-jest-development
- test-unit-mocha
- test-unit-global: - test-unit-global:
requires: requires:
- prep-deps - prep-deps
@ -127,8 +139,11 @@ workflows:
- test-lint-shellcheck - test-lint-shellcheck
- test-lint-lockfile - test-lint-lockfile
- test-lint-changelog - test-lint-changelog
- test-unit - test-unit-jest-main
- test-unit-jest-development
- test-unit-global - test-unit-global
- test-unit-mocha
- upload-and-validate-coverage
- validate-source-maps - validate-source-maps
- validate-source-maps-beta - validate-source-maps-beta
- validate-source-maps-flask - validate-source-maps-flask
@ -823,9 +838,6 @@ jobs:
- store_artifacts: - store_artifacts:
path: coverage path: coverage
destination: coverage destination: coverage
- store_artifacts:
path: jest-coverage
destination: jest-coverage
- store_artifacts: - store_artifacts:
path: test-artifacts path: test-artifacts
destination: test-artifacts destination: test-artifacts
@ -902,8 +914,8 @@ jobs:
git config user.email metamaskbot@users.noreply.github.com git config user.email metamaskbot@users.noreply.github.com
yarn ts-migration:dashboard:deploy yarn ts-migration:dashboard:deploy
test-unit: test-unit-mocha:
executor: node-browsers executor: node-browsers-medium-plus
steps: steps:
- checkout - checkout
- attach_workspace: - attach_workspace:
@ -911,24 +923,60 @@ jobs:
- run: - run:
name: test:coverage:mocha name: test:coverage:mocha
command: yarn test:coverage:mocha command: yarn test:coverage:mocha
- run:
name: test:coverage:jest
command: yarn test:coverage:jest
- run:
name: Validate coverage thresholds
command: |
if ! git diff --exit-code jest.config.js development/jest.config.js; then
echo "Detected changes in coverage thresholds"
exit 1
fi
- persist_to_workspace: - persist_to_workspace:
root: . root: .
paths: paths:
- .nyc_output - .nyc_output
- coverage - coverage
- jest-coverage
test-unit-jest-development:
executor: node-browsers
steps:
- checkout
- attach_workspace:
at: .
- run:
name: jest development unit tests
command: yarn test:coverage:jest:dev
- persist_to_workspace:
root: .
paths:
- coverage
- store_test_results: - store_test_results:
path: test/test-results/junit.xml path: test/test-results/junit.xml
test-unit-jest-main:
executor: node-browsers-medium-plus
parallelism: 12
steps:
- checkout
- attach_workspace:
at: .
- run:
name: test:coverage:jest
command: yarn test:coverage:jest
- persist_to_workspace:
root: .
paths:
- coverage
- store_test_results:
path: test/test-results/junit.xml
upload-and-validate-coverage:
executor: node-browsers
steps:
- checkout
- attach_workspace:
at: .
- codecov/upload
- run:
name: test:coverage:validate
command: yarn test:coverage:validate
- persist_to_workspace:
root: .
paths:
- coverage
test-unit-global: test-unit-global:
executor: node-browsers executor: node-browsers
steps: steps:

View File

@ -36,6 +36,7 @@ ignores:
- 'source-map-explorer' - 'source-map-explorer'
# development tool # development tool
- 'improved-yarn-audit' - 'improved-yarn-audit'
- 'nyc'
# storybook # storybook
- '@storybook/core' - '@storybook/core'
- '@storybook/addon-essentials' - '@storybook/addon-essentials'

View File

@ -42,6 +42,8 @@ module.exports = {
'test/e2e/**/*.js', 'test/e2e/**/*.js',
'test/helpers/*.js', 'test/helpers/*.js',
'test/lib/wait-until-called.js', 'test/lib/wait-until-called.js',
'test/run-unit-tests.js',
'test/merge-coverage.js',
], ],
extends: [ extends: [
path.resolve(__dirname, '.eslintrc.base.js'), path.resolve(__dirname, '.eslintrc.base.js'),
@ -337,6 +339,8 @@ module.exports = {
'development/**/*.js', 'development/**/*.js',
'test/e2e/benchmark.js', 'test/e2e/benchmark.js',
'test/helpers/setup-helper.js', 'test/helpers/setup-helper.js',
'test/run-unit-tests.js',
'test/merge-coverage.js',
], ],
rules: { rules: {
'node/no-process-exit': 'off', 'node/no-process-exit': 'off',

16
codecov.yml Normal file
View File

@ -0,0 +1,16 @@
codecov:
bot: 'codecov-io'
require_ci_to_pass: yes
coverage:
round: nearest
status:
project:
global:
target: auto
threshold: 0%
base: auto
transforms:
target: 100%
threshold: 0%
paths:
- development/build/transforms/**/*.js

20
coverage-targets.js Normal file
View File

@ -0,0 +1,20 @@
// Codecov uses a yaml file for its configuration and it targets line coverage.
// To keep our policy in place we have thile file separate from our
// codecov.yml file that specifies coverage targets for each project in the
// codecov.yml file. These targets are read by the test/merge-coverage.js
// script, and the paths from the codecov.yml file are used to figure out which
// subset of files to check against these targets.
module.exports = {
global: {
branches: 20,
functions: 30,
lines: 57,
statements: 40,
},
transforms: {
branches: 100,
functions: 100,
lines: 100,
statements: 100,
},
};

View File

@ -1,19 +1,11 @@
module.exports = { module.exports = {
displayName: '/development', displayName: '/development',
collectCoverageFrom: ['<rootDir>/**/*.js'], collectCoverageFrom: ['<rootDir>/build/transforms/**/*.js'],
coverageDirectory: '../jest-coverage/development/', coverageDirectory: '../coverage',
coverageReporters: ['html', 'text-summary', 'json-summary'], coverageReporters: ['json'],
coverageThreshold: {
'./development/build/transforms/**/*.js': {
branches: 100,
functions: 100,
lines: 100,
statements: 100,
},
},
resetMocks: true, resetMocks: true,
restoreMocks: true, restoreMocks: true,
testEnvironment: 'node', testEnvironment: 'node',
testMatch: ['<rootDir>/build/**/*.test.js'], testMatch: ['<rootDir>/build/transforms/**/*.test.js'],
testTimeout: 2500, testTimeout: 2500,
}; };

View File

@ -1,33 +1,19 @@
module.exports = { module.exports = {
collectCoverageFrom: [ collectCoverageFrom: [
'<rootDir>/app/scripts/constants/error-utils.js',
'<rootDir>/app/scripts/controllers/network/**/*.js',
'<rootDir>/app/scripts/controllers/permissions/**/*.js', '<rootDir>/app/scripts/controllers/permissions/**/*.js',
'<rootDir>/app/scripts/flask/**/*.js',
'<rootDir>/app/scripts/lib/**/*.js',
'<rootDir>/app/scripts/lib/createRPCMethodTrackingMiddleware.js', '<rootDir>/app/scripts/lib/createRPCMethodTrackingMiddleware.js',
'<rootDir>/app/scripts/migrations/*.js',
'<rootDir>/app/scripts/platforms/*.js',
'<rootDir>/shared/**/*.js', '<rootDir>/shared/**/*.js',
'<rootDir>/ui/**/*.js', '<rootDir>/ui/**/*.js',
], ],
coverageDirectory: './jest-coverage/main', coverageDirectory: './coverage',
coveragePathIgnorePatterns: ['.stories.js', '.snap'], coveragePathIgnorePatterns: ['.stories.js', '.snap'],
coverageReporters: ['html', 'text-summary', 'json-summary'], coverageReporters: ['json'],
coverageThreshold: {
global: {
branches: 48,
functions: 46,
lines: 52,
statements: 52,
},
'./app/scripts/controllers/permissions/**/*.js': {
branches: 100,
functions: 100,
lines: 100,
statements: 100,
},
'./app/scripts/lib/createRPCMethodTrackingMiddleware.js': {
branches: 95.65,
functions: 100,
lines: 100,
statements: 100,
},
},
reporters: [ reporters: [
'default', 'default',
[ [
@ -48,16 +34,16 @@ module.exports = {
], ],
setupFilesAfterEnv: ['<rootDir>/test/jest/setup.js'], setupFilesAfterEnv: ['<rootDir>/test/jest/setup.js'],
testMatch: [ testMatch: [
'<rootDir>/ui/**/*.test.js', '<rootDir>/app/scripts/constants/error-utils.test.js',
'<rootDir>/shared/**/*.test.js',
'<rootDir>/app/scripts/lib/**/*.test.js',
'<rootDir>/app/scripts/migrations/*.test.js',
'<rootDir>/app/scripts/platforms/*.test.js',
'<rootDir>/app/scripts/controllers/network/**/*.test.js', '<rootDir>/app/scripts/controllers/network/**/*.test.js',
'<rootDir>/app/scripts/controllers/permissions/**/*.test.js', '<rootDir>/app/scripts/controllers/permissions/**/*.test.js',
'<rootDir>/app/scripts/flask/**/*.test.js', '<rootDir>/app/scripts/flask/**/*.test.js',
'<rootDir>/app/scripts/lib/**/*.test.js',
'<rootDir>/app/scripts/lib/createRPCMethodTrackingMiddleware.test.js', '<rootDir>/app/scripts/lib/createRPCMethodTrackingMiddleware.test.js',
'<rootDir>/app/scripts/constants/error-utils.test.js', '<rootDir>/app/scripts/migrations/*.test.js',
'<rootDir>/app/scripts/platforms/*.test.js',
'<rootDir>/shared/**/*.test.js',
'<rootDir>/ui/**/*.test.js',
], ],
testTimeout: 2500, testTimeout: 2500,
// We have to specify the environment we are running in, which is jsdom. The // We have to specify the environment we are running in, which is jsdom. The

View File

@ -1,6 +0,0 @@
module.exports = {
branches: 95,
lines: 95,
functions: 95,
statements: 95,
};

View File

@ -26,17 +26,21 @@
"dapp-chain": "GANACHE_ARGS='-b 2' concurrently -k -n ganache,dapp -p '[{time}][{name}]' 'yarn ganache:start' 'sleep 5 && yarn dapp'", "dapp-chain": "GANACHE_ARGS='-b 2' concurrently -k -n ganache,dapp -p '[{time}][{name}]' 'yarn ganache:start' 'sleep 5 && yarn dapp'",
"forwarder": "node ./development/static-server.js ./node_modules/@metamask/forwarder/dist/ --port 9010", "forwarder": "node ./development/static-server.js ./node_modules/@metamask/forwarder/dist/ --port 9010",
"dapp-forwarder": "concurrently -k -n forwarder,dapp -p '[{time}][{name}]' 'yarn forwarder' 'yarn dapp'", "dapp-forwarder": "concurrently -k -n forwarder,dapp -p '[{time}][{name}]' 'yarn forwarder' 'yarn dapp'",
"test:unit": "./test/test-unit-combined.sh", "test:unit": "node ./test/run-unit-tests.js --mocha --jestGlobal --jestDev",
"test:unit:jest": "./test/test-unit-jest.sh", "test:unit:jest": "./test/test-unit-jest.sh",
"test:unit:global": "mocha test/unit-global/*.test.js", "test:unit:global": "mocha test/unit-global/*.test.js",
"test:unit:mocha": "mocha './app/**/*.test.js'", "test:unit:mocha": "node ./test/run-unit-tests.js --mocha",
"test:e2e:chrome": "SELENIUM_BROWSER=chrome node test/e2e/run-all.js", "test:e2e:chrome": "SELENIUM_BROWSER=chrome node test/e2e/run-all.js",
"test:e2e:chrome:snaps": "SELENIUM_BROWSER=chrome node test/e2e/run-all.js --snaps", "test:e2e:chrome:snaps": "SELENIUM_BROWSER=chrome node test/e2e/run-all.js --snaps",
"test:e2e:firefox": "SELENIUM_BROWSER=firefox node test/e2e/run-all.js", "test:e2e:firefox": "SELENIUM_BROWSER=firefox node test/e2e/run-all.js",
"test:e2e:firefox:snaps": "SELENIUM_BROWSER=firefox node test/e2e/run-all.js --snaps", "test:e2e:firefox:snaps": "SELENIUM_BROWSER=firefox node test/e2e/run-all.js --snaps",
"test:e2e:single": "node test/e2e/run-e2e-test.js", "test:e2e:single": "node test/e2e/run-e2e-test.js",
"test:coverage:mocha": "nyc --reporter=text --reporter=html yarn test:unit:mocha", "test:coverage:mocha": "node ./test/run-unit-tests.js --mocha --coverage",
"test:coverage:jest": "yarn test:unit:jest --coverage --maxWorkers=2 && yarn jest-it-up -m 5", "test:coverage:jest": "node ./test/run-unit-tests.js --jestGlobal --coverage",
"test:coverage:jest:dev": "node ./test/run-unit-tests.js --jestDev --coverage",
"test:coverage:validate": "node ./test/merge-coverage.js",
"test:coverage": "node ./test/run-unit-tests.js --mocha --jestGlobal --jestDev --coverage && yarn test:coverage:validate",
"test:coverage:html": "yarn test:coverage --html",
"ganache:start": "./development/run-ganache.sh", "ganache:start": "./development/run-ganache.sh",
"sentry:publish": "node ./development/sentry-publish.js", "sentry:publish": "node ./development/sentry-publish.js",
"lint": "yarn lint:prettier && yarn lint:eslint && yarn lint:tsc && yarn lint:styles", "lint": "yarn lint:prettier && yarn lint:eslint && yarn lint:tsc && yarn lint:styles",
@ -447,10 +451,13 @@
"history": "^5.0.0", "history": "^5.0.0",
"improved-yarn-audit": "^3.0.0", "improved-yarn-audit": "^3.0.0",
"ini": "^3.0.0", "ini": "^3.0.0",
"istanbul-lib-coverage": "^3.2.0",
"istanbul-lib-report": "^3.0.0",
"istanbul-reports": "^3.1.5",
"jest": "^29.1.2", "jest": "^29.1.2",
"jest-canvas-mock": "^2.3.1", "jest-canvas-mock": "^2.3.1",
"jest-environment-jsdom": "^29.1.2", "jest-environment-jsdom": "^29.1.2",
"jest-it-up": "^2.0.2", "js-yaml": "^4.1.0",
"jsdom": "^11.2.0", "jsdom": "^11.2.0",
"koa": "^2.7.0", "koa": "^2.7.0",
"lavamoat": "^6.2.0", "lavamoat": "^6.2.0",

245
test/merge-coverage.js Normal file
View File

@ -0,0 +1,245 @@
const fs = require('fs');
const path = require('path');
const libCoverage = require('istanbul-lib-coverage');
const libReport = require('istanbul-lib-report');
const reports = require('istanbul-reports');
const glob = require('fast-glob');
const yargs = require('yargs/yargs');
const { hideBin } = require('yargs/helpers');
const yaml = require('js-yaml');
const codecovTargets = require('../coverage-targets');
const codecovConfig = yaml.load(fs.readFileSync('codecov.yml', 'utf8'));
const COVERAGE_DIR = './coverage/';
/**
* Load .json file at path and parse it into a javascript object
*
* @param {string} filePath - path to the file to load
* @returns {object} the JavaScript object parsed from the file
*/
function loadData(filePath) {
const json = fs.readFileSync(filePath);
return JSON.parse(json);
}
/**
* Loads an array of json coverage files and merges them into a final coverage
* report.
*
* @param {string[]} files - array of strings that are paths to files
* @returns {libCoverage.CoverageMap} CoverageMap
*/
function mergeCoverageMaps(files) {
const coverageMap = libCoverage.createCoverageMap({});
files.forEach((covergeFinalFile) => {
coverageMap.merge(loadData(covergeFinalFile));
});
return coverageMap;
}
/**
* Given a target directory and a coverageMap generates a finalized coverage
* summary report and saves it to the directory.
*
* @param {string} dir - target directory
* @param {libCoverage.CoverageMap} coverageMap - CoverageMap to report on
* @param reportType
* @param reportOptions
*/
function generateSummaryReport(dir, coverageMap, reportType, reportOptions) {
const context = libReport.createContext({
dir,
coverageMap,
});
reports.create(reportType, reportOptions ?? {}).execute(context);
}
/**
* Generates a multiline string with coverage data
*
* @param {CoverageTarget} target - Target coverage threshold
* @param {import('istanbul-lib-coverage').CoverageSummaryData} actual -
* istanbul coverage summary detailing actual summary
* @returns {string} multiline report of coverage
*/
function generateConsoleReport(target, actual) {
const { lines, branches, functions, statements } = actual.data;
const breakdown =
`Lines: ${lines.covered}/${lines.total} (${lines.pct}%). Target: ${target.lines}%\n` +
`Branches: ${branches.covered}/${branches.total} (${branches.pct}%). Target: ${target.branches}%\n` +
`Statements: ${statements.covered}/${statements.total} (${statements.pct}%). Target: ${target.statements}%\n` +
`Functions: ${functions.covered}/${functions.total} (${functions.pct}%). Target: ${target.functions}%`;
return breakdown;
}
/**
* @typedef {object} CoverageTarget
* @property {number} lines - percentage of lines that must be covered
* @property {number} statements - percentage of statements that must be covered
* @property {number} branches - percentage of branches that must be covered
* @property {number} functions - percentage of functions that must be covered
*/
/**
* Checks if the coverage meets target
*
* @param {CoverageTarget} target
* @param {import('istanbul-lib-coverage').CoverageSummaryData} actual
* @returns {boolean}
*/
function isCoverageInsufficient(target, actual) {
const lineCoverageNotMet = actual.lines.pct < target.lines;
const branchCoverageNotMet = actual.branches.pct < target.branches;
const functionCoverageNotMet = actual.functions.pct < target.functions;
const statementCoverageNotMet = actual.statements.pct < target.statements;
return (
lineCoverageNotMet ||
branchCoverageNotMet ||
functionCoverageNotMet ||
statementCoverageNotMet
);
}
/**
* Checks if the coverage should be bumped up
*
* @param {CoverageTarget} target
* @param {import('istanbul-lib-coverage').CoverageSummaryData} actual
* @returns {boolean}
*/
function shouldCoverageBeBumped(target, actual) {
const lineCoverageNeedsBumped = actual.lines.pct > target.lines + 5;
const branchCoverageNeedsBumped = actual.branches.pct > target.branches + 5;
const functionCoverageNeedsBumped =
actual.functions.pct > target.functions + 5;
const statementCoverageNeedsBumped =
actual.statements.pct > target.statements + 5;
return (
lineCoverageNeedsBumped ||
branchCoverageNeedsBumped ||
functionCoverageNeedsBumped ||
statementCoverageNeedsBumped
);
}
/**
* Creates and returns a combined coverage summary report of every file in the
* provided array.
*
* @param {string[]} files - array of files generated by fast-glob
* @param {libCoverage.CoverageMap} coverageMap
* @returns {import('istanbul-lib-coverage').CoverageSummaryData}
*/
function getFileCoverage(files, coverageMap) {
const subCoverageMap = libCoverage.createCoverageMap({});
files.forEach((file) => {
try {
subCoverageMap.merge(
coverageMap.fileCoverageFor(`${process.cwd()}/${file}`),
);
} catch {
// If the coverage doesn't exist, it means that it was excluded from
// coverage or had no coverage to report, which is fine. Glob is a lot
// wider of a net then what the test file runners match against.
}
});
const summary = subCoverageMap.getCoverageSummary();
return summary;
}
/**
* Checks coverage and reports to console
* Throws an error if coverage isn't met
*
* @param {string} name - The target's name from coverageThresholds in jest
* config
* @param {CoverageTarget} target - the target coverage threshold
* @param {import('istanbul-lib-coverage').CoverageSummaryData} actual -
* istanbul coverage summary representing actual coverage
*/
function checkCoverage(name, target, actual) {
const breakdown = generateConsoleReport(target, actual);
if (isCoverageInsufficient(target, actual)) {
const errorMsg = `Coverage thresholds for ${name} NOT met\n${breakdown}`;
throw new Error(errorMsg);
} else if (shouldCoverageBeBumped(target, actual)) {
const errorMsg = `Coverage EXCEEDS threshold for ${name} and must be bumped\n${breakdown}`;
throw new Error(errorMsg);
}
console.log(`Coverage thresholds for ${name} met\n${breakdown}\n\n`);
}
/**
* Primary script function
*/
async function start() {
const {
argv: { html },
} = yargs(hideBin(process.argv)).usage(
'$0 [options]',
'Run unit tests on the application code.',
(yargsInstance) =>
yargsInstance
.option('html', {
alias: ['h'],
default: false,
description: 'Generate HTML report',
type: 'boolean',
})
.strict(),
);
// First get all of the files matching the pattern coverage-final-${n}.json
// from the coverage directory
const files = fs.readdirSync(COVERAGE_DIR);
const filePaths = files
.filter(
(file) =>
path.basename(file).startsWith('coverage-final') &&
path.extname(file) === '.json',
)
.map((file) => path.join(COVERAGE_DIR, file));
// Next, generate a coverageMap
const coverageMap = mergeCoverageMaps(filePaths, true);
// Persist this to file, which may eventually be used in more steps
generateSummaryReport(COVERAGE_DIR, coverageMap, 'json-summary');
if (html) {
generateSummaryReport(COVERAGE_DIR, coverageMap, 'html');
}
// Use the keys in coverageThreshold in jest config to determine targets
const coverageTargets = Object.keys(codecovConfig.coverage.status.project);
// Check coverage totals for each target
coverageTargets.forEach((target) => {
const summary =
target === 'global'
? coverageMap.getCoverageSummary()
: getFileCoverage(
glob.sync([
...codecovConfig.coverage.status.project[target].paths,
// checking test file coverage is redundant.
'!**/*.test.js',
'!**/__mocks__/**/*.js',
'!**/*.stories.js',
]),
coverageMap,
);
// Check and validate the coverage
checkCoverage(target, codecovTargets[target], summary);
});
}
start().catch((error) => {
// Report the errored coverage check
console.error(error);
process.exit(1);
});

192
test/run-unit-tests.js Normal file
View File

@ -0,0 +1,192 @@
const { hideBin } = require('yargs/helpers');
const yargs = require('yargs/yargs');
const { runCommand, runInShell } = require('../development/lib/run-command');
const { CIRCLE_NODE_INDEX, CIRCLE_NODE_TOTAL } = process.env;
const GLOBAL_JEST_CONFIG = './jest.config.js';
const DEVELOPMENT_JEST_CONFIG = './development/jest.config.js';
start().catch((error) => {
console.error(error);
process.exit(1);
});
/**
* @typedef {object} JestParams
* @property {'global' | 'dev'} target - Which configuration to use for Jest.
* @property {boolean} [coverage] - Whether to collect coverage during testing.
* @property {number} [currentShard] - Current process number when using test
* splitting across many processes.
* @property {number} [totalShards] - Total number of processes tests will be
* split across.
* @property {number} [maxWorkers] - Total number of workers to use when
* running tests.
*/
/**
* Execute jest test runner with given params
*
* @param {JestParams} params - Configuration for jest test runner
*/
async function runJest(
{ target, coverage, currentShard, totalShards, maxWorkers } = {
target: 'global',
coverage: false,
currentShard: 1,
totalShards: 1,
maxWorkers: 2,
},
) {
const options = [
'jest',
`--config=${
target === 'global' ? GLOBAL_JEST_CONFIG : DEVELOPMENT_JEST_CONFIG
}`,
];
options.push(`--maxWorkers=${maxWorkers}`);
if (coverage) {
options.push('--coverage');
}
// We use jest's new 'shard' feature to run tests in parallel across many
// different processes if totalShards > 1
if (totalShards > 1) {
options.push(`--shard=${currentShard}/${totalShards}`);
}
await runInShell('yarn', options);
if (coverage) {
// Once done we rename the coverage file so that it is unique among test
// runners and job number
await runCommand('mv', [
'./coverage/coverage-final.json',
`./coverage/coverage-final-${target}-${currentShard}.json`,
]);
}
}
/**
* Run mocha tests on the app directory. Mocha tests do not yet support
* parallelism / test-splitting.
*
* @param {boolean} coverage - Use nyc to collect coverage
*/
async function runMocha({ coverage }) {
const options = ['mocha', './app/**/*.test.js'];
// If coverage is true, then we need to run nyc as the first command
// and mocha after, so we use unshift to add three options to the beginning
// of the options array.
if (coverage) {
options.unshift('nyc', '--reporter=json', 'yarn');
}
await runInShell('yarn', options);
if (coverage) {
// Once done we rename the coverage file so that it is unique among test
// runners
await runCommand('mv', [
'./coverage/coverage-final.json',
`./coverage/coverage-final-mocha.json`,
]);
}
}
async function start() {
const {
argv: { mocha, jestGlobal, jestDev, coverage, fakeParallelism, maxWorkers },
} = yargs(hideBin(process.argv)).usage(
'$0 [options]',
'Run unit tests on the application code.',
(yargsInstance) =>
yargsInstance
.option('mocha', {
alias: ['m'],
default: false,
description: 'Run Mocha tests',
type: 'boolean',
})
.option('jestDev', {
alias: ['d'],
default: false,
description: 'Run Jest tests with development folder config',
type: 'boolean',
})
.option('jestGlobal', {
alias: ['g'],
default: false,
demandOption: false,
description: 'Run Jest global (primary config) tests',
type: 'boolean',
})
.option('coverage', {
alias: ['c'],
default: true,
demandOption: false,
description: 'Collect coverage',
type: 'boolean',
})
.option('fakeParallelism', {
alias: ['f'],
default: 0,
demandOption: false,
description:
'Pretend to be CircleCI and fake parallelism (use at your own risk)',
type: 'number',
})
.option('maxWorkers', {
alias: ['mw'],
default: 2,
demandOption: false,
description:
'The safer way to increase performance locally, sets the number of processes to use internally. Recommended 2',
type: 'number',
})
.strict(),
);
const circleNodeIndex = parseInt(CIRCLE_NODE_INDEX ?? '0', 10);
const circleNodeTotal = parseInt(CIRCLE_NODE_TOTAL ?? '1', 10);
const maxProcesses = fakeParallelism > 0 ? fakeParallelism : circleNodeTotal;
const currentProcess = circleNodeIndex;
if (fakeParallelism) {
console.log(
`Using fake parallelism of ${fakeParallelism}. Your machine may become as useful as a brick during this operation.`,
);
if (jestGlobal && jestDev) {
throw new Error(
'Do not try to run both jest test configs with fakeParallelism, bad things could happen.',
);
} else if (mocha) {
throw new Error('Test splitting is not supported for mocha yet.');
} else {
const processes = [];
for (let x = 0; x < fakeParallelism; x++) {
processes.push(
runJest({
target: jestGlobal ? 'global' : 'dev',
totalShards: fakeParallelism,
currentShard: x + 1,
maxWorkers: 1, // ignore maxWorker option on purpose
}),
);
}
await Promise.all(processes);
}
} else {
const options = {
coverage,
currentShard: currentProcess + 1,
totalShards: maxProcesses,
maxWorkers,
};
if (mocha) {
await runMocha(options);
}
if (jestDev) {
await runJest({ target: 'dev', ...options });
}
if (jestGlobal) {
await runJest({ target: 'global', ...options });
}
}
}

View File

@ -1,11 +0,0 @@
#!/usr/bin/env bash
set -x
set -e
set -u
set -o pipefail
concurrently --raw -n mocha,jest,global \
"yarn test:unit:mocha" \
"yarn test:unit:jest" \
"yarn test:unit:global"

View File

@ -1,10 +0,0 @@
#!/usr/bin/env bash
set -x
set -e
set -u
set -o pipefail
concurrently -n production,development \
"jest --config=./jest.config.js $*" \
"jest --config=./development/jest.config.js $*"

View File

@ -2621,44 +2621,6 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@inquirer/confirm@npm:^0.0.14-alpha.0":
version: 0.0.14-alpha.0
resolution: "@inquirer/confirm@npm:0.0.14-alpha.0"
dependencies:
"@inquirer/core": ^0.0.15-alpha.0
"@inquirer/input": ^0.0.15-alpha.0
chalk: ^4.1.1
checksum: 84daf47030318e0adf6eeef85388e341f6f68c0c06c0d1d329cb6185f6d21abf74c3124102bf62f4f42145c8dc9193d719209e3759987a1bdbcfb88139b9936c
languageName: node
linkType: hard
"@inquirer/core@npm:^0.0.15-alpha.0":
version: 0.0.15-alpha.0
resolution: "@inquirer/core@npm:0.0.15-alpha.0"
dependencies:
ansi-escapes: ^4.2.1
chalk: ^4.1.1
cli-spinners: ^2.6.0
cli-width: ^3.0.0
lodash: ^4.17.21
mute-stream: ^0.0.8
run-async: ^2.3.0
string-width: ^4.1.0
strip-ansi: ^6.0.0
checksum: 26a94a8db80e57f926c889b5730c6c9a0f18d6bf1e6dbfb68eaa6bcda33550db66118ce6afd9e91130ea2e4da42b6d7236b94c16fbab931ed04b4f442b9a5c78
languageName: node
linkType: hard
"@inquirer/input@npm:^0.0.15-alpha.0":
version: 0.0.15-alpha.0
resolution: "@inquirer/input@npm:0.0.15-alpha.0"
dependencies:
"@inquirer/core": ^0.0.15-alpha.0
chalk: ^4.1.1
checksum: c294b2fa100e2955e271b1b0590b5f360c65c560d653f39554cd381f145fdf13be9ea2c3c069d39a71f84fc3c8ba20d9d78a0b210f8cb5533d138567029e28ee
languageName: node
linkType: hard
"@istanbuljs/load-nyc-config@npm:^1.0.0": "@istanbuljs/load-nyc-config@npm:^1.0.0":
version: 1.0.0 version: 1.0.0
resolution: "@istanbuljs/load-nyc-config@npm:1.0.0" resolution: "@istanbuljs/load-nyc-config@npm:1.0.0"
@ -8363,13 +8325,6 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"ansi-colors@npm:^4.1.0":
version: 4.1.1
resolution: "ansi-colors@npm:4.1.1"
checksum: 138d04a51076cb085da0a7e2d000c5c0bb09f6e772ed5c65c53cb118d37f6c5f1637506d7155fb5f330f0abcf6f12fa2e489ac3f8cdab9da393bf1bb4f9a32b0
languageName: node
linkType: hard
"ansi-escapes@npm:^4.2.1": "ansi-escapes@npm:^4.2.1":
version: 4.3.0 version: 4.3.0
resolution: "ansi-escapes@npm:4.3.0" resolution: "ansi-escapes@npm:4.3.0"
@ -11227,7 +11182,7 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"cli-spinners@npm:^2.5.0, cli-spinners@npm:^2.6.0": "cli-spinners@npm:^2.5.0":
version: 2.6.1 version: 2.6.1
resolution: "cli-spinners@npm:2.6.1" resolution: "cli-spinners@npm:2.6.1"
checksum: 423409baaa7a58e5104b46ca1745fbfc5888bbd0b0c5a626e052ae1387060839c8efd512fb127e25769b3dc9562db1dc1b5add6e0b93b7ef64f477feb6416a45 checksum: 423409baaa7a58e5104b46ca1745fbfc5888bbd0b0c5a626e052ae1387060839c8efd512fb127e25769b3dc9562db1dc1b5add6e0b93b7ef64f477feb6416a45
@ -11247,13 +11202,6 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"cli-width@npm:^3.0.0":
version: 3.0.0
resolution: "cli-width@npm:3.0.0"
checksum: 4c94af3769367a70e11ed69aa6095f1c600c0ff510f3921ab4045af961820d57c0233acfa8b6396037391f31b4c397e1f614d234294f979ff61430a6c166c3f6
languageName: node
linkType: hard
"cliui@npm:^3.2.0": "cliui@npm:^3.2.0":
version: 3.2.0 version: 3.2.0
resolution: "cliui@npm:3.2.0" resolution: "cliui@npm:3.2.0"
@ -11614,13 +11562,6 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"commander@npm:^9.0.0":
version: 9.2.0
resolution: "commander@npm:9.2.0"
checksum: 7c82e4cd969712aa6d7c055b8351807a7230f9f31ef7ec7881e11a1147511de85adf5d6ccfd200240a118eecf693b220caf6865b8efbcea558a70d35aa9ed711
languageName: node
linkType: hard
"comment-parser@npm:1.3.1": "comment-parser@npm:1.3.1":
version: 1.3.1 version: 1.3.1
resolution: "comment-parser@npm:1.3.1" resolution: "comment-parser@npm:1.3.1"
@ -20149,7 +20090,7 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"istanbul-reports@npm:^3.0.0, istanbul-reports@npm:^3.1.3": "istanbul-reports@npm:^3.0.0, istanbul-reports@npm:^3.1.3, istanbul-reports@npm:^3.1.5":
version: 3.1.5 version: 3.1.5
resolution: "istanbul-reports@npm:3.1.5" resolution: "istanbul-reports@npm:3.1.5"
dependencies: dependencies:
@ -20437,19 +20378,6 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"jest-it-up@npm:^2.0.2":
version: 2.0.2
resolution: "jest-it-up@npm:2.0.2"
dependencies:
"@inquirer/confirm": ^0.0.14-alpha.0
ansi-colors: ^4.1.0
commander: ^9.0.0
bin:
jest-it-up: bin/jest-it-up
checksum: 1482d8b9862331b6cc7d858fd1c05d95fb1bc2adbc37e243c20e6e10f259fecf600d3b1023dadf11acadb158ea171677f8f375969e058dff46002be81a49079b
languageName: node
linkType: hard
"jest-junit@npm:^14.0.1": "jest-junit@npm:^14.0.1":
version: 14.0.1 version: 14.0.1
resolution: "jest-junit@npm:14.0.1" resolution: "jest-junit@npm:14.0.1"
@ -23126,11 +23054,14 @@ __metadata:
improved-yarn-audit: ^3.0.0 improved-yarn-audit: ^3.0.0
ini: ^3.0.0 ini: ^3.0.0
is-retry-allowed: ^2.2.0 is-retry-allowed: ^2.2.0
istanbul-lib-coverage: ^3.2.0
istanbul-lib-report: ^3.0.0
istanbul-reports: ^3.1.5
jest: ^29.1.2 jest: ^29.1.2
jest-canvas-mock: ^2.3.1 jest-canvas-mock: ^2.3.1
jest-environment-jsdom: ^29.1.2 jest-environment-jsdom: ^29.1.2
jest-it-up: ^2.0.2
jest-junit: ^14.0.1 jest-junit: ^14.0.1
js-yaml: ^4.1.0
jsdom: ^11.2.0 jsdom: ^11.2.0
json-rpc-engine: ^6.1.0 json-rpc-engine: ^6.1.0
json-rpc-middleware-stream: ^2.1.1 json-rpc-middleware-stream: ^2.1.1
@ -23985,13 +23916,6 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"mute-stream@npm:^0.0.8":
version: 0.0.8
resolution: "mute-stream@npm:0.0.8"
checksum: ff48d251fc3f827e5b1206cda0ffdaec885e56057ee86a3155e1951bc940fd5f33531774b1cc8414d7668c10a8907f863f6561875ee6e8768931a62121a531a1
languageName: node
linkType: hard
"nan@npm:^2.12.1, nan@npm:^2.13.2, nan@npm:^2.14.0": "nan@npm:^2.12.1, nan@npm:^2.13.2, nan@npm:^2.14.0":
version: 2.15.0 version: 2.15.0
resolution: "nan@npm:2.15.0" resolution: "nan@npm:2.15.0"
@ -29048,13 +28972,6 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"run-async@npm:^2.3.0":
version: 2.4.1
resolution: "run-async@npm:2.4.1"
checksum: a2c88aa15df176f091a2878eb840e68d0bdee319d8d97bbb89112223259cebecb94bc0defd735662b83c2f7a30bed8cddb7d1674eb48ae7322dc602b22d03797
languageName: node
linkType: hard
"run-parallel@npm:^1.1.9": "run-parallel@npm:^1.1.9":
version: 1.1.9 version: 1.1.9
resolution: "run-parallel@npm:1.1.9" resolution: "run-parallel@npm:1.1.9"