Unverified Commit 304448af authored by Jonah Williams's avatar Jonah Williams Committed by GitHub

[flutter_tools] consume package:process exceptions (#79657)

parent d60cc088
......@@ -533,6 +533,8 @@ class ErrorHandlingLink
String toString() => delegate.toString();
}
const String _kNoExecutableFound = 'The Flutter tool could not locate an executable with suitable permissions';
Future<T> _run<T>(Future<T> Function() op, {
required Platform platform,
String? failureMessage,
......@@ -540,6 +542,11 @@ Future<T> _run<T>(Future<T> Function() op, {
assert(platform != null);
try {
return await op();
} on ProcessPackageExecutableNotFoundException catch (e) {
if (e.candidates.isNotEmpty) {
throwToolExit('$_kNoExecutableFound: $e');
}
rethrow;
} on FileSystemException catch (e) {
if (platform.isWindows) {
_handleWindowsException(e, failureMessage, e.osError?.errorCode ?? 0);
......@@ -564,6 +571,11 @@ T _runSync<T>(T Function() op, {
assert(platform != null);
try {
return op();
} on ProcessPackageExecutableNotFoundException catch (e) {
if (e.candidates.isNotEmpty) {
throwToolExit('$_kNoExecutableFound: $e');
}
rethrow;
} on FileSystemException catch (e) {
if (platform.isWindows) {
_handleWindowsException(e, failureMessage, e.osError?.errorCode ?? 0);
......@@ -581,69 +593,6 @@ T _runSync<T>(T Function() op, {
}
}
class _ProcessDelegate {
const _ProcessDelegate();
Future<io.Process> start(
List<String> command, {
String? workingDirectory,
Map<String, String>? environment,
bool includeParentEnvironment = true,
bool runInShell = false,
io.ProcessStartMode mode = io.ProcessStartMode.normal,
}) {
return io.Process.start(
command[0],
command.skip(1).toList(),
workingDirectory: workingDirectory,
environment: environment,
includeParentEnvironment: includeParentEnvironment,
runInShell: runInShell,
);
}
Future<io.ProcessResult> run(
List<String> command, {
String? workingDirectory,
Map<String, String>? environment,
bool includeParentEnvironment = true,
bool runInShell = false,
Encoding stdoutEncoding = io.systemEncoding,
Encoding stderrEncoding = io.systemEncoding,
}) {
return io.Process.run(
command[0],
command.skip(1).toList(),
workingDirectory: workingDirectory,
environment: environment,
includeParentEnvironment: includeParentEnvironment,
runInShell: runInShell,
stdoutEncoding: stdoutEncoding,
stderrEncoding: stderrEncoding,
);
}
io.ProcessResult runSync(
List<String> command, {
String? workingDirectory,
Map<String, String>? environment,
bool includeParentEnvironment = true,
bool runInShell = false,
Encoding stdoutEncoding = io.systemEncoding,
Encoding stderrEncoding = io.systemEncoding,
}) {
return io.Process.runSync(
command[0],
command.skip(1).toList(),
workingDirectory: workingDirectory,
environment: environment,
includeParentEnvironment: includeParentEnvironment,
runInShell: runInShell,
stdoutEncoding: stdoutEncoding,
stderrEncoding: stderrEncoding,
);
}
}
/// A [ProcessManager] that throws a [ToolExit] on certain errors.
///
......@@ -662,21 +611,6 @@ class ErrorHandlingProcessManager extends ProcessManager {
final ProcessManager _delegate;
final Platform _platform;
static const _ProcessDelegate _processDelegate = _ProcessDelegate();
static bool _skipCommandLookup = false;
/// Bypass package:process command lookup for all functions in this block.
///
/// This required that the fully resolved executable path is provided.
static Future<T> skipCommandLookup<T>(Future<T> Function() operation) async {
final bool previousValue = ErrorHandlingProcessManager._skipCommandLookup;
try {
ErrorHandlingProcessManager._skipCommandLookup = true;
return await operation();
} finally {
ErrorHandlingProcessManager._skipCommandLookup = previousValue;
}
}
@override
bool canRun(dynamic executable, {String? workingDirectory}) {
......@@ -705,17 +639,6 @@ class ErrorHandlingProcessManager extends ProcessManager {
Encoding stderrEncoding = io.systemEncoding,
}) {
return _run(() {
if (_skipCommandLookup && _delegate is LocalProcessManager) {
return _processDelegate.run(
command.cast<String>(),
workingDirectory: workingDirectory,
environment: environment,
includeParentEnvironment: includeParentEnvironment,
runInShell: runInShell,
stdoutEncoding: stdoutEncoding,
stderrEncoding: stderrEncoding,
);
}
return _delegate.run(
command,
workingDirectory: workingDirectory,
......@@ -738,15 +661,6 @@ class ErrorHandlingProcessManager extends ProcessManager {
io.ProcessStartMode mode = io.ProcessStartMode.normal,
}) {
return _run(() {
if (_skipCommandLookup && _delegate is LocalProcessManager) {
return _processDelegate.start(
command.cast<String>(),
workingDirectory: workingDirectory,
environment: environment,
includeParentEnvironment: includeParentEnvironment,
runInShell: runInShell,
);
}
return _delegate.start(
command,
workingDirectory: workingDirectory,
......@@ -768,17 +682,6 @@ class ErrorHandlingProcessManager extends ProcessManager {
Encoding stderrEncoding = io.systemEncoding,
}) {
return _runSync(() {
if (_skipCommandLookup && _delegate is LocalProcessManager) {
return _processDelegate.runSync(
command.cast<String>(),
workingDirectory: workingDirectory,
environment: environment,
includeParentEnvironment: includeParentEnvironment,
runInShell: runInShell,
stdoutEncoding: stdoutEncoding,
stderrEncoding: stderrEncoding,
);
}
return _delegate.runSync(
command,
workingDirectory: workingDirectory,
......
......@@ -11,7 +11,6 @@ import 'package:process/process.dart';
import '../base/bot_detector.dart';
import '../base/common.dart';
import '../base/context.dart';
import '../base/error_handling_io.dart';
import '../base/file_system.dart';
import '../base/io.dart' as io;
import '../base/logger.dart';
......@@ -334,13 +333,11 @@ class _DefaultPub implements Pub {
bool generateSyntheticPackage = false,
}) async {
// Fully resolved pub or pub.bat is calculated based on current platform.
final io.Process process = await ErrorHandlingProcessManager.skipCommandLookup(() async {
return _processUtils.start(
final io.Process process = await _processUtils.start(
_pubCommand(arguments),
workingDirectory: directory,
environment: await _createPubEnvironment(PubContext.interactive),
);
});
// Pipe the Flutter tool stdin to the pub stdin.
unawaited(process.stdin.addStream(stdio.stdin)
......
......@@ -684,24 +684,46 @@ void main() {
});
});
test('skipCommandLookup invokes Process calls directly', () async {
final ErrorHandlingProcessManager processManager = ErrorHandlingProcessManager(
delegate: const LocalProcessManager(),
group('ProcessManager on windows throws tool exit', () {
const int kDeviceFull = 112;
const int kUserMappedSectionOpened = 1224;
const int kUserPermissionDenied = 5;
test('when PackageProcess throws an exception containg non-executable bits', () {
final FakeProcessManager fakeProcessManager = FakeProcessManager.list(<FakeCommand>[
const FakeCommand(command: <String>['foo'], exception: ProcessPackageExecutableNotFoundException('', candidates: <String>['not-empty'])),
const FakeCommand(command: <String>['foo'], exception: ProcessPackageExecutableNotFoundException('', candidates: <String>['not-empty'])),
]);
final ProcessManager processManager = ErrorHandlingProcessManager(
delegate: fakeProcessManager,
platform: windowsPlatform,
);
// Throws process exception because the executable does not exist.
await ErrorHandlingProcessManager.skipCommandLookup<void>(() async {
expect(() => processManager.runSync(<String>['foo']), throwsA(isA<ProcessException>()));
expect(() => processManager.run(<String>['foo']), throwsA(isA<ProcessException>()));
expect(() => processManager.start(<String>['foo']), throwsA(isA<ProcessException>()));
});
const String expectedMessage = 'The Flutter tool could not locate an executable with suitable permissions';
expect(() async => processManager.start(<String>['foo']),
throwsToolExit(message: expectedMessage));
expect(() async => processManager.runSync(<String>['foo']),
throwsToolExit(message: expectedMessage));
});
group('ProcessManager on windows throws tool exit', () {
const int kDeviceFull = 112;
const int kUserMappedSectionOpened = 1224;
const int kUserPermissionDenied = 5;
test('when PackageProcess throws an exception without containing non-executable bits', () {
final FakeProcessManager fakeProcessManager = FakeProcessManager.list(<FakeCommand>[
const FakeCommand(command: <String>['foo'], exception: ProcessPackageExecutableNotFoundException('', candidates: <String>[])),
const FakeCommand(command: <String>['foo'], exception: ProcessPackageExecutableNotFoundException('', candidates: <String>[])),
]);
final ProcessManager processManager = ErrorHandlingProcessManager(
delegate: fakeProcessManager,
platform: windowsPlatform,
);
// If there were no located executables treat this as a programming error and rethrow the original
// exception.
expect(() async => processManager.start(<String>['foo']), throwsProcessException());
expect(() async => processManager.runSync(<String>['foo']), throwsProcessException());
});
test('when the device is full', () {
final FakeProcessManager fakeProcessManager = FakeProcessManager.list(<FakeCommand>[
......
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