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
This commit is contained in:
Alex Rickabaugh 2019-11-13 14:45:46 -08:00
parent 6bf2531b19
commit 51720745dd
6 changed files with 38 additions and 4 deletions

View File

@ -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); }

View File

@ -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(); }

View File

@ -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 {

View File

@ -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;

View File

@ -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<T extends string>(file: T): T;

View File

@ -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",