mirror of
https://github.com/arduino/arduino-ide.git
synced 2025-04-19 12:57:17 +00:00
fix: handle UNKNOWN
code on syscall: 'stat'
Closes #2166 Signed-off-by: Akos Kitta <a.kitta@arduino.cc>
This commit is contained in:
parent
420d31ff4b
commit
b25665561e
@ -677,6 +677,7 @@ async function isInvalidSketchNameError(
|
||||
*
|
||||
* The sketch folder name and sketch file name can be different. This method is not sketch folder name compliant.
|
||||
* The `path` must be an absolute, resolved path. This method does not handle EACCES (Permission denied) errors.
|
||||
* This method handles `UNKNOWN` errors ([nodejs/node#19965](https://github.com/nodejs/node/issues/19965#issuecomment-380750573)).
|
||||
*
|
||||
* When `fallbackToInvalidFolderPath` is `true`, and the `path` is an accessible folder without any sketch files,
|
||||
* this method returns with the `path` argument instead of `undefined`.
|
||||
@ -689,7 +690,7 @@ export async function isAccessibleSketchPath(
|
||||
try {
|
||||
stats = await fs.stat(path);
|
||||
} catch (err) {
|
||||
if (ErrnoException.isENOENT(err)) {
|
||||
if (ErrnoException.isENOENT(err) || ErrnoException.isUNKNOWN(err)) {
|
||||
return undefined;
|
||||
}
|
||||
throw err;
|
||||
|
@ -15,7 +15,16 @@ export namespace ErrnoException {
|
||||
}
|
||||
|
||||
/**
|
||||
* (No such file or directory): Commonly raised by `fs` operations to indicate that a component of the specified pathname does not exist — no entity (file or directory) could be found by the given path.
|
||||
* _(Permission denied):_ An attempt was made to access a file in a way forbidden by its file access permissions.
|
||||
*/
|
||||
export function isEACCES(
|
||||
arg: unknown
|
||||
): arg is ErrnoException & { code: 'EACCES' } {
|
||||
return is(arg) && arg.code === 'EACCES';
|
||||
}
|
||||
|
||||
/**
|
||||
* _(No such file or directory):_ Commonly raised by `fs` operations to indicate that a component of the specified pathname does not exist — no entity (file or directory) could be found by the given path.
|
||||
*/
|
||||
export function isENOENT(
|
||||
arg: unknown
|
||||
@ -24,11 +33,22 @@ export namespace ErrnoException {
|
||||
}
|
||||
|
||||
/**
|
||||
* (Not a directory): A component of the given pathname existed, but was not a directory as expected. Commonly raised by `fs.readdir`.
|
||||
* _(Not a directory):_ A component of the given pathname existed, but was not a directory as expected. Commonly raised by `fs.readdir`.
|
||||
*/
|
||||
export function isENOTDIR(
|
||||
arg: unknown
|
||||
): arg is ErrnoException & { code: 'ENOTDIR' } {
|
||||
return is(arg) && arg.code === 'ENOTDIR';
|
||||
}
|
||||
|
||||
/**
|
||||
* _"That 4094 error code is a generic network-or-configuration error, Node.js just passes it on from the operating system."_
|
||||
*
|
||||
* See [nodejs/node#19965](https://github.com/nodejs/node/issues/19965#issuecomment-380750573) for more details.
|
||||
*/
|
||||
export function isUNKNOWN(
|
||||
arg: unknown
|
||||
): arg is ErrnoException & { code: 'UNKNOWN' } {
|
||||
return is(arg) && arg.code === 'UNKNOWN';
|
||||
}
|
||||
}
|
||||
|
@ -6,16 +6,116 @@ import { isWindows } from '@theia/core/lib/common/os';
|
||||
import { FileUri } from '@theia/core/lib/node/file-uri';
|
||||
import { Container } from '@theia/core/shared/inversify';
|
||||
import { expect } from 'chai';
|
||||
import { rejects } from 'node:assert/strict';
|
||||
import { promises as fs } from 'node:fs';
|
||||
import { basename, join } from 'node:path';
|
||||
import { sync as rimrafSync } from 'rimraf';
|
||||
import temp from 'temp';
|
||||
import { Sketch, SketchesService } from '../../common/protocol';
|
||||
import { SketchesServiceImpl } from '../../node/sketches-service-impl';
|
||||
import {
|
||||
isAccessibleSketchPath,
|
||||
SketchesServiceImpl,
|
||||
} from '../../node/sketches-service-impl';
|
||||
import { ErrnoException } from '../../node/utils/errors';
|
||||
import { createBaseContainer, startDaemon } from './node-test-bindings';
|
||||
|
||||
const testTimeout = 10_000;
|
||||
|
||||
describe('isAccessibleSketchPath', () => {
|
||||
let tracked: typeof temp;
|
||||
let testDirPath: string;
|
||||
|
||||
before(() => (tracked = temp.track()));
|
||||
beforeEach(() => (testDirPath = tracked.mkdirSync()));
|
||||
after(() => tracked.cleanupSync());
|
||||
|
||||
it('should be accessible by the main sketch file', async () => {
|
||||
const sketchFolderPath = join(testDirPath, 'my_sketch');
|
||||
const mainSketchFilePath = join(sketchFolderPath, 'my_sketch.ino');
|
||||
await fs.mkdir(sketchFolderPath, { recursive: true });
|
||||
await fs.writeFile(mainSketchFilePath, '', { encoding: 'utf8' });
|
||||
const actual = await isAccessibleSketchPath(mainSketchFilePath);
|
||||
expect(actual).to.be.equal(mainSketchFilePath);
|
||||
});
|
||||
|
||||
it('should be accessible by the sketch folder', async () => {
|
||||
const sketchFolderPath = join(testDirPath, 'my_sketch');
|
||||
const mainSketchFilePath = join(sketchFolderPath, 'my_sketch.ino');
|
||||
await fs.mkdir(sketchFolderPath, { recursive: true });
|
||||
await fs.writeFile(mainSketchFilePath, '', { encoding: 'utf8' });
|
||||
const actual = await isAccessibleSketchPath(sketchFolderPath);
|
||||
expect(actual).to.be.equal(mainSketchFilePath);
|
||||
});
|
||||
|
||||
it('should be accessible when the sketch folder and main sketch file basenames are different', async () => {
|
||||
const sketchFolderPath = join(testDirPath, 'my_sketch');
|
||||
const mainSketchFilePath = join(sketchFolderPath, 'other_name_sketch.ino');
|
||||
await fs.mkdir(sketchFolderPath, { recursive: true });
|
||||
await fs.writeFile(mainSketchFilePath, '', { encoding: 'utf8' });
|
||||
const actual = await isAccessibleSketchPath(sketchFolderPath);
|
||||
expect(actual).to.be.equal(mainSketchFilePath);
|
||||
});
|
||||
|
||||
it('should be deterministic (and sort by basename) when multiple sketch files exist', async () => {
|
||||
const sketchFolderPath = join(testDirPath, 'my_sketch');
|
||||
const aSketchFilePath = join(sketchFolderPath, 'a.ino');
|
||||
const bSketchFilePath = join(sketchFolderPath, 'b.ino');
|
||||
await fs.mkdir(sketchFolderPath, { recursive: true });
|
||||
await fs.writeFile(aSketchFilePath, '', { encoding: 'utf8' });
|
||||
await fs.writeFile(bSketchFilePath, '', { encoding: 'utf8' });
|
||||
const actual = await isAccessibleSketchPath(sketchFolderPath);
|
||||
expect(actual).to.be.equal(aSketchFilePath);
|
||||
});
|
||||
|
||||
it('should ignore EACCESS (non-Windows)', async function () {
|
||||
if (isWindows) {
|
||||
// `stat` syscall does not result in an EACCESS on Windows after stripping the file permissions.
|
||||
// an `open` syscall would, but IDE2 on purpose does not check the files.
|
||||
// the sketch files are provided by the CLI after loading the sketch.
|
||||
return this.skip();
|
||||
}
|
||||
const sketchFolderPath = join(testDirPath, 'my_sketch');
|
||||
const mainSketchFilePath = join(sketchFolderPath, 'my_sketch.ino');
|
||||
await fs.mkdir(sketchFolderPath, { recursive: true });
|
||||
await fs.writeFile(mainSketchFilePath, '', { encoding: 'utf8' });
|
||||
await fs.chmod(mainSketchFilePath, 0o000); // remove all permissions
|
||||
await rejects(fs.readFile(mainSketchFilePath), ErrnoException.isEACCES);
|
||||
const actual = await isAccessibleSketchPath(sketchFolderPath);
|
||||
expect(actual).to.be.equal(mainSketchFilePath);
|
||||
});
|
||||
|
||||
it("should not be accessible when there are no '.ino' files in the folder", async () => {
|
||||
const sketchFolderPath = join(testDirPath, 'my_sketch');
|
||||
await fs.mkdir(sketchFolderPath, { recursive: true });
|
||||
const actual = await isAccessibleSketchPath(sketchFolderPath);
|
||||
expect(actual).to.be.undefined;
|
||||
});
|
||||
|
||||
it("should not be accessible when the main sketch file extension is not '.ino'", async () => {
|
||||
const sketchFolderPath = join(testDirPath, 'my_sketch');
|
||||
const mainSketchFilePath = join(sketchFolderPath, 'my_sketch.cpp');
|
||||
await fs.mkdir(sketchFolderPath, { recursive: true });
|
||||
await fs.writeFile(mainSketchFilePath, '', { encoding: 'utf8' });
|
||||
const actual = await isAccessibleSketchPath(sketchFolderPath);
|
||||
expect(actual).to.be.undefined;
|
||||
});
|
||||
|
||||
it('should handle ENOENT', async () => {
|
||||
const sketchFolderPath = join(testDirPath, 'my_sketch');
|
||||
const actual = await isAccessibleSketchPath(sketchFolderPath);
|
||||
expect(actual).to.be.undefined;
|
||||
});
|
||||
|
||||
it('should handle UNKNOWN (Windows)', async function () {
|
||||
if (!isWindows) {
|
||||
return this.skip();
|
||||
}
|
||||
this.timeout(60_000);
|
||||
const actual = await isAccessibleSketchPath('\\\\10.0.0.200\\path');
|
||||
expect(actual).to.be.undefined;
|
||||
});
|
||||
});
|
||||
|
||||
describe('sketches-service-impl', () => {
|
||||
let container: Container;
|
||||
let toDispose: DisposableCollection;
|
||||
|
Loading…
x
Reference in New Issue
Block a user