mirror of
https://github.com/angular/angular-cli.git
synced 2025-05-15 01:54:04 +08:00
This way cleanup logic runs on timeout. It also simplifies timeout definition for runTargetSpec and Jasmine.
217 lines
6.8 KiB
TypeScript
217 lines
6.8 KiB
TypeScript
/**
|
|
* @license
|
|
* Copyright Google Inc. All Rights Reserved.
|
|
*
|
|
* Use of this source code is governed by an MIT-style license that can be
|
|
* found in the LICENSE file at https://angular.io/license
|
|
*/
|
|
// tslint:disable:no-implicit-dependencies
|
|
import { logging } from '@angular-devkit/core';
|
|
import * as glob from 'glob';
|
|
import * as Istanbul from 'istanbul';
|
|
import 'jasmine';
|
|
import { SpecReporter as JasmineSpecReporter } from 'jasmine-spec-reporter';
|
|
import { ParsedArgs } from 'minimist';
|
|
import { join, relative } from 'path';
|
|
import { Position, SourceMapConsumer } from 'source-map';
|
|
import * as ts from 'typescript';
|
|
import { packages } from '../lib/packages';
|
|
|
|
const codeMap = require('../lib/istanbul-local').codeMap;
|
|
const Jasmine = require('jasmine');
|
|
|
|
const projectBaseDir = join(__dirname, '..');
|
|
require('source-map-support').install({
|
|
hookRequire: true,
|
|
});
|
|
|
|
|
|
interface CoverageLocation {
|
|
start: Position;
|
|
end: Position;
|
|
}
|
|
|
|
type CoverageType = any; // tslint:disable-line:no-any
|
|
declare const global: {
|
|
__coverage__: CoverageType;
|
|
};
|
|
|
|
|
|
// Add the Istanbul (not Constantinople) reporter.
|
|
const istanbulCollector = new Istanbul.Collector({});
|
|
const istanbulReporter = new Istanbul.Reporter(undefined, 'coverage/');
|
|
istanbulReporter.addAll(['json', 'lcov']);
|
|
|
|
|
|
class IstanbulReporter implements jasmine.CustomReporter {
|
|
// Update a location object from a SourceMap. Will ignore the location if the sourcemap does
|
|
// not have a valid mapping.
|
|
private _updateLocation(consumer: SourceMapConsumer, location: CoverageLocation) {
|
|
const start = consumer.originalPositionFor(location.start);
|
|
const end = consumer.originalPositionFor(location.end);
|
|
|
|
// Filter invalid original positions.
|
|
if (start.line !== null && start.column !== null) {
|
|
// Filter unwanted properties.
|
|
location.start = { line: start.line, column: start.column };
|
|
}
|
|
if (end.line !== null && end.column !== null) {
|
|
location.end = { line: end.line, column: end.column };
|
|
}
|
|
}
|
|
|
|
private _updateCoverageJsonSourceMap(coverageJson: CoverageType) {
|
|
// Update the coverageJson with the SourceMap.
|
|
for (const path of Object.keys(coverageJson)) {
|
|
const entry = codeMap.get(path);
|
|
if (!entry) {
|
|
continue;
|
|
}
|
|
|
|
const consumer = entry.map;
|
|
const coverage = coverageJson[path];
|
|
|
|
// Update statement maps.
|
|
for (const branchId of Object.keys(coverage.branchMap)) {
|
|
const branch = coverage.branchMap[branchId];
|
|
let line: number | null = null;
|
|
let column = 0;
|
|
do {
|
|
line = consumer.originalPositionFor({ line: branch.line, column: column++ }).line;
|
|
} while (line === null && column < 100);
|
|
|
|
branch.line = line;
|
|
|
|
for (const location of branch.locations) {
|
|
this._updateLocation(consumer, location);
|
|
}
|
|
}
|
|
|
|
for (const id of Object.keys(coverage.statementMap)) {
|
|
const location = coverage.statementMap[id];
|
|
this._updateLocation(consumer, location);
|
|
}
|
|
|
|
for (const id of Object.keys(coverage.fnMap)) {
|
|
const fn = coverage.fnMap[id];
|
|
fn.line = consumer.originalPositionFor({ line: fn.line, column: 0 }).line;
|
|
this._updateLocation(consumer, fn.loc);
|
|
}
|
|
}
|
|
}
|
|
|
|
jasmineDone(_runDetails: jasmine.RunDetails): void {
|
|
if (global.__coverage__) {
|
|
this._updateCoverageJsonSourceMap(global.__coverage__);
|
|
istanbulCollector.add(global.__coverage__);
|
|
|
|
istanbulReporter.write(istanbulCollector, true, () => {});
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// Create a Jasmine runner and configure it.
|
|
const runner = new Jasmine({ projectBaseDir: projectBaseDir });
|
|
|
|
if (process.argv.indexOf('--spec-reporter') != -1) {
|
|
runner.env.clearReporters();
|
|
runner.env.addReporter(new JasmineSpecReporter({
|
|
stacktrace: {
|
|
// Filter all JavaScript files that appear after a TypeScript file (callers) from the stack
|
|
// trace.
|
|
filter: (x: string) => {
|
|
return x.substr(0, x.indexOf('\n', x.indexOf('\n', x.lastIndexOf('.ts:')) + 1));
|
|
},
|
|
},
|
|
spec: {
|
|
displayDuration: true,
|
|
},
|
|
suite: {
|
|
displayNumber: true,
|
|
},
|
|
summary: {
|
|
displayStacktrace: true,
|
|
displayErrorMessages: true,
|
|
displayDuration: true,
|
|
},
|
|
}));
|
|
}
|
|
|
|
|
|
// Manually set exit code (needed with custom reporters)
|
|
runner.onComplete((success: boolean) => {
|
|
process.exitCode = success ? 0 : 1;
|
|
if (process.platform.startsWith('win')) {
|
|
// TODO(filipesilva): finish figuring out why this happens.
|
|
// We should not need to force exit here, but when:
|
|
// - on windows
|
|
// - running webpack-dev-server
|
|
// - with ngtools/webpack on the compilation
|
|
// Something seems to hang and the process never exists.
|
|
// This does not happen on linux, nor with webpack on watch mode.
|
|
// Until this is figured out, we need to exit the process manually after tests finish
|
|
// otherwise appveyor will hang until it timeouts.
|
|
process.exit();
|
|
}
|
|
});
|
|
|
|
|
|
glob.sync('packages/**/*.spec.ts')
|
|
.filter(p => !/\/schematics\/.*\/(other-)?files\//.test(p))
|
|
.forEach(path => {
|
|
console.error(`Invalid spec file name: ${path}. You're using the old convention.`);
|
|
});
|
|
|
|
export default function (args: ParsedArgs, logger: logging.Logger) {
|
|
const specGlob = args.large ? '*_spec_large.ts' : '*_spec.ts';
|
|
const regex = args.glob ? args.glob : `packages/**/${specGlob}`;
|
|
|
|
if (args['code-coverage']) {
|
|
runner.env.addReporter(new IstanbulReporter());
|
|
}
|
|
|
|
if (args.large) {
|
|
// Default timeout for large specs is 2.5 minutes.
|
|
jasmine.DEFAULT_TIMEOUT_INTERVAL = 150000;
|
|
}
|
|
|
|
// Run the tests.
|
|
const allTests =
|
|
glob.sync(regex)
|
|
.map(p => relative(projectBaseDir, p));
|
|
|
|
const tsConfigPath = join(__dirname, '../tsconfig.json');
|
|
const tsConfig = ts.readConfigFile(tsConfigPath, ts.sys.readFile);
|
|
const pattern = '^('
|
|
+ (tsConfig.config.exclude as string[])
|
|
.map(ex => '('
|
|
+ ex.split(/[\/\\]/g).map(f => f
|
|
.replace(/[\-\[\]{}()+?.^$|]/g, '\\$&')
|
|
.replace(/^\*\*/g, '(.+?)?')
|
|
.replace(/\*/g, '[^/\\\\]*'))
|
|
.join('[\/\\\\]')
|
|
+ ')')
|
|
.join('|')
|
|
+ ')($|/|\\\\)';
|
|
const excludeRe = new RegExp(pattern);
|
|
let tests = allTests.filter(x => !excludeRe.test(x));
|
|
|
|
if (!args.full) {
|
|
// Remove the tests from packages that haven't changed.
|
|
tests = tests
|
|
.filter(p => Object.keys(packages).some(name => {
|
|
const relativeRoot = relative(projectBaseDir, packages[name].root);
|
|
|
|
return p.startsWith(relativeRoot) && packages[name].dirty;
|
|
}));
|
|
|
|
logger.info(`Found ${tests.length} spec files, out of ${allTests.length}.`);
|
|
}
|
|
|
|
return new Promise(resolve => {
|
|
runner.onComplete((passed: boolean) => resolve(passed ? 0 : 1));
|
|
runner.execute(tests);
|
|
});
|
|
}
|