From ad130d62d8b7d40f423cd7d0ccca810cd520e938 Mon Sep 17 00:00:00 2001 From: Tobias Bosch Date: Fri, 13 Oct 2017 09:03:28 -0700 Subject: [PATCH] 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 --- .circleci/config.yml | 1 + BUILD.bazel | 1 + WORKSPACE | 5 +- packages/bazel/src/ngc-wrapped/BUILD.bazel | 4 +- packages/bazel/src/ngc-wrapped/index.ts | 2 +- packages/bazel/test/ngc-wrapped/BUILD.bazel | 39 +++++ .../bazel/test/ngc-wrapped/empty/BUILD.bazel | 10 ++ .../bazel/test/ngc-wrapped/empty/README.md | 1 + .../bazel/test/ngc-wrapped/empty/empty.ts | 10 ++ .../test/ngc-wrapped/empty/tsconfig.json | 7 + packages/bazel/test/ngc-wrapped/index_test.ts | 36 ++++ .../bazel/test/ngc-wrapped/test_support.ts | 161 ++++++++++++++++++ packages/bazel/test/ngc-wrapped/tsconfig.json | 6 + .../test/ngc-wrapped/tsconfig_template.ts | 98 +++++++++++ 14 files changed, 376 insertions(+), 5 deletions(-) create mode 100644 packages/bazel/test/ngc-wrapped/BUILD.bazel create mode 100644 packages/bazel/test/ngc-wrapped/empty/BUILD.bazel create mode 100644 packages/bazel/test/ngc-wrapped/empty/README.md create mode 100644 packages/bazel/test/ngc-wrapped/empty/empty.ts create mode 100644 packages/bazel/test/ngc-wrapped/empty/tsconfig.json create mode 100644 packages/bazel/test/ngc-wrapped/index_test.ts create mode 100644 packages/bazel/test/ngc-wrapped/test_support.ts create mode 100644 packages/bazel/test/ngc-wrapped/tsconfig.json create mode 100644 packages/bazel/test/ngc-wrapped/tsconfig_template.ts diff --git a/.circleci/config.yml b/.circleci/config.yml index 0d315dd985..78885785aa 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -42,6 +42,7 @@ jobs: - run: bazel run @yarn//:yarn - run: bazel build packages/... + - run: bazel test @angular//... - save_cache: key: angular-{{ .Branch }}-{{ checksum "yarn.lock" }} paths: diff --git a/BUILD.bazel b/BUILD.bazel index 718cd4f758..d0dc7aaac4 100644 --- a/BUILD.bazel +++ b/BUILD.bazel @@ -12,6 +12,7 @@ filegroup( # This won't scale in the general case. # TODO(alexeagle): figure out what to do srcs = glob(["/".join(["node_modules", pkg, "**", ext]) for pkg in [ + "jasmine", "typescript", "zone.js", "rxjs", diff --git a/WORKSPACE b/WORKSPACE index ca542ac43f..dcb8c4d232 100644 --- a/WORKSPACE +++ b/WORKSPACE @@ -5,7 +5,8 @@ load("@bazel_tools//tools/build_defs/repo:git.bzl", "git_repository") git_repository( name = "build_bazel_rules_nodejs", 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") @@ -20,4 +21,4 @@ local_repository( local_repository( name = "angular", path = "packages/bazel", -) \ No newline at end of file +) diff --git a/packages/bazel/src/ngc-wrapped/BUILD.bazel b/packages/bazel/src/ngc-wrapped/BUILD.bazel index 8c2897d859..5db7f46a84 100644 --- a/packages/bazel/src/ngc-wrapped/BUILD.bazel +++ b/packages/bazel/src/ngc-wrapped/BUILD.bazel @@ -1,14 +1,13 @@ load("@build_bazel_rules_nodejs//:defs.bzl", "nodejs_binary") load("@build_bazel_rules_typescript//:defs.bzl", "ts_library") -licenses(["notice"]) # Apache 2.0 - ts_library( name = "ngc_lib", srcs = [ "index.ts", "extract_i18n.ts", ], + module_name = "@angular/bazel", deps = [ # BEGIN-INTERNAL # Only needed when compiling within the Angular repo. @@ -18,6 +17,7 @@ ts_library( "@build_bazel_rules_typescript//internal/tsc_wrapped" ], tsconfig = ":tsconfig.json", + visibility = ["//test/ngc-wrapped:__subpackages__"], ) nodejs_binary( diff --git a/packages/bazel/src/ngc-wrapped/index.ts b/packages/bazel/src/ngc-wrapped/index.ts index 0d3a2dceaa..78e2ee1486 100644 --- a/packages/bazel/src/ngc-wrapped/index.ts +++ b/packages/bazel/src/ngc-wrapped/index.ts @@ -40,7 +40,7 @@ export function main(args) { /** The one FileCache instance used in this process. */ const fileCache = new FileCache(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(); // Strip leading at-signs, used to indicate a params file const project = args[0].replace(/^@+/, ''); diff --git a/packages/bazel/test/ngc-wrapped/BUILD.bazel b/packages/bazel/test/ngc-wrapped/BUILD.bazel new file mode 100644 index 0000000000..14a7edde2b --- /dev/null +++ b/packages/bazel/test/ngc-wrapped/BUILD.bazel @@ -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", +) diff --git a/packages/bazel/test/ngc-wrapped/empty/BUILD.bazel b/packages/bazel/test/ngc-wrapped/empty/BUILD.bazel new file mode 100644 index 0000000000..7da580ffea --- /dev/null +++ b/packages/bazel/test/ngc-wrapped/empty/BUILD.bazel @@ -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", +) diff --git a/packages/bazel/test/ngc-wrapped/empty/README.md b/packages/bazel/test/ngc-wrapped/empty/README.md new file mode 100644 index 0000000000..a17da4174b --- /dev/null +++ b/packages/bazel/test/ngc-wrapped/empty/README.md @@ -0,0 +1 @@ +# Empty ng_module to capture the default tsconfig.json diff --git a/packages/bazel/test/ngc-wrapped/empty/empty.ts b/packages/bazel/test/ngc-wrapped/empty/empty.ts new file mode 100644 index 0000000000..40b61645a2 --- /dev/null +++ b/packages/bazel/test/ngc-wrapped/empty/empty.ts @@ -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. diff --git a/packages/bazel/test/ngc-wrapped/empty/tsconfig.json b/packages/bazel/test/ngc-wrapped/empty/tsconfig.json new file mode 100644 index 0000000000..35ac957478 --- /dev/null +++ b/packages/bazel/test/ngc-wrapped/empty/tsconfig.json @@ -0,0 +1,7 @@ +{ + "compilerOptions": { + "paths": { + "@angular/core": ["../../../../dist/packages/core"] + } + } +} diff --git a/packages/bazel/test/ngc-wrapped/index_test.ts b/packages/bazel/test/ngc-wrapped/index_test.ts new file mode 100644 index 0000000000..b7cce77b1d --- /dev/null +++ b/packages/bazel/test/ngc-wrapped/index_test.ts @@ -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);`); + }); +}); diff --git a/packages/bazel/test/ngc-wrapped/test_support.ts b/packages/bazel/test/ngc-wrapped/test_support.ts new file mode 100644 index 0000000000..63cc5060ef --- /dev/null +++ b/packages/bazel/test/ngc-wrapped/test_support.ts @@ -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; +} diff --git a/packages/bazel/test/ngc-wrapped/tsconfig.json b/packages/bazel/test/ngc-wrapped/tsconfig.json new file mode 100644 index 0000000000..19f586e9f6 --- /dev/null +++ b/packages/bazel/test/ngc-wrapped/tsconfig.json @@ -0,0 +1,6 @@ +{ + "compilerOptions": { + "lib": ["es5", "es2015.collection", "es2015.core"], + "types": ["node", "jasmine"] + } +} diff --git a/packages/bazel/test/ngc-wrapped/tsconfig_template.ts b/packages/bazel/test/ngc-wrapped/tsconfig_template.ts new file mode 100644 index 0000000000..b99f45a1c3 --- /dev/null +++ b/packages/bazel/test/ngc-wrapped/tsconfig_template.ts @@ -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'; +} \ No newline at end of file