mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-11-21 17:37:01 +01:00
split unit tests (#16455)
Co-authored-by: legobeat <109787230+legobeat@users.noreply.github.com>
This commit is contained in:
parent
01c0d7823d
commit
64839b6bf7
@ -16,6 +16,7 @@ executors:
|
||||
|
||||
orbs:
|
||||
gh: circleci/github-cli@2.0
|
||||
codecov: codecov/codecov@3.2.2
|
||||
|
||||
workflows:
|
||||
test_and_release:
|
||||
@ -93,9 +94,20 @@ workflows:
|
||||
- test-e2e-chrome-mv3:
|
||||
requires:
|
||||
- prep-build-test-mv3
|
||||
- test-unit:
|
||||
- test-unit-mocha:
|
||||
requires:
|
||||
- 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:
|
||||
requires:
|
||||
- prep-deps
|
||||
@ -127,8 +139,11 @@ workflows:
|
||||
- test-lint-shellcheck
|
||||
- test-lint-lockfile
|
||||
- test-lint-changelog
|
||||
- test-unit
|
||||
- test-unit-jest-main
|
||||
- test-unit-jest-development
|
||||
- test-unit-global
|
||||
- test-unit-mocha
|
||||
- upload-and-validate-coverage
|
||||
- validate-source-maps
|
||||
- validate-source-maps-beta
|
||||
- validate-source-maps-flask
|
||||
@ -823,9 +838,6 @@ jobs:
|
||||
- store_artifacts:
|
||||
path: coverage
|
||||
destination: coverage
|
||||
- store_artifacts:
|
||||
path: jest-coverage
|
||||
destination: jest-coverage
|
||||
- store_artifacts:
|
||||
path: test-artifacts
|
||||
destination: test-artifacts
|
||||
@ -902,8 +914,8 @@ jobs:
|
||||
git config user.email metamaskbot@users.noreply.github.com
|
||||
yarn ts-migration:dashboard:deploy
|
||||
|
||||
test-unit:
|
||||
executor: node-browsers
|
||||
test-unit-mocha:
|
||||
executor: node-browsers-medium-plus
|
||||
steps:
|
||||
- checkout
|
||||
- attach_workspace:
|
||||
@ -911,24 +923,60 @@ jobs:
|
||||
- run:
|
||||
name: 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:
|
||||
root: .
|
||||
paths:
|
||||
- .nyc_output
|
||||
- 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:
|
||||
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:
|
||||
executor: node-browsers
|
||||
steps:
|
||||
|
@ -36,6 +36,7 @@ ignores:
|
||||
- 'source-map-explorer'
|
||||
# development tool
|
||||
- 'improved-yarn-audit'
|
||||
- 'nyc'
|
||||
# storybook
|
||||
- '@storybook/core'
|
||||
- '@storybook/addon-essentials'
|
||||
|
@ -42,6 +42,8 @@ module.exports = {
|
||||
'test/e2e/**/*.js',
|
||||
'test/helpers/*.js',
|
||||
'test/lib/wait-until-called.js',
|
||||
'test/run-unit-tests.js',
|
||||
'test/merge-coverage.js',
|
||||
],
|
||||
extends: [
|
||||
path.resolve(__dirname, '.eslintrc.base.js'),
|
||||
@ -337,6 +339,8 @@ module.exports = {
|
||||
'development/**/*.js',
|
||||
'test/e2e/benchmark.js',
|
||||
'test/helpers/setup-helper.js',
|
||||
'test/run-unit-tests.js',
|
||||
'test/merge-coverage.js',
|
||||
],
|
||||
rules: {
|
||||
'node/no-process-exit': 'off',
|
||||
|
16
codecov.yml
Normal file
16
codecov.yml
Normal 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
20
coverage-targets.js
Normal 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,
|
||||
},
|
||||
};
|
@ -1,19 +1,11 @@
|
||||
module.exports = {
|
||||
displayName: '/development',
|
||||
collectCoverageFrom: ['<rootDir>/**/*.js'],
|
||||
coverageDirectory: '../jest-coverage/development/',
|
||||
coverageReporters: ['html', 'text-summary', 'json-summary'],
|
||||
coverageThreshold: {
|
||||
'./development/build/transforms/**/*.js': {
|
||||
branches: 100,
|
||||
functions: 100,
|
||||
lines: 100,
|
||||
statements: 100,
|
||||
},
|
||||
},
|
||||
collectCoverageFrom: ['<rootDir>/build/transforms/**/*.js'],
|
||||
coverageDirectory: '../coverage',
|
||||
coverageReporters: ['json'],
|
||||
resetMocks: true,
|
||||
restoreMocks: true,
|
||||
testEnvironment: 'node',
|
||||
testMatch: ['<rootDir>/build/**/*.test.js'],
|
||||
testMatch: ['<rootDir>/build/transforms/**/*.test.js'],
|
||||
testTimeout: 2500,
|
||||
};
|
||||
|
@ -1,33 +1,19 @@
|
||||
module.exports = {
|
||||
collectCoverageFrom: [
|
||||
'<rootDir>/app/scripts/constants/error-utils.js',
|
||||
'<rootDir>/app/scripts/controllers/network/**/*.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/migrations/*.js',
|
||||
'<rootDir>/app/scripts/platforms/*.js',
|
||||
'<rootDir>/shared/**/*.js',
|
||||
'<rootDir>/ui/**/*.js',
|
||||
],
|
||||
coverageDirectory: './jest-coverage/main',
|
||||
coverageDirectory: './coverage',
|
||||
coveragePathIgnorePatterns: ['.stories.js', '.snap'],
|
||||
coverageReporters: ['html', 'text-summary', 'json-summary'],
|
||||
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,
|
||||
},
|
||||
},
|
||||
coverageReporters: ['json'],
|
||||
reporters: [
|
||||
'default',
|
||||
[
|
||||
@ -48,16 +34,16 @@ module.exports = {
|
||||
],
|
||||
setupFilesAfterEnv: ['<rootDir>/test/jest/setup.js'],
|
||||
testMatch: [
|
||||
'<rootDir>/ui/**/*.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/constants/error-utils.test.js',
|
||||
'<rootDir>/app/scripts/controllers/network/**/*.test.js',
|
||||
'<rootDir>/app/scripts/controllers/permissions/**/*.test.js',
|
||||
'<rootDir>/app/scripts/flask/**/*.test.js',
|
||||
'<rootDir>/app/scripts/lib/**/*.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,
|
||||
// We have to specify the environment we are running in, which is jsdom. The
|
||||
|
@ -1,6 +0,0 @@
|
||||
module.exports = {
|
||||
branches: 95,
|
||||
lines: 95,
|
||||
functions: 95,
|
||||
statements: 95,
|
||||
};
|
17
package.json
17
package.json
@ -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'",
|
||||
"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'",
|
||||
"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: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: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:snaps": "SELENIUM_BROWSER=firefox node test/e2e/run-all.js --snaps",
|
||||
"test:e2e:single": "node test/e2e/run-e2e-test.js",
|
||||
"test:coverage:mocha": "nyc --reporter=text --reporter=html yarn test:unit:mocha",
|
||||
"test:coverage:jest": "yarn test:unit:jest --coverage --maxWorkers=2 && yarn jest-it-up -m 5",
|
||||
"test:coverage:mocha": "node ./test/run-unit-tests.js --mocha --coverage",
|
||||
"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",
|
||||
"sentry:publish": "node ./development/sentry-publish.js",
|
||||
"lint": "yarn lint:prettier && yarn lint:eslint && yarn lint:tsc && yarn lint:styles",
|
||||
@ -447,10 +451,13 @@
|
||||
"history": "^5.0.0",
|
||||
"improved-yarn-audit": "^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-canvas-mock": "^2.3.1",
|
||||
"jest-environment-jsdom": "^29.1.2",
|
||||
"jest-it-up": "^2.0.2",
|
||||
"js-yaml": "^4.1.0",
|
||||
"jsdom": "^11.2.0",
|
||||
"koa": "^2.7.0",
|
||||
"lavamoat": "^6.2.0",
|
||||
|
245
test/merge-coverage.js
Normal file
245
test/merge-coverage.js
Normal 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
192
test/run-unit-tests.js
Normal 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 });
|
||||
}
|
||||
}
|
||||
}
|
@ -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"
|
@ -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 $*"
|
95
yarn.lock
95
yarn.lock
@ -2621,44 +2621,6 @@ __metadata:
|
||||
languageName: node
|
||||
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":
|
||||
version: 1.0.0
|
||||
resolution: "@istanbuljs/load-nyc-config@npm:1.0.0"
|
||||
@ -8363,13 +8325,6 @@ __metadata:
|
||||
languageName: node
|
||||
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":
|
||||
version: 4.3.0
|
||||
resolution: "ansi-escapes@npm:4.3.0"
|
||||
@ -11227,7 +11182,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"cli-spinners@npm:^2.5.0, cli-spinners@npm:^2.6.0":
|
||||
"cli-spinners@npm:^2.5.0":
|
||||
version: 2.6.1
|
||||
resolution: "cli-spinners@npm:2.6.1"
|
||||
checksum: 423409baaa7a58e5104b46ca1745fbfc5888bbd0b0c5a626e052ae1387060839c8efd512fb127e25769b3dc9562db1dc1b5add6e0b93b7ef64f477feb6416a45
|
||||
@ -11247,13 +11202,6 @@ __metadata:
|
||||
languageName: node
|
||||
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":
|
||||
version: 3.2.0
|
||||
resolution: "cliui@npm:3.2.0"
|
||||
@ -11614,13 +11562,6 @@ __metadata:
|
||||
languageName: node
|
||||
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":
|
||||
version: 1.3.1
|
||||
resolution: "comment-parser@npm:1.3.1"
|
||||
@ -20149,7 +20090,7 @@ __metadata:
|
||||
languageName: node
|
||||
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
|
||||
resolution: "istanbul-reports@npm:3.1.5"
|
||||
dependencies:
|
||||
@ -20437,19 +20378,6 @@ __metadata:
|
||||
languageName: node
|
||||
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":
|
||||
version: 14.0.1
|
||||
resolution: "jest-junit@npm:14.0.1"
|
||||
@ -23126,11 +23054,14 @@ __metadata:
|
||||
improved-yarn-audit: ^3.0.0
|
||||
ini: ^3.0.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-canvas-mock: ^2.3.1
|
||||
jest-environment-jsdom: ^29.1.2
|
||||
jest-it-up: ^2.0.2
|
||||
jest-junit: ^14.0.1
|
||||
js-yaml: ^4.1.0
|
||||
jsdom: ^11.2.0
|
||||
json-rpc-engine: ^6.1.0
|
||||
json-rpc-middleware-stream: ^2.1.1
|
||||
@ -23985,13 +23916,6 @@ __metadata:
|
||||
languageName: node
|
||||
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":
|
||||
version: 2.15.0
|
||||
resolution: "nan@npm:2.15.0"
|
||||
@ -29048,13 +28972,6 @@ __metadata:
|
||||
languageName: node
|
||||
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":
|
||||
version: 1.1.9
|
||||
resolution: "run-parallel@npm:1.1.9"
|
||||
|
Loading…
Reference in New Issue
Block a user