Skip to content

Commit

Permalink
[tool] Add tests for FakeProcessManager (#104456)
Browse files Browse the repository at this point in the history
Adds a bit more clarifying documentation to the implementation of the
outputFollowsExit case, and adds tests that verify the behaviour of
stderr, stdout of processes launched via FakeProcessManager.

Specifically:
* Verifies that stderr, stdout are not emitted immediately after process
  exit if outputFollowsExit is true. They must be emitted at least one
  turn through the event loop later.
* Verifies that ProcessResult.stderr, stdout have the type documented
  according to the encoding passted to Process.run/runSync:
  * List<int> if null is passed as the encoding.
  * String (in the default system encoding) if no encoding is specified.
  * String (in the specified encoding) if an encoding is specified.

This is additional testing relating to refactoring landed in:
flutter/flutter#103947

Issue: flutter/flutter#102451
  • Loading branch information
cbracken authored May 24, 2022
1 parent 893b461 commit f9765c1
Show file tree
Hide file tree
Showing 2 changed files with 152 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@
// found in the LICENSE file.

import 'dart:async';

import 'package:fake_async/fake_async.dart';
import 'package:flutter_tools/src/base/io.dart';
import 'package:flutter_tools/src/convert.dart' show utf8;

import '../src/common.dart';
import '../src/fake_process_manager.dart';
Expand Down Expand Up @@ -172,4 +175,149 @@ void main() {
expect(stdout, 'stdout'.codeUnits);
});
});

group(FakeProcessManager, () {
late FakeProcessManager manager;

setUp(() {
manager = FakeProcessManager.empty();
});

group('start', () {
testWithoutContext('can run a fake command', () async {
manager.addCommand(const FakeCommand(command: <String>['faketool']));

final Process process = await manager.start(<String>['faketool']);
expect(await process.exitCode, 0);
expect(await utf8.decodeStream(process.stdout), isEmpty);
expect(await utf8.decodeStream(process.stderr), isEmpty);
});

testWithoutContext('outputFollowsExit delays stderr, stdout until after process exit', () async {
manager.addCommand(const FakeCommand(
command: <String>['faketool'],
stderr: 'hello',
stdout: 'world',
outputFollowsExit: true,
));

final List<int> stderrBytes = <int>[];
final List<int> stdoutBytes = <int>[];

// Start the process.
final Process process = await manager.start(<String>['faketool']);
final StreamSubscription<List<int>> stderrSubscription = process.stderr.listen((List<int> chunk) { stderrBytes.addAll(chunk); });
final StreamSubscription<List<int>> stdoutSubscription = process.stdout.listen((List<int> chunk) { stdoutBytes.addAll(chunk); });

// Immediately after exit, no output is emitted.
await process.exitCode;
expect(utf8.decode(stderrBytes), isEmpty);
expect(utf8.decode(stdoutBytes), isEmpty);

// Output is emitted asynchronously after process exit.
await Future.wait(<Future<void>>[
stderrSubscription.asFuture(),
stdoutSubscription.asFuture(),
]);
expect(utf8.decode(stderrBytes), 'hello');
expect(utf8.decode(stdoutBytes), 'world');

// Clean up stream subscriptions.
await stderrSubscription.cancel();
await stdoutSubscription.cancel();
});
});

group('run', () {
testWithoutContext('can run a fake command', () async {
manager.addCommand(const FakeCommand(command: <String>['faketool']));

final ProcessResult result = await manager.run(<String>['faketool']);
expect(result.exitCode, 0);
expect(result.stdout, isEmpty);
expect(result.stderr, isEmpty);
});

testWithoutContext('stderr, stdout are String if encoding is unspecified', () async {
manager.addCommand(const FakeCommand(command: <String>['faketool']));

final ProcessResult result = await manager.run(<String>['faketool']);
expect(result.exitCode, 0);
expect(result.stdout, isA<String>());
expect(result.stderr, isA<String>());
});

testWithoutContext('stderr, stdout are List<int> if encoding is null', () async {
manager.addCommand(const FakeCommand(command: <String>['faketool']));

final ProcessResult result = await manager.run(
<String>['faketool'],
stderrEncoding: null,
stdoutEncoding: null,
);
expect(result.exitCode, 0);
expect(result.stdout, isA<List<int>>());
expect(result.stderr, isA<List<int>>());
});

testWithoutContext('stderr, stdout are String if encoding is specified', () async {
manager.addCommand(const FakeCommand(command: <String>['faketool']));

final ProcessResult result = await manager.run(
<String>['faketool'],
stderrEncoding: utf8,
stdoutEncoding: utf8,
);
expect(result.exitCode, 0);
expect(result.stdout, isA<String>());
expect(result.stderr, isA<String>());
});
});

group('runSync', () {
testWithoutContext('can run a fake command', () {
manager.addCommand(const FakeCommand(command: <String>['faketool']));

final ProcessResult result = manager.runSync(<String>['faketool']);
expect(result.exitCode, 0);
expect(result.stdout, isEmpty);
expect(result.stderr, isEmpty);
});

testWithoutContext('stderr, stdout are String if encoding is unspecified', () {
manager.addCommand(const FakeCommand(command: <String>['faketool']));

final ProcessResult result = manager.runSync(<String>['faketool']);
expect(result.exitCode, 0);
expect(result.stdout, isA<String>());
expect(result.stderr, isA<String>());
});

testWithoutContext('stderr, stdout are List<int> if encoding is null', () {
manager.addCommand(const FakeCommand(command: <String>['faketool']));

final ProcessResult result = manager.runSync(
<String>['faketool'],
stderrEncoding: null,
stdoutEncoding: null,
);
expect(result.exitCode, 0);
expect(result.stdout, isA<List<int>>());
expect(result.stderr, isA<List<int>>());
});

testWithoutContext('stderr, stdout are String if encoding is specified', () {
manager.addCommand(const FakeCommand(command: <String>['faketool']));

final ProcessResult result = manager.runSync(
<String>['faketool'],
stderrEncoding: utf8,
stdoutEncoding: utf8,
);
expect(result.exitCode, 0);
expect(result.stdout, isA<String>());
expect(result.stderr, isA<String>());
});
});
});
}
4 changes: 4 additions & 0 deletions packages/flutter_tools/test/src/fake_process_manager.dart
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,8 @@ class FakeProcess implements io.Process {
} else if (outputFollowsExit) {
// Wait for the process to exit before emitting stderr.
this.stderr = Stream<List<int>>.fromFuture(this.exitCode.then((_) {
// Return a Future so stderr isn't immediately available to those who
// await exitCode, but is available asynchronously later.
return Future<List<int>>(() => _stderr);
}));
} else {
Expand All @@ -169,6 +171,8 @@ class FakeProcess implements io.Process {
} else if (outputFollowsExit) {
// Wait for the process to exit before emitting stdout.
this.stdout = Stream<List<int>>.fromFuture(this.exitCode.then((_) {
// Return a Future so stdout isn't immediately available to those who
// await exitCode, but is available asynchronously later.
return Future<List<int>>(() => _stdout);
}));
} else {
Expand Down

0 comments on commit f9765c1

Please sign in to comment.