Skip to content

Commit 0f6a003

Browse files
Ignore FS errors from stat() in some cases. (#10021)
(for #9901) The earlier FS code ignored errors in a couple of situations. Recent changes inadvertently stopped ignoring them. This change restores the earlier behavior (and adds logging of the errors).
1 parent ead69d5 commit 0f6a003

File tree

5 files changed

+114
-11
lines changed

5 files changed

+114
-11
lines changed

news/2 Fixes/9901.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Ignore errors coming from stat(), where appropriate.

src/client/common/platform/fileSystem.ts

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { injectable } from 'inversify';
99
import { promisify } from 'util';
1010
import * as vscode from 'vscode';
1111
import '../../common/extensions';
12+
import { traceError } from '../logger';
1213
import { createDeferred } from '../utils/async';
1314
import { isFileNotFoundError, isNoPermissionsError } from './errors';
1415
import { FileSystemPaths, FileSystemPathUtils } from './fs-paths';
@@ -213,9 +214,15 @@ export class RawFileSystem implements IRawFileSystem {
213214
const files = await this.fsExtra.readdir(dirname);
214215
const promises = files.map(async basename => {
215216
const filename = this.paths.join(dirname, basename);
216-
// Note that this follows symlinks (while still preserving
217-
// the Symlink flag).
218-
const fileType = await this.getFileType(filename);
217+
let fileType: FileType;
218+
try {
219+
// Note that getFileType() follows symlinks (while still
220+
// preserving the Symlink flag).
221+
fileType = await this.getFileType(filename);
222+
} catch (err) {
223+
traceError(`failure while getting file type for "${filename}"`, err);
224+
fileType = FileType.Unknown;
225+
}
219226
return [filename, fileType] as [string, FileType];
220227
});
221228
return Promise.all(promises);
@@ -352,7 +359,8 @@ export class FileSystemUtils implements IFileSystemUtils {
352359
if (isFileNotFoundError(err)) {
353360
return false;
354361
}
355-
throw err;
362+
traceError(`stat() failed for "${filename}"`, err);
363+
return false;
356364
}
357365

358366
if (fileType === undefined) {

src/test/common/platform/filesystem.functional.test.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -678,6 +678,31 @@ suite('FileSystem - raw', () => {
678678

679679
await expect(promise).to.eventually.be.rejected;
680680
});
681+
682+
test('ignores errors from getFileType()', async function() {
683+
if (WINDOWS) {
684+
// tslint:disable-next-line:no-invalid-this
685+
this.skip();
686+
}
687+
const dirname = await fix.createDirectory('x/y/z');
688+
const file1 = await fix.createFile('x/y/z/__init__.py', '');
689+
const file2 = await fix.createFile('x/y/z/spam.py', '...');
690+
const file3 = await fix.createFile('x/y/z/eggs.py', '...');
691+
await fs.chmod(dirname, 0o400);
692+
693+
let entries: [string, FileType][];
694+
try {
695+
entries = await fileSystem.listdir(dirname);
696+
} finally {
697+
await fs.chmod(dirname, 0o755);
698+
}
699+
700+
expect(entries.sort()).to.deep.equal([
701+
[file1, FileType.Unknown],
702+
[file3, FileType.Unknown],
703+
[file2, FileType.Unknown]
704+
]);
705+
});
681706
});
682707

683708
// non-async

src/test/common/platform/filesystem.test.ts

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import {
1717
// prettier-ignore
1818
import {
1919
assertDoesNotExist, DOES_NOT_EXIST, FSFixture,
20-
SUPPORTS_SOCKETS, SUPPORTS_SYMLINKS
20+
SUPPORTS_SOCKETS, SUPPORTS_SYMLINKS, WINDOWS
2121
} from './utils';
2222

2323
// Note: all functional tests that do not trigger the VS Code "fs" API
@@ -242,6 +242,25 @@ suite('FileSystem - utils', () => {
242242

243243
expect(exists).to.equal(false);
244244
});
245+
246+
test('failure in stat()', async function() {
247+
if (WINDOWS) {
248+
// tslint:disable-next-line:no-invalid-this
249+
this.skip();
250+
}
251+
const dirname = await fix.createDirectory('x/y/z');
252+
const filename = await fix.createFile('x/y/z/spam.py', '...');
253+
await fsextra.chmod(dirname, 0o400);
254+
255+
let exists: boolean;
256+
try {
257+
exists = await utils.fileExists(filename);
258+
} finally {
259+
await fsextra.chmod(dirname, 0o755);
260+
}
261+
262+
expect(exists).to.equal(false);
263+
});
245264
});
246265

247266
suite('directoryExists', () => {
@@ -282,6 +301,25 @@ suite('FileSystem - utils', () => {
282301

283302
expect(exists).to.equal(false);
284303
});
304+
305+
test('failure in stat()', async function() {
306+
if (WINDOWS) {
307+
// tslint:disable-next-line:no-invalid-this
308+
this.skip();
309+
}
310+
const parentdir = await fix.createDirectory('x/y/z');
311+
const dirname = await fix.createDirectory('x/y/z/spam');
312+
await fsextra.chmod(parentdir, 0o400);
313+
314+
let exists: boolean;
315+
try {
316+
exists = await utils.fileExists(dirname);
317+
} finally {
318+
await fsextra.chmod(parentdir, 0o755);
319+
}
320+
321+
expect(exists).to.equal(false);
322+
});
285323
});
286324

287325
suite('getSubDirectories', () => {

src/test/common/platform/filesystem.unit.test.ts

Lines changed: 37 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -597,6 +597,38 @@ suite('Raw FileSystem', () => {
597597
await expect(promise).to.eventually.be.rejected;
598598
verifyAll();
599599
});
600+
601+
test('ignores errors from getFileType()', async () => {
602+
const dirname = 'x/y/z';
603+
const names = [
604+
// These match the items in "expected".
605+
'__init__.py',
606+
'spam.py',
607+
'eggs.py'
608+
];
609+
const expected: [string, FileType][] = [
610+
['x/y/z/__init__.py', FileType.File],
611+
['x/y/z/spam.py', FileType.File],
612+
['x/y/z/eggs.py', FileType.Unknown]
613+
];
614+
raw.setup(r => r.readdir(dirname)) // expect the specific filename
615+
.returns(() => Promise.resolve(names));
616+
names.forEach((name, i) => {
617+
const [filename, filetype] = expected[i];
618+
raw.setup(r => r.join(dirname, name)) // expect the specific filename
619+
.returns(() => filename);
620+
if (filetype === FileType.Unknown) {
621+
raw.setup(r => r.lstat(filename)) // expect the specific filename
622+
.throws(new Error('oops!'));
623+
} else {
624+
setupForFileType(filename, filetype);
625+
}
626+
});
627+
628+
const entries = await filesystem.listdir(dirname);
629+
630+
expect(entries.sort()).to.deep.equal(expected.sort());
631+
});
600632
});
601633

602634
suite('readTextSync', () => {
@@ -781,15 +813,14 @@ suite('FileSystemUtils', () => {
781813
verifyAll();
782814
});
783815

784-
test('fails if stat fails', async () => {
816+
test('ignores errors from stat()', async () => {
785817
const filename = 'x/y/z/spam.py';
786-
const err = new Error('oops!');
787-
deps.setup(d => d.stat(filename)) // There was a problem while stat'ing the file.
788-
.throws(err);
818+
deps.setup(d => d.stat(filename)) // It's broken.
819+
.returns(() => Promise.reject(new Error('oops!')));
789820

790-
const promise = utils.pathExists(filename);
821+
const exists = await utils.pathExists(filename);
791822

792-
await expect(promise).to.eventually.be.rejected;
823+
expect(exists).to.equal(false);
793824
verifyAll();
794825
});
795826

0 commit comments

Comments
 (0)