Unverified Commit bfe16a86 authored by Jonah Williams's avatar Jonah Williams Committed by GitHub

[flutter_tools] remove even more mocks (#81618)

parent 67aaa0e7
......@@ -4,21 +4,15 @@
// @dart = 2.8
import 'dart:async';
import 'package:fake_async/fake_async.dart';
import 'package:flutter_tools/src/base/io.dart';
import 'package:flutter_tools/src/base/logger.dart';
import 'package:flutter_tools/src/base/platform.dart';
import 'package:flutter_tools/src/base/process.dart';
import 'package:flutter_tools/src/base/terminal.dart';
import 'package:mockito/mockito.dart';
import '../../src/common.dart';
import '../../src/fake_process_manager.dart';
import '../../src/fakes.dart';
import '../../src/mocks.dart' show MockProcessManager,
flakyProcessFactory;
void main() {
group('process exceptions', () {
......@@ -64,66 +58,44 @@ void main() {
});
group('output formatting', () {
MockProcessManager mockProcessManager;
FakeProcessManager processManager;
ProcessUtils processUtils;
BufferLogger mockLogger;
BufferLogger logger;
setUp(() {
mockProcessManager = MockProcessManager();
mockLogger = BufferLogger(
terminal: AnsiTerminal(
stdio: FakeStdio(),
platform: FakePlatform(stdoutSupportsAnsi: false),
),
outputPreferences: OutputPreferences(wrapText: true, wrapColumn: 40),
);
processManager = FakeProcessManager.empty();
logger = BufferLogger.test();
processUtils = ProcessUtils(
processManager: mockProcessManager,
logger: mockLogger,
processManager: processManager,
logger: logger,
);
});
FakeProcess Function(List<String>) processMetaFactory(List<String> stdout, {
List<String> stderr = const <String>[],
}) {
final Stream<List<int>> stdoutStream = Stream<List<int>>.fromIterable(
stdout.map<List<int>>((String s) => s.codeUnits,
));
final Stream<List<int>> stderrStream = Stream<List<int>>.fromIterable(
stderr.map<List<int>>((String s) => s.codeUnits,
));
return (List<String> command) => FakeProcess(stdout: stdoutStream, stderr: stderrStream);
}
testWithoutContext('Command output is not wrapped.', () async {
final List<String> testString = <String>['0123456789' * 10];
mockProcessManager.processFactory = processMetaFactory(testString, stderr: testString);
processManager.addCommand(FakeCommand(
command: const <String>['command'],
stdout: testString.join(''),
stderr: testString.join(''),
));
await processUtils.stream(<String>['command']);
expect(mockLogger.statusText, equals('${testString[0]}\n'));
expect(mockLogger.errorText, equals('${testString[0]}\n'));
expect(logger.statusText, equals('${testString[0]}\n'));
expect(logger.errorText, equals('${testString[0]}\n'));
});
});
group('run', () {
const Duration delay = Duration(seconds: 2);
MockProcessManager flakyProcessManager;
FakeProcessManager fakeProcessManager;
ProcessUtils processUtils;
ProcessUtils flakyProcessUtils;
setUp(() {
// MockProcessManager has an implementation of start() that returns the
// result of processFactory.
flakyProcessManager = MockProcessManager();
fakeProcessManager = FakeProcessManager.empty();
processUtils = ProcessUtils(
processManager: fakeProcessManager,
logger: BufferLogger.test(),
);
flakyProcessUtils = ProcessUtils(
processManager: flakyProcessManager,
logger: BufferLogger.test(),
);
});
testWithoutContext(' succeeds on success', () async {
......@@ -189,68 +161,6 @@ void main() {
throwsA(isA<ProcessException>()),
);
});
testWithoutContext(' flaky process fails without retry', () async {
flakyProcessManager.processFactory = flakyProcessFactory(
flakes: 1,
delay: delay,
);
await FakeAsync().run((FakeAsync time) async {
final Duration timeout = delay + const Duration(seconds: 1);
final RunResult result = await flakyProcessUtils.run(
<String>['dummy'],
timeout: timeout,
);
time.elapse(timeout);
expect(result.exitCode, -9);
});
}, skip: true); // TODO(jonahwilliams): clean up with https://github.com/flutter/flutter/issues/60675
testWithoutContext(' flaky process succeeds with retry', () async {
flakyProcessManager.processFactory = flakyProcessFactory(
flakes: 1,
delay: delay,
);
await FakeAsync().run((FakeAsync time) async {
final Duration timeout = delay - const Duration(milliseconds: 500);
final RunResult result = await flakyProcessUtils.run(
<String>['dummy'],
timeout: timeout,
timeoutRetries: 1,
);
time.elapse(timeout);
expect(result.exitCode, 0);
});
}, skip: true); // TODO(jonahwilliams): clean up with https://github.com/flutter/flutter/issues/60675
testWithoutContext(' flaky process generates ProcessException on timeout', () async {
final Completer<List<int>> flakyStderr = Completer<List<int>>();
final Completer<List<int>> flakyStdout = Completer<List<int>>();
flakyProcessManager.processFactory = flakyProcessFactory(
flakes: 1,
delay: delay,
stderr: () => Stream<List<int>>.fromFuture(flakyStderr.future),
stdout: () => Stream<List<int>>.fromFuture(flakyStdout.future),
);
when(flakyProcessManager.killPid(any)).thenAnswer((_) {
// Don't let the stderr stream stop until the process is killed. This
// ensures that runAsync() does not delay killing the process until
// stdout and stderr are drained (which won't happen).
flakyStderr.complete(<int>[]);
flakyStdout.complete(<int>[]);
return true;
});
await FakeAsync().run((FakeAsync time) async {
final Duration timeout = delay - const Duration(milliseconds: 500);
expect(() => flakyProcessUtils.run(
<String>['dummy'],
timeout: timeout,
timeoutRetries: 0,
), throwsA(isA<ProcessException>()));
time.elapse(timeout);
});
}, skip: true); // TODO(jonahwilliams): clean up with https://github.com/flutter/flutter/issues/60675
});
group('runSync', () {
......@@ -404,50 +314,55 @@ void main() {
});
group('exitsHappySync', () {
MockProcessManager mockProcessManager;
FakeProcessManager processManager;
ProcessUtils processUtils;
setUp(() {
mockProcessManager = MockProcessManager();
processManager = FakeProcessManager.empty();
processUtils = ProcessUtils(
processManager: mockProcessManager,
processManager: processManager,
logger: BufferLogger.test(),
);
});
testWithoutContext(' succeeds on success', () async {
when(mockProcessManager.runSync(<String>['whoohoo'])).thenReturn(
ProcessResult(0, 0, '', '')
);
testWithoutContext('succeeds on success', () async {
processManager.addCommand(const FakeCommand(
command: <String>['whoohoo'],
));
expect(processUtils.exitsHappySync(<String>['whoohoo']), isTrue);
});
testWithoutContext(' fails on failure', () async {
when(mockProcessManager.runSync(<String>['boohoo'])).thenReturn(
ProcessResult(0, 1, '', '')
);
testWithoutContext('fails on failure', () async {
processManager.addCommand(const FakeCommand(
command: <String>['boohoo'],
exitCode: 1,
));
expect(processUtils.exitsHappySync(<String>['boohoo']), isFalse);
});
testWithoutContext('catches Exception and returns false', () {
when(mockProcessManager.runSync(<String>['boohoo'])).thenThrow(
const ProcessException('Process failed', <String>[]),
);
processManager.addCommand(const FakeCommand(
command: <String>['boohoo'],
exception: ProcessException('Process failed', <String>[]),
));
expect(processUtils.exitsHappySync(<String>['boohoo']), isFalse);
});
testWithoutContext('does not throw Exception and returns false if binary cannot run', () {
mockProcessManager.canRunSucceeds = false;
processManager.excludedExecutables.add('nonesuch');
expect(processUtils.exitsHappySync(<String>['nonesuch']), isFalse);
verifyNever(
mockProcessManager.runSync(any, environment: anyNamed('environment')),
);
});
testWithoutContext('does not catch ArgumentError', () async {
when(mockProcessManager.runSync(<String>['invalid'])).thenThrow(
ArgumentError('Bad input'),
);
processManager.addCommand(FakeCommand(
command: const <String>['invalid'],
exception: ArgumentError('Bad input'),
));
expect(
() => processUtils.exitsHappySync(<String>['invalid']),
throwsArgumentError,
......@@ -456,50 +371,55 @@ void main() {
});
group('exitsHappy', () {
MockProcessManager mockProcessManager;
FakeProcessManager processManager;
ProcessUtils processUtils;
setUp(() {
mockProcessManager = MockProcessManager();
processManager = FakeProcessManager.empty();
processUtils = ProcessUtils(
processManager: mockProcessManager,
processManager: processManager,
logger: BufferLogger.test(),
);
});
testWithoutContext('succeeds on success', () async {
when(mockProcessManager.run(<String>['whoohoo'])).thenAnswer((_) {
return Future<ProcessResult>.value(ProcessResult(0, 0, '', ''));
});
processManager.addCommand(const FakeCommand(
command: <String>['whoohoo']
));
expect(await processUtils.exitsHappy(<String>['whoohoo']), isTrue);
});
testWithoutContext('fails on failure', () async {
when(mockProcessManager.run(<String>['boohoo'])).thenAnswer((_) {
return Future<ProcessResult>.value(ProcessResult(0, 1, '', ''));
});
processManager.addCommand(const FakeCommand(
command: <String>['boohoo'],
exitCode: 1,
));
expect(await processUtils.exitsHappy(<String>['boohoo']), isFalse);
});
testWithoutContext('catches Exception and returns false', () async {
when(mockProcessManager.run(<String>['boohoo'])).thenThrow(
const ProcessException('Process failed', <String>[]),
);
processManager.addCommand(const FakeCommand(
command: <String>['boohoo'],
exception: ProcessException('Process failed', <String>[])
));
expect(await processUtils.exitsHappy(<String>['boohoo']), isFalse);
});
testWithoutContext('does not throw Exception and returns false if binary cannot run', () async {
mockProcessManager.canRunSucceeds = false;
processManager.excludedExecutables.add('nonesuch');
expect(await processUtils.exitsHappy(<String>['nonesuch']), isFalse);
verifyNever(
mockProcessManager.runSync(any, environment: anyNamed('environment')),
);
});
testWithoutContext('does not catch ArgumentError', () async {
when(mockProcessManager.run(<String>['invalid'])).thenThrow(
ArgumentError('Bad input'),
);
processManager.addCommand(FakeCommand(
command: const <String>['invalid'],
exception: ArgumentError('Bad input')
));
expect(
() async => processUtils.exitsHappy(<String>['invalid']),
throwsArgumentError,
......
......@@ -4,21 +4,16 @@
// @dart = 2.8
import 'dart:convert';
import 'dart:io' hide Directory, File;
import 'package:file/memory.dart';
import 'package:flutter_tools/src/artifacts.dart';
import 'package:flutter_tools/src/base/file_system.dart';
import 'package:flutter_tools/src/base/logger.dart';
import 'package:flutter_tools/src/base/platform.dart';
import 'package:flutter_tools/src/base/terminal.dart';
import 'package:flutter_tools/src/build_info.dart';
import 'package:flutter_tools/src/build_system/build_system.dart';
import 'package:flutter_tools/src/build_system/targets/icon_tree_shaker.dart';
import 'package:flutter_tools/src/devfs.dart';
import 'package:meta/meta.dart';
import 'package:mockito/mockito.dart';
import '../../../src/common.dart';
import '../../../src/fake_process_manager.dart';
......@@ -34,8 +29,7 @@ const String relativePath = 'fonts/MaterialIcons-Regular.otf';
void main() {
BufferLogger logger;
MemoryFileSystem fileSystem;
MockProcessManager mockProcessManager;
MockProcess fontSubsetProcess;
FakeProcessManager processManager;
Artifacts artifacts;
DevFSStringContent fontManifestContent;
......@@ -59,9 +53,12 @@ void main() {
String stdout = '',
String stderr = '',
}) {
when(mockProcessManager.run(_getConstFinderArgs(appDillPath))).thenAnswer((_) async {
return ProcessResult(0, exitCode, stdout, stderr);
});
processManager.addCommand(FakeCommand(
command: _getConstFinderArgs(appDillPath),
exitCode: exitCode,
stdout: stdout,
stderr: stderr,
));
}
void _resetFontSubsetInvocation({
......@@ -72,30 +69,21 @@ void main() {
}) {
assert(stdinSink != null);
stdinSink.clear();
when(fontSubsetProcess.exitCode).thenAnswer((_) async => exitCode);
when(fontSubsetProcess.stdout).thenAnswer((_) => Stream<List<int>>.fromIterable(<List<int>>[utf8.encode(stdout)]));
when(fontSubsetProcess.stderr).thenAnswer((_) => Stream<List<int>>.fromIterable(<List<int>>[utf8.encode(stderr)]));
when(fontSubsetProcess.stdin).thenReturn(stdinSink);
when(mockProcessManager.start(fontSubsetArgs)).thenAnswer((_) async {
return fontSubsetProcess;
});
processManager.addCommand(FakeCommand(
command: fontSubsetArgs,
exitCode: exitCode,
stdout: stdout,
stderr: stderr,
stdin: stdinSink,
));
}
setUp(() {
processManager = FakeProcessManager.empty();
fontManifestContent = DevFSStringContent(validFontManifestJson);
mockProcessManager = MockProcessManager();
fontSubsetProcess = MockProcess();
artifacts = Artifacts.test();
fileSystem = MemoryFileSystem.test();
logger = BufferLogger(
terminal: AnsiTerminal(
stdio: FakeStdio(),
platform: kNoAnsiPlatform,
),
outputPreferences: OutputPreferences.test(showColor: false),
);
logger = BufferLogger.test();
dartPath = artifacts.getHostArtifact(HostArtifact.engineDartBinary).path;
constFinderPath = artifacts.getArtifactPath(Artifact.constFinder);
fontSubsetPath = artifacts.getArtifactPath(Artifact.fontSubset);
......@@ -135,7 +123,7 @@ void main() {
environment,
fontManifestContent,
logger: logger,
processManager: mockProcessManager,
processManager: processManager,
fileSystem: fileSystem,
artifacts: artifacts,
);
......@@ -153,9 +141,7 @@ void main() {
relativePath: relativePath,
);
expect(subsets, false);
verifyNever(mockProcessManager.run(any));
verifyNever(mockProcessManager.start(any));
expect(processManager, hasNoRemainingExpectations);
});
testWithoutContext('Does not get enabled without font manifest', () {
......@@ -168,7 +154,7 @@ void main() {
environment,
null,
logger: logger,
processManager: mockProcessManager,
processManager: processManager,
fileSystem: fileSystem,
artifacts: artifacts,
);
......@@ -178,8 +164,7 @@ void main() {
isEmpty,
);
expect(iconTreeShaker.enabled, false);
verifyNever(mockProcessManager.run(any));
verifyNever(mockProcessManager.start(any));
expect(processManager, hasNoRemainingExpectations);
});
testWithoutContext('Gets enabled', () {
......@@ -192,7 +177,7 @@ void main() {
environment,
fontManifestContent,
logger: logger,
processManager: mockProcessManager,
processManager: processManager,
fileSystem: fileSystem,
artifacts: artifacts,
);
......@@ -202,8 +187,7 @@ void main() {
isEmpty,
);
expect(iconTreeShaker.enabled, true);
verifyNever(mockProcessManager.run(any));
verifyNever(mockProcessManager.start(any));
expect(processManager, hasNoRemainingExpectations);
});
test('No app.dill throws exception', () async {
......@@ -216,7 +200,7 @@ void main() {
environment,
fontManifestContent,
logger: logger,
processManager: mockProcessManager,
processManager: processManager,
fileSystem: fileSystem,
artifacts: artifacts,
);
......@@ -229,6 +213,7 @@ void main() {
),
throwsA(isA<IconTreeShakerException>()),
);
expect(processManager, hasNoRemainingExpectations);
});
testWithoutContext('Can subset a font', () async {
......@@ -243,7 +228,7 @@ void main() {
environment,
fontManifestContent,
logger: logger,
processManager: mockProcessManager,
processManager: processManager,
fileSystem: fileSystem,
artifacts: artifacts,
);
......@@ -268,9 +253,7 @@ void main() {
);
expect(subsetted, true);
expect(stdinSink.getAndClear(), '59470\n');
verify(mockProcessManager.run(_getConstFinderArgs(appDill.path))).called(1);
verify(mockProcessManager.start(fontSubsetArgs)).called(2);
expect(processManager, hasNoRemainingExpectations);
});
testWithoutContext('Does not subset a non-supported font', () async {
......@@ -285,7 +268,7 @@ void main() {
environment,
fontManifestContent,
logger: logger,
processManager: mockProcessManager,
processManager: processManager,
fileSystem: fileSystem,
artifacts: artifacts,
);
......@@ -303,9 +286,6 @@ void main() {
relativePath: relativePath,
);
expect(subsetted, false);
verifyNever(mockProcessManager.run(_getConstFinderArgs(appDill.path)));
verifyNever(mockProcessManager.start(fontSubsetArgs));
});
testWithoutContext('Does not subset an invalid ttf font', () async {
......@@ -320,7 +300,7 @@ void main() {
environment,
fontManifestContent,
logger: logger,
processManager: mockProcessManager,
processManager: processManager,
fileSystem: fileSystem,
artifacts: artifacts,
);
......@@ -338,8 +318,6 @@ void main() {
);
expect(subsetted, false);
verifyNever(mockProcessManager.run(_getConstFinderArgs(appDill.path)));
verifyNever(mockProcessManager.start(fontSubsetArgs));
});
testWithoutContext('Non-constant instances', () async {
......@@ -354,7 +332,7 @@ void main() {
environment,
fontManifestContent,
logger: logger,
processManager: mockProcessManager,
processManager: processManager,
fileSystem: fileSystem,
artifacts: artifacts,
);
......@@ -362,7 +340,7 @@ void main() {
_addConstFinderInvocation(appDill.path, stdout: constFinderResultWithInvalid);
await expectLater(
() async => iconTreeShaker.subsetFont(
() => iconTreeShaker.subsetFont(
input: fileSystem.file(inputPath),
outputPath: outputPath,
relativePath: relativePath,
......@@ -373,9 +351,7 @@ void main() {
' again with --no-tree-shake-icons.',
),
);
verify(mockProcessManager.run(_getConstFinderArgs(appDill.path))).called(1);
verifyNever(mockProcessManager.start(fontSubsetArgs));
expect(processManager, hasNoRemainingExpectations);
});
testWithoutContext('Non-zero font-subset exit code', () async {
......@@ -391,7 +367,7 @@ void main() {
environment,
fontManifestContent,
logger: logger,
processManager: mockProcessManager,
processManager: processManager,
fileSystem: fileSystem,
artifacts: artifacts,
);
......@@ -401,16 +377,14 @@ void main() {
_resetFontSubsetInvocation(exitCode: -1, stdinSink: stdinSink);
await expectLater(
() async => iconTreeShaker.subsetFont(
() => iconTreeShaker.subsetFont(
input: fileSystem.file(inputPath),
outputPath: outputPath,
relativePath: relativePath,
),
throwsA(isA<IconTreeShakerException>()),
);
verify(mockProcessManager.run(_getConstFinderArgs(appDill.path))).called(1);
verify(mockProcessManager.start(fontSubsetArgs)).called(1);
expect(processManager, hasNoRemainingExpectations);
});
testWithoutContext('font-subset throws on write to sdtin', () async {
......@@ -425,7 +399,7 @@ void main() {
environment,
fontManifestContent,
logger: logger,
processManager: mockProcessManager,
processManager: processManager,
fileSystem: fileSystem,
artifacts: artifacts,
);
......@@ -435,16 +409,14 @@ void main() {
_resetFontSubsetInvocation(exitCode: -1, stdinSink: stdinSink);
await expectLater(
() async => iconTreeShaker.subsetFont(
() => iconTreeShaker.subsetFont(
input: fileSystem.file(inputPath),
outputPath: outputPath,
relativePath: relativePath,
),
throwsA(isA<IconTreeShakerException>()),
);
verify(mockProcessManager.run(_getConstFinderArgs(appDill.path))).called(1);
verify(mockProcessManager.start(fontSubsetArgs)).called(1);
expect(processManager, hasNoRemainingExpectations);
});
testWithoutContext('Invalid font manifest', () async {
......@@ -461,7 +433,7 @@ void main() {
environment,
fontManifestContent,
logger: logger,
processManager: mockProcessManager,
processManager: processManager,
fileSystem: fileSystem,
artifacts: artifacts,
);
......@@ -469,16 +441,14 @@ void main() {
_addConstFinderInvocation(appDill.path, stdout: validConstFinderResult);
await expectLater(
() async => iconTreeShaker.subsetFont(
() => iconTreeShaker.subsetFont(
input: fileSystem.file(inputPath),
outputPath: outputPath,
relativePath: relativePath,
),
throwsA(isA<IconTreeShakerException>()),
);
verify(mockProcessManager.run(_getConstFinderArgs(appDill.path))).called(1);
verifyNever(mockProcessManager.start(fontSubsetArgs));
expect(processManager, hasNoRemainingExpectations);
});
testWithoutContext('ConstFinder non-zero exit', () async {
......@@ -495,7 +465,7 @@ void main() {
environment,
fontManifestContent,
logger: logger,
processManager: mockProcessManager,
processManager: processManager,
fileSystem: fileSystem,
artifacts: artifacts,
);
......@@ -510,9 +480,7 @@ void main() {
),
throwsA(isA<IconTreeShakerException>()),
);
verify(mockProcessManager.run(_getConstFinderArgs(appDill.path))).called(1);
verifyNever(mockProcessManager.start(fontSubsetArgs));
expect(processManager, hasNoRemainingExpectations);
});
}
......@@ -589,6 +557,3 @@ const String invalidFontManifestJson = '''
]
}
''';
class MockProcessManager extends Mock implements ProcessManager {}
class MockProcess extends Mock implements Process {}
......@@ -20,7 +20,7 @@ import 'package:flutter_tools/src/ios/devices.dart';
import 'package:flutter_tools/src/ios/ios_deploy.dart';
import 'package:flutter_tools/src/ios/iproxy.dart';
import 'package:flutter_tools/src/ios/mac.dart';
import 'package:mockito/mockito.dart';
import 'package:test/fake.dart';
import '../../src/common.dart';
import '../../src/fake_devices.dart';
......@@ -86,12 +86,10 @@ stdout: '(lldb) run\nsuccess',
);
void main() {
// TODO(jonahwilliams): This test doesn't really belong here but
// I don't have a better place for it for now.
testWithoutContext('disposing device disposes the portForwarder and logReader', () async {
final IOSDevice device = setUpIOSDevice();
final DevicePortForwarder devicePortForwarder = MockDevicePortForwarder();
final DeviceLogReader deviceLogReader = MockDeviceLogReader();
final FakeDevicePortForwarder devicePortForwarder = FakeDevicePortForwarder();
final FakeDeviceLogReader deviceLogReader = FakeDeviceLogReader();
final IOSApp iosApp = PrebuiltIOSApp(
projectBundleId: 'app',
bundleName: 'Runner',
......@@ -101,8 +99,8 @@ void main() {
device.setLogReader(iosApp, deviceLogReader);
await device.dispose();
verify(deviceLogReader.dispose()).called(1);
verify(devicePortForwarder.dispose()).called(1);
expect(deviceLogReader.disposed, true);
expect(devicePortForwarder.disposed, true);
});
testWithoutContext('IOSDevice.startApp attaches in debug mode via log reading on iOS 13+', () async {
......@@ -384,5 +382,11 @@ IOSDevice setUpIOSDevice({
);
}
class MockDevicePortForwarder extends Mock implements DevicePortForwarder {}
class MockDeviceLogReader extends Mock implements DeviceLogReader {}
class FakeDevicePortForwarder extends Fake implements DevicePortForwarder {
bool disposed = false;
@override
Future<void> dispose() async {
disposed = true;
}
}
......@@ -16,55 +16,17 @@ import 'package:flutter_tools/src/ios/xcode_build_settings.dart';
import 'package:flutter_tools/src/ios/xcodeproj.dart';
import 'package:flutter_tools/src/project.dart';
import 'package:flutter_tools/src/reporting/reporting.dart';
import 'package:mockito/mockito.dart';
import '../../src/common.dart';
import '../../src/context.dart';
import '../../src/mocks.dart' as mocks;
const String xcodebuild = '/usr/bin/xcodebuild';
void main() {
group('MockProcessManager', () {
mocks.MockProcessManager processManager;
XcodeProjectInterpreter xcodeProjectInterpreter;
FakePlatform platform;
BufferLogger logger;
setUp(() {
processManager = mocks.MockProcessManager();
platform = FakePlatform(operatingSystem: 'macos');
final FileSystem fileSystem = MemoryFileSystem.test();
fileSystem.file(xcodebuild).createSync(recursive: true);
logger = BufferLogger.test();
xcodeProjectInterpreter = XcodeProjectInterpreter(
logger: logger,
fileSystem: fileSystem,
platform: platform,
processManager: processManager,
usage: null,
);
});
testUsingContext('xcodebuild build settings flakes', () async {
const Duration delay = Duration(seconds: 1);
processManager.processFactory = mocks.flakyProcessFactory(
flakes: 1,
delay: delay + const Duration(seconds: 1),
);
platform.environment = const <String, String>{};
when(processManager.runSync(<String>['which', 'sysctl']))
.thenReturn(ProcessResult(0, 0, '', ''));
when(processManager.runSync(<String>['sysctl', 'hw.optional.arm64']))
.thenReturn(ProcessResult(0, 1, '', ''));
expect(await xcodeProjectInterpreter.getBuildSettings('', buildContext: const XcodeProjectBuildContext(scheme: 'Runner'), timeout: delay),
const <String, String>{});
// build settings times out and is killed once, then succeeds.
verify(processManager.killPid(any)).called(1);
// The verbose logs should tell us something timed out.
expect(logger.traceText, contains('timed out'));
});
});
......
......@@ -7,66 +7,60 @@
import 'dart:async';
import 'package:file/memory.dart';
import 'package:flutter_tools/src/application_package.dart';
import 'package:flutter_tools/src/base/common.dart';
import 'package:flutter_tools/src/base/file_system.dart';
import 'package:flutter_tools/src/base/logger.dart';
import 'package:flutter_tools/src/base/time.dart';
import 'package:flutter_tools/src/build_info.dart';
import 'package:flutter_tools/src/build_system/build_system.dart';
import 'package:flutter_tools/src/devfs.dart';
import 'package:flutter_tools/src/device.dart';
import 'package:flutter_tools/src/globals_null_migrated.dart' as globals;
import 'package:flutter_tools/src/isolated/devfs_web.dart';
import 'package:flutter_tools/src/isolated/resident_web_runner.dart';
import 'package:flutter_tools/src/project.dart';
import 'package:flutter_tools/src/reporting/reporting.dart';
import 'package:flutter_tools/src/resident_runner.dart';
import 'package:flutter_tools/src/web/chrome.dart';
import 'package:flutter_tools/src/web/web_device.dart';
import 'package:mockito/mockito.dart';
import 'package:webkit_inspection_protocol/webkit_inspection_protocol.dart';
import 'package:flutter_tools/src/vmservice.dart';
import 'package:test/fake.dart';
import '../src/common.dart';
import '../src/context.dart';
import '../src/fakes.dart';
import '../src/test_build_system.dart';
void main() {
ResidentWebRunner residentWebRunner;
MockFlutterDevice mockFlutterDevice;
MockWebDevFS mockWebDevFS;
FakeFlutterDevice mockFlutterDevice;
FakeWebDevFS mockWebDevFS;
FileSystem fileSystem;
setUp(() {
mockWebDevFS = MockWebDevFS();
final MockWebDevice mockWebDevice = MockWebDevice();
mockFlutterDevice = MockFlutterDevice();
when(mockFlutterDevice.device).thenReturn(mockWebDevice);
when(mockFlutterDevice.devFS).thenReturn(mockWebDevFS);
when(mockWebDevFS.sources).thenReturn(<Uri>[]);
fileSystem = MemoryFileSystem.test();
mockWebDevFS = FakeWebDevFS();
final FakeWebDevice mockWebDevice = FakeWebDevice();
mockFlutterDevice = FakeFlutterDevice(mockWebDevice);
mockFlutterDevice._devFS = mockWebDevFS;
fileSystem.file('.packages').writeAsStringSync('\n');
fileSystem.file('pubspec.yaml').createSync();
fileSystem.file(fileSystem.path.join('lib', 'main.dart')).createSync(recursive: true);
fileSystem.file(fileSystem.path.join('web', 'index.html')).createSync(recursive: true);
});
void _setupMocks() {
globals.fs.file('.packages').writeAsStringSync('\n');
globals.fs.file('pubspec.yaml').createSync();
globals.fs.file(globals.fs.path.join('lib', 'main.dart')).createSync(recursive: true);
globals.fs.file(globals.fs.path.join('web', 'index.html')).createSync(recursive: true);
final FlutterProject project = FlutterProject.fromDirectoryTest(globals.fs.currentDirectory);
residentWebRunner = ResidentWebRunner(
testUsingContext('Can successfully run and connect without vmservice', () async {
final FlutterProject project = FlutterProject.fromDirectoryTest(fileSystem.currentDirectory);
final ResidentWebRunner residentWebRunner = ResidentWebRunner(
mockFlutterDevice,
flutterProject: project,
debuggingOptions: DebuggingOptions.disabled(BuildInfo.release),
ipv6: true,
stayResident: true,
urlTunneller: null,
fileSystem: globals.fs,
logger: globals.logger,
systemClock: globals.systemClock,
usage: globals.flutterUsage,
fileSystem: fileSystem,
logger: BufferLogger.test(),
systemClock: SystemClock.fixed(DateTime(0, 0, 0)),
usage: TestUsage(),
);
}
testUsingContext('Can successfully run and connect without vmservice', () async {
_setupMocks();
final FakeStatusLogger fakeStatusLogger = globals.logger as FakeStatusLogger;
final MockStatus mockStatus = MockStatus();
fakeStatusLogger.status = mockStatus;
final Completer<DebugConnectionInfo> connectionInfoCompleter = Completer<DebugConnectionInfo>();
unawaited(residentWebRunner.run(
connectionInfoCompleter: connectionInfoCompleter,
......@@ -74,40 +68,74 @@ void main() {
final DebugConnectionInfo debugConnectionInfo = await connectionInfoCompleter.future;
expect(debugConnectionInfo.wsUri, null);
verify(mockStatus.stop()).called(1);
}, overrides: <Type, Generator>{
BuildSystem: () => TestBuildSystem.all(BuildResult(success: true)),
Logger: () => FakeStatusLogger(BufferLogger.test()),
FileSystem: () => MemoryFileSystem.test(),
FileSystem: () => fileSystem,
ProcessManager: () => FakeProcessManager.any(),
});
// Regression test for https://github.com/flutter/flutter/issues/60613
testUsingContext('ResidentWebRunner calls appFailedToStart if initial compilation fails', () async {
_setupMocks();
final FlutterProject project = FlutterProject.fromDirectoryTest(fileSystem.currentDirectory);
final ResidentWebRunner residentWebRunner = ResidentWebRunner(
mockFlutterDevice,
flutterProject: project,
debuggingOptions: DebuggingOptions.disabled(BuildInfo.release),
ipv6: true,
stayResident: true,
urlTunneller: null,
fileSystem: fileSystem,
logger: BufferLogger.test(),
systemClock: SystemClock.fixed(DateTime(0, 0, 0)),
usage: TestUsage(),
);
expect(() async => residentWebRunner.run(), throwsToolExit());
expect(() => residentWebRunner.run(), throwsToolExit());
expect(await residentWebRunner.waitForAppToFinish(), 1);
}, overrides: <Type, Generator>{
BuildSystem: () => TestBuildSystem.all(BuildResult(success: false)),
FileSystem: () => MemoryFileSystem.test(),
FileSystem: () => fileSystem,
ProcessManager: () => FakeProcessManager.any(),
});
// Regression test for https://github.com/flutter/flutter/issues/60613
testUsingContext('ResidentWebRunner calls appFailedToStart if error is thrown during startup', () async {
_setupMocks();
final FlutterProject project = FlutterProject.fromDirectoryTest(fileSystem.currentDirectory);
final ResidentWebRunner residentWebRunner = ResidentWebRunner(
mockFlutterDevice,
flutterProject: project,
debuggingOptions: DebuggingOptions.disabled(BuildInfo.release),
ipv6: true,
stayResident: true,
urlTunneller: null,
fileSystem: fileSystem,
logger: BufferLogger.test(),
systemClock: SystemClock.fixed(DateTime(0, 0, 0)),
usage: TestUsage(),
);
expect(() async => residentWebRunner.run(), throwsA(isA<Exception>()));
expect(await residentWebRunner.waitForAppToFinish(), 1);
}, overrides: <Type, Generator>{
BuildSystem: () => TestBuildSystem.error(Exception('foo')),
FileSystem: () => MemoryFileSystem.test(),
FileSystem: () => fileSystem,
ProcessManager: () => FakeProcessManager.any(),
});
testUsingContext('Can full restart after attaching', () async {
_setupMocks();
final FlutterProject project = FlutterProject.fromDirectoryTest(fileSystem.currentDirectory);
final ResidentWebRunner residentWebRunner = ResidentWebRunner(
mockFlutterDevice,
flutterProject: project,
debuggingOptions: DebuggingOptions.disabled(BuildInfo.release),
ipv6: true,
stayResident: true,
urlTunneller: null,
fileSystem: fileSystem,
logger: BufferLogger.test(),
systemClock: SystemClock.fixed(DateTime(0, 0, 0)),
usage: TestUsage(),
);
final Completer<DebugConnectionInfo> connectionInfoCompleter = Completer<DebugConnectionInfo>();
unawaited(residentWebRunner.run(
connectionInfoCompleter: connectionInfoCompleter,
......@@ -118,12 +146,24 @@ void main() {
expect(result.code, 0);
}, overrides: <Type, Generator>{
BuildSystem: () => TestBuildSystem.all(BuildResult(success: true)),
FileSystem: () => MemoryFileSystem.test(),
FileSystem: () => fileSystem,
ProcessManager: () => FakeProcessManager.any(),
});
testUsingContext('Fails on compilation errors in hot restart', () async {
_setupMocks();
final FlutterProject project = FlutterProject.fromDirectoryTest(fileSystem.currentDirectory);
final ResidentWebRunner residentWebRunner = ResidentWebRunner(
mockFlutterDevice,
flutterProject: project,
debuggingOptions: DebuggingOptions.disabled(BuildInfo.release),
ipv6: true,
stayResident: true,
urlTunneller: null,
fileSystem: fileSystem,
logger: BufferLogger.test(),
systemClock: SystemClock.fixed(DateTime(0, 0, 0)),
usage: TestUsage(),
);
final Completer<DebugConnectionInfo> connectionInfoCompleter = Completer<DebugConnectionInfo>();
unawaited(residentWebRunner.run(
connectionInfoCompleter: connectionInfoCompleter,
......@@ -138,55 +178,63 @@ void main() {
BuildResult(success: true),
BuildResult(success: false),
]),
FileSystem: () => MemoryFileSystem.test(),
FileSystem: () => fileSystem,
ProcessManager: () => FakeProcessManager.any(),
});
}
testUsingContext('Correctly performs a full refresh on attached chrome device.', () async {
_setupMocks();
final MockChromeDevice chromeDevice = MockChromeDevice();
final MockChrome chrome = MockChrome();
final MockChromeConnection mockChromeConnection = MockChromeConnection();
final MockChromeTab mockChromeTab = MockChromeTab();
final MockWipConnection mockWipConnection = MockWipConnection();
final MockChromiumLauncher chromiumLauncher = MockChromiumLauncher();
when(mockChromeConnection.getTab(any)).thenAnswer((Invocation invocation) async {
return mockChromeTab;
});
when(mockChromeTab.connect()).thenAnswer((Invocation invocation) async {
return mockWipConnection;
});
when(chromiumLauncher.connectedInstance).thenAnswer((Invocation invocation) async {
return chrome;
});
when(chrome.chromeConnection).thenReturn(mockChromeConnection);
when(chromeDevice.chromeLauncher).thenReturn(chromiumLauncher);
when(mockFlutterDevice.device).thenReturn(chromeDevice);
final Completer<DebugConnectionInfo> connectionInfoCompleter = Completer<DebugConnectionInfo>();
unawaited(residentWebRunner.run(
connectionInfoCompleter: connectionInfoCompleter,
));
await connectionInfoCompleter.future;
final OperationResult result = await residentWebRunner.restart(fullRestart: true);
class FakeWebDevFS extends Fake implements WebDevFS {
@override
List<Uri> get sources => <Uri>[];
expect(result.code, 0);
verify(mockWipConnection.sendCommand('Page.reload', <String, Object>{
'ignoreCache': true,
})).called(1);
}, overrides: <Type, Generator>{
BuildSystem: () => TestBuildSystem.all(BuildResult(success: true)),
FileSystem: () => MemoryFileSystem.test(),
ProcessManager: () => FakeProcessManager.any(),
});
@override
Future<Uri> create() async {
return Uri.base;
}
}
class FakeWebDevice extends Fake implements Device {
@override
String get name => 'web';
@override
Future<bool> stopApp(
covariant ApplicationPackage app, {
String userIdentifier,
}) async {
return true;
}
@override
Future<LaunchResult> startApp(
covariant ApplicationPackage package, {
String mainPath,
String route,
DebuggingOptions debuggingOptions,
Map<String, dynamic> platformArgs,
bool prebuiltApplication = false,
bool ipv6 = false,
String userIdentifier,
}) async {
return LaunchResult.succeeded();
}
}
class MockWebDevFS extends Mock implements WebDevFS {}
class MockWebDevice extends Mock implements Device {}
class MockStatus extends Mock implements Status {}
class MockFlutterDevice extends Mock implements FlutterDevice {}
class MockChromeDevice extends Mock implements ChromiumDevice {}
class MockChrome extends Mock implements Chromium {}
class MockChromeConnection extends Mock implements ChromeConnection {}
class MockChromeTab extends Mock implements ChromeTab {}
class MockWipConnection extends Mock implements WipConnection {}
class MockChromiumLauncher extends Mock implements ChromiumLauncher {}
class FakeFlutterDevice extends Fake implements FlutterDevice {
FakeFlutterDevice(this.device);
@override
final FakeWebDevice device;
DevFS _devFS;
@override
DevFS get devFS => _devFS;
@override
set devFS(DevFS value) { }
@override
FlutterVmService vmService;
}
......@@ -184,6 +184,8 @@ class FakeDeviceLogReader extends DeviceLogReader {
StreamController<String> _cachedLinesController;
bool disposed = false;
final List<String> _lineQueue = <String>[];
StreamController<String> get _linesController {
_cachedLinesController ??= StreamController<String>
......@@ -209,5 +211,6 @@ class FakeDeviceLogReader extends DeviceLogReader {
Future<void> dispose() async {
_lineQueue.clear();
await _linesController.close();
disposed = true;
}
}
// 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.
// @dart = 2.8
import 'dart:async';
import 'package:flutter_tools/src/base/io.dart';
import 'package:mockito/mockito.dart';
import 'package:process/process.dart';
import 'fakes.dart';
/// A strategy for creating Process objects from a list of commands.
typedef _ProcessFactory = Process Function(List<String> command);
/// A ProcessManager that starts Processes by delegating to a ProcessFactory.
class MockProcessManager extends Mock implements ProcessManager {
_ProcessFactory processFactory = _defaulProcessFactory;
bool canRunSucceeds = true;
bool runSucceeds = true;
List<String> commands;
static Process _defaulProcessFactory(List<String> commands) => FakeProcess();
@override
bool canRun(dynamic command, { String workingDirectory }) => canRunSucceeds;
@override
Future<Process> start(
List<dynamic> command, {
String workingDirectory,
Map<String, String> environment,
bool includeParentEnvironment = true,
bool runInShell = false,
ProcessStartMode mode = ProcessStartMode.normal,
}) {
final List<String> commands = command.cast<String>();
if (!runSucceeds) {
final String executable = commands[0];
final List<String> arguments = commands.length > 1 ? commands.sublist(1) : <String>[];
throw ProcessException(executable, arguments);
}
this.commands = commands;
return Future<Process>.value(processFactory(commands));
}
}
/// A function that generates a process factory that gives processes that fail
/// a given number of times before succeeding. The returned processes will
/// fail after a delay if one is supplied.
_ProcessFactory flakyProcessFactory({
int flakes,
bool Function(List<String> command) filter,
Duration delay,
Stream<List<int>> Function() stdout,
Stream<List<int>> Function() stderr,
}) {
int flakesLeft = flakes;
stdout ??= () => const Stream<List<int>>.empty();
stderr ??= () => const Stream<List<int>>.empty();
return (List<String> command) {
if (filter != null && !filter(command)) {
return FakeProcess();
}
if (flakesLeft == 0) {
return FakeProcess(
exitCode: Future<int>.value(0),
stdout: stdout(),
stderr: stderr(),
);
}
flakesLeft = flakesLeft - 1;
Future<int> exitFuture;
if (delay == null) {
exitFuture = Future<int>.value(-9);
} else {
exitFuture = Future<int>.delayed(delay, () => Future<int>.value(-9));
}
return FakeProcess(
exitCode: exitFuture,
stdout: stdout(),
stderr: stderr(),
);
};
}
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