test: improve language service tests performance (#30585)

With this change we reduce the amount of IO operations. This is especially a huge factor in windows since IO ops are slower.

With this change mainly we cache `existsSync` and `readFileSync` calls

Here's the results

Before
```
//packages/language-service/test:test
INFO: Elapsed time: 258.755s, Critical Path: 253.91s
```

After
```
//packages/language-service/test:test
INFO: Elapsed time: 66.403s, Critical Path: 63.13s
```

PR Close #30585
This commit is contained in:
Alan 2019-05-22 08:26:05 +02:00 committed by Matias Niemelä
parent 661c6e6f10
commit 41cf066906
2 changed files with 39 additions and 19 deletions

View File

@ -18,9 +18,14 @@ describe('reflector_host_spec', () => {
// Regression #21811 // Regression #21811
it('should be able to find angular under windows', () => { it('should be able to find angular under windows', () => {
const originalJoin = path.join; const originalJoin = path.join;
let mockHost = new MockTypescriptHost( const originalPosixJoin = path.posix.join;
['/app/main.ts', '/app/parsing-cases.ts'], toh, 'app/node_modules', let mockHost =
{...path, join: (...args: string[]) => originalJoin.apply(path, args)}); new MockTypescriptHost(['/app/main.ts', '/app/parsing-cases.ts'], toh, 'app/node_modules', {
...path,
join: (...args: string[]) => originalJoin.apply(path, args),
posix:
{...path.posix, join: (...args: string[]) => originalPosixJoin.apply(path, args)}
});
const reflectorHost = new ReflectorHost(() => undefined as any, mockHost, {basePath: '\\app'}); const reflectorHost = new ReflectorHost(() => undefined as any, mockHost, {basePath: '\\app'});
if (process.platform !== 'win32') { if (process.platform !== 'win32') {

View File

@ -72,13 +72,15 @@ export class MockTypescriptHost implements ts.LanguageServiceHost {
private projectVersion = 0; private projectVersion = 0;
private options: ts.CompilerOptions; private options: ts.CompilerOptions;
private overrideDirectory = new Set<string>(); private overrideDirectory = new Set<string>();
private existsCache = new Map<string, boolean>();
private fileCache = new Map<string, string|undefined>();
constructor( constructor(
private scriptNames: string[], private data: MockData, private scriptNames: string[], private data: MockData,
private node_modules: string = 'node_modules', private myPath: typeof path = path) { private node_modules: string = 'node_modules', private myPath: typeof path = path) {
const support = setup(); const support = setup();
this.nodeModulesPath = path.join(support.basePath, 'node_modules'); this.nodeModulesPath = path.posix.join(support.basePath, 'node_modules');
this.angularPath = path.join(this.nodeModulesPath, '@angular'); this.angularPath = path.posix.join(this.nodeModulesPath, '@angular');
this.options = { this.options = {
target: ts.ScriptTarget.ES5, target: ts.ScriptTarget.ES5,
module: ts.ModuleKind.CommonJS, module: ts.ModuleKind.CommonJS,
@ -146,7 +148,7 @@ export class MockTypescriptHost implements ts.LanguageServiceHost {
} else if (effectiveName == '/' + this.node_modules) { } else if (effectiveName == '/' + this.node_modules) {
return true; return true;
} else { } else {
return fs.existsSync(effectiveName); return this.pathExists(effectiveName);
} }
} }
@ -186,14 +188,19 @@ export class MockTypescriptHost implements ts.LanguageServiceHost {
cacheUsed.add(fileName); cacheUsed.add(fileName);
return undefined; return undefined;
} }
let effectiveName = this.getEffectiveName(fileName);
if (effectiveName === fileName) const effectiveName = this.getEffectiveName(fileName);
if (effectiveName === fileName) {
return open(fileName, this.data); return open(fileName, this.data);
else if ( } else if (
!fileName.match(angularts) && !fileName.match(rxjsts) && !fileName.match(rxjsmetadata) && !fileName.match(angularts) && !fileName.match(rxjsts) && !fileName.match(rxjsmetadata) &&
!fileName.match(tsxfile)) { !fileName.match(tsxfile)) {
if (fs.existsSync(effectiveName)) { if (this.fileCache.has(effectiveName)) {
return fs.readFileSync(effectiveName, 'utf8'); return this.fileCache.get(effectiveName);
} else if (this.pathExists(effectiveName)) {
const content = fs.readFileSync(effectiveName, 'utf8');
this.fileCache.set(effectiveName, content);
return content;
} else { } else {
missingCache.set(fileName, true); missingCache.set(fileName, true);
reportedMissing.add(fileName); reportedMissing.add(fileName);
@ -203,19 +210,29 @@ export class MockTypescriptHost implements ts.LanguageServiceHost {
} }
} }
private pathExists(path: string): boolean {
if (this.existsCache.has(path)) {
return this.existsCache.get(path) !;
}
const exists = fs.existsSync(path);
this.existsCache.set(path, exists);
return exists;
}
private getEffectiveName(name: string): string { private getEffectiveName(name: string): string {
const node_modules = this.node_modules; const node_modules = this.node_modules;
const at_angular = '/@angular'; const at_angular = '/@angular';
if (name.startsWith('/' + node_modules)) { if (name.startsWith('/' + node_modules)) {
if (this.nodeModulesPath && !name.startsWith('/' + node_modules + at_angular)) { if (this.nodeModulesPath && !name.startsWith('/' + node_modules + at_angular)) {
let result = this.myPath.join(this.nodeModulesPath, name.substr(node_modules.length + 1)); const result =
if (!name.match(rxjsts)) this.myPath.posix.join(this.nodeModulesPath, name.substr(node_modules.length + 1));
if (fs.existsSync(result)) { if (!name.match(rxjsts) && this.pathExists(result)) {
return result; return result;
} }
} }
if (this.angularPath && name.startsWith('/' + node_modules + at_angular)) { if (this.angularPath && name.startsWith('/' + node_modules + at_angular)) {
return this.myPath.join( return this.myPath.posix.join(
this.angularPath, name.substr(node_modules.length + at_angular.length + 1)); this.angularPath, name.substr(node_modules.length + at_angular.length + 1));
} }
} }
@ -279,8 +296,6 @@ function getLocationMarkers(value: string): {[name: string]: number} {
} }
const referenceMarker = /«(((\w|\-)+)|([^ᐱ]*ᐱ(\w+)ᐱ.[^»]*))»/g; const referenceMarker = /«(((\w|\-)+)|([^ᐱ]*ᐱ(\w+)ᐱ.[^»]*))»/g;
const definitionMarkerGroup = 1;
const nameMarkerGroup = 2;
export type ReferenceMarkers = { export type ReferenceMarkers = {
[name: string]: Span[] [name: string]: Span[]