Unverified Commit 9248fda4 authored by Jonah Williams's avatar Jonah Williams Committed by GitHub

[flutter_tools] add EACCES to list of immediate exit tool conditions (#65125)

Similar to the permission denied error on Windows, this is not resolvable by the tool.
parent 48f1a0a8
...@@ -357,15 +357,15 @@ Future<T> _run<T>(Future<T> Function() op, { ...@@ -357,15 +357,15 @@ Future<T> _run<T>(Future<T> Function() op, {
} on FileSystemException catch (e) { } on FileSystemException catch (e) {
if (platform.isWindows) { if (platform.isWindows) {
_handleWindowsException(e, failureMessage, e.osError?.errorCode ?? 0); _handleWindowsException(e, failureMessage, e.osError?.errorCode ?? 0);
} else if (platform.isLinux) { } else if (platform.isLinux || platform.isMacOS) {
_handleLinuxException(e, failureMessage, e.osError?.errorCode ?? 0); _handlePosixException(e, failureMessage, e.osError?.errorCode ?? 0);
} }
rethrow; rethrow;
} on io.ProcessException catch (e) { } on io.ProcessException catch (e) {
if (platform.isWindows) { if (platform.isWindows) {
_handleWindowsException(e, failureMessage, e.errorCode ?? 0); _handleWindowsException(e, failureMessage, e.errorCode ?? 0);
} else if (platform.isLinux) { } else if (platform.isLinux || platform.isMacOS) {
_handleLinuxException(e, failureMessage, e.errorCode ?? 0); _handlePosixException(e, failureMessage, e.errorCode ?? 0);
} }
rethrow; rethrow;
} }
...@@ -381,15 +381,15 @@ T _runSync<T>(T Function() op, { ...@@ -381,15 +381,15 @@ T _runSync<T>(T Function() op, {
} on FileSystemException catch (e) { } on FileSystemException catch (e) {
if (platform.isWindows) { if (platform.isWindows) {
_handleWindowsException(e, failureMessage, e.osError?.errorCode ?? 0); _handleWindowsException(e, failureMessage, e.osError?.errorCode ?? 0);
} else if (platform.isLinux) { } else if (platform.isLinux || platform.isMacOS) {
_handleLinuxException(e, failureMessage, e.osError?.errorCode ?? 0); _handlePosixException(e, failureMessage, e.osError?.errorCode ?? 0);
} }
rethrow; rethrow;
} on io.ProcessException catch (e) { } on io.ProcessException catch (e) {
if (platform.isWindows) { if (platform.isWindows) {
_handleWindowsException(e, failureMessage, e.errorCode ?? 0); _handleWindowsException(e, failureMessage, e.errorCode ?? 0);
} else if (platform.isLinux) { } else if (platform.isLinux || platform.isMacOS) {
_handleLinuxException(e, failureMessage, e.errorCode ?? 0); _handlePosixException(e, failureMessage, e.errorCode ?? 0);
} }
rethrow; rethrow;
} }
...@@ -490,11 +490,13 @@ class ErrorHandlingProcessManager extends ProcessManager { ...@@ -490,11 +490,13 @@ class ErrorHandlingProcessManager extends ProcessManager {
} }
} }
void _handleLinuxException(Exception e, String message, int errorCode) { void _handlePosixException(Exception e, String message, int errorCode) {
// From: // From:
// https://github.com/torvalds/linux/blob/master/include/uapi/asm-generic/errno.h // https://github.com/torvalds/linux/blob/master/include/uapi/asm-generic/errno.h
// https://github.com/torvalds/linux/blob/master/include/uapi/asm-generic/errno-base.h // https://github.com/torvalds/linux/blob/master/include/uapi/asm-generic/errno-base.h
// https://github.com/apple/darwin-xnu/blob/master/bsd/dev/dtrace/scripts/errno.d
const int enospc = 28; const int enospc = 28;
const int eacces = 13;
// Catch errors and bail when: // Catch errors and bail when:
switch (errorCode) { switch (errorCode) {
case enospc: case enospc:
...@@ -504,6 +506,13 @@ void _handleLinuxException(Exception e, String message, int errorCode) { ...@@ -504,6 +506,13 @@ void _handleLinuxException(Exception e, String message, int errorCode) {
'Free up space and try again.', 'Free up space and try again.',
); );
break; break;
case eacces:
throwToolExit(
'$message. The flutter tool cannot access the file.\n'
'Please ensure that the SDK and/or project is installed in a location '
'that has read/write permissions for the current user.'
);
break;
default: default:
// Caller must rethrow the exception. // Caller must rethrow the exception.
break; break;
......
...@@ -28,6 +28,11 @@ final Platform linuxPlatform = FakePlatform( ...@@ -28,6 +28,11 @@ final Platform linuxPlatform = FakePlatform(
environment: <String, String>{} environment: <String, String>{}
); );
final Platform macOSPlatform = FakePlatform(
operatingSystem: 'macos',
environment: <String, String>{}
);
void setupWriteMocks({ void setupWriteMocks({
FileSystem mockFileSystem, FileSystem mockFileSystem,
ErrorHandlingFileSystem fs, ErrorHandlingFileSystem fs,
...@@ -194,6 +199,7 @@ void main() { ...@@ -194,6 +199,7 @@ void main() {
group('throws ToolExit on Linux', () { group('throws ToolExit on Linux', () {
const int enospc = 28; const int enospc = 28;
const int eacces = 13;
MockFileSystem mockFileSystem; MockFileSystem mockFileSystem;
ErrorHandlingFileSystem fs; ErrorHandlingFileSystem fs;
...@@ -206,6 +212,103 @@ void main() { ...@@ -206,6 +212,103 @@ void main() {
when(mockFileSystem.path).thenReturn(MockPathContext()); when(mockFileSystem.path).thenReturn(MockPathContext());
}); });
testWithoutContext('when access is denied', () async {
setupWriteMocks(
mockFileSystem: mockFileSystem,
fs: fs,
errorCode: eacces,
);
final File file = fs.file('file');
const String expectedMessage = 'The flutter tool cannot access the file';
expect(() async => await file.writeAsBytes(<int>[0]),
throwsToolExit(message: expectedMessage));
expect(() async => await file.writeAsString(''),
throwsToolExit(message: expectedMessage));
expect(() => file.writeAsBytesSync(<int>[0]),
throwsToolExit(message: expectedMessage));
expect(() => file.writeAsStringSync(''),
throwsToolExit(message: expectedMessage));
expect(() => file.openSync(),
throwsToolExit(message: expectedMessage));
});
testWithoutContext('when writing to a full device', () async {
setupWriteMocks(
mockFileSystem: mockFileSystem,
fs: fs,
errorCode: enospc,
);
final File file = fs.file('file');
const String expectedMessage = 'The target device is full';
expect(() async => await file.writeAsBytes(<int>[0]),
throwsToolExit(message: expectedMessage));
expect(() async => await file.writeAsString(''),
throwsToolExit(message: expectedMessage));
expect(() => file.writeAsBytesSync(<int>[0]),
throwsToolExit(message: expectedMessage));
expect(() => file.writeAsStringSync(''),
throwsToolExit(message: expectedMessage));
});
testWithoutContext('when creating a temporary dir on a full device', () async {
setupDirectoryMocks(
mockFileSystem: mockFileSystem,
fs: fs,
errorCode: enospc,
);
final Directory directory = fs.directory('directory');
const String expectedMessage = 'The target device is full';
expect(() async => await directory.createTemp('prefix'),
throwsToolExit(message: expectedMessage));
expect(() => directory.createTempSync('prefix'),
throwsToolExit(message: expectedMessage));
});
});
group('throws ToolExit on macOS', () {
const int enospc = 28;
const int eacces = 13;
MockFileSystem mockFileSystem;
ErrorHandlingFileSystem fs;
setUp(() {
mockFileSystem = MockFileSystem();
fs = ErrorHandlingFileSystem(
delegate: mockFileSystem,
platform: macOSPlatform,
);
when(mockFileSystem.path).thenReturn(MockPathContext());
});
testWithoutContext('when access is denied', () async {
setupWriteMocks(
mockFileSystem: mockFileSystem,
fs: fs,
errorCode: eacces,
);
final File file = fs.file('file');
const String expectedMessage = 'The flutter tool cannot access the file';
expect(() async => await file.writeAsBytes(<int>[0]),
throwsToolExit(message: expectedMessage));
expect(() async => await file.writeAsString(''),
throwsToolExit(message: expectedMessage));
expect(() => file.writeAsBytesSync(<int>[0]),
throwsToolExit(message: expectedMessage));
expect(() => file.writeAsStringSync(''),
throwsToolExit(message: expectedMessage));
expect(() => file.openSync(),
throwsToolExit(message: expectedMessage));
});
testWithoutContext('when writing to a full device', () async { testWithoutContext('when writing to a full device', () async {
setupWriteMocks( setupWriteMocks(
mockFileSystem: mockFileSystem, mockFileSystem: mockFileSystem,
...@@ -365,6 +468,7 @@ void main() { ...@@ -365,6 +468,7 @@ void main() {
group('ProcessManager on linux throws tool exit', () { group('ProcessManager on linux throws tool exit', () {
const int enospc = 28; const int enospc = 28;
const int eacces = 13;
test('when writing to a full device', () { test('when writing to a full device', () {
final MockProcessManager mockProcessManager = MockProcessManager(); final MockProcessManager mockProcessManager = MockProcessManager();
...@@ -386,6 +490,74 @@ void main() { ...@@ -386,6 +490,74 @@ void main() {
expect(() => processManager.runSync(<String>['foo']), expect(() => processManager.runSync(<String>['foo']),
throwsToolExit(message: expectedMessage)); throwsToolExit(message: expectedMessage));
}); });
test('when permissions are denied', () {
final MockProcessManager mockProcessManager = MockProcessManager();
final ProcessManager processManager = ErrorHandlingProcessManager(
delegate: mockProcessManager,
platform: linuxPlatform,
);
setupProcessManagerMocks(mockProcessManager, eacces);
const String expectedMessage = 'The flutter tool cannot access the file';
expect(() => processManager.canRun('foo'),
throwsToolExit(message: expectedMessage));
expect(() => processManager.killPid(1),
throwsToolExit(message: expectedMessage));
expect(() async => await processManager.start(<String>['foo']),
throwsToolExit(message: expectedMessage));
expect(() async => await processManager.run(<String>['foo']),
throwsToolExit(message: expectedMessage));
expect(() => processManager.runSync(<String>['foo']),
throwsToolExit(message: expectedMessage));
});
});
group('ProcessManager on macOS throws tool exit', () {
const int enospc = 28;
const int eacces = 13;
test('when writing to a full device', () {
final MockProcessManager mockProcessManager = MockProcessManager();
final ProcessManager processManager = ErrorHandlingProcessManager(
delegate: mockProcessManager,
platform: macOSPlatform,
);
setupProcessManagerMocks(mockProcessManager, enospc);
const String expectedMessage = 'The target device is full';
expect(() => processManager.canRun('foo'),
throwsToolExit(message: expectedMessage));
expect(() => processManager.killPid(1),
throwsToolExit(message: expectedMessage));
expect(() async => await processManager.start(<String>['foo']),
throwsToolExit(message: expectedMessage));
expect(() async => await processManager.run(<String>['foo']),
throwsToolExit(message: expectedMessage));
expect(() => processManager.runSync(<String>['foo']),
throwsToolExit(message: expectedMessage));
});
test('when permissions are denied', () {
final MockProcessManager mockProcessManager = MockProcessManager();
final ProcessManager processManager = ErrorHandlingProcessManager(
delegate: mockProcessManager,
platform: linuxPlatform,
);
setupProcessManagerMocks(mockProcessManager, eacces);
const String expectedMessage = 'The flutter tool cannot access the file';
expect(() => processManager.canRun('foo'),
throwsToolExit(message: expectedMessage));
expect(() => processManager.killPid(1),
throwsToolExit(message: expectedMessage));
expect(() async => await processManager.start(<String>['foo']),
throwsToolExit(message: expectedMessage));
expect(() async => await processManager.run(<String>['foo']),
throwsToolExit(message: expectedMessage));
expect(() => processManager.runSync(<String>['foo']),
throwsToolExit(message: expectedMessage));
});
}); });
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment