Unverified Commit f0e832cd authored by Zachary Anderson's avatar Zachary Anderson Committed by GitHub

[flutter_tool] Fix DesktopLogReader to capture all output (#88116)

parent 5848a162
...@@ -308,9 +308,25 @@ class DesktopLogReader extends DeviceLogReader { ...@@ -308,9 +308,25 @@ class DesktopLogReader extends DeviceLogReader {
/// Begin listening to the stdout and stderr streams of the provided [process]. /// Begin listening to the stdout and stderr streams of the provided [process].
void initializeProcess(Process process) { void initializeProcess(Process process) {
process.stdout.listen(_inputController.add); final StreamSubscription<List<int>> stdoutSub = process.stdout.listen(
process.stderr.listen(_inputController.add); _inputController.add,
process.exitCode.whenComplete(_inputController.close); );
final StreamSubscription<List<int>> stderrSub = process.stderr.listen(
_inputController.add,
);
process.exitCode.whenComplete(() async {
// Wait for output to be fully processed.
await Future.wait<void>(<Future<void>>[
stdoutSub.asFuture<void>(),
stderrSub.asFuture<void>(),
]);
// The streams as futures have already completed, so waiting for the
// potentially async stream cancellation to complete likely has no
// benefit.
unawaited(stdoutSub.cancel());
unawaited(stderrSub.cancel());
await _inputController.close();
});
} }
@override @override
......
...@@ -258,7 +258,7 @@ void main() { ...@@ -258,7 +258,7 @@ void main() {
FakeCommand( FakeCommand(
command: const <String>['debug', 'arg1', 'arg2'], command: const <String>['debug', 'arg1', 'arg2'],
stdout: 'Observatory listening on http://127.0.0.1/0\n', stdout: 'Observatory listening on http://127.0.0.1/0\n',
completer: completer completer: completer,
), ),
]); ]);
final FakeDesktopDevice device = setUpDesktopDevice(processManager: processManager); final FakeDesktopDevice device = setUpDesktopDevice(processManager: processManager);
...@@ -268,12 +268,49 @@ void main() { ...@@ -268,12 +268,49 @@ void main() {
prebuiltApplication: true, prebuiltApplication: true,
debuggingOptions: DebuggingOptions.enabled( debuggingOptions: DebuggingOptions.enabled(
BuildInfo.debug, BuildInfo.debug,
dartEntrypointArgs: <String>['arg1', 'arg2'] dartEntrypointArgs: <String>['arg1', 'arg2'],
), ),
); );
expect(result.started, true); expect(result.started, true);
}); });
testWithoutContext('Device logger captues all output', () async {
final Completer<void> exitCompleter = Completer<void>();
final FakeProcessManager processManager = FakeProcessManager.list(<FakeCommand>[
FakeCommand(
command: const <String>['debug', 'arg1', 'arg2'],
exitCode: -1,
stderr: 'Oops\n',
completer: exitCompleter,
outputFollowsExit: true,
),
]);
final FakeDesktopDevice device = setUpDesktopDevice(
processManager: processManager,
);
final Completer<void> testCompleter = Completer<void>();
final List<String> logOutput = <String>[];
device.getLogReader().logLines.listen((String line) {
logOutput.add(line);
}, onDone: () {
expect(logOutput, contains('Oops'));
testCompleter.complete();
});
unawaited(Future<void>(() {
exitCompleter.complete();
}));
final FakeApplicationPackage package = FakeApplicationPackage();
await device.startApp(
package,
prebuiltApplication: true,
debuggingOptions: DebuggingOptions.enabled(
BuildInfo.debug,
dartEntrypointArgs: <String>['arg1', 'arg2'],
),
);
await testCompleter.future;
});
} }
FakeDesktopDevice setUpDesktopDevice({ FakeDesktopDevice setUpDesktopDevice({
......
...@@ -62,8 +62,8 @@ void main() { ...@@ -62,8 +62,8 @@ void main() {
processManager: FakeProcessManager.list(<FakeCommand>[ processManager: FakeProcessManager.list(<FakeCommand>[
FakeCommand( FakeCommand(
command: const <String>['release/executable'], command: const <String>['release/executable'],
stdout: 'Hello World', stdout: 'Hello World\n',
stderr: 'Goodnight, Moon', stderr: 'Goodnight, Moon\n',
completer: completer, completer: completer,
) )
]), ]),
...@@ -82,7 +82,7 @@ void main() { ...@@ -82,7 +82,7 @@ void main() {
final DeviceLogReader logReader = device.getLogReader(app: package); final DeviceLogReader logReader = device.getLogReader(app: package);
expect(logReader.logLines, emits('Hello WorldGoodnight, Moon')); expect(logReader.logLines, emitsInAnyOrder(<String>['Hello World', 'Goodnight, Moon']));
completer.complete(); completer.complete();
}); });
......
...@@ -32,6 +32,7 @@ class FakeCommand { ...@@ -32,6 +32,7 @@ class FakeCommand {
this.completer, this.completer,
this.stdin, this.stdin,
this.exception, this.exception,
this.outputFollowsExit = false,
}) : assert(command != null), }) : assert(command != null),
assert(duration != null), assert(duration != null),
assert(exitCode != null); assert(exitCode != null);
...@@ -99,6 +100,10 @@ class FakeCommand { ...@@ -99,6 +100,10 @@ class FakeCommand {
/// If provided, this exception will be thrown when the fake command is run. /// If provided, this exception will be thrown when the fake command is run.
final Object? exception; final Object? exception;
/// Indicates that output will only be emitted after the `exitCode` [Future]
/// on [io.Process] completes.
final bool outputFollowsExit;
void _matches( void _matches(
List<String> command, List<String> command,
String? workingDirectory, String? workingDirectory,
...@@ -127,19 +132,35 @@ class _FakeProcess implements io.Process { ...@@ -127,19 +132,35 @@ class _FakeProcess implements io.Process {
IOSink? stdin, IOSink? stdin,
this._stdout, this._stdout,
this._completer, this._completer,
bool outputFollowsExit,
) : exitCode = Future<void>.delayed(duration).then((void value) { ) : exitCode = Future<void>.delayed(duration).then((void value) {
if (_completer != null) { if (_completer != null) {
return _completer.future.then((void _) => _exitCode); return _completer.future.then((void _) => _exitCode);
} }
return _exitCode; return _exitCode;
}), }),
stdin = stdin ?? IOSink(StreamController<List<int>>().sink), stdin = stdin ?? IOSink(StreamController<List<int>>().sink)
stderr = _stderr == null {
? const Stream<List<int>>.empty() if (_stderr == null) {
: Stream<List<int>>.value(utf8.encode(_stderr)), stderr = const Stream<List<int>>.empty();
stdout = _stdout == null } else if (outputFollowsExit) {
? const Stream<List<int>>.empty() stderr = Stream<List<int>>.fromFuture(exitCode.then((_) {
: Stream<List<int>>.value(utf8.encode(_stdout)); return Future<List<int>>(() => utf8.encode(_stderr));
}));
} else {
stderr = Stream<List<int>>.value(utf8.encode(_stderr));
}
if (_stdout == null) {
stdout = const Stream<List<int>>.empty();
} else if (outputFollowsExit) {
stdout = Stream<List<int>>.fromFuture(exitCode.then((_) {
return Future<List<int>>(() => utf8.encode(_stdout));
}));
} else {
stdout = Stream<List<int>>.value(utf8.encode(_stdout));
}
}
final int _exitCode; final int _exitCode;
final Completer<void>? _completer; final Completer<void>? _completer;
...@@ -153,13 +174,13 @@ class _FakeProcess implements io.Process { ...@@ -153,13 +174,13 @@ class _FakeProcess implements io.Process {
final String _stderr; final String _stderr;
@override @override
final Stream<List<int>> stderr; late final Stream<List<int>> stderr;
@override @override
final IOSink stdin; final IOSink stdin;
@override @override
final Stream<List<int>> stdout; late final Stream<List<int>> stdout;
final String _stdout; final String _stdout;
...@@ -250,6 +271,7 @@ abstract class FakeProcessManager implements ProcessManager { ...@@ -250,6 +271,7 @@ abstract class FakeProcessManager implements ProcessManager {
fakeCommand.stdin, fakeCommand.stdin,
fakeCommand.stdout, fakeCommand.stdout,
fakeCommand.completer, fakeCommand.completer,
fakeCommand.outputFollowsExit,
); );
} }
...@@ -350,6 +372,7 @@ class _FakeAnyProcessManager extends FakeProcessManager { ...@@ -350,6 +372,7 @@ class _FakeAnyProcessManager extends FakeProcessManager {
exitCode: 0, exitCode: 0,
stdout: '', stdout: '',
stderr: '', stderr: '',
outputFollowsExit: false,
); );
} }
......
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