From 51720745dd8f9c5e9b6da82aa4075c098f60f68b Mon Sep 17 00:00:00 2001 From: Alex Rickabaugh Date: Wed, 13 Nov 2019 14:45:46 -0800 Subject: [PATCH] test(ivy): support chdir() on the compiler's filesystem abstraction (#33828) This commit adds the ability to change directories using the compiler's internal filesystem abstraction. This is a prerequisite for writing tests which are sensitive to the current working directory. In addition to supporting the `chdir()` operation, this commit also fixes `getDefaultLibLocation()` for mock filesystems to not assume `node_modules` is in the current directory, but to resolve it similarly to how Node does by progressively looking higher in the directory tree. PR Close #33828 --- .../file_system/src/cached_file_system.ts | 1 + .../file_system/src/invalid_file_system.ts | 1 + .../file_system/src/node_js_file_system.ts | 1 + .../src/ngtsc/file_system/src/types.ts | 1 + .../testing/src/mock_file_system.ts | 31 ++++++++++++++++++- packages/compiler-cli/test/ngtsc/env.ts | 7 +++-- 6 files changed, 38 insertions(+), 4 deletions(-) diff --git a/packages/compiler-cli/src/ngtsc/file_system/src/cached_file_system.ts b/packages/compiler-cli/src/ngtsc/file_system/src/cached_file_system.ts index 7b083ab7c1..26481d46cf 100644 --- a/packages/compiler-cli/src/ngtsc/file_system/src/cached_file_system.ts +++ b/packages/compiler-cli/src/ngtsc/file_system/src/cached_file_system.ts @@ -99,6 +99,7 @@ export class CachedFileSystem implements FileSystem { // The following methods simply call through to the delegate. readdir(path: AbsoluteFsPath): PathSegment[] { return this.delegate.readdir(path); } pwd(): AbsoluteFsPath { return this.delegate.pwd(); } + chdir(path: AbsoluteFsPath): void { this.delegate.chdir(path); } extname(path: AbsoluteFsPath|PathSegment): string { return this.delegate.extname(path); } isCaseSensitive(): boolean { return this.delegate.isCaseSensitive(); } isRoot(path: AbsoluteFsPath): boolean { return this.delegate.isRoot(path); } diff --git a/packages/compiler-cli/src/ngtsc/file_system/src/invalid_file_system.ts b/packages/compiler-cli/src/ngtsc/file_system/src/invalid_file_system.ts index fcc7cc2034..36e7858caa 100644 --- a/packages/compiler-cli/src/ngtsc/file_system/src/invalid_file_system.ts +++ b/packages/compiler-cli/src/ngtsc/file_system/src/invalid_file_system.ts @@ -24,6 +24,7 @@ export class InvalidFileSystem implements FileSystem { lstat(path: AbsoluteFsPath): FileStats { throw makeError(); } stat(path: AbsoluteFsPath): FileStats { throw makeError(); } pwd(): AbsoluteFsPath { throw makeError(); } + chdir(path: AbsoluteFsPath): void { throw makeError(); } extname(path: AbsoluteFsPath|PathSegment): string { throw makeError(); } copyFile(from: AbsoluteFsPath, to: AbsoluteFsPath): void { throw makeError(); } moveFile(from: AbsoluteFsPath, to: AbsoluteFsPath): void { throw makeError(); } diff --git a/packages/compiler-cli/src/ngtsc/file_system/src/node_js_file_system.ts b/packages/compiler-cli/src/ngtsc/file_system/src/node_js_file_system.ts index 940afcb2cf..891426f969 100644 --- a/packages/compiler-cli/src/ngtsc/file_system/src/node_js_file_system.ts +++ b/packages/compiler-cli/src/ngtsc/file_system/src/node_js_file_system.ts @@ -26,6 +26,7 @@ export class NodeJSFileSystem implements FileSystem { lstat(path: AbsoluteFsPath): FileStats { return fs.lstatSync(path); } stat(path: AbsoluteFsPath): FileStats { return fs.statSync(path); } pwd(): AbsoluteFsPath { return this.normalize(process.cwd()) as AbsoluteFsPath; } + chdir(dir: AbsoluteFsPath): void { process.chdir(dir); } copyFile(from: AbsoluteFsPath, to: AbsoluteFsPath): void { fs.copyFileSync(from, to); } moveFile(from: AbsoluteFsPath, to: AbsoluteFsPath): void { fs.renameSync(from, to); } ensureDir(path: AbsoluteFsPath): void { diff --git a/packages/compiler-cli/src/ngtsc/file_system/src/types.ts b/packages/compiler-cli/src/ngtsc/file_system/src/types.ts index a037459f1a..9c3dc11bf6 100644 --- a/packages/compiler-cli/src/ngtsc/file_system/src/types.ts +++ b/packages/compiler-cli/src/ngtsc/file_system/src/types.ts @@ -43,6 +43,7 @@ export interface FileSystem { lstat(path: AbsoluteFsPath): FileStats; stat(path: AbsoluteFsPath): FileStats; pwd(): AbsoluteFsPath; + chdir(path: AbsoluteFsPath): void; extname(path: AbsoluteFsPath|PathSegment): string; copyFile(from: AbsoluteFsPath, to: AbsoluteFsPath): void; moveFile(from: AbsoluteFsPath, to: AbsoluteFsPath): void; diff --git a/packages/compiler-cli/src/ngtsc/file_system/testing/src/mock_file_system.ts b/packages/compiler-cli/src/ngtsc/file_system/testing/src/mock_file_system.ts index 5f7cbbbe71..a1609ab28c 100644 --- a/packages/compiler-cli/src/ngtsc/file_system/testing/src/mock_file_system.ts +++ b/packages/compiler-cli/src/ngtsc/file_system/testing/src/mock_file_system.ts @@ -132,7 +132,36 @@ export abstract class MockFileSystem implements FileSystem { pwd(): AbsoluteFsPath { return this._cwd; } - getDefaultLibLocation(): AbsoluteFsPath { return this.resolve('node_modules/typescript/lib'); } + chdir(path: AbsoluteFsPath): void { this._cwd = this.normalize(path); } + + getDefaultLibLocation(): AbsoluteFsPath { + // Mimic the node module resolution algorithm and start in the current directory, then look + // progressively further up the tree until reaching the FS root. + // E.g. if the current directory is /foo/bar, look in /foo/bar/node_modules, then + // /foo/node_modules, then /node_modules. + + let path = 'node_modules/typescript/lib'; + let resolvedPath = this.resolve(path); + + // Construct a path for the top-level node_modules to identify the stopping point. + const topLevelNodeModules = this.resolve('/' + path); + + while (resolvedPath !== topLevelNodeModules) { + if (this.exists(resolvedPath)) { + return resolvedPath; + } + + // Not here, look one level higher. + path = '../' + path; + resolvedPath = this.resolve(path); + } + + // The loop exits before checking the existence of /node_modules/typescript at the top level. + // This is intentional - if no /node_modules/typescript exists anywhere in the tree, there's + // nothing this function can do about it, and TS may error later if it looks for a lib.d.ts file + // within this directory. It might be okay, though, if TS never checks for one. + return topLevelNodeModules; + } abstract resolve(...paths: string[]): AbsoluteFsPath; abstract dirname(file: T): T; diff --git a/packages/compiler-cli/test/ngtsc/env.ts b/packages/compiler-cli/test/ngtsc/env.ts index 63d1584931..0eb391b783 100644 --- a/packages/compiler-cli/test/ngtsc/env.ts +++ b/packages/compiler-cli/test/ngtsc/env.ts @@ -35,7 +35,8 @@ export class NgtscTestEnvironment { /** * Set up a new testing environment. */ - static setup(files?: Folder): NgtscTestEnvironment { + static setup(files?: Folder, workingDir: AbsoluteFsPath = absoluteFrom('/')): + NgtscTestEnvironment { const fs = getFileSystem(); if (files !== undefined && fs instanceof MockFileSystem) { fs.init(files); @@ -44,7 +45,8 @@ export class NgtscTestEnvironment { const host = new AugmentedCompilerHost(fs); setWrapHostForTest(makeWrapHost(host)); - const env = new NgtscTestEnvironment(fs, fs.resolve('/built'), absoluteFrom('/')); + const env = new NgtscTestEnvironment(fs, fs.resolve('/built'), workingDir); + fs.chdir(workingDir); env.write(absoluteFrom('/tsconfig-base.json'), `{ "compilerOptions": { @@ -54,7 +56,6 @@ export class NgtscTestEnvironment { "noImplicitAny": true, "strictNullChecks": true, "outDir": "built", - "rootDir": ".", "baseUrl": ".", "declaration": true, "target": "es5",