Commit d9f071fd authored by Zachary Anderson's avatar Zachary Anderson Committed by Flutter GitHub Bot

[flutter_tool] Don't crash when writing to pub stdin fails (#49323)

parent 7faf2d73
...@@ -308,13 +308,25 @@ class _DefaultPub implements Pub { ...@@ -308,13 +308,25 @@ class _DefaultPub implements Pub {
); );
// Pipe the Flutter tool stdin to the pub stdin. // Pipe the Flutter tool stdin to the pub stdin.
unawaited(process.stdin.addStream(io.stdin)); unawaited(process.stdin.addStream(io.stdin)
// If pub exits unexpectedly with an error, that will be reported below
// Pipe the put stdout and stderr to the tool stdout and stderr. // by the tool exit after the exit code check.
await Future.wait<dynamic>(<Future<dynamic>>[ .catchError((dynamic err, StackTrace stack) {
io.stdout.addStream(process.stdout), globals.printTrace('Echoing stdin to the pub subprocess failed:');
io.stderr.addStream(process.stderr), globals.printTrace('$err\n$stack');
]); }
));
// Pipe the pub stdout and stderr to the tool stdout and stderr.
try {
await Future.wait<dynamic>(<Future<dynamic>>[
io.stdout.addStream(process.stdout),
io.stderr.addStream(process.stderr),
]);
} catch (err, stack) {
globals.printTrace('Echoing stdout or stderr from the pub subprocess failed:');
globals.printTrace('$err\n$stack');
}
// Wait for pub to exit. // Wait for pub to exit.
final int code = await process.exitCode; final int code = await process.exitCode;
......
...@@ -424,6 +424,27 @@ void main() { ...@@ -424,6 +424,27 @@ void main() {
Pub: () => const Pub(), Pub: () => const Pub(),
}); });
testUsingContext('pub publish input fails', () async {
final PromptingProcess process = PromptingProcess(stdinError: true);
mockProcessManager.processFactory = (List<String> commands) => process;
final Future<void> runPackages = createTestCommandRunner(PackagesCommand()).run(<String>['pub', 'publish']);
final Future<void> runPrompt = process.showPrompt('Proceed (y/n)? ', <String>['hello', 'world']);
final Future<void> simulateUserInput = Future<void>(() {
mockStdio.simulateStdin('y');
});
await Future.wait<void>(<Future<void>>[runPackages, runPrompt, simulateUserInput]);
final List<String> commands = mockProcessManager.commands;
expect(commands, hasLength(2));
expect(commands[0], matches(r'dart-sdk[\\/]bin[\\/]pub'));
expect(commands[1], 'publish');
// We get a trace message about the write to stdin failing.
expect(testLogger.traceText, contains('Echoing stdin to the pub subprocess failed'));
}, overrides: <Type, Generator>{
ProcessManager: () => mockProcessManager,
Stdio: () => mockStdio,
Pub: () => const Pub(),
});
testUsingContext('publish', () async { testUsingContext('publish', () async {
await createTestCommandRunner(PackagesCommand()).run(<String>['pub', 'publish']); await createTestCommandRunner(PackagesCommand()).run(<String>['pub', 'publish']);
final List<String> commands = mockProcessManager.commands; final List<String> commands = mockProcessManager.commands;
......
...@@ -310,21 +310,28 @@ class FakeProcess implements Process { ...@@ -310,21 +310,28 @@ class FakeProcess implements Process {
/// A process that prompts the user to proceed, then asynchronously writes /// A process that prompts the user to proceed, then asynchronously writes
/// some lines to stdout before it exits. /// some lines to stdout before it exits.
class PromptingProcess implements Process { class PromptingProcess implements Process {
PromptingProcess({
bool stdinError = false,
}) : _stdin = CompleterIOSink(throwOnAdd: stdinError);
Future<void> showPrompt(String prompt, List<String> outputLines) async { Future<void> showPrompt(String prompt, List<String> outputLines) async {
_stdoutController.add(utf8.encode(prompt)); try {
final List<int> bytesOnStdin = await _stdin.future; _stdoutController.add(utf8.encode(prompt));
// Echo stdin to stdout. final List<int> bytesOnStdin = await _stdin.future;
_stdoutController.add(bytesOnStdin); // Echo stdin to stdout.
if (bytesOnStdin[0] == utf8.encode('y')[0]) { _stdoutController.add(bytesOnStdin);
for (final String line in outputLines) { if (bytesOnStdin.isNotEmpty && bytesOnStdin[0] == utf8.encode('y')[0]) {
_stdoutController.add(utf8.encode('$line\n')); for (final String line in outputLines) {
_stdoutController.add(utf8.encode('$line\n'));
}
} }
} finally {
await _stdoutController.close();
} }
await _stdoutController.close();
} }
final StreamController<List<int>> _stdoutController = StreamController<List<int>>(); final StreamController<List<int>> _stdoutController = StreamController<List<int>>();
final CompleterIOSink _stdin = CompleterIOSink(); final CompleterIOSink _stdin;
@override @override
Stream<List<int>> get stdout => _stdoutController.stream; Stream<List<int>> get stdout => _stdoutController.stream;
...@@ -347,6 +354,12 @@ class PromptingProcess implements Process { ...@@ -347,6 +354,12 @@ class PromptingProcess implements Process {
/// An IOSink that completes a future with the first line written to it. /// An IOSink that completes a future with the first line written to it.
class CompleterIOSink extends MemoryIOSink { class CompleterIOSink extends MemoryIOSink {
CompleterIOSink({
this.throwOnAdd = false,
});
final bool throwOnAdd;
final Completer<List<int>> _completer = Completer<List<int>>(); final Completer<List<int>> _completer = Completer<List<int>>();
Future<List<int>> get future => _completer.future; Future<List<int>> get future => _completer.future;
...@@ -354,7 +367,12 @@ class CompleterIOSink extends MemoryIOSink { ...@@ -354,7 +367,12 @@ class CompleterIOSink extends MemoryIOSink {
@override @override
void add(List<int> data) { void add(List<int> data) {
if (!_completer.isCompleted) { if (!_completer.isCompleted) {
_completer.complete(data); // When throwOnAdd is true, complete with empty so any expected output
// doesn't appear.
_completer.complete(throwOnAdd ? <int>[] : data);
}
if (throwOnAdd) {
throw 'CompleterIOSink Error';
} }
super.add(data); super.add(data);
} }
...@@ -375,9 +393,20 @@ class MemoryIOSink implements IOSink { ...@@ -375,9 +393,20 @@ class MemoryIOSink implements IOSink {
@override @override
Future<void> addStream(Stream<List<int>> stream) { Future<void> addStream(Stream<List<int>> stream) {
final Completer<void> completer = Completer<void>(); final Completer<void> completer = Completer<void>();
stream.listen((List<int> data) { StreamSubscription<List<int>> sub;
add(data); sub = stream.listen(
}).onDone(() => completer.complete()); (List<int> data) {
try {
add(data);
} catch (err, stack) {
sub.cancel();
completer.completeError(err, stack);
}
},
onError: completer.completeError,
onDone: completer.complete,
cancelOnError: true,
);
return completer.future; return completer.future;
} }
......
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