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

This commit is contained in:
Zachary Anderson 2021-08-12 14:57:07 -07:00 committed by GitHub
parent 5848a1620d
commit f0e832cdea
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 93 additions and 17 deletions

View file

@ -308,9 +308,25 @@ class DesktopLogReader extends DeviceLogReader {
/// Begin listening to the stdout and stderr streams of the provided [process].
void initializeProcess(Process process) {
process.stdout.listen(_inputController.add);
process.stderr.listen(_inputController.add);
process.exitCode.whenComplete(_inputController.close);
final StreamSubscription<List<int>> stdoutSub = process.stdout.listen(
_inputController.add,
);
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

View file

@ -258,7 +258,7 @@ void main() {
FakeCommand(
command: const <String>['debug', 'arg1', 'arg2'],
stdout: 'Observatory listening on http://127.0.0.1/0\n',
completer: completer
completer: completer,
),
]);
final FakeDesktopDevice device = setUpDesktopDevice(processManager: processManager);
@ -268,12 +268,49 @@ void main() {
prebuiltApplication: true,
debuggingOptions: DebuggingOptions.enabled(
BuildInfo.debug,
dartEntrypointArgs: <String>['arg1', 'arg2']
dartEntrypointArgs: <String>['arg1', 'arg2'],
),
);
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({

View file

@ -62,8 +62,8 @@ void main() {
processManager: FakeProcessManager.list(<FakeCommand>[
FakeCommand(
command: const <String>['release/executable'],
stdout: 'Hello World',
stderr: 'Goodnight, Moon',
stdout: 'Hello World\n',
stderr: 'Goodnight, Moon\n',
completer: completer,
)
]),
@ -82,7 +82,7 @@ void main() {
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();
});

View file

@ -32,6 +32,7 @@ class FakeCommand {
this.completer,
this.stdin,
this.exception,
this.outputFollowsExit = false,
}) : assert(command != null),
assert(duration != null),
assert(exitCode != null);
@ -99,6 +100,10 @@ class FakeCommand {
/// If provided, this exception will be thrown when the fake command is run.
final Object? exception;
/// Indicates that output will only be emitted after the `exitCode` [Future]
/// on [io.Process] completes.
final bool outputFollowsExit;
void _matches(
List<String> command,
String? workingDirectory,
@ -127,19 +132,35 @@ class _FakeProcess implements io.Process {
IOSink? stdin,
this._stdout,
this._completer,
bool outputFollowsExit,
) : exitCode = Future<void>.delayed(duration).then((void value) {
if (_completer != null) {
return _completer.future.then((void _) => _exitCode);
}
return _exitCode;
}),
stdin = stdin ?? IOSink(StreamController<List<int>>().sink),
stderr = _stderr == null
? const Stream<List<int>>.empty()
: Stream<List<int>>.value(utf8.encode(_stderr)),
stdout = _stdout == null
? const Stream<List<int>>.empty()
: Stream<List<int>>.value(utf8.encode(_stdout));
stdin = stdin ?? IOSink(StreamController<List<int>>().sink)
{
if (_stderr == null) {
stderr = const Stream<List<int>>.empty();
} else if (outputFollowsExit) {
stderr = Stream<List<int>>.fromFuture(exitCode.then((_) {
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 Completer<void>? _completer;
@ -153,13 +174,13 @@ class _FakeProcess implements io.Process {
final String _stderr;
@override
final Stream<List<int>> stderr;
late final Stream<List<int>> stderr;
@override
final IOSink stdin;
@override
final Stream<List<int>> stdout;
late final Stream<List<int>> stdout;
final String _stdout;
@ -250,6 +271,7 @@ abstract class FakeProcessManager implements ProcessManager {
fakeCommand.stdin,
fakeCommand.stdout,
fakeCommand.completer,
fakeCommand.outputFollowsExit,
);
}
@ -350,6 +372,7 @@ class _FakeAnyProcessManager extends FakeProcessManager {
exitCode: 0,
stdout: '',
stderr: '',
outputFollowsExit: false,
);
}