test(compiler-cli): improve testing harness for incremental compilation (#25275)

In tsc 3.0 the check that enables program structure reuse in tryReuseStructureFromOldProgram has changed
and now uses identity comparison on arrays within CompilerOptions. Since we recreate the options
on each incremental compilation, we now fail this check.

After this change the default set of options is reused in between incremental compilations, but we still
allow options to be overriden if needed.

PR Close #25275
This commit is contained in:
Igor Minar 2018-08-21 16:27:44 -07:00 committed by Matias Niemelä
parent ab32ac6bb7
commit 317d40d879
3 changed files with 87 additions and 79 deletions

View File

@ -393,9 +393,6 @@ export class TsCompilerAotCompilerTypeCheckHostAdapter implements ts.CompilerHos
getSourceFile( getSourceFile(
fileName: string, languageVersion: ts.ScriptTarget, fileName: string, languageVersion: ts.ScriptTarget,
onError?: ((message: string) => void)|undefined): ts.SourceFile { onError?: ((message: string) => void)|undefined): ts.SourceFile {
if (fileName.endsWith('@angular/core/src/di/injection_token.d.ts')) {
debugger;
}
// Note: Don't exit early in this method to make sure // Note: Don't exit early in this method to make sure
// we always have up to date references on the file! // we always have up to date references on the file!
let genFileNames: string[] = []; let genFileNames: string[] = [];

View File

@ -49,6 +49,33 @@ export interface TestSupport {
} }
function createTestSupportFor(basePath: string) { function createTestSupportFor(basePath: string) {
// Typescript uses identity comparison on `paths` and other arrays in order to determine
// if program structure can be reused for incremental compilation, so we reuse the default
// values unless overriden, and freeze them so that they can't be accidentaly changed somewhere
// in tests.
const defaultCompilerOptions = {
basePath,
'experimentalDecorators': true,
'skipLibCheck': true,
'strict': true,
'strictPropertyInitialization': false,
'types': Object.freeze<string>([]) as string[],
'outDir': path.resolve(basePath, 'built'),
'rootDir': basePath,
'baseUrl': basePath,
'declaration': true,
'target': ts.ScriptTarget.ES5,
'module': ts.ModuleKind.ES2015,
'moduleResolution': ts.ModuleResolutionKind.NodeJs,
'lib': Object.freeze([
path.resolve(basePath, 'node_modules/typescript/lib/lib.es6.d.ts'),
]) as string[],
// clang-format off
'paths': Object.freeze({'@angular/*': ['./node_modules/@angular/*']}) as {[index: string]: string[]}
// clang-format on
};
return {basePath, write, writeFiles, createCompilerOptions, shouldExist, shouldNotExist}; return {basePath, write, writeFiles, createCompilerOptions, shouldExist, shouldNotExist};
function write(fileName: string, content: string) { function write(fileName: string, content: string) {
@ -66,25 +93,7 @@ function createTestSupportFor(basePath: string) {
} }
function createCompilerOptions(overrideOptions: ng.CompilerOptions = {}): ng.CompilerOptions { function createCompilerOptions(overrideOptions: ng.CompilerOptions = {}): ng.CompilerOptions {
return { return {...defaultCompilerOptions, ...overrideOptions};
basePath,
'experimentalDecorators': true,
'skipLibCheck': true,
'strict': true,
'strictPropertyInitialization': false,
'types': [],
'outDir': path.resolve(basePath, 'built'),
'rootDir': basePath,
'baseUrl': basePath,
'declaration': true,
'target': ts.ScriptTarget.ES5,
'module': ts.ModuleKind.ES2015,
'moduleResolution': ts.ModuleResolutionKind.NodeJs,
'lib': [
path.resolve(basePath, 'node_modules/typescript/lib/lib.es6.d.ts'),
],
'paths': {'@angular/*': ['./node_modules/@angular/*']}, ...overrideOptions,
};
} }
function shouldExist(fileName: string) { function shouldExist(fileName: string) {

View File

@ -59,7 +59,7 @@ describe('ng program', () => {
function compile( function compile(
oldProgram?: ng.Program, overrideOptions?: ng.CompilerOptions, rootNames?: string[], oldProgram?: ng.Program, overrideOptions?: ng.CompilerOptions, rootNames?: string[],
host?: CompilerHost): {program: ng.Program, emitResult: ts.EmitResult, host: ng.CompilerHost} { host?: CompilerHost): {program: ng.Program, emitResult: ts.EmitResult} {
const options = testSupport.createCompilerOptions(overrideOptions); const options = testSupport.createCompilerOptions(overrideOptions);
if (!rootNames) { if (!rootNames) {
rootNames = [path.resolve(testSupport.basePath, 'src/index.ts')]; rootNames = [path.resolve(testSupport.basePath, 'src/index.ts')];
@ -75,7 +75,7 @@ describe('ng program', () => {
}); });
expectNoDiagnosticsInProgram(options, program); expectNoDiagnosticsInProgram(options, program);
const emitResult = program.emit(); const emitResult = program.emit();
return {emitResult, program, host}; return {emitResult, program};
} }
function createWatchModeHost(): ng.CompilerHost { function createWatchModeHost(): ng.CompilerHost {
@ -85,17 +85,16 @@ describe('ng program', () => {
const originalGetSourceFile = host.getSourceFile; const originalGetSourceFile = host.getSourceFile;
const cache = new Map<string, ts.SourceFile>(); const cache = new Map<string, ts.SourceFile>();
host.getSourceFile = function(fileName: string): ts.SourceFile { host.getSourceFile = function(fileName: string): ts.SourceFile {
if (fileName.endsWith('@angular/core/src/di/injection_token.d.ts')) {
debugger;
}
const sf = originalGetSourceFile.call(host, fileName) as ts.SourceFile; const sf = originalGetSourceFile.call(host, fileName) as ts.SourceFile;
if (sf && cache.has(sf.fileName)) { if (sf) {
const oldSf = cache.get(sf.fileName)!; if (cache.has(sf.fileName)) {
const oldSf = cache.get(sf.fileName) !;
if (oldSf.getFullText() === sf.getFullText()) { if (oldSf.getFullText() === sf.getFullText()) {
return oldSf; return oldSf;
} }
} }
sf && cache.set(sf.fileName, sf); cache.set(sf.fileName, sf);
}
return sf; return sf;
}; };
return host; return host;
@ -290,7 +289,10 @@ describe('ng program', () => {
.toBe(false); .toBe(false);
}); });
describe('reuse tests', () => { describe(
'verify that program structure is reused within tsc in order to speed up incremental compilation',
() => {
it('should reuse the old ts program completely if nothing changed', () => { it('should reuse the old ts program completely if nothing changed', () => {
testSupport.writeFiles({'src/index.ts': createModuleAndCompSource('main')}); testSupport.writeFiles({'src/index.ts': createModuleAndCompSource('main')});
const host = createWatchModeHost(); const host = createWatchModeHost();
@ -298,12 +300,12 @@ describe('ng program', () => {
// and therefore changes the structure again // and therefore changes the structure again
const p1 = compile(undefined, undefined, undefined, host).program; const p1 = compile(undefined, undefined, undefined, host).program;
const p2 = compile(p1, undefined, undefined, host).program; const p2 = compile(p1, undefined, undefined, host).program;
debugger;
compile(p2, undefined, undefined, host); compile(p2, undefined, undefined, host);
expect(tsStructureIsReused(p2.getTsProgram())).toBe(StructureIsReused.Completely); expect(tsStructureIsReused(p2.getTsProgram())).toBe(StructureIsReused.Completely);
}); });
it('should reuse the old ts program completely if a template or a ts file changed', () => { it('should reuse the old ts program completely if a template or a ts file changed',
() => {
const host = createWatchModeHost(); const host = createWatchModeHost();
testSupport.writeFiles({ testSupport.writeFiles({
'src/main.ts': createModuleAndCompSource('main', 'main.html'), 'src/main.ts': createModuleAndCompSource('main', 'main.html'),