From 09099432595ff18a11122c0ea03ea18dd5de845d Mon Sep 17 00:00:00 2001 From: Filipe Silva Date: Wed, 31 Oct 2018 16:09:20 +0000 Subject: [PATCH] test(@angular-devkit/build-angular): test karma on watch mode --- .../src/angular-cli-files/plugins/karma.ts | 28 ++++-- .../build_angular/src/karma/index.ts | 1 + .../test/karma/rebuilds_spec_large.ts | 86 ++++++++++++++++++- 3 files changed, 104 insertions(+), 11 deletions(-) diff --git a/packages/angular_devkit/build_angular/src/angular-cli-files/plugins/karma.ts b/packages/angular_devkit/build_angular/src/angular-cli-files/plugins/karma.ts index 54de40d221..bdc4e2a047 100644 --- a/packages/angular_devkit/build_angular/src/angular-cli-files/plugins/karma.ts +++ b/packages/angular_devkit/build_angular/src/angular-cli-files/plugins/karma.ts @@ -9,13 +9,15 @@ // TODO: cleanup this file, it's copied as is from Angular CLI. import * as path from 'path'; -import * as fs from 'fs'; import * as glob from 'glob'; import * as webpack from 'webpack'; const webpackDevMiddleware = require('webpack-dev-middleware'); -import { AssetPattern } from '../../browser/schema'; import { KarmaWebpackFailureCb } from './karma-webpack-failure-cb'; +import { statsErrorsToString } from '../utilities/stats'; +import { getWebpackStatsConfig } from '../models/webpack-configs/stats'; +import { createConsoleLogger } from '@angular-devkit/core/node'; +import { logging } from '@angular-devkit/core'; /** * Enumerate needed (but not require/imported) dependencies from this file @@ -62,7 +64,7 @@ const init: any = (config: any, emitter: any, customFileHandlers: any) => { ) } const options = config.buildWebpack.options; - const projectRoot = config.buildWebpack.projectRoot as string; + const logger: logging.Logger = config.buildWebpack.logger || createConsoleLogger(); successCb = config.buildWebpack.successCb; failureCb = config.buildWebpack.failureCb; @@ -93,7 +95,9 @@ const init: any = (config: any, emitter: any, customFileHandlers: any) => { // Add webpack config. const webpackConfig = config.buildWebpack.webpackConfig; const webpackMiddlewareConfig = { - logLevel: 'error', // Hide webpack output because its noisy. + // Hide webpack output because its noisy. + logLevel: 'error', + stats: false, watchOptions: { poll: options.poll }, publicPath: '/_karma_webpack_/', }; @@ -147,9 +151,9 @@ const init: any = (config: any, emitter: any, customFileHandlers: any) => { try { compiler = webpack(webpackConfig); } catch (e) { - console.error(e.stack || e); + logger.error(e.stack || e) if (e.details) { - console.error(e.details); + logger.error(e.details) } throw e; } @@ -175,9 +179,17 @@ const init: any = (config: any, emitter: any, customFileHandlers: any) => { } let lastCompilationHash: string | undefined; + const statsConfig = getWebpackStatsConfig(); compiler.hooks.done.tap('karma', (stats: any) => { - // Refresh karma only when there are no webpack errors, and if the compilation changed. - if (stats.compilation.errors.length === 0 && stats.hash != lastCompilationHash) { + if (stats.compilation.errors.length > 0) { + const json = stats.toJson(config.stats); + // Print compilation errors. + logger.error(statsErrorsToString(json, statsConfig)); + lastCompilationHash = undefined; + // Emit a failure build event if there are compilation errors. + failureCb && failureCb(); + } else if (stats.hash != lastCompilationHash) { + // Refresh karma only when there are no webpack errors, and if the compilation changed. lastCompilationHash = stats.hash; emitter.refreshFiles(); } diff --git a/packages/angular_devkit/build_angular/src/karma/index.ts b/packages/angular_devkit/build_angular/src/karma/index.ts index ef3f17acd5..1bd80e9119 100644 --- a/packages/angular_devkit/build_angular/src/karma/index.ts +++ b/packages/angular_devkit/build_angular/src/karma/index.ts @@ -97,6 +97,7 @@ export class KarmaBuilder implements Builder { // When this workaround is removed, user projects need to be updated to use a Karma // version that has a fix for this issue. toJSON: () => { }, + logger: this.context.logger, }; // TODO: inside the configs, always use the project root and not the workspace root. diff --git a/packages/angular_devkit/build_angular/test/karma/rebuilds_spec_large.ts b/packages/angular_devkit/build_angular/test/karma/rebuilds_spec_large.ts index 6d08de6276..a961385937 100644 --- a/packages/angular_devkit/build_angular/test/karma/rebuilds_spec_large.ts +++ b/packages/angular_devkit/build_angular/test/karma/rebuilds_spec_large.ts @@ -6,10 +6,12 @@ * found in the LICENSE file at https://angular.io/license */ -import { runTargetSpec } from '@angular-devkit/architect/testing'; -import { debounceTime, take, tap } from 'rxjs/operators'; +import { DefaultTimeout, runTargetSpec } from '@angular-devkit/architect/testing'; +import { Subject } from 'rxjs'; +import { debounceTime, delay, take, takeUntil, takeWhile, tap } from 'rxjs/operators'; import { host, karmaTargetSpec } from '../utils'; + describe('Karma Builder watch mode', () => { beforeEach(done => host.initialize().toPromise().then(done, done.fail)); afterEach(done => host.restore().toPromise().then(done, done.fail)); @@ -23,5 +25,83 @@ describe('Karma Builder watch mode', () => { ).toPromise(); expect(res).toEqual({ success: true }); - }, 30000); + }); + + it('recovers from compilation failures in watch mode', (done) => { + const overrides = { watch: true }; + let buildCount = 0; + let phase = 1; + + runTargetSpec(host, karmaTargetSpec, overrides, DefaultTimeout * 3).pipe( + debounceTime(500), + tap((buildEvent) => { + buildCount += 1; + switch (phase) { + case 1: + // Karma run should succeed. + // Add a compilation error. + expect(buildEvent.success).toBe(true); + // Add an syntax error to a non-main file. + host.appendToFile('src/app/app.component.spec.ts', `]]]`); + phase = 2; + break; + + case 2: + // Karma run should fail due to compilation error. Fix it. + expect(buildEvent.success).toBe(false); + host.replaceInFile('src/app/app.component.spec.ts', `]]]`, ''); + phase = 3; + break; + + case 3: + // Karma run should succeed again. + expect(buildEvent.success).toBe(true); + phase = 4; + break; + } + }), + takeWhile(() => phase < 4), + ).toPromise().then( + () => done(), + () => done.fail(`stuck at phase ${phase} [builds: ${buildCount}]`), + ); + }); + + it('does not rebuild when nothing changed', (done) => { + const overrides = { watch: true }; + let buildCount = 0; + let phase = 1; + + const stopSubject = new Subject(); + const stop$ = stopSubject.asObservable().pipe(delay(5000)); + + runTargetSpec(host, karmaTargetSpec, overrides, DefaultTimeout * 3).pipe( + debounceTime(500), + tap((buildEvent) => { + buildCount += 1; + switch (phase) { + case 1: + // Karma run should succeed. + // Add a compilation error. + expect(buildEvent.success).toBe(true); + // Touch the file. + host.appendToFile('src/app/app.component.spec.ts', ``); + // Signal the stopper, which delays emission by 5s. + // If there's no rebuild within that time then the test is successful. + stopSubject.next(); + phase = 2; + break; + + case 2: + // Should never trigger this second build. + expect(true).toBeFalsy('Should not trigger second build.'); + break; + } + }), + takeUntil(stop$), + ).toPromise().then( + () => done(), + () => done.fail(`stuck at phase ${phase} [builds: ${buildCount}]`), + ); + }); });