blob: bfa6478613c61ffa185d010b1b18cfdcfcff5dcb [file] [log] [blame]
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// 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';
void main() {
group(FakeProcess, () {
testWithoutContext('exits with specified exit code', () async {
final FakeProcess process = FakeProcess(exitCode: 42);
expect(await process.exitCode, 42);
});
testWithoutContext('exits with specified stderr, stdout', () async {
final FakeProcess process = FakeProcess(
stderr: 'stderr\u{FFFD}'.codeUnits,
stdout: 'stdout\u{FFFD}'.codeUnits,
);
await process.exitCode;
// Verify that no encoding changes have been applied to output.
//
// In the past, we had hardcoded UTF-8 encoding for these streams in
// FakeProcess. When a specific encoding is desired, it can be specified
// on FakeCommand or in the encoding parameter of FakeProcessManager.run
// or FakeProcessManager.runAsync.
expect((await process.stderr.toList()).expand((List<int> x) => x), 'stderr\u{FFFD}'.codeUnits);
expect((await process.stdout.toList()).expand((List<int> x) => x), 'stdout\u{FFFD}'.codeUnits);
});
testWithoutContext('exits after specified delay (if no completer specified)', () {
final bool done = FakeAsync().run<bool>((FakeAsync time) {
final FakeProcess process = FakeProcess(
duration: const Duration(seconds: 30),
);
bool hasExited = false;
unawaited(process.exitCode.then((int _) { hasExited = true; }));
// Verify process hasn't exited before specified delay.
time.elapse(const Duration(seconds: 15));
expect(hasExited, isFalse);
// Verify process has exited after specified delay.
time.elapse(const Duration(seconds: 20));
expect(hasExited, isTrue);
return true;
});
expect(done, isTrue);
});
testWithoutContext('exits when completer completes (if no duration specified)', () {
final bool done = FakeAsync().run<bool>((FakeAsync time) {
final Completer<void> completer = Completer<void>();
final FakeProcess process = FakeProcess(
completer: completer,
);
bool hasExited = false;
unawaited(process.exitCode.then((int _) { hasExited = true; }));
// Verify process hasn't exited when all async tasks flushed.
time.elapse(Duration.zero);
expect(hasExited, isFalse);
// Verify process has exited after completer completes.
completer.complete();
time.flushMicrotasks();
expect(hasExited, isTrue);
return true;
});
expect(done, isTrue);
});
testWithoutContext('when completer and duration are specified, does not exit until completer is completed', () {
final bool done = FakeAsync().run<bool>((FakeAsync time) {
final Completer<void> completer = Completer<void>();
final FakeProcess process = FakeProcess(
duration: const Duration(seconds: 30),
completer: completer,
);
bool hasExited = false;
unawaited(process.exitCode.then((int _) { hasExited = true; }));
// Verify process hasn't exited before specified delay.
time.elapse(const Duration(seconds: 15));
expect(hasExited, isFalse);
// Verify process hasn't exited until the completer completes.
time.elapse(const Duration(seconds: 20));
expect(hasExited, isFalse);
// Verify process exits after the completer completes.
completer.complete();
time.flushMicrotasks();
expect(hasExited, isTrue);
return true;
});
expect(done, isTrue);
});
testWithoutContext('when completer and duration are specified, does not exit until duration has elapsed', () {
final bool done = FakeAsync().run<bool>((FakeAsync time) {
final Completer<void> completer = Completer<void>();
final FakeProcess process = FakeProcess(
duration: const Duration(seconds: 30),
completer: completer,
);
bool hasExited = false;
unawaited(process.exitCode.then((int _) { hasExited = true; }));
// Verify process hasn't exited before specified delay.
time.elapse(const Duration(seconds: 15));
expect(hasExited, isFalse);
// Verify process does not exit until duration has elapsed.
completer.complete();
expect(hasExited, isFalse);
// Verify process exits after the duration elapses.
time.elapse(const Duration(seconds: 20));
expect(hasExited, isTrue);
return true;
});
expect(done, isTrue);
});
testWithoutContext('process exit is asynchronous', () async {
final FakeProcess process = FakeProcess();
bool hasExited = false;
unawaited(process.exitCode.then((int _) { hasExited = true; }));
// Verify process hasn't completed.
expect(hasExited, isFalse);
// Flush async tasks. Verify process completes.
await Future<void>.delayed(Duration.zero);
expect(hasExited, isTrue);
});
testWithoutContext('stderr, stdout stream data after exit when outputFollowsExit is true', () async {
final FakeProcess process = FakeProcess(
stderr: 'stderr'.codeUnits,
stdout: 'stdout'.codeUnits,
outputFollowsExit: true,
);
final List<int> stderr = <int>[];
final List<int> stdout = <int>[];
process.stderr.listen(stderr.addAll);
process.stdout.listen(stdout.addAll);
// Ensure that no bytes have been received at process exit.
await process.exitCode;
expect(stderr, isEmpty);
expect(stdout, isEmpty);
// Flush all remaining async work. Ensure stderr, stdout is received.
await Future<void>.delayed(Duration.zero);
expect(stderr, 'stderr'.codeUnits);
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>());
});
});
});
}