// This may seem awkward but we're using Logger in our e2e. At this point the unit tests // have run already so it should be "safe", teehee. import { ConsoleLoggerStack, LogEntry, IndentLogger, NullLogger } from '../packages/@ngtools/logger/src/index'; import {blue, bold, green, red, yellow, white} from 'chalk'; import {gitClean} from './e2e/utils/git'; import * as glob from 'glob'; import * as minimist from 'minimist'; import * as path from 'path'; import {setGlobalVariable} from './e2e/utils/env'; // RxJS import 'rxjs/add/operator/filter'; import 'rxjs/add/observable/empty'; Error.stackTraceLimit = Infinity; /** * Here's a short description of those flags: * --debug If a test fails, block the thread so the temporary directory isn't deleted. * --noproject Skip creating a project or using one. * --nobuild Skip building the packages. Use with --nolink and --reuse to quickly * rerun tests. * --nolink Skip linking your local @angular/cli directory. Can save a few seconds. * --ng-sha=SHA Use a specific ng-sha. Similar to nightly but point to a master SHA instead * of using the latest. * --glob Run tests matching this glob pattern (relative to tests/e2e/). * --ignore Ignore tests matching this glob pattern. * --nightly Install angular nightly builds over the test project. * --reuse=/path Use a path instead of create a new project. That project should have been * created, and npm installed. Ideally you want a project created by a previous * run of e2e. * If unnamed flags are passed in, the list of tests will be filtered to include only those passed. */ const argv = minimist(process.argv.slice(2), { 'boolean': ['debug', 'nolink', 'nightly', 'noproject', 'verbose', 'eject'], 'string': ['glob', 'ignore', 'reuse', 'ng-sha', ] }); /** * Set the error code of the process to 255. This is to ensure that if something forces node * to exit without finishing properly, the error code will be 255. Right now that code is not used. * * When tests succeed we already call `process.exit(0)`, so this doesn't change any correct * behaviour. * * One such case that would force node <= v6 to exit with code 0, is a Promise that doesn't resolve. */ process.exitCode = 255; ConsoleLoggerStack.start(new IndentLogger('name')) .filter((entry: LogEntry) => (entry.level != 'debug' || argv.verbose)) .subscribe((entry: LogEntry) => { let color: (s: string) => string = white; let output = process.stdout; switch (entry.level) { case 'info': color = white; break; case 'warn': color = yellow; break; case 'error': color = red; output = process.stderr; break; case 'fatal': color = (x: string) => bold(red(x)); output = process.stderr; break; } output.write(color(entry.message) + '\n'); }); const testGlob = argv.glob || 'tests/**/*.ts'; let currentFileName = null; let index = 0; const e2eRoot = path.join(__dirname, 'e2e'); const allSetups = glob.sync(path.join(e2eRoot, 'setup/**/*.ts'), { nodir: true }) .map(name => path.relative(e2eRoot, name)) .sort(); const allTests = glob.sync(path.join(e2eRoot, testGlob), { nodir: true, ignore: argv.ignore }) .map(name => path.relative(e2eRoot, name)) .sort(); const testsToRun = allSetups .concat(allTests .filter(name => { // Check for naming tests on command line. if (argv._.length == 0) { return true; } return argv._.some(argName => { return path.join(process.cwd(), argName) == path.join(__dirname, 'e2e', name) || argName == name || argName == name.replace(/\.ts$/, ''); }); })); /** * Load all the files from the e2e, filter and sort them and build a promise of their default * export. */ if (testsToRun.length == allTests.length) { console.log(`Running ${testsToRun.length} tests`); } else { console.log(`Running ${testsToRun.length} tests (${allTests.length + allSetups.length} total)`); } setGlobalVariable('argv', argv); testsToRun.reduce((previous, relativeName) => { // Make sure this is a windows compatible path. let absoluteName = path.join(e2eRoot, relativeName); if (/^win/.test(process.platform)) { absoluteName = absoluteName.replace(/\\/g, path.posix.sep); } return previous.then(() => { currentFileName = relativeName.replace(/\.ts$/, ''); const start = +new Date(); const module = require(absoluteName); const fn: (...args: any[]) => Promise | any = (typeof module == 'function') ? module : (typeof module.default == 'function') ? module.default : () => { throw new Error('Invalid test module.'); }; let clean = true; let previousDir = null; return Promise.resolve() .then(() => printHeader(currentFileName)) .then(() => previousDir = process.cwd()) .then(() => ConsoleLoggerStack.push(currentFileName)) .then(() => fn(() => clean = false)) .then(() => ConsoleLoggerStack.pop(), (err: any) => { ConsoleLoggerStack.pop(); throw err; }) .then(() => console.log('----')) .then(() => { // If we're not in a setup, change the directory back to where it was before the test. // This allows tests to chdir without worrying about keeping the original directory. if (allSetups.indexOf(relativeName) == -1 && previousDir) { process.chdir(previousDir); } }) .then(() => { // Only clean after a real test, not a setup step. Also skip cleaning if the test // requested an exception. if (allSetups.indexOf(relativeName) == -1 && clean) { ConsoleLoggerStack.push(NullLogger); return gitClean() .then(() => ConsoleLoggerStack.pop(), (err: any) => { ConsoleLoggerStack.pop(); throw err; }); } }) .then(() => printFooter(currentFileName, start), (err) => { printFooter(currentFileName, start); console.error(err); throw err; }); }); }, Promise.resolve()) .then(() => { console.log(green('Done.')); process.exit(0); }, (err) => { console.log('\n'); console.error(red(`Test "${currentFileName}" failed...`)); console.error(red(err.message)); console.error(red(err.stack)); if (argv.debug) { console.log(`Current Directory: ${process.cwd()}`); console.log('Will loop forever while you debug... CTRL-C to quit.'); /* eslint-disable no-constant-condition */ while (1) { // That's right! } } process.exit(1); }); function encode(str) { return str.replace(/[^A-Za-z\d\/]+/g, '-').replace(/\//g, '.').replace(/[\/-]$/, ''); } function isTravis() { return process.env['TRAVIS']; } function printHeader(testName) { const text = `${++index} of ${testsToRun.length}`; console.log(green(`Running "${bold(blue(testName))}" (${bold(white(text))})...`)); if (isTravis()) { console.log(`travis_fold:start:${encode(testName)}`); } } function printFooter(testName, startTime) { if (isTravis()) { console.log(`travis_fold:end:${encode(testName)}`); } // Round to hundredth of a second. const t = Math.round((Date.now() - startTime) / 10) / 100; console.log(green('Last step took ') + bold(blue(t)) + green('s...')); console.log(''); }