forked from angular/angular
-
Notifications
You must be signed in to change notification settings - Fork 0
/
test_helpers.js
163 lines (146 loc) · 7.51 KB
/
test_helpers.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
/**
* @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/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'),
// Note, @bazel/typescript does not appear here because it's not listed as a dependency of
// @angular/compiler-cli
'@types/jasmine': resolveNpmTreeArtifact('npm/node_modules/@types/jasmine'),
'@types/node': resolveNpmTreeArtifact('npm/node_modules/@types/node'),
// 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.
'tslib': resolveNpmTreeArtifact('npm/node_modules/tslib'),
'domino': resolveNpmTreeArtifact('npm/node_modules/domino'),
'xhr2': resolveNpmTreeArtifact('npm/node_modules/xhr2'),
'fs-extra': resolveNpmTreeArtifact('npm/node_modules/fs-extra'),
// 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.
'buffer-from': resolveNpmTreeArtifact('npm/node_modules/buffer-from'),
'reflect-metadata': resolveNpmTreeArtifact('npm/node_modules/reflect-metadata'),
'rxjs': resolveNpmTreeArtifact('npm/node_modules/rxjs'),
'source-map': resolveNpmTreeArtifact('npm/node_modules/source-map'),
'source-map-support': resolveNpmTreeArtifact('npm/node_modules/source-map-support'),
'typescript': resolveNpmTreeArtifact('npm/node_modules/typescript'),
'zone.js': resolveNpmTreeArtifact('angular/packages/zone.js/npm_package'),
};
/** 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];
shx.mkdir('-p', path.dirname(outputPath));
fs.symlinkSync(moduleDir, outputPath, 'junction');
});
}
/**
* 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.lstatSync(filePath).isDirectory());
}