angular/packages/compiler-cli/test/test_support.ts
JoostK 52a6785a82 test(compiler-cli): run watch mode tests using Ivy compiler (#43893)
This commit re-enables the `perform_watch` test target and updates the
test to run with the Ivy compiler.

Additionally, this target was switched over to use Angular v12 packages
as input to the test, to allow the ViewEngine tests to continue working
with v13 packages which are Ivy-only. This commit reverts those changes
now that View Engine tests are disabled, as it's desirable to test
against local artifacts that are build within the monorepo instead of
depending on NPM packages.

PR Close #43893
2021-10-19 16:26:21 -07:00

181 lines
6 KiB
TypeScript

/**
* @license
* Copyright Google LLC 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
*/
/// <reference types="node" />
import * as fs from 'fs';
import * as path from 'path';
import ts from 'typescript';
import * as ng from '../index';
import {NodeJSFileSystem, setFileSystem} from '../src/ngtsc/file_system';
import {getAngularPackagesFromRunfiles, resolveNpmTreeArtifact} from '../src/ngtsc/testing';
// TEST_TMPDIR is always set by Bazel.
const tmpdir = process.env.TEST_TMPDIR!;
export function makeTempDir(): string {
let dir: string;
while (true) {
const id = (Math.random() * 1000000).toFixed(0);
dir = path.posix.join(tmpdir, `tmp.${id}`);
if (!fs.existsSync(dir)) break;
}
fs.mkdirSync(dir);
return dir;
}
export interface TestSupport {
basePath: string;
write(fileName: string, content: string): void;
writeFiles(...mockDirs: {[fileName: string]: string}[]): void;
createCompilerOptions(overrideOptions?: ng.CompilerOptions): ng.CompilerOptions;
shouldExist(fileName: string): void;
shouldNotExist(fileName: string): void;
}
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,
'newLine': ts.NewLineKind.LineFeed,
'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 {
// We normalize the basePath into a posix path, so that multiple assertions which compare
// paths don't need to normalize the path separators each time.
basePath: normalizeSeparators(basePath),
write,
writeFiles,
createCompilerOptions,
shouldExist,
shouldNotExist
};
function ensureDirExists(absolutePathToDir: string) {
if (fs.existsSync(absolutePathToDir)) {
if (!fs.statSync(absolutePathToDir).isDirectory()) {
throw new Error(`'${absolutePathToDir}' exists and is not a directory.`);
}
} else {
const parentDir = path.dirname(absolutePathToDir);
ensureDirExists(parentDir);
fs.mkdirSync(absolutePathToDir);
}
}
function write(fileName: string, content: string) {
const absolutePathToFile = path.resolve(basePath, fileName);
ensureDirExists(path.dirname(absolutePathToFile));
fs.writeFileSync(absolutePathToFile, content);
}
function writeFiles(...mockDirs: {[fileName: string]: string}[]) {
mockDirs.forEach((dir) => {
Object.keys(dir).forEach((fileName) => {
write(fileName, dir[fileName]);
});
});
}
function createCompilerOptions(overrideOptions: ng.CompilerOptions = {}): ng.CompilerOptions {
return {...defaultCompilerOptions, ...overrideOptions};
}
function shouldExist(fileName: string) {
if (!fs.existsSync(path.resolve(basePath, fileName))) {
throw new Error(`Expected ${fileName} to be emitted (basePath: ${basePath})`);
}
}
function shouldNotExist(fileName: string) {
if (fs.existsSync(path.resolve(basePath, fileName))) {
throw new Error(`Did not expect ${fileName} to be emitted (basePath: ${basePath})`);
}
}
}
export function setupBazelTo(tmpDirPath: string) {
const nodeModulesPath = path.join(tmpDirPath, 'node_modules');
const angularDirectory = path.join(nodeModulesPath, '@angular');
fs.mkdirSync(nodeModulesPath);
fs.mkdirSync(angularDirectory);
getAngularPackagesFromRunfiles().forEach(({pkgPath, name}) => {
fs.symlinkSync(pkgPath, path.join(angularDirectory, name), 'junction');
});
// Link typescript
const typeScriptSource = resolveNpmTreeArtifact('npm/node_modules/typescript');
const typescriptDest = path.join(nodeModulesPath, 'typescript');
fs.symlinkSync(typeScriptSource, typescriptDest, 'junction');
// Link "rxjs" if it has been set up as a runfile. "rxjs" is linked optionally because
// not all compiler-cli tests need "rxjs" set up.
try {
const rxjsSource = resolveNpmTreeArtifact('rxjs', 'index.js');
const rxjsDest = path.join(nodeModulesPath, 'rxjs');
fs.symlinkSync(rxjsSource, rxjsDest, 'junction');
} catch (e) {
if (e.code !== 'MODULE_NOT_FOUND') throw e;
}
}
export function setup(): TestSupport {
// // `TestSupport` provides its own file-system abstraction so we just use
// // the native `NodeJSFileSystem` under the hood.
setFileSystem(new NodeJSFileSystem());
const tmpDirPath = makeTempDir();
setupBazelTo(tmpDirPath);
return createTestSupportFor(tmpDirPath);
}
export function expectNoDiagnostics(options: ng.CompilerOptions, diags: ng.Diagnostics) {
const errorDiags = diags.filter(d => d.category !== ts.DiagnosticCategory.Message);
if (errorDiags.length) {
throw new Error(`Expected no diagnostics: ${ng.formatDiagnostics(errorDiags)}`);
}
}
export function expectNoDiagnosticsInProgram(options: ng.CompilerOptions, p: ng.Program) {
expectNoDiagnostics(options, [
...p.getNgStructuralDiagnostics(), ...p.getTsSemanticDiagnostics(),
...p.getNgSemanticDiagnostics()
]);
}
export function normalizeSeparators(path: string): string {
return path.replace(/\\/g, '/');
}
const STRIP_ANSI = /\x1B\x5B\d+m/g;
export function stripAnsi(diags: string): string {
return diags.replace(STRIP_ANSI, '');
}