voluptasmollitia
Version:
Monorepo for the Firebase JavaScript SDK
179 lines (167 loc) • 5.5 kB
JavaScript
/**
* @license
* Copyright 2019 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
const { spawn } = require('child-process-promise');
const { exists } = require('mz/fs');
const yargs = require('yargs');
const glob = require('glob');
const path = require('path');
const chalk = require('chalk');
// Check for 'configFiles' flag to run on specified karma.conf.js files instead
// of on all files.
const { configFiles } = yargs
.option('configFiles', {
default: [],
describe: 'specify individual karma.conf.js files to run on',
type: 'array'
})
.version(false)
.help().argv;
// Get all karma.conf.js files that need to be run.
// runNextTest() pulls filenames one-by-one from this queue.
const testFiles = configFiles.length
? configFiles
: glob
.sync(`packages/*/karma.conf.js`)
// Skip integration namespace tests, not very useful, introduce errors.
.concat('integration/firestore/karma.conf.js')
// Exclude database - currently too many failures.
.filter(name => !name.includes('packages/database'));
// Get CI build number or generate one if running locally.
const buildNumber =
process.env.TRAVIS_BUILD_NUMBER ||
process.env.GITHUB_RUN_ID ||
`local_${process.env.USER}_${new Date().getTime()}`;
/**
* Runs a set of SauceLabs browser tests based on this karma config file.
*
* @param {string} testFile Path to karma.conf.js file that defines this test
* group.
*/
async function runTest(testFile) {
if (!(await exists(testFile))) {
console.error(chalk`{red ERROR: ${testFile} does not exist.}`);
return 1;
}
// Run pretest if this dir has a package.json with a pretest script.
const testFileDir =
path.resolve(__dirname, '../') + '/' + path.dirname(testFile);
const pkgPath = testFileDir + '/package.json';
let pkgName = testFile;
if (await exists(pkgPath)) {
const pkg = require(pkgPath);
pkgName = pkg.name;
if (pkg.scripts.pretest) {
await spawn('yarn', ['--cwd', testFileDir, 'pretest'], {
stdio: 'inherit'
});
}
}
if (testFile.includes('integration/firestore')) {
console.log(
chalk`{blue Generating memory-only build for integration/firestore.}`
);
await spawn('yarn', ['--cwd', 'integration/firestore', 'build:memory'], {
stdio: 'inherit'
});
console.log(
chalk`{blue Running tests on memory-only build for integration/firestore.}`
);
const exitCode1 = await runKarma(testFile, `${pkgName}-memory`);
console.log(
chalk`{blue Generating persistence build for integration/firestore.}`
);
await spawn(
'yarn',
['--cwd', 'integration/firestore', 'build:persistence'],
{ stdio: 'inherit' }
);
console.log(
chalk`{blue Running tests on persistence build for integration/firestore.}`
);
const exitCode2 = await runKarma(testFile, `${pkgName}-persistence`);
return Math.max(exitCode1, exitCode2);
} else {
return runKarma(testFile, pkgName);
}
}
/**
* Runs the karma test command for one package.
*
* @param {string} testFile - path to karma.conf.js file
* @param {string} testTag - package label for messages (usually package name)
*/
async function runKarma(testFile, testTag) {
const karmaArgs = [
'karma',
'start',
'config/karma.saucelabs.js',
'--single-run',
'--testConfigFile',
testFile,
'--buildNumber',
buildNumber
];
const promise = spawn('npx', karmaArgs, { stdio: 'inherit' });
const childProcess = promise.childProcess;
let exitCode = 0;
// Capture exit code of this single package test run
childProcess.on('exit', code => {
exitCode = code;
});
return promise
.then(() => {
console.log(chalk`{green [${testTag}] ******* DONE *******}`);
return exitCode;
})
.catch(err => {
console.error(chalk`{red [${testTag}] ERROR: ${err.message}}`);
return exitCode;
});
}
/**
* Runs next file in testFiles queue as long as there are files in the queue.
*
* @param {number} maxExitCode Current highest exit code from all processes
* run so far. When main process is complete, it will exit with highest code
* of all child processes. This allows any failing test to result in a CI
* build failure for the whole Saucelabs run.
*/
async function runNextTest(maxExitCode = 0, results = {}) {
// When test queue is empty, exit with code 0 if no tests failed or
// 1 if any tests failed.
if (!testFiles.length) {
for (const fileName of Object.keys(results)) {
if (results[fileName] > 0) {
console.log(`FAILED: ${fileName}`);
}
}
process.exit(maxExitCode);
}
const nextFile = testFiles.shift();
let exitCode;
try {
exitCode = await runTest(nextFile);
} catch (e) {
console.error(chalk`{red [${nextFile}] ERROR: ${e.message}}`);
exitCode = 1;
}
runNextTest(Math.max(exitCode, maxExitCode), {
...results,
[nextFile]: exitCode
});
}
runNextTest();