Skip to content

Commit

Permalink
ci: fix npm5 on CircleCI
Browse files Browse the repository at this point in the history
  • Loading branch information
filipesilva committed Sep 25, 2017
1 parent 5421e69 commit b437a61
Show file tree
Hide file tree
Showing 6 changed files with 138 additions and 121 deletions.
10 changes: 6 additions & 4 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,17 @@ jobs:
working_directory: ~/angular-cli
docker:
- image: angular/ngcontainer
- image: node:8.4
steps:
- checkout
- restore_cache:
key: angular-cli-{{ .Branch }}-{{ checksum "yarn.lock" }}
key: angular-cli-{{ .Branch }}-{{ checksum "package-lock.json" }}
- run: |
npm install -g npm@~5.3.0
npm install
node --version
npm --version
npm install --quiet
- save_cache:
key: angular-cli-{{ .Branch }}-{{ checksum "yarn.lock" }}
key: angular-cli-{{ .Branch }}-{{ checksum "package-lock.json" }}
paths:
- "node_modules"
- run: xvfb-run -a node tests/run_e2e.js --glob=tests/build/**
3 changes: 0 additions & 3 deletions tests/e2e/tests/build/rebuild-css-change.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import {
killAllProcesses,
exec,
waitForAnyProcessOutputToMatch,
execAndWaitForOutputToMatch
} from '../../utils/process';
Expand All @@ -21,8 +20,6 @@ export default function() {

return execAndWaitForOutputToMatch('ng', ['serve'], webpackGoodRegEx)
// Should trigger a rebuild.
.then(() => exec('touch', 'src/main.ts'))
.then(() => waitForAnyProcessOutputToMatch(webpackGoodRegEx, 10000))
.then(() => appendToFile('src/app/app.component.css', ':host { color: blue; }'))
.then(() => waitForAnyProcessOutputToMatch(webpackGoodRegEx, 10000))
.then(() => killAllProcesses(), (err: any) => {
Expand Down
54 changes: 29 additions & 25 deletions tests/e2e/tests/build/rebuild-deps-type-check.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import {
execAndWaitForOutputToMatch,
} from '../../utils/process';
import {writeFile, prependToFile, appendToFile} from '../../utils/fs';
import {wait} from '../../utils/utils';
import {getGlobalVariable} from '../../utils/env';


Expand Down Expand Up @@ -37,44 +36,49 @@ export default function() {
.then(() => appendToFile('src/main.ts', `
console.log(funky2('town'));
`))
.then(() => wait(2000))
// Should trigger a rebuild, no error expected.
.then(() => execAndWaitForOutputToMatch('ng', ['serve'], doneRe))
// Make an invalid version of the file.
.then(() => wait(2000))
.then(() => writeFile('src/funky2.ts', `
export function funky2(value: number): number {
return value + 1;
}
`))
// Should trigger a rebuild, this time an error is expected.
.then(() => waitForAnyProcessOutputToMatch(doneRe, 20000))
.then(({ stderr }) => {
.then(() => Promise.all([
waitForAnyProcessOutputToMatch(doneRe, 20000),
writeFile('src/funky2.ts', `
export function funky2(value: number): number {
return value + 1;
}
`)
]))
.then((results) => {
const stderr = results[0].stderr;
if (!/ERROR in (.*\/src\/)?main\.ts/.test(stderr)) {
throw new Error('Expected an error but none happened.');
}
})
// Change an UNRELATED file and the error should still happen.
.then(() => wait(2000))
.then(() => appendToFile('src/app/app.module.ts', `
function anything(): number {}
`))
// Should trigger a rebuild, this time an error is expected.
.then(() => waitForAnyProcessOutputToMatch(doneRe, 20000))
.then(({ stderr }) => {
// Should trigger a rebuild, this time an error is also expected.
.then(() => Promise.all([
waitForAnyProcessOutputToMatch(doneRe, 20000),
appendToFile('src/app/app.module.ts', `
function anything(): number {}
`)
]))
.then((results) => {
const stderr = results[0].stderr;
if (!/ERROR in (.*\/src\/)?main\.ts/.test(stderr)) {
throw new Error('Expected an error but none happened.');
}
})
// Fix the error!
.then(() => wait(2000))
.then(() => writeFile('src/funky2.ts', `
export function funky2(value: string): string {
return value + 'hello';
}
`))
.then(() => waitForAnyProcessOutputToMatch(doneRe, 20000))
.then(({ stderr }) => {
.then(() => Promise.all([
waitForAnyProcessOutputToMatch(doneRe, 20000),
writeFile('src/funky2.ts', `
export function funky2(value: string): string {
return value + 'hello';
}
`)
]))
.then((results) => {
const stderr = results[0].stderr;
if (/ERROR in (.*\/src\/)?main\.ts/.test(stderr)) {
throw new Error('Expected no error but an error was shown.');
}
Expand Down
127 changes: 63 additions & 64 deletions tests/e2e/tests/build/rebuild.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import {
killAllProcesses,
exec,
waitForAnyProcessOutputToMatch,
execAndWaitForOutputToMatch,
ng,
Expand All @@ -11,7 +10,6 @@ import {request} from '../../utils/http';
import {getGlobalVariable} from '../../utils/env';

const validBundleRegEx = /webpack: bundle is now VALID|webpack: Compiled successfully./;
const invalidBundleRegEx = /webpack: bundle is now INVALID|webpack: Compiling.../;

export default function() {
if (process.platform.startsWith('win')) {
Expand All @@ -26,95 +24,96 @@ export default function() {
const chunkRegExp = /chunk\s+\{/g;

return execAndWaitForOutputToMatch('ng', ['serve'], validBundleRegEx)
// Should trigger a rebuild.
.then(() => exec('touch', 'src/main.ts'))
.then(() => waitForAnyProcessOutputToMatch(invalidBundleRegEx, 10000))
.then(() => waitForAnyProcessOutputToMatch(validBundleRegEx, 10000))
// Count the bundles.
.then(({ stdout }) => {
oldNumberOfChunks = stdout.split(chunkRegExp).length;
})
// Add a lazy module.
.then(() => ng('generate', 'module', 'lazy', '--routing'))
// Just wait for the rebuild, otherwise we might be validating the last build.
.then(() => waitForAnyProcessOutputToMatch(validBundleRegEx, 10000))
.then(() => writeFile('src/app/app.module.ts', `
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { HttpModule } from '@angular/http';
import { AppComponent } from './app.component';
import { RouterModule } from '@angular/router';
@NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule,
FormsModule,
HttpModule,
RouterModule.forRoot([
{ path: 'lazy', loadChildren: './lazy/lazy.module#LazyModule' }
])
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
`))
// Should trigger a rebuild with a new bundle.
.then(() => waitForAnyProcessOutputToMatch(validBundleRegEx, 10000))
// Count the bundles.
.then(({ stdout }) => {
let newNumberOfChunks = stdout.split(chunkRegExp).length;
if (oldNumberOfChunks >= newNumberOfChunks) {
throw new Error('Expected webpack to create a new chunk, but did not.');
}
})
// Change multiple files and check that all of them are invalidated and recompiled.
.then(() => writeMultipleFiles({
'src/app/app.module.ts': `
// We need to use Promise.all to ensure we are waiting for the rebuild just before we write
// the file, otherwise rebuilds can be too fast and fail CI.
.then(() => Promise.all([
waitForAnyProcessOutputToMatch(validBundleRegEx, 10000),
writeFile('src/app/app.module.ts', `
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { HttpModule } from '@angular/http';
import { AppComponent } from './app.component';
import { RouterModule } from '@angular/router';
@NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule
BrowserModule,
FormsModule,
HttpModule,
RouterModule.forRoot([
{ path: 'lazy', loadChildren: './lazy/lazy.module#LazyModule' }
])
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
`)
]))
// Count the bundles.
.then((results) => {
const stdout = results[0].stdout;
let newNumberOfChunks = stdout.split(chunkRegExp).length;
if (oldNumberOfChunks >= newNumberOfChunks) {
throw new Error('Expected webpack to create a new chunk, but did not.');
}
})
// Change multiple files and check that all of them are invalidated and recompiled.
.then(() => Promise.all([
waitForAnyProcessOutputToMatch(validBundleRegEx, 10000),
writeMultipleFiles({
'src/app/app.module.ts': `
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { AppComponent } from './app.component';
console.log('$$_E2E_GOLDEN_VALUE_1');
export let X = '$$_E2E_GOLDEN_VALUE_2';
`,
'src/main.ts': `
import { enableProdMode } from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
@NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
import { AppModule } from './app/app.module';
import { environment } from './environments/environment';
console.log('$$_E2E_GOLDEN_VALUE_1');
export let X = '$$_E2E_GOLDEN_VALUE_2';
`,
'src/main.ts': `
import { enableProdMode } from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
if (environment.production) {
enableProdMode();
}
import { AppModule } from './app/app.module';
import { environment } from './environments/environment';
platformBrowserDynamic().bootstrapModule(AppModule);
if (environment.production) {
enableProdMode();
}
import * as m from './app/app.module';
console.log(m.X);
console.log('$$_E2E_GOLDEN_VALUE_3');
`
}))
.then(() => waitForAnyProcessOutputToMatch(validBundleRegEx, 10000))
platformBrowserDynamic().bootstrapModule(AppModule);
import * as m from './app/app.module';
console.log(m.X);
console.log('$$_E2E_GOLDEN_VALUE_3');
`
})
]))
.then(() => wait(2000))
.then(() => request('http://localhost:4200/main.bundle.js'))
.then((body) => {
Expand Down
63 changes: 39 additions & 24 deletions tests/e2e/utils/process.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import * as child_process from 'child_process';
import {blue, yellow} from 'chalk';
import {getGlobalVariable} from './env';
import {rimraf, writeFile} from './fs';
import {rimraf} from './fs';
import {wait} from './utils';
const treeKill = require('tree-kill');


Expand All @@ -13,7 +14,7 @@ interface ExecOptions {

let _processes: child_process.ChildProcess[] = [];

type ProcessOutput = {
export type ProcessOutput = {
stdout: string;
stderr: string;
};
Expand Down Expand Up @@ -97,26 +98,29 @@ function _exec(options: ExecOptions, cmd: string, args: string[]): Promise<Proce
export function waitForAnyProcessOutputToMatch(match: RegExp,
timeout = 30000): Promise<ProcessOutput> {
// Race between _all_ processes, and the timeout. First one to resolve/reject wins.
return Promise.race(_processes.map(childProcess => new Promise(resolve => {
let stdout = '';
let stderr = '';
childProcess.stdout.on('data', (data: Buffer) => {
stdout += data.toString();
if (data.toString().match(match)) {
resolve({ stdout, stderr });
}
});
childProcess.stderr.on('data', (data: Buffer) => {
stderr += data.toString();
});
})).concat([
new Promise((resolve, reject) => {
// Wait for 30 seconds and timeout.
setTimeout(() => {
reject(new Error(`Waiting for ${match} timed out (timeout: ${timeout}msec)...`));
}, timeout);
})
]));
const timeoutPromise: Promise<ProcessOutput> = new Promise((_resolve, reject) => {
// Wait for 30 seconds and timeout.
setTimeout(() => {
reject(new Error(`Waiting for ${match} timed out (timeout: ${timeout}msec)...`));
}, timeout);
});

const matchPromises: Promise<ProcessOutput>[] = _processes.map(
childProcess => new Promise(resolve => {
let stdout = '';
let stderr = '';
childProcess.stdout.on('data', (data: Buffer) => {
stdout += data.toString();
if (data.toString().match(match)) {
resolve({ stdout, stderr });
}
});
childProcess.stderr.on('data', (data: Buffer) => {
stderr += data.toString();
});
}));

return Promise.race(matchPromises.concat([timeoutPromise]));
}

export function killAllProcesses(signal = 'SIGTERM') {
Expand All @@ -133,11 +137,22 @@ export function silentExec(cmd: string, ...args: string[]) {
}

export function execAndWaitForOutputToMatch(cmd: string, args: string[], match: RegExp) {
return _exec({ waitForMatch: match }, cmd, args);
let maybeWait = Promise.resolve();
if (cmd === 'ng' && args[0] === 'serve') {
// Webpack watcher can rebuild a few times due to files changes that happened just before the
// build (e.g. `git clean`), so we wait here.
maybeWait = wait(5000);
}
return maybeWait.then(() => _exec({ waitForMatch: match }, cmd, args));
}

export function silentExecAndWaitForOutputToMatch(cmd: string, args: string[], match: RegExp) {
return _exec({ silent: true, waitForMatch: match }, cmd, args);
let maybeWait = Promise.resolve();
if (cmd === 'ng' && args[0] === 'serve') {
// See execAndWaitForOutputToMatch for why the wait.
maybeWait = wait(5000);
}
return maybeWait.then(() => _exec({ silent: true, waitForMatch: match }, cmd, args));
}


Expand Down
2 changes: 1 addition & 1 deletion tests/e2e/utils/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export function expectToFail(fn: () => Promise<any>, errorMessage?: string): Pro
}, () => { });
}

export function wait(msecs: number) {
export function wait(msecs: number): Promise<void> {
return new Promise((resolve) => {
setTimeout(resolve, msecs);
});
Expand Down

0 comments on commit b437a61

Please sign in to comment.