fix(compiler): add first bazel test for `ng_module` (#19703)

We were missing quite a bit of test coverage,
this is the start of recreating it.
PR Close #19703
This commit is contained in:
Tobias Bosch 2017-10-13 09:03:28 -07:00
parent 621f87b2bd
commit ad130d62d8
14 changed files with 376 additions and 5 deletions

View File

@ -42,6 +42,7 @@ jobs:
- run: bazel run @yarn//:yarn - run: bazel run @yarn//:yarn
- run: bazel build packages/... - run: bazel build packages/...
- run: bazel test @angular//...
- save_cache: - save_cache:
key: angular-{{ .Branch }}-{{ checksum "yarn.lock" }} key: angular-{{ .Branch }}-{{ checksum "yarn.lock" }}
paths: paths:

View File

@ -12,6 +12,7 @@ filegroup(
# This won't scale in the general case. # This won't scale in the general case.
# TODO(alexeagle): figure out what to do # TODO(alexeagle): figure out what to do
srcs = glob(["/".join(["node_modules", pkg, "**", ext]) for pkg in [ srcs = glob(["/".join(["node_modules", pkg, "**", ext]) for pkg in [
"jasmine",
"typescript", "typescript",
"zone.js", "zone.js",
"rxjs", "rxjs",

View File

@ -5,7 +5,8 @@ load("@bazel_tools//tools/build_defs/repo:git.bzl", "git_repository")
git_repository( git_repository(
name = "build_bazel_rules_nodejs", name = "build_bazel_rules_nodejs",
remote = "https://github.com/bazelbuild/rules_nodejs.git", remote = "https://github.com/bazelbuild/rules_nodejs.git",
tag = "0.1.6", # TODO(alexeagle): use the correct tag here.
commit = "2c6243df53fd33fdab283ebdd01582e4eb815db8",
) )
load("@build_bazel_rules_nodejs//:defs.bzl", "node_repositories") load("@build_bazel_rules_nodejs//:defs.bzl", "node_repositories")
@ -20,4 +21,4 @@ local_repository(
local_repository( local_repository(
name = "angular", name = "angular",
path = "packages/bazel", path = "packages/bazel",
) )

View File

@ -1,14 +1,13 @@
load("@build_bazel_rules_nodejs//:defs.bzl", "nodejs_binary") load("@build_bazel_rules_nodejs//:defs.bzl", "nodejs_binary")
load("@build_bazel_rules_typescript//:defs.bzl", "ts_library") load("@build_bazel_rules_typescript//:defs.bzl", "ts_library")
licenses(["notice"]) # Apache 2.0
ts_library( ts_library(
name = "ngc_lib", name = "ngc_lib",
srcs = [ srcs = [
"index.ts", "index.ts",
"extract_i18n.ts", "extract_i18n.ts",
], ],
module_name = "@angular/bazel",
deps = [ deps = [
# BEGIN-INTERNAL # BEGIN-INTERNAL
# Only needed when compiling within the Angular repo. # Only needed when compiling within the Angular repo.
@ -18,6 +17,7 @@ ts_library(
"@build_bazel_rules_typescript//internal/tsc_wrapped" "@build_bazel_rules_typescript//internal/tsc_wrapped"
], ],
tsconfig = ":tsconfig.json", tsconfig = ":tsconfig.json",
visibility = ["//test/ngc-wrapped:__subpackages__"],
) )
nodejs_binary( nodejs_binary(

View File

@ -40,7 +40,7 @@ export function main(args) {
/** The one FileCache instance used in this process. */ /** The one FileCache instance used in this process. */
const fileCache = new FileCache<ts.SourceFile>(debug); const fileCache = new FileCache<ts.SourceFile>(debug);
function runOneBuild(args: string[], inputs?: {[path: string]: string}): boolean { export function runOneBuild(args: string[], inputs?: {[path: string]: string}): boolean {
if (args[0] === '-p') args.shift(); if (args[0] === '-p') args.shift();
// Strip leading at-signs, used to indicate a params file // Strip leading at-signs, used to indicate a params file
const project = args[0].replace(/^@+/, ''); const project = args[0].replace(/^@+/, '');

View File

@ -0,0 +1,39 @@
load("@build_bazel_rules_nodejs//:defs.bzl", "jasmine_node_test")
load("@build_bazel_rules_typescript//:defs.bzl", "ts_library")
ts_library(
name = "ngc_test_lib",
srcs = [
"index_test.ts",
"test_support.ts",
"tsconfig_template.ts",
],
deps = [
# BEGIN-INTERNAL
# Only needed when compiling within the Angular repo.
# Users will get this dependency from node_modules.
"@//packages/compiler-cli",
# END-INTERNAL
"//src/ngc-wrapped:ngc_lib"
],
tsconfig = ":tsconfig.json",
)
# We need a filegroup so that we can refer
# .d.ts files (by default, jasmine_node_test would get the .js files).
filegroup(
name = "angular_core",
srcs = ["@//packages/core"]
)
jasmine_node_test(
name = "ngc_test",
srcs = [":ngc_test_lib"],
data = [
"@build_bazel_rules_typescript//internal:worker_protocol.proto",
":angular_core",
"//test/ngc-wrapped/empty:empty_tsconfig.json",
"//test/ngc-wrapped/empty:tsconfig.json",
],
size="small",
)

View File

@ -0,0 +1,10 @@
load("@angular//:index.bzl", "ng_module")
package(default_visibility=["//test:__subpackages__"])
ng_module(
name = "empty",
srcs = ["empty.ts"],
deps = ["@//packages/core"],
tsconfig = ":tsconfig.json",
)

View File

@ -0,0 +1 @@
# Empty ng_module to capture the default tsconfig.json

View File

@ -0,0 +1,10 @@
/**
* @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
*/
// Empty file for an empty ng_module
// to capture the tsconfig used by ng_module.

View File

@ -0,0 +1,7 @@
{
"compilerOptions": {
"paths": {
"@angular/core": ["../../../../dist/packages/core"]
}
}
}

View File

@ -0,0 +1,36 @@
/**
* @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
*/
import * as fs from 'fs';
import * as path from 'path';
import {setup} from './test_support';
describe('ngc_wrapped', () => {
it('should work', () => {
const {read, write, runOneBuild, writeConfig, shouldExist, basePath} = setup();
write('some_project/index.ts', `
import {Component} from '@angular/core';
console.log('works: ', Component);
`);
writeConfig({
srcTargetPath: 'some_project',
});
// expect no error
expect(runOneBuild()).toBe(true);
shouldExist('bazel-bin/some_project/index.js');
expect(read('bazel-bin/some_project/index.js'))
.toContain(`console.log('works: ', core_1.Component);`);
});
});

View File

@ -0,0 +1,161 @@
/**
* @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
*/
import {runOneBuild} from '@angular/bazel';
import * as fs from 'fs';
import * as os from 'os';
import * as path from 'path';
import * as ts from 'typescript';
import {createTsConfig} from './tsconfig_template';
export interface TestSupport {
basePath: string;
runfilesPath: string;
angularCorePath: string;
writeConfig({
srcTargetPath, depPaths, pathMapping,
}: {
srcTargetPath: string,
depPaths?: string[],
pathMapping?: Array<{moduleName: string; path: string;}>,
}): void;
read(fileName: string): string;
write(fileName: string, content: string): void;
writeFiles(...mockDirs: {[fileName: string]: string}[]): void;
shouldExist(fileName: string): void;
shouldNotExist(fileName: string): void;
runOneBuild(): void;
}
export function setup(
{
bazelBin = 'bazel-bin', tsconfig = 'tsconfig.json',
}: {
bazelBin?: string,
tsconfig?: string,
} = {}): TestSupport {
const runfilesPath = process.env['RUNFILES'];
const basePath = makeTempDir(runfilesPath);
const bazelBinPath = path.resolve(basePath, bazelBin);
fs.mkdirSync(bazelBinPath);
const angularCorePath = path.resolve(runfilesPath, 'angular_src', 'packages', 'core');
const ngFiles = listFilesRecursive(angularCorePath);
const tsConfigJsonPath = path.resolve(basePath, tsconfig);
return {
basePath,
runfilesPath,
angularCorePath,
write,
read,
writeFiles,
writeConfig,
shouldExist,
shouldNotExist,
runOneBuild: runOneBuildImpl
};
// -----------------
// helpers
function write(fileName: string, content: string) {
const dir = path.dirname(fileName);
if (dir != '.') {
const newDir = path.resolve(basePath, dir);
if (!fs.existsSync(newDir)) fs.mkdirSync(newDir);
}
fs.writeFileSync(path.resolve(basePath, fileName), content, {encoding: 'utf-8'});
}
function read(fileName: string) {
return fs.readFileSync(path.resolve(basePath, fileName), {encoding: 'utf-8'});
}
function writeFiles(...mockDirs: {[fileName: string]: string}[]) {
mockDirs.forEach(
(dir) => { Object.keys(dir).forEach((fileName) => { write(fileName, dir[fileName]); }); });
}
function writeConfig({
srcTargetPath, depPaths = [], pathMapping = [],
}: {
srcTargetPath: string,
depPaths?: string[],
pathMapping?: Array<{moduleName: string; path: string;}>,
}) {
srcTargetPath = path.resolve(basePath, srcTargetPath);
const compilationTargetSrc = listFilesRecursive(srcTargetPath);
const target = '//' + path.relative(basePath, srcTargetPath);
const files = [...compilationTargetSrc];
depPaths = depPaths.concat([angularCorePath]);
pathMapping = pathMapping.concat([{moduleName: '@angular/core', path: angularCorePath}]);
for (const depPath of depPaths) {
files.push(...listFilesRecursive(depPath).filter(f => f.endsWith('.d.ts')));
}
const pathMappingObj = {};
for (const mapping of pathMapping) {
pathMappingObj[mapping.moduleName] = [mapping.path];
pathMappingObj[path.join(mapping.moduleName, '*')] = [path.join(mapping.path, '*')];
}
const emptyTsConfig = ts.readConfigFile(
path.resolve(
runfilesPath, 'angular', 'test', 'ngc-wrapped', 'empty', 'empty_tsconfig.json'),
read);
const tsconfig = createTsConfig({
defaultTsConfig: emptyTsConfig.config,
rootDir: basePath,
target: target,
outDir: bazelBinPath, compilationTargetSrc,
files: files,
pathMapping: pathMappingObj,
});
write(path.resolve(basePath, tsConfigJsonPath), JSON.stringify(tsconfig, null, 2));
}
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})`);
}
}
function runOneBuildImpl(): boolean { return runOneBuild(['@' + tsConfigJsonPath]); }
}
function makeTempDir(baseDir): string {
const id = (Math.random() * 1000000).toFixed(0);
const dir = path.join(baseDir, `tmp.${id}`);
fs.mkdirSync(dir);
return dir;
}
export function listFilesRecursive(dir: string, fileList: string[] = []) {
fs.readdirSync(dir).map(file => {
if (fs.statSync(path.join(dir, file)).isDirectory()) {
listFilesRecursive(path.join(dir, file), fileList);
} else {
fileList.push(path.join(dir, file));
}
});
return fileList;
}

View File

@ -0,0 +1,6 @@
{
"compilerOptions": {
"lib": ["es5", "es2015.collection", "es2015.core"],
"types": ["node", "jasmine"]
}
}

View File

@ -0,0 +1,98 @@
/**
* @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
*/
import * as path from 'path';
import * as ts from 'typescript';
const EXT = /(\.ts|\.d\.ts|\.js|\.jsx|\.tsx)$/;
export interface TsConfigOptions {
defaultTsConfig: any;
outDir: string;
rootDir: string;
pathMapping: {[pattern: string]: string[]};
// e.g. //packages/core:core
target: string;
compilationTargetSrc: string[];
files: string[];
}
/**
* Creates a tsconfig based on the default tsconfig
* to adjust paths, ...
*
* @param options
*/
export function createTsConfig(options: TsConfigOptions) {
const result = options.defaultTsConfig;
return {
'extends': '../angular/test/ngc-wrapped/empty/tsconfig',
'compilerOptions': {
...result.compilerOptions,
'outDir': options.outDir,
'rootDir': options.rootDir,
'rootDirs': [
options.rootDir,
],
'baseUrl': options.rootDir,
'paths': {
'*': [
'./*',
],
...options.pathMapping,
},
// we have to set this as the default tsconfig is made of es6 mode
'target': 'es5',
// we have to set this as the default tsconfig is made of es6 mode
'module': 'commonjs',
// if we specify declarationDir, we also have to specify
// declaration in the same tsconfig.json, otherwise ts will error.
'declaration': true,
'declarationDir': options.outDir,
},
'bazelOptions': {
...result.bazelOptions,
'workspaceName': 'angular',
'target': options.target,
// we have to set this as the default tsconfig is made of es6 mode
'es5Mode': true,
'manifest': createManifestPath(options),
'compilationTargetSrc': options.compilationTargetSrc,
},
'files': options.files,
'angularCompilerOptions': {
...result.angularCompilerOptions,
'expectedOut': [
...options.compilationTargetSrc.map(src => srcToExpectedOut(src, 'js', options)),
...options.compilationTargetSrc.map(src => srcToExpectedOut(src, 'd.ts', options)),
...options.compilationTargetSrc.map(src => srcToExpectedOut(src, 'ngfactory.js', options)),
...options.compilationTargetSrc.map(
src => srcToExpectedOut(src, 'ngfactory.d.ts', options)),
...options.compilationTargetSrc.map(src => srcToExpectedOut(src, 'ngsummary.js', options)),
...options.compilationTargetSrc.map(
src => srcToExpectedOut(src, 'ngsummary.d.ts', options)),
...options.compilationTargetSrc.map(
src => srcToExpectedOut(src, 'ngsummary.json', options)),
]
}
};
}
function srcToExpectedOut(srcFile: string, suffix: string, options: TsConfigOptions): string {
const baseName = path.basename(srcFile).replace(EXT, '');
return path.join(
path.relative(options.rootDir, options.outDir),
path.relative(options.rootDir, path.dirname(srcFile)), baseName) +
'.' + suffix;
}
function createManifestPath(options: TsConfigOptions): string {
return path.resolve(options.outDir, options.target.replace(/\/\/|@/g, '').replace(/:/g, '/')) +
'.es5.MF';
}