diff --git a/packages/flutter_tools/test/general.shard/base/process_test.dart b/packages/flutter_tools/test/general.shard/base/process_test.dart index 220261400b1..9249d86977a 100644 --- a/packages/flutter_tools/test/general.shard/base/process_test.dart +++ b/packages/flutter_tools/test/general.shard/base/process_test.dart @@ -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) processMetaFactory(List stdout, { - List stderr = const [], - }) { - final Stream> stdoutStream = Stream>.fromIterable( - stdout.map>((String s) => s.codeUnits, - )); - final Stream> stderrStream = Stream>.fromIterable( - stderr.map>((String s) => s.codeUnits, - )); - return (List command) => FakeProcess(stdout: stdoutStream, stderr: stderrStream); - } - testWithoutContext('Command output is not wrapped.', () async { final List testString = ['0123456789' * 10]; - mockProcessManager.processFactory = processMetaFactory(testString, stderr: testString); + processManager.addCommand(FakeCommand( + command: const ['command'], + stdout: testString.join(''), + stderr: testString.join(''), + )); + await processUtils.stream(['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()), ); }); - - 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( - ['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( - ['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> flakyStderr = Completer>(); - final Completer> flakyStdout = Completer>(); - flakyProcessManager.processFactory = flakyProcessFactory( - flakes: 1, - delay: delay, - stderr: () => Stream>.fromFuture(flakyStderr.future), - stdout: () => Stream>.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([]); - flakyStdout.complete([]); - return true; - }); - await FakeAsync().run((FakeAsync time) async { - final Duration timeout = delay - const Duration(milliseconds: 500); - expect(() => flakyProcessUtils.run( - ['dummy'], - timeout: timeout, - timeoutRetries: 0, - ), throwsA(isA())); - 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(['whoohoo'])).thenReturn( - ProcessResult(0, 0, '', '') - ); + testWithoutContext('succeeds on success', () async { + processManager.addCommand(const FakeCommand( + command: ['whoohoo'], + )); + expect(processUtils.exitsHappySync(['whoohoo']), isTrue); }); - testWithoutContext(' fails on failure', () async { - when(mockProcessManager.runSync(['boohoo'])).thenReturn( - ProcessResult(0, 1, '', '') - ); + testWithoutContext('fails on failure', () async { + processManager.addCommand(const FakeCommand( + command: ['boohoo'], + exitCode: 1, + )); + expect(processUtils.exitsHappySync(['boohoo']), isFalse); }); testWithoutContext('catches Exception and returns false', () { - when(mockProcessManager.runSync(['boohoo'])).thenThrow( - const ProcessException('Process failed', []), - ); + processManager.addCommand(const FakeCommand( + command: ['boohoo'], + exception: ProcessException('Process failed', []), + )); + expect(processUtils.exitsHappySync(['boohoo']), isFalse); }); testWithoutContext('does not throw Exception and returns false if binary cannot run', () { - mockProcessManager.canRunSucceeds = false; + processManager.excludedExecutables.add('nonesuch'); + expect(processUtils.exitsHappySync(['nonesuch']), isFalse); - verifyNever( - mockProcessManager.runSync(any, environment: anyNamed('environment')), - ); }); testWithoutContext('does not catch ArgumentError', () async { - when(mockProcessManager.runSync(['invalid'])).thenThrow( - ArgumentError('Bad input'), - ); + processManager.addCommand(FakeCommand( + command: const ['invalid'], + exception: ArgumentError('Bad input'), + )); + expect( () => processUtils.exitsHappySync(['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(['whoohoo'])).thenAnswer((_) { - return Future.value(ProcessResult(0, 0, '', '')); - }); + processManager.addCommand(const FakeCommand( + command: ['whoohoo'] + )); + expect(await processUtils.exitsHappy(['whoohoo']), isTrue); }); testWithoutContext('fails on failure', () async { - when(mockProcessManager.run(['boohoo'])).thenAnswer((_) { - return Future.value(ProcessResult(0, 1, '', '')); - }); + processManager.addCommand(const FakeCommand( + command: ['boohoo'], + exitCode: 1, + )); + expect(await processUtils.exitsHappy(['boohoo']), isFalse); }); testWithoutContext('catches Exception and returns false', () async { - when(mockProcessManager.run(['boohoo'])).thenThrow( - const ProcessException('Process failed', []), - ); + processManager.addCommand(const FakeCommand( + command: ['boohoo'], + exception: ProcessException('Process failed', []) + )); + expect(await processUtils.exitsHappy(['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(['nonesuch']), isFalse); - verifyNever( - mockProcessManager.runSync(any, environment: anyNamed('environment')), - ); }); testWithoutContext('does not catch ArgumentError', () async { - when(mockProcessManager.run(['invalid'])).thenThrow( - ArgumentError('Bad input'), - ); + processManager.addCommand(FakeCommand( + command: const ['invalid'], + exception: ArgumentError('Bad input') + )); + expect( () async => processUtils.exitsHappy(['invalid']), throwsArgumentError, diff --git a/packages/flutter_tools/test/general.shard/build_system/targets/icon_tree_shaker_test.dart b/packages/flutter_tools/test/general.shard/build_system/targets/icon_tree_shaker_test.dart index baabbb4940f..5088e2fa78e 100644 --- a/packages/flutter_tools/test/general.shard/build_system/targets/icon_tree_shaker_test.dart +++ b/packages/flutter_tools/test/general.shard/build_system/targets/icon_tree_shaker_test.dart @@ -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>.fromIterable(>[utf8.encode(stdout)])); - when(fontSubsetProcess.stderr).thenAnswer((_) => Stream>.fromIterable(>[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()), ); + 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()), ); - - 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()), ); - - 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()), ); - - 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()), ); - - 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 {} diff --git a/packages/flutter_tools/test/general.shard/ios/ios_device_start_prebuilt_test.dart b/packages/flutter_tools/test/general.shard/ios/ios_device_start_prebuilt_test.dart index 35e835146cb..7fcf1bf466b 100644 --- a/packages/flutter_tools/test/general.shard/ios/ios_device_start_prebuilt_test.dart +++ b/packages/flutter_tools/test/general.shard/ios/ios_device_start_prebuilt_test.dart @@ -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 dispose() async { + disposed = true; + } +} diff --git a/packages/flutter_tools/test/general.shard/ios/xcodeproj_test.dart b/packages/flutter_tools/test/general.shard/ios/xcodeproj_test.dart index 94c3ed46a5f..e8611adafb3 100644 --- a/packages/flutter_tools/test/general.shard/ios/xcodeproj_test.dart +++ b/packages/flutter_tools/test/general.shard/ios/xcodeproj_test.dart @@ -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 {}; - - when(processManager.runSync(['which', 'sysctl'])) - .thenReturn(ProcessResult(0, 0, '', '')); - when(processManager.runSync(['sysctl', 'hw.optional.arm64'])) - .thenReturn(ProcessResult(0, 1, '', '')); - - expect(await xcodeProjectInterpreter.getBuildSettings('', buildContext: const XcodeProjectBuildContext(scheme: 'Runner'), timeout: delay), - const {}); - // 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')); }); }); diff --git a/packages/flutter_tools/test/general.shard/resident_web_runner_cold_test.dart b/packages/flutter_tools/test/general.shard/resident_web_runner_cold_test.dart index cdd90541e6e..df50558d9ec 100644 --- a/packages/flutter_tools/test/general.shard/resident_web_runner_cold_test.dart +++ b/packages/flutter_tools/test/general.shard/resident_web_runner_cold_test.dart @@ -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([]); + 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 connectionInfoCompleter = Completer(); 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: { 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: { 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())); expect(await residentWebRunner.waitForAppToFinish(), 1); }, overrides: { 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 connectionInfoCompleter = Completer(); unawaited(residentWebRunner.run( connectionInfoCompleter: connectionInfoCompleter, @@ -118,12 +146,24 @@ void main() { expect(result.code, 0); }, overrides: { 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 connectionInfoCompleter = Completer(); unawaited(residentWebRunner.run( connectionInfoCompleter: connectionInfoCompleter, @@ -138,55 +178,63 @@ void main() { BuildResult(success: true), BuildResult(success: false), ]), - FileSystem: () => MemoryFileSystem.test(), - 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 connectionInfoCompleter = Completer(); - unawaited(residentWebRunner.run( - connectionInfoCompleter: connectionInfoCompleter, - )); - await connectionInfoCompleter.future; - final OperationResult result = await residentWebRunner.restart(fullRestart: true); - - expect(result.code, 0); - verify(mockWipConnection.sendCommand('Page.reload', { - 'ignoreCache': true, - })).called(1); - }, overrides: { - BuildSystem: () => TestBuildSystem.all(BuildResult(success: true)), - FileSystem: () => MemoryFileSystem.test(), + FileSystem: () => fileSystem, ProcessManager: () => FakeProcessManager.any(), }); } -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 FakeWebDevFS extends Fake implements WebDevFS { + @override + List get sources => []; + + @override + Future create() async { + return Uri.base; + } +} + +class FakeWebDevice extends Fake implements Device { + @override + String get name => 'web'; + + @override + Future stopApp( + covariant ApplicationPackage app, { + String userIdentifier, + }) async { + return true; + } + + @override + Future startApp( + covariant ApplicationPackage package, { + String mainPath, + String route, + DebuggingOptions debuggingOptions, + Map platformArgs, + bool prebuiltApplication = false, + bool ipv6 = false, + String userIdentifier, + }) async { + return LaunchResult.succeeded(); + } +} + +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; +} diff --git a/packages/flutter_tools/test/src/fake_devices.dart b/packages/flutter_tools/test/src/fake_devices.dart index 2c24c2ff790..5bf0f714c05 100644 --- a/packages/flutter_tools/test/src/fake_devices.dart +++ b/packages/flutter_tools/test/src/fake_devices.dart @@ -184,6 +184,8 @@ class FakeDeviceLogReader extends DeviceLogReader { StreamController _cachedLinesController; + bool disposed = false; + final List _lineQueue = []; StreamController get _linesController { _cachedLinesController ??= StreamController @@ -209,5 +211,6 @@ class FakeDeviceLogReader extends DeviceLogReader { Future dispose() async { _lineQueue.clear(); await _linesController.close(); + disposed = true; } } diff --git a/packages/flutter_tools/test/src/mocks.dart b/packages/flutter_tools/test/src/mocks.dart deleted file mode 100644 index 479a3559115..00000000000 --- a/packages/flutter_tools/test/src/mocks.dart +++ /dev/null @@ -1,88 +0,0 @@ -// 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 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 commands; - - static Process _defaulProcessFactory(List commands) => FakeProcess(); - - @override - bool canRun(dynamic command, { String workingDirectory }) => canRunSucceeds; - - @override - Future start( - List command, { - String workingDirectory, - Map environment, - bool includeParentEnvironment = true, - bool runInShell = false, - ProcessStartMode mode = ProcessStartMode.normal, - }) { - final List commands = command.cast(); - if (!runSucceeds) { - final String executable = commands[0]; - final List arguments = commands.length > 1 ? commands.sublist(1) : []; - throw ProcessException(executable, arguments); - } - - this.commands = commands; - return Future.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 command) filter, - Duration delay, - Stream> Function() stdout, - Stream> Function() stderr, -}) { - int flakesLeft = flakes; - stdout ??= () => const Stream>.empty(); - stderr ??= () => const Stream>.empty(); - return (List command) { - if (filter != null && !filter(command)) { - return FakeProcess(); - } - if (flakesLeft == 0) { - return FakeProcess( - exitCode: Future.value(0), - stdout: stdout(), - stderr: stderr(), - ); - } - flakesLeft = flakesLeft - 1; - Future exitFuture; - if (delay == null) { - exitFuture = Future.value(-9); - } else { - exitFuture = Future.delayed(delay, () => Future.value(-9)); - } - return FakeProcess( - exitCode: exitFuture, - stdout: stdout(), - stderr: stderr(), - ); - }; -}