| 
									
										
										
										
											2019-01-22 18:46:51 +01:00
										 |  |  | /** | 
					
						
							|  |  |  |  * @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
 | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | const child_process = require('child_process'); | 
					
						
							|  |  |  | const fs = require('fs'); | 
					
						
							|  |  |  | const path = require('path'); | 
					
						
							|  |  |  | const shx = require('shelljs'); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /** Manifest path that refers to the Bazel package that contains all test sources. */ | 
					
						
							|  |  |  | const baseManifestPath = 'angular/packages/compiler-cli/integrationtest'; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /** | 
					
						
							|  |  |  |  * Temporary directory which will be used to build and run the integration tests. Note that | 
					
						
							|  |  |  |  * this environment variable is automatically set by Bazel for such test actions. | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | const tmpDir = process.env.TEST_TMPDIR; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /** Fine grained node modules which are required in order to run the integration test. */ | 
					
						
							|  |  |  | const requiredNodeModules = { | 
					
						
							|  |  |  |   '@angular/animations': resolveNpmTreeArtifact('angular/packages/animations/npm_package'), | 
					
						
							|  |  |  |   '@angular/common': resolveNpmTreeArtifact('angular/packages/common/npm_package'), | 
					
						
							|  |  |  |   '@angular/compiler': resolveNpmTreeArtifact('angular/packages/compiler/npm_package'), | 
					
						
							|  |  |  |   '@angular/compiler-cli': resolveNpmTreeArtifact('angular/packages/compiler-cli/npm_package'), | 
					
						
							|  |  |  |   '@angular/core': resolveNpmTreeArtifact('angular/packages/core/npm_package'), | 
					
						
							|  |  |  |   '@angular/forms': resolveNpmTreeArtifact('angular/packages/forms/npm_package'), | 
					
						
							|  |  |  |   '@angular/http': resolveNpmTreeArtifact('angular/packages/http/npm_package'), | 
					
						
							|  |  |  |   '@angular/platform-browser': | 
					
						
							|  |  |  |       resolveNpmTreeArtifact('angular/packages/platform-browser/npm_package'), | 
					
						
							|  |  |  |   '@angular/platform-browser-dynamic': | 
					
						
							|  |  |  |       resolveNpmTreeArtifact('angular/packages/platform-browser-dynamic/npm_package'), | 
					
						
							|  |  |  |   '@angular/platform-server': | 
					
						
							|  |  |  |       resolveNpmTreeArtifact('angular/packages/platform-server/npm_package'), | 
					
						
							|  |  |  |   '@angular/router': resolveNpmTreeArtifact('angular/packages/router/npm_package'), | 
					
						
							| 
									
										
										
										
											2019-01-29 11:33:37 -08:00
										 |  |  |   // Note, @bazel/typescript does not appear here because it's not listed as a dependency of
 | 
					
						
							|  |  |  |   // @angular/compiler-cli
 | 
					
						
							| 
									
										
										
										
											2019-02-20 09:54:42 -08:00
										 |  |  |   '@types/jasmine': resolveNpmTreeArtifact('npm/node_modules/@types/jasmine'), | 
					
						
							|  |  |  |   '@types/node': resolveNpmTreeArtifact('npm/node_modules/@types/node'), | 
					
						
							| 
									
										
										
										
											2019-01-22 18:46:51 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |   // Transitive dependencies which need to be specified because the Angular NPM packages
 | 
					
						
							|  |  |  |   // depend on these without the Angular NPM packages being part of the Bazel managed deps.
 | 
					
						
							|  |  |  |   // This means that transitive dependencies need to be manually declared as required.
 | 
					
						
							| 
									
										
										
										
											2019-02-20 09:54:42 -08:00
										 |  |  |   'tslib': resolveNpmTreeArtifact('npm/node_modules/tslib'), | 
					
						
							|  |  |  |   'domino': resolveNpmTreeArtifact('npm/node_modules/domino'), | 
					
						
							|  |  |  |   'xhr2': resolveNpmTreeArtifact('npm/node_modules/xhr2'), | 
					
						
							| 
									
										
										
										
											2019-01-22 18:46:51 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |   // Fine grained dependencies which are used by the integration test Angular modules, and
 | 
					
						
							|  |  |  |   // need to be symlinked so that they can be resolved by NodeJS or NGC.
 | 
					
						
							| 
									
										
										
										
											2019-02-20 09:54:42 -08:00
										 |  |  |   'reflect-metadata': resolveNpmTreeArtifact('npm/node_modules/reflect-metadata'), | 
					
						
							|  |  |  |   'rxjs': resolveNpmTreeArtifact('npm/node_modules/rxjs'), | 
					
						
							|  |  |  |   'source-map-support': resolveNpmTreeArtifact('npm/node_modules/source-map-support'), | 
					
						
							|  |  |  |   'typescript': resolveNpmTreeArtifact('npm/node_modules/typescript'), | 
					
						
							|  |  |  |   'zone.js': resolveNpmTreeArtifact('npm/node_modules/zone.js'), | 
					
						
							| 
									
										
										
										
											2019-01-22 18:46:51 +01:00
										 |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /** Sets up the temporary test directory and returns the path to the directory. */ | 
					
						
							|  |  |  | exports.setupTestDirectory = function() { | 
					
						
							|  |  |  |   copySourceFilesToTempDir(); | 
					
						
							|  |  |  |   symlinkNodeModules(); | 
					
						
							|  |  |  |   return tmpDir; | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /** | 
					
						
							|  |  |  |  * Runs a given binary with the specified command line arguments. The working directory for | 
					
						
							|  |  |  |  * the spawned process will be the temporary directory. | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | exports.runCommand = function runCommand(binary, args = []) { | 
					
						
							|  |  |  |   const ngcProcess = child_process.spawnSync(binary, args, { | 
					
						
							|  |  |  |     stdio: 'inherit', | 
					
						
							|  |  |  |     cwd: tmpDir, | 
					
						
							|  |  |  |     env: { | 
					
						
							|  |  |  |       ...process.env, | 
					
						
							|  |  |  |       // We need to set the "NODE_PATH" here because the built Angular NPM packages are symlinks
 | 
					
						
							|  |  |  |       // which NodeJS resolves into the output location. This is problematic because the output
 | 
					
						
							|  |  |  |       // location does not have the required dependencies of these NPM packages installed. This
 | 
					
						
							|  |  |  |       // could be fixed by setting the NodeJS "--preserve-symlinks" option, but this would mean
 | 
					
						
							|  |  |  |       // that transitive dependencies of fine-grained dependencies cannot be resolved either.
 | 
					
						
							|  |  |  |       NODE_PATH: path.join(tmpDir, 'node_modules/') | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   if (ngcProcess.status !== 0) { | 
					
						
							|  |  |  |     console.error(`Command ${binary} failed with arguments: "${args.join(' ')}". See error above.`); | 
					
						
							|  |  |  |     process.exit(1); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /** | 
					
						
							|  |  |  |  * Symlinks the specified node modules within the temporary directory. This is necessary because | 
					
						
							|  |  |  |  * this test is an integration test and we don't want to rely on any path-mapped module resolution. | 
					
						
							|  |  |  |  * Additionally, NGC expects types and imported packages to be within the project's root dir. | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | function symlinkNodeModules() { | 
					
						
							|  |  |  |   Object.keys(requiredNodeModules).forEach(importName => { | 
					
						
							|  |  |  |     const outputPath = path.join(tmpDir, 'node_modules', importName); | 
					
						
							|  |  |  |     const moduleDir = requiredNodeModules[importName]; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     findFilesWithinDirectory(moduleDir).forEach(filePath => { | 
					
						
							|  |  |  |       const outputFilePath = path.join(outputPath, path.relative(moduleDir, filePath)); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       shx.mkdir('-p', path.dirname(outputFilePath)); | 
					
						
							|  |  |  |       fs.symlinkSync(filePath, outputFilePath); | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  |   }); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /** | 
					
						
							|  |  |  |  * Copies all source files for the integration test to a temporary directory. This | 
					
						
							|  |  |  |  * is necessary because runfiles resolve on Windows to the original source location, | 
					
						
							|  |  |  |  * and we don't want to pollute the workspace sources. This breaks hermeticity. | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | function copySourceFilesToTempDir() { | 
					
						
							|  |  |  |   getSourceFilesFromRunfiles().forEach(({realPath, relativeFilePath}) => { | 
					
						
							|  |  |  |     const tmpFilePath = path.join(tmpDir, relativeFilePath); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     shx.mkdir('-p', path.dirname(tmpFilePath)); | 
					
						
							|  |  |  |     shx.cp(realPath, tmpFilePath); | 
					
						
							|  |  |  |   }); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /** | 
					
						
							|  |  |  |  * Gets all source files for the integration test by querying the Bazel runfiles. | 
					
						
							|  |  |  |  * In case there is a runfiles manifest (e.g. on Windows), the source files are resolved | 
					
						
							|  |  |  |  * through the manifest because on these platforms the runfiles are not symlinked and | 
					
						
							|  |  |  |  * cannot be searched within the real filesystem. | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | function getSourceFilesFromRunfiles() { | 
					
						
							|  |  |  |   // Path to the Bazel runfiles manifest if present. This file is present if runfiles are
 | 
					
						
							|  |  |  |   // not symlinked into the runfiles directory.
 | 
					
						
							|  |  |  |   const runfilesManifestPath = process.env.RUNFILES_MANIFEST_FILE; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   if (!runfilesManifestPath) { | 
					
						
							|  |  |  |     const packageRunfilesDir = path.join(process.env.RUNFILES, baseManifestPath); | 
					
						
							|  |  |  |     return findFilesWithinDirectory(packageRunfilesDir).map(filePath => ({ | 
					
						
							|  |  |  |                                                               realPath: filePath, | 
					
						
							|  |  |  |                                                               relativeFilePath: path.relative( | 
					
						
							|  |  |  |                                                                   packageRunfilesDir, filePath) | 
					
						
							|  |  |  |                                                             })); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   return fs.readFileSync(runfilesManifestPath, 'utf8') | 
					
						
							|  |  |  |       .split('\n') | 
					
						
							|  |  |  |       .map(mapping => mapping.split(' ')) | 
					
						
							|  |  |  |       .filter(([runfilePath]) => runfilePath.startsWith(baseManifestPath)) | 
					
						
							|  |  |  |       .map( | 
					
						
							|  |  |  |           ([runfilePath, realPath]) => | 
					
						
							|  |  |  |               ({realPath, relativeFilePath: path.relative(baseManifestPath, runfilePath)})); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /** | 
					
						
							|  |  |  |  * Resolves a NPM package from the Bazel runfiles. We need to resolve the Bazel tree | 
					
						
							|  |  |  |  * artifacts using a "resolve file" because the NodeJS module resolution does not allow | 
					
						
							|  |  |  |  * resolving to directory paths. | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | function resolveNpmTreeArtifact(manifestPath, resolveFile = 'package.json') { | 
					
						
							|  |  |  |   return path.dirname(require.resolve(path.posix.join(manifestPath, resolveFile))); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /** Finds all files within a specified directory. */ | 
					
						
							|  |  |  | function findFilesWithinDirectory(directoryPath) { | 
					
						
							|  |  |  |   return shx.find(directoryPath).filter(filePath => !fs.statSync(filePath).isDirectory()); | 
					
						
							|  |  |  | } |