test(compiler-cli): load test files into memory only once (#38909)

Prior to this change, each invocation of `loadStandardTestFiles` would
load the necessary files from disk. This function is typically called
at the top-level of a test module in order to share the result across
tests. The `//packages/compiler-cli/test/ngtsc` target has 8 modules
where this call occurs, each loading their own copy of
`node_modules/typescript` which is ~60MB in size, so the memory overhead
used to be significant. This commit loads the individual packages into
a standalone `Folder` and mounts this folder into the filesystem of
standard test files, such that all file contents are no longer
duplicated in memory.

PR Close #38909
This commit is contained in:
JoostK
2020-09-20 01:06:19 +02:00
committed by Alex Rickabaugh
parent b627f7f02e
commit e790c8547e
2 changed files with 72 additions and 18 deletions

View File

@ -127,12 +127,17 @@ export abstract class MockFileSystem implements FileSystem {
delete folder[name];
}
ensureDir(path: AbsoluteFsPath): void {
ensureDir(path: AbsoluteFsPath): Folder {
const segments = this.splitPath(path).map(segment => this.getCanonicalPath(segment));
let current: Folder = this._fileTree;
// Convert the root folder to a canonical empty string `''` (on Windows it would be `'C:'`).
segments[0] = '';
if (segments.length > 1 && segments[segments.length - 1] === '') {
// Remove a trailing slash (unless the path was only `/`)
segments.pop();
}
let current: Folder = this._fileTree;
for (const segment of segments) {
if (isFile(current[segment])) {
throw new Error(`Folder already exists as a file.`);
@ -142,6 +147,7 @@ export abstract class MockFileSystem implements FileSystem {
}
current = current[segment] as Folder;
}
return current;
}
removeDeep(path: AbsoluteFsPath): void {
@ -221,26 +227,45 @@ export abstract class MockFileSystem implements FileSystem {
protected abstract splitPath<T extends PathString>(path: T): string[];
dump(): Folder {
return this.cloneFolder(this._fileTree);
const {entity} = this.findFromPath(this.resolve('/'));
if (entity === null || !isFolder(entity)) {
return {};
}
return this.cloneFolder(entity);
}
init(folder: Folder): void {
this._fileTree = this.cloneFolder(folder);
this.mount(this.resolve('/'), folder);
}
mount(path: AbsoluteFsPath, folder: Folder): void {
if (this.exists(path)) {
throw new Error(`Unable to mount in '${path}' as it already exists.`);
}
const mountFolder = this.ensureDir(path);
this.copyInto(folder, mountFolder);
}
private cloneFolder(folder: Folder): Folder {
const clone: Folder = {};
for (const path in folder) {
const item = folder[path];
this.copyInto(folder, clone);
return clone;
}
private copyInto(from: Folder, to: Folder): void {
for (const path in from) {
const item = from[path];
const canonicalPath = this.getCanonicalPath(path);
if (isSymLink(item)) {
clone[canonicalPath] = new SymLink(this.getCanonicalPath(item.path));
to[canonicalPath] = new SymLink(this.getCanonicalPath(item.path));
} else if (isFolder(item)) {
clone[canonicalPath] = this.cloneFolder(item);
to[canonicalPath] = this.cloneFolder(item);
} else {
clone[canonicalPath] = folder[path];
to[canonicalPath] = from[path];
}
}
return clone;
}