pulsar/script/test
icecream17 4774c56d9e Bump async dependency to 3.2.0 in atom/script
Update lockfileVersion to *2*, but that's a side affect from npm

https://github.com/caolan/async/blob/master/CHANGELOG.md#v200
v2.0.0 allows importing functions modularily.
So I changed the code to only import the functions needed
instead of the whole thing.
2021-06-25 16:51:14 -05:00

501 lines
13 KiB
JavaScript
Executable File

#!/usr/bin/env node
'use strict';
require('colors');
const argv = require('yargs')
.option('core-main', {
describe: 'Run core main process tests',
boolean: true,
default: false
})
.option('skip-main', {
describe:
'Skip main process tests if they would otherwise run on your platform',
boolean: true,
default: false,
conflicts: 'core-main'
})
.option('core-renderer', {
describe: 'Run core renderer process tests',
boolean: true,
default: false
})
.option('core-benchmark', {
describe: 'Run core benchmarks',
boolean: true,
default: false
})
.option('package', {
describe: 'Run bundled package specs',
boolean: true,
default: false
})
.help().argv;
const assert = require('assert');
const asyncSeries = require('async/series');
const childProcess = require('child_process');
const fs = require('fs-extra');
const glob = require('glob');
const path = require('path');
const temp = require('temp').track();
const CONFIG = require('./config');
const backupNodeModules = require('./lib/backup-node-modules');
const runApmInstall = require('./lib/run-apm-install');
function assertExecutablePaths(executablePaths) {
assert(
executablePaths.length !== 0,
`No atom build found. Please run "script/build" and try again.`
);
assert(
executablePaths.length === 1,
`More than one application to run tests against was found. ${executablePaths.join(
','
)}`
);
}
const resourcePath = CONFIG.repositoryRootPath;
let executablePath;
if (process.platform === 'darwin') {
const executablePaths = glob.sync(path.join(CONFIG.buildOutputPath, '*.app'));
assertExecutablePaths(executablePaths);
executablePath = path.join(
executablePaths[0],
'Contents',
'MacOS',
path.basename(executablePaths[0], '.app')
);
} else if (process.platform === 'linux') {
const executablePaths = glob.sync(
path.join(CONFIG.buildOutputPath, 'atom-*', 'atom')
);
assertExecutablePaths(executablePaths);
executablePath = executablePaths[0];
} else if (process.platform === 'win32') {
const executablePaths = glob.sync(
path.join(CONFIG.buildOutputPath, '**', 'atom*.exe')
);
assertExecutablePaths(executablePaths);
executablePath = executablePaths[0];
} else {
throw new Error('##[error] Running tests on this platform is not supported.');
}
function prepareEnv(suiteName) {
const atomHomeDirPath = temp.mkdirSync(suiteName);
const env = Object.assign({}, process.env, { ATOM_HOME: atomHomeDirPath });
if (process.env.TEST_JUNIT_XML_ROOT) {
// Tell Jasmine to output this suite's results as a JUnit XML file to a subdirectory of the root, so that a
// CI system can interpret it.
const fileName = suiteName + '.xml';
const outputPath = path.join(process.env.TEST_JUNIT_XML_ROOT, fileName);
env.TEST_JUNIT_XML_PATH = outputPath;
}
return env;
}
function spawnTest(
executablePath,
testArguments,
options,
callback,
testName,
finalize = null
) {
const cp = childProcess.spawn(executablePath, testArguments, options);
// collect outputs and errors
let stderrOutput = '';
if (cp.stdout) {
cp.stderr.on('data', data => {
stderrOutput += data;
});
cp.stdout.on('data', data => {
stderrOutput += data;
});
}
// on error
cp.on('error', error => {
console.log(error, 'error');
if (finalize) {
finalize();
} // if finalizer provided
callback(error);
});
// on close
cp.on('close', exitCode => {
if (exitCode !== 0) {
retryOrFailTest(
stderrOutput,
exitCode,
executablePath,
testArguments,
options,
callback,
testName,
finalize
);
} else {
// successful test
if (finalize) {
finalize();
} // if finalizer provided
callback(null, {
exitCode,
step: testName,
testCommand: `You can run the test again using: \n\t ${executablePath} ${testArguments.join(
' '
)}`
});
}
});
}
const retryNumber = 6; // the number of times a tests repeats
const retriedTests = new Map(); // a cache of retried tests
// Retries the tests if it is timed out for a number of times. Fails the rest of the tests or those that are tried enough times.
function retryOrFailTest(
stderrOutput,
exitCode,
executablePath,
testArguments,
options,
callback,
testName,
finalize
) {
const testKey = createTestKey(executablePath, testArguments, testName);
if (isTimedOut(stderrOutput) && shouldTryAgain(testKey)) {
// retry the timed out test
let triedNumber = retriedTests.get(testKey) || 0;
retriedTests.set(testKey, triedNumber + 1);
console.warn(`\n##[warning] Retrying the timed out step: ${testName} \n`);
spawnTest(
executablePath,
testArguments,
options,
callback,
testName,
finalize
);
} else {
// fail the test
if (finalize) {
finalize();
} // if finalizer provided
console.log(`##[error] Tests for ${testName} failed.`.red);
console.log(stderrOutput);
callback(null, {
exitCode,
step: testName,
testCommand: `You can run the test again using: \n\t ${executablePath} ${testArguments.join(
' '
)}`
});
}
}
// creates a key that is specific to a certain test
function createTestKey(executablePath, testArguments, testName) {
return `${executablePath} ${testArguments.join(' ')} ${testName}`;
}
// check if a test is timed out
function isTimedOut(stderrOutput) {
if (stderrOutput) {
return (
stderrOutput.includes('timeout: timed out after') || // happens in core renderer tests
stderrOutput.includes('Error: Timed out waiting on') || // happens in core renderer tests
stderrOutput.includes('Error: timeout of') || // happens in core main tests
stderrOutput.includes(
'Error Downloading Update: Could not get code signature for running application'
) // happens in github tests
);
} else {
return false;
}
}
// check if a tests should be tried again
function shouldTryAgain(testKey) {
if (retriedTests.has(testKey)) {
return retriedTests.get(testKey) < retryNumber;
} else {
return true;
}
}
function runCoreMainProcessTests(callback) {
const testPath = path.join(CONFIG.repositoryRootPath, 'spec', 'main-process');
const testArguments = [
'--resource-path',
resourcePath,
'--test',
'--main-process',
testPath
];
if (process.env.CI && process.platform === 'linux') {
testArguments.push('--no-sandbox');
}
const testEnv = Object.assign({}, prepareEnv('core-main-process'), {
ATOM_GITHUB_INLINE_GIT_EXEC: 'true'
});
console.log('##[command] Executing core main process tests'.bold.green);
spawnTest(
executablePath,
testArguments,
{ stdio: 'inherit', env: testEnv },
callback,
'core-main-process'
);
}
function getCoreRenderProcessTestSuites() {
// Build an array of functions, each running tests for a different rendering test
const coreRenderProcessTestSuites = [];
const testPath = path.join(CONFIG.repositoryRootPath, 'spec');
let testFiles = glob.sync(
path.join(testPath, '*-spec.+(js|coffee|ts|jsx|tsx|mjs)')
);
for (let testFile of testFiles) {
const testArguments = ['--resource-path', resourcePath, '--test', testFile];
// the function which runs by async:
coreRenderProcessTestSuites.push(function(callback) {
const testEnv = prepareEnv('core-render-process');
console.log(
`##[command] Executing core render process tests for ${testFile}`.bold
.green
);
spawnTest(
executablePath,
testArguments,
{ env: testEnv },
callback,
`core-render-process in ${testFile}.`
);
});
}
return coreRenderProcessTestSuites;
}
function getPackageTestSuites() {
// Build an array of functions, each running tests for a different bundled package
const packageTestSuites = [];
for (let packageName in CONFIG.appMetadata.packageDependencies) {
if (process.env.ATOM_PACKAGES_TO_TEST) {
const packagesToTest = process.env.ATOM_PACKAGES_TO_TEST.split(',').map(
pkg => pkg.trim()
);
if (!packagesToTest.includes(packageName)) continue;
}
const repositoryPackagePath = path.join(
CONFIG.repositoryRootPath,
'node_modules',
packageName
);
const testSubdir = ['spec', 'test'].find(subdir =>
fs.existsSync(path.join(repositoryPackagePath, subdir))
);
if (!testSubdir) {
console.log(`No test folder found for package: ${packageName}`.yellow);
continue;
}
const testFolder = path.join(repositoryPackagePath, testSubdir);
const testArguments = [
'--resource-path',
resourcePath,
'--test',
testFolder
];
const pkgJsonPath = path.join(repositoryPackagePath, 'package.json');
const nodeModulesPath = path.join(repositoryPackagePath, 'node_modules');
// the function which runs by async:
packageTestSuites.push(function(callback) {
const testEnv = prepareEnv(`bundled-package-${packageName}`);
let finalize = () => null;
if (require(pkgJsonPath).atomTestRunner) {
console.log(
`##[command] Installing test runner dependencies for ${packageName}`
.bold.green
);
if (fs.existsSync(nodeModulesPath)) {
const backup = backupNodeModules(repositoryPackagePath);
finalize = backup.restore;
} else {
finalize = () => fs.removeSync(nodeModulesPath);
}
runApmInstall(repositoryPackagePath);
console.log(`##[command] Executing ${packageName} tests`.green);
} else {
console.log(`##[command] Executing ${packageName} tests`.bold.green);
}
spawnTest(
executablePath,
testArguments,
{ env: testEnv },
callback,
`${packageName} package`,
finalize
);
});
}
return packageTestSuites;
}
function runBenchmarkTests(callback) {
const benchmarksPath = path.join(CONFIG.repositoryRootPath, 'benchmarks');
const testArguments = ['--benchmark-test', benchmarksPath];
const testEnv = prepareEnv('benchmark');
console.log('##[command] Executing benchmark tests'.bold.green);
spawnTest(
executablePath,
testArguments,
{ stdio: 'inherit', env: testEnv },
callback,
`core-benchmarks`
);
}
let testSuitesToRun = requestedTestSuites(process.platform);
function requestedTestSuites(platform) {
// env variable or argv options
let coreAll = process.env.ATOM_RUN_CORE_TESTS === 'true';
let coreMain =
process.env.ATOM_RUN_CORE_MAIN_TESTS === 'true' || argv.coreMain;
let coreRenderer =
argv.coreRenderer || process.env.ATOM_RUN_CORE_RENDER_TESTS === 'true';
let coreRenderer1 = process.env.ATOM_RUN_CORE_RENDER_TESTS === '1';
let coreRenderer2 = process.env.ATOM_RUN_CORE_RENDER_TESTS === '2';
let packageAll =
argv.package || process.env.ATOM_RUN_PACKAGE_TESTS === 'true';
let packages1 = process.env.ATOM_RUN_PACKAGE_TESTS === '1';
let packages2 = process.env.ATOM_RUN_PACKAGE_TESTS === '2';
let benchmark = argv.coreBenchmark;
// Operating system overrides:
coreMain =
coreMain ||
platform === 'linux' ||
(platform === 'win32' && process.arch === 'x86');
// split package tests (used for macos in CI)
const PACKAGES_TO_TEST_IN_PARALLEL = 23;
// split core render test (used for windows x64 in CI)
const CORE_RENDER_TO_TEST_IN_PARALLEL = 45;
let suites = [];
// Core tess
if (coreAll) {
suites.push(
...[runCoreMainProcessTests, ...getCoreRenderProcessTestSuites()]
);
} else {
// Core main tests
if (coreMain) {
suites.push(runCoreMainProcessTests);
}
// Core renderer tests
if (coreRenderer) {
suites.push(...getCoreRenderProcessTestSuites());
} else {
// split
if (coreRenderer1) {
suites.push(
...getCoreRenderProcessTestSuites().slice(
0,
CORE_RENDER_TO_TEST_IN_PARALLEL
)
);
}
if (coreRenderer2) {
suites.push(
...getCoreRenderProcessTestSuites().slice(
CORE_RENDER_TO_TEST_IN_PARALLEL
)
);
}
}
}
// Package tests
if (packageAll) {
suites.push(...getPackageTestSuites());
} else {
// split
if (packages1) {
suites.push(
...getPackageTestSuites().slice(0, PACKAGES_TO_TEST_IN_PARALLEL)
);
}
if (packages2) {
suites.push(
...getPackageTestSuites().slice(PACKAGES_TO_TEST_IN_PARALLEL)
);
}
}
// Benchmark tests
if (benchmark) {
suites.push(runBenchmarkTests);
}
if (argv.skipMainProcessTests) {
suites = suites.filter(suite => suite !== runCoreMainProcessTests);
}
// Remove duplicates
suites = Array.from(new Set(suites));
if (suites.length === 0) {
throw new Error('No tests was requested');
}
return suites;
}
asyncSeries(testSuitesToRun, function(err, results) {
if (err) {
console.error(err);
process.exit(1);
} else {
const failedSteps = results.filter(({ exitCode }) => exitCode !== 0);
if (failedSteps.length > 0) {
console.warn(
'\n \n ##[error] *** Reporting the errors that happened in all of the tests: *** \n \n'
);
for (const { step, testCommand } of failedSteps) {
console.error(
`##[error] The '${step}' test step finished with a non-zero exit code \n ${testCommand}`
);
}
process.exit(1);
}
process.exit(0);
}
});