Skip to content

Commit 9a0c7b1

Browse files
Reduce file system calls during test setup. (#56791)
1 parent e769725 commit 9a0c7b1

File tree

8 files changed

+53
-62
lines changed

8 files changed

+53
-62
lines changed

src/compiler/sys.ts

+2
Original file line numberDiff line numberDiff line change
@@ -1445,6 +1445,7 @@ export interface System {
14451445
realpath?(path: string): string;
14461446
/** @internal */ getEnvironmentVariable(name: string): string;
14471447
/** @internal */ tryEnableSourceMapsForHost?(): void;
1448+
/** @internal */ getAccessibleFileSystemEntries?(path: string): FileSystemEntries;
14481449
/** @internal */ debugMode?: boolean;
14491450
setTimeout?(callback: (...args: any[]) => void, ms: number, ...args: any[]): any;
14501451
clearTimeout?(timeoutId: any): void;
@@ -1553,6 +1554,7 @@ export let sys: System = (() => {
15531554
resolvePath: path => _path.resolve(path),
15541555
fileExists,
15551556
directoryExists,
1557+
getAccessibleFileSystemEntries,
15561558
createDirectory(directoryName: string) {
15571559
if (!nodeSystem.directoryExists(directoryName)) {
15581560
// Wrapped in a try-catch to prevent crashing if we are in a race

src/harness/harnessIO.ts

+12-39
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ export interface IO {
2525
fileExists(fileName: string): boolean;
2626
directoryExists(path: string): boolean;
2727
deleteFile(fileName: string): void;
28-
enumerateTestFiles(runner: RunnerBase): (string | FileBasedTest)[];
28+
enumerateTestFiles(runner: RunnerBase): string[];
2929
listFiles(path: string, filter?: RegExp, options?: { recursive?: boolean; }): string[];
3030
log(text: string): void;
3131
args(): string[];
@@ -84,55 +84,28 @@ function createNodeIO(): IO {
8484
return runner.getTestFiles();
8585
}
8686

87-
function listFiles(path: string, spec: RegExp, options: { recursive?: boolean; } = {}) {
87+
function listFiles(path: string, spec: RegExp | undefined, options: { recursive?: boolean; } = {}) {
8888
function filesInFolder(folder: string): string[] {
89+
const { files, directories } = ts.sys.getAccessibleFileSystemEntries!(folder);
8990
let paths: string[] = [];
90-
91-
for (const file of fs.readdirSync(folder)) {
91+
for (const file of files) {
9292
const pathToFile = pathModule.join(folder, file);
93-
if (!fs.existsSync(pathToFile)) continue; // ignore invalid symlinks
94-
const stat = fs.statSync(pathToFile);
95-
if (options.recursive && stat.isDirectory()) {
96-
paths = paths.concat(filesInFolder(pathToFile));
97-
}
98-
else if (stat.isFile() && (!spec || file.match(spec))) {
93+
if (!spec || file.match(spec)) {
9994
paths.push(pathToFile);
10095
}
10196
}
102-
97+
if (options.recursive) {
98+
for (const dir of directories) {
99+
const pathToDir = pathModule.join(folder, dir);
100+
paths = paths.concat(filesInFolder(pathToDir));
101+
}
102+
}
103103
return paths;
104104
}
105105

106106
return filesInFolder(path);
107107
}
108108

109-
function getAccessibleFileSystemEntries(dirname: string): ts.FileSystemEntries {
110-
try {
111-
const entries: string[] = fs.readdirSync(dirname || ".").sort(ts.sys.useCaseSensitiveFileNames ? ts.compareStringsCaseSensitive : ts.compareStringsCaseInsensitive);
112-
const files: string[] = [];
113-
const directories: string[] = [];
114-
for (const entry of entries) {
115-
if (entry === "." || entry === "..") continue;
116-
const name = vpath.combine(dirname, entry);
117-
try {
118-
const stat = fs.statSync(name);
119-
if (!stat) continue;
120-
if (stat.isFile()) {
121-
files.push(entry);
122-
}
123-
else if (stat.isDirectory()) {
124-
directories.push(entry);
125-
}
126-
}
127-
catch { /*ignore*/ }
128-
}
129-
return { files, directories };
130-
}
131-
catch (e) {
132-
return { files: [], directories: [] };
133-
}
134-
}
135-
136109
function createDirectory(path: string) {
137110
try {
138111
fs.mkdirSync(path);
@@ -170,7 +143,7 @@ function createNodeIO(): IO {
170143
getWorkspaceRoot: () => workspaceRoot,
171144
exit: exitCode => ts.sys.exit(exitCode),
172145
readDirectory: (path, extension, exclude, include, depth) => ts.sys.readDirectory(path, extension, exclude, include, depth),
173-
getAccessibleFileSystemEntries,
146+
getAccessibleFileSystemEntries: ts.sys.getAccessibleFileSystemEntries!,
174147
tryEnableSourceMapsForHost: () => ts.sys.tryEnableSourceMapsForHost && ts.sys.tryEnableSourceMapsForHost(),
175148
getMemoryUsage: () => ts.sys.getMemoryUsage && ts.sys.getMemoryUsage(),
176149
getEnvironmentVariable: name => ts.sys.getEnvironmentVariable(name),

src/harness/runnerbase.ts

+2-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import {
2-
FileBasedTest,
32
IO,
43
userSpecifiedRoot,
54
} from "./_namespaces/Harness";
@@ -22,7 +21,7 @@ export function setShardId(id: number) {
2221

2322
export abstract class RunnerBase {
2423
// contains the tests to run
25-
public tests: (string | FileBasedTest)[] = [];
24+
public tests: string[] = [];
2625

2726
/** Add a source file to the runner's list of tests that need to be initialized with initializeTests */
2827
public addTest(fileName: string) {
@@ -35,7 +34,7 @@ export abstract class RunnerBase {
3534

3635
abstract kind(): TestRunnerKind;
3736

38-
abstract enumerateTestFiles(): (string | FileBasedTest)[];
37+
abstract enumerateTestFiles(): string[];
3938

4039
getTestFiles(): ReturnType<this["enumerateTestFiles"]> {
4140
const all = this.enumerateTestFiles();

src/testRunner/compilerRunner.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -51,9 +51,10 @@ export class CompilerBaselineRunner extends RunnerBase {
5151
return this.testSuiteName;
5252
}
5353

54+
private testFiles: string[] | undefined;
5455
public enumerateTestFiles() {
5556
// see also: `enumerateTestFiles` in tests/webTestServer.ts
56-
return this.enumerateFiles(this.basePath, /\.tsx?$/, { recursive: true }).map(CompilerTest.getConfigurations);
57+
return this.testFiles ??= this.enumerateFiles(this.basePath, /\.tsx?$/, { recursive: true });
5758
}
5859

5960
public initializeTests() {
@@ -64,9 +65,8 @@ export class CompilerBaselineRunner extends RunnerBase {
6465

6566
// this will set up a series of describe/it blocks to run between the setup and cleanup phases
6667
const files = this.tests.length > 0 ? this.tests : IO.enumerateTestFiles(this);
67-
files.forEach(test => {
68-
const file = typeof test === "string" ? test : test.file;
69-
this.checkTestCodeOutput(vpath.normalizeSeparators(file), typeof test === "string" ? CompilerTest.getConfigurations(test) : test);
68+
files.forEach(file => {
69+
this.checkTestCodeOutput(vpath.normalizeSeparators(file), CompilerTest.getConfigurations(file));
7070
});
7171
});
7272
}

src/testRunner/fourslashRunner.ts

+1-2
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,7 @@ export class FourSlashRunner extends RunnerBase {
4141
}
4242

4343
describe(this.testSuiteName + " tests", () => {
44-
this.tests.forEach(test => {
45-
const file = typeof test === "string" ? test : test.file;
44+
this.tests.forEach(file => {
4645
describe(file, () => {
4746
let fn = ts.normalizeSlashes(file);
4847
const justName = fn.replace(/^.*[\\/]/, "");

src/testRunner/parallel/host.ts

+1-2
Original file line numberDiff line numberDiff line change
@@ -217,8 +217,7 @@ export function start() {
217217
console.log("Discovering runner-based tests...");
218218
const discoverStart = +(new Date());
219219
for (const runner of runners) {
220-
for (const test of runner.getTestFiles()) {
221-
const file = typeof test === "string" ? test : test.file;
220+
for (const file of runner.getTestFiles()) {
222221
let size: number;
223222
if (!perfData) {
224223
try {

src/testRunner/projectsRunner.ts

+30-9
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ export class ProjectRunner extends Harness.RunnerBase {
5454
describe("projects tests", () => {
5555
const tests = this.tests.length === 0 ? this.enumerateTestFiles() : this.tests;
5656
for (const test of tests) {
57-
this.runProjectTestCase(typeof test === "string" ? test : test.file);
57+
this.runProjectTestCase(test);
5858
}
5959
});
6060
}
@@ -202,15 +202,36 @@ class ProjectTestCase {
202202
throw assert(false, "Testcase: " + testCaseFileName + " does not contain valid json format: " + e.message);
203203
}
204204

205-
const fs = vfs.createFromFileSystem(Harness.IO, /*ignoreCase*/ false);
206-
fs.mountSync(vpath.resolve(Harness.IO.getWorkspaceRoot(), "tests"), vpath.combine(vfs.srcFolder, "tests"), vfs.createResolver(Harness.IO));
207-
fs.mkdirpSync(vpath.combine(vfs.srcFolder, testCase.projectRoot));
208-
fs.chdir(vpath.combine(vfs.srcFolder, testCase.projectRoot));
209-
fs.makeReadonly();
210-
205+
function makeFileSystem() {
206+
const fs = vfs.createFromFileSystem(Harness.IO, /*ignoreCase*/ false);
207+
fs.mountSync(vpath.resolve(Harness.IO.getWorkspaceRoot(), "tests"), vpath.combine(vfs.srcFolder, "tests"), vfs.createResolver(Harness.IO));
208+
fs.mkdirpSync(vpath.combine(vfs.srcFolder, testCase.projectRoot));
209+
fs.chdir(vpath.combine(vfs.srcFolder, testCase.projectRoot));
210+
fs.makeReadonly();
211+
return fs;
212+
}
213+
let fs: vfs.FileSystem | undefined;
211214
return [
212-
{ name: `@module: commonjs`, payload: { testCase, moduleKind: ts.ModuleKind.CommonJS, vfs: fs } },
213-
{ name: `@module: amd`, payload: { testCase, moduleKind: ts.ModuleKind.AMD, vfs: fs } },
215+
{
216+
name: `@module: commonjs`,
217+
payload: {
218+
testCase,
219+
moduleKind: ts.ModuleKind.CommonJS,
220+
get vfs() {
221+
return fs ??= makeFileSystem();
222+
},
223+
},
224+
},
225+
{
226+
name: `@module: amd`,
227+
payload: {
228+
testCase,
229+
moduleKind: ts.ModuleKind.AMD,
230+
get vfs() {
231+
return fs ??= makeFileSystem();
232+
},
233+
},
234+
},
214235
];
215236
}
216237

src/testRunner/runner.ts

+1-3
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,7 @@ function runTests(runners: RunnerBase[]) {
2727
const dupes: [string, string][] = [];
2828
for (const runner of runners) {
2929
if (runner instanceof CompilerBaselineRunner || runner instanceof FourSlashRunner) {
30-
for (const sf of runner.enumerateTestFiles()) {
31-
const full = typeof sf === "string" ? sf : sf.file;
30+
for (const full of runner.enumerateTestFiles()) {
3231
const base = vpath.basename(full).toLowerCase();
3332
// allow existing dupes in fourslash/shims and fourslash/server
3433
if (seen.has(base) && !/fourslash\/(shim|server)/.test(full)) {
@@ -191,7 +190,6 @@ function handleTestConfig() {
191190
case "fourslash-generated":
192191
runners.push(new GeneratedFourslashRunner(FourSlash.FourSlashTestType.Native));
193192
break;
194-
break;
195193
}
196194
}
197195
}

0 commit comments

Comments
 (0)