diff --git a/packages/flutter_tools/test/general.shard/devfs_test.dart b/packages/flutter_tools/test/general.shard/devfs_test.dart index cf950ec9da7..f7bb7458543 100644 --- a/packages/flutter_tools/test/general.shard/devfs_test.dart +++ b/packages/flutter_tools/test/general.shard/devfs_test.dart @@ -14,13 +14,15 @@ import 'package:flutter_tools/src/base/file_system.dart'; import 'package:flutter_tools/src/base/io.dart'; import 'package:flutter_tools/src/base/logger.dart'; import 'package:flutter_tools/src/base/os.dart'; +import 'package:flutter_tools/src/base/platform.dart'; import 'package:flutter_tools/src/compile.dart'; import 'package:flutter_tools/src/devfs.dart'; import 'package:flutter_tools/src/vmservice.dart'; -import 'package:mockito/mockito.dart'; import 'package:package_config/package_config.dart'; +import 'package:test/fake.dart'; import '../src/common.dart'; +import '../src/context.dart'; import '../src/fake_http_client.dart'; import '../src/fake_vm_services.dart'; import '../src/fakes.dart'; @@ -123,7 +125,7 @@ void main() { testWithoutContext('DevFS create throws a DevFSException when vmservice disconnects unexpectedly', () async { final FileSystem fileSystem = MemoryFileSystem.test(); - final OperatingSystemUtils osUtils = MockOperatingSystemUtils(); + final OperatingSystemUtils osUtils = FakeOperatingSystemUtils(); final FakeVmServiceHost fakeVmServiceHost = FakeVmServiceHost( requests: [failingCreateDevFSRequest], httpAddress: Uri.parse('http://localhost'), @@ -143,7 +145,7 @@ void main() { testWithoutContext('DevFS destroy is resilient to vmservice disconnection', () async { final FileSystem fileSystem = MemoryFileSystem.test(); - final OperatingSystemUtils osUtils = MockOperatingSystemUtils(); + final OperatingSystemUtils osUtils = FakeOperatingSystemUtils(); final FakeVmServiceHost fakeVmServiceHost = FakeVmServiceHost( requests: [ createDevFSRequest, @@ -168,26 +170,28 @@ void main() { testWithoutContext('DevFS retries uploads when connection reset by peer', () async { final FileSystem fileSystem = MemoryFileSystem.test(); - final OperatingSystemUtils osUtils = MockOperatingSystemUtils(); - final MockResidentCompiler residentCompiler = MockResidentCompiler(); + final OperatingSystemUtils osUtils = OperatingSystemUtils( + fileSystem: fileSystem, + platform: FakePlatform(), + logger: BufferLogger.test(), + processManager: FakeProcessManager.any(), + ); + final FakeResidentCompiler residentCompiler = FakeResidentCompiler(); final FakeVmServiceHost fakeVmServiceHost = FakeVmServiceHost( requests: [createDevFSRequest], httpAddress: Uri.parse('http://localhost'), ); - - when(residentCompiler.recompile( - any, - any, - outputPath: anyNamed('outputPath'), - packageConfig: anyNamed('packageConfig'), - projectRootPath: anyNamed('projectRootPath'), - fs: anyNamed('fs'), - )).thenAnswer((Invocation invocation) async { + residentCompiler.onRecompile = (Uri mainUri, List invalidatedFiles) async { fileSystem.file('lib/foo.dill') ..createSync(recursive: true) ..writeAsBytesSync([1, 2, 3, 4, 5]); return const CompilerOutput('lib/foo.dill', 0, []); - }); + }; + + /// This output can change based on the host platform. + final List> expectedEncoded = await osUtils.gzipLevel1Stream( + Stream>.value([1, 2, 3, 4, 5]), + ).toList(); final DevFS devFS = DevFS( fakeVmServiceHost.vmService, @@ -202,7 +206,8 @@ void main() { FakeRequest(Uri.parse('http://localhost'), method: HttpMethod.put, responseError: const OSError('Connection Reset by peer')), FakeRequest(Uri.parse('http://localhost'), method: HttpMethod.put, responseError: const OSError('Connection Reset by peer')), FakeRequest(Uri.parse('http://localhost'), method: HttpMethod.put, responseError: const OSError('Connection Reset by peer')), - FakeRequest(Uri.parse('http://localhost'), method: HttpMethod.put) + // This is the value of `[1, 2, 3, 4, 5]` run through `osUtils.gzipLevel1Stream`. + FakeRequest(Uri.parse('http://localhost'), method: HttpMethod.put, body: [for (List chunk in expectedEncoded) ...chunk]) ]), uploadRetryThrottle: Duration.zero, ); @@ -220,7 +225,6 @@ void main() { expect(report.syncedBytes, 5); expect(report.success, isTrue); - verify(osUtils.gzipLevel1Stream(any)).called(6); }); testWithoutContext('DevFS reports unsuccessful compile when errors are returned', () async { @@ -243,17 +247,10 @@ void main() { await devFS.create(); final DateTime previousCompile = devFS.lastCompiled; - final MockResidentCompiler residentCompiler = MockResidentCompiler(); - when(residentCompiler.recompile( - any, - any, - outputPath: anyNamed('outputPath'), - packageConfig: anyNamed('packageConfig'), - projectRootPath: anyNamed('projectRootPath'), - fs: anyNamed('fs'), - )).thenAnswer((Invocation invocation) async { + final FakeResidentCompiler residentCompiler = FakeResidentCompiler(); + residentCompiler.onRecompile = (Uri mainUri, List invalidatedFiles) async { return const CompilerOutput('lib/foo.dill', 2, []); - }); + }; final UpdateFSReport report = await devFS.update( mainUri: Uri.parse('lib/foo.txt'), @@ -289,18 +286,11 @@ void main() { await devFS.create(); final DateTime previousCompile = devFS.lastCompiled; - final MockResidentCompiler residentCompiler = MockResidentCompiler(); - when(residentCompiler.recompile( - any, - any, - outputPath: anyNamed('outputPath'), - packageConfig: anyNamed('packageConfig'), - projectRootPath: anyNamed('projectRootPath'), - fs: anyNamed('fs'), - )).thenAnswer((Invocation invocation) async { - fileSystem.file('example').createSync(); + final FakeResidentCompiler residentCompiler = FakeResidentCompiler(); + residentCompiler.onRecompile = (Uri mainUri, List invalidatedFiles) async { + fileSystem.file('lib/foo.txt.dill').createSync(recursive: true); return const CompilerOutput('lib/foo.txt.dill', 0, []); - }); + }; final UpdateFSReport report = await devFS.update( mainUri: Uri.parse('lib/main.dart'), @@ -337,18 +327,11 @@ void main() { await devFS.create(); final DateTime previousCompile = devFS.lastCompiled; - final MockResidentCompiler residentCompiler = MockResidentCompiler(); - when(residentCompiler.recompile( - any, - any, - outputPath: anyNamed('outputPath'), - packageConfig: anyNamed('packageConfig'), - projectRootPath: anyNamed('projectRootPath'), - fs: anyNamed('fs'), - )).thenAnswer((Invocation invocation) async { + final FakeResidentCompiler residentCompiler = FakeResidentCompiler(); + residentCompiler.onRecompile = (Uri mainUri, List invalidatedFiles) async { fileSystem.file('lib/foo.txt.dill').createSync(recursive: true); return const CompilerOutput('lib/foo.txt.dill', 0, []); - }); + }; final UpdateFSReport report = await devFS.update( mainUri: Uri.parse('lib/main.dart'), @@ -391,18 +374,11 @@ void main() { await devFS.create(); - final MockResidentCompiler residentCompiler = MockResidentCompiler(); - when(residentCompiler.recompile( - any, - any, - outputPath: anyNamed('outputPath'), - packageConfig: anyNamed('packageConfig'), - projectRootPath: anyNamed('projectRootPath'), - fs: anyNamed('fs'), - )).thenAnswer((Invocation invocation) async { + final FakeResidentCompiler residentCompiler = FakeResidentCompiler(); + residentCompiler.onRecompile = (Uri mainUri, List invalidatedFiles) async { fileSystem.file('example').createSync(); return const CompilerOutput('lib/foo.txt.dill', 0, []); - }); + }; expect(writer.written, false); @@ -439,10 +415,11 @@ void main() { }); testWithoutContext('Local DevFSWriter turns FileSystemException into DevFSException', () async { - final FileSystem fileSystem = MemoryFileSystem.test(); + final FileExceptionHandler handler = FileExceptionHandler(); + final FileSystem fileSystem = MemoryFileSystem.test(opHandle: handler.opHandle); final LocalDevFSWriter writer = LocalDevFSWriter(fileSystem: fileSystem); - final File file = MockFile(); - when(file.copySync(any)).thenThrow(const FileSystemException('foo')); + final File file = fileSystem.file('foo'); + handler.addError(file, FileSystemOp.read, const FileSystemException('foo')); await expectLater(() async => writer.write({ Uri.parse('goodbye'): DevFSFileContent(file), @@ -450,9 +427,16 @@ void main() { }); } -class MockOperatingSystemUtils extends Mock implements OperatingSystemUtils {} -class MockResidentCompiler extends Mock implements ResidentCompiler {} -class MockFile extends Mock implements File {} +class FakeResidentCompiler extends Fake implements ResidentCompiler { + Future Function(Uri mainUri, List invalidatedFiles) onRecompile; + + @override + Future recompile(Uri mainUri, List invalidatedFiles, {String outputPath, PackageConfig packageConfig, String projectRootPath, FileSystem fs, bool suppressErrors = false}) { + return onRecompile?.call(mainUri, invalidatedFiles) + ?? Future.value(const CompilerOutput('', 1, [])); + } +} + class FakeDevFSWriter implements DevFSWriter { bool written = false; diff --git a/packages/flutter_tools/test/src/fake_http_client.dart b/packages/flutter_tools/test/src/fake_http_client.dart index 4f3d9be6c49..e9b39c9f2f7 100644 --- a/packages/flutter_tools/test/src/fake_http_client.dart +++ b/packages/flutter_tools/test/src/fake_http_client.dart @@ -7,6 +7,8 @@ import 'dart:convert'; import 'dart:io'; +import 'package:collection/collection.dart'; + /// The HTTP verb for a [FakeRequest]. enum HttpMethod { get, @@ -85,12 +87,14 @@ class FakeRequest { this.method = HttpMethod.get, this.response = FakeResponse.empty, this.responseError, + this.body, }); final Uri uri; final HttpMethod method; final FakeResponse response; final Object? responseError; + final List? body; @override String toString() => 'Request{${_toMethodString(method)}, $uri}'; @@ -182,7 +186,7 @@ class FakeHttpClient implements HttpClient { @override Future deleteUrl(Uri url) async { - return _findRequest(HttpMethod.delete, url); + return _findRequest(HttpMethod.delete, url, StackTrace.current); } @override @@ -196,7 +200,7 @@ class FakeHttpClient implements HttpClient { @override Future getUrl(Uri url) async { - return _findRequest(HttpMethod.get, url); + return _findRequest(HttpMethod.get, url, StackTrace.current); } @override @@ -207,7 +211,7 @@ class FakeHttpClient implements HttpClient { @override Future headUrl(Uri url) async { - return _findRequest(HttpMethod.head, url); + return _findRequest(HttpMethod.head, url, StackTrace.current); } @override @@ -218,7 +222,7 @@ class FakeHttpClient implements HttpClient { @override Future openUrl(String method, Uri url) async { - return _findRequest(_fromMethodString(method), url); + return _findRequest(_fromMethodString(method), url, StackTrace.current); } @override @@ -229,7 +233,7 @@ class FakeHttpClient implements HttpClient { @override Future patchUrl(Uri url) async { - return _findRequest(HttpMethod.patch, url); + return _findRequest(HttpMethod.patch, url, StackTrace.current); } @override @@ -240,7 +244,7 @@ class FakeHttpClient implements HttpClient { @override Future postUrl(Uri url) async { - return _findRequest(HttpMethod.post, url); + return _findRequest(HttpMethod.post, url, StackTrace.current); } @override @@ -251,12 +255,12 @@ class FakeHttpClient implements HttpClient { @override Future putUrl(Uri url) async { - return _findRequest(HttpMethod.put, url); + return _findRequest(HttpMethod.put, url, StackTrace.current); } int _requestCount = 0; - _FakeHttpClientRequest _findRequest(HttpMethod method, Uri uri) { + _FakeHttpClientRequest _findRequest(HttpMethod method, Uri uri, StackTrace stackTrace) { // Ensure the fake client throws similar errors to the real client. if (uri.host.isEmpty) { throw ArgumentError('No host specified in URI $uri'); @@ -270,6 +274,8 @@ class FakeHttpClient implements HttpClient { uri, methodString, null, + null, + stackTrace, ); } FakeRequest? matchedRequest; @@ -292,17 +298,22 @@ class FakeHttpClient implements HttpClient { uri, methodString, matchedRequest.responseError, + matchedRequest.body, + stackTrace, ); } } class _FakeHttpClientRequest implements HttpClientRequest { - _FakeHttpClientRequest(this._response, this._uri, this._method, this._responseError); + _FakeHttpClientRequest(this._response, this._uri, this._method, this._responseError, this._expectedBody, this._stackTrace); final FakeResponse _response; final String _method; final Uri _uri; final Object? _responseError; + final List _body = []; + final List? _expectedBody; + final StackTrace _stackTrace; @override bool bufferOutput = true; @@ -328,16 +339,33 @@ class _FakeHttpClientRequest implements HttpClientRequest { } @override - void add(List data) { } + void add(List data) { + _body.addAll(data); + } @override void addError(Object error, [StackTrace? stackTrace]) { } @override - Future addStream(Stream> stream) async { } + Future addStream(Stream> stream) async { + final Completer completer = Completer(); + stream.listen(_body.addAll, onDone: completer.complete); + await completer.future; + } @override Future close() async { + final Completer completer = Completer(); + Timer.run(() { + if (_expectedBody != null && !const ListEquality().equals(_expectedBody, _body)) { + completer.completeError(StateError( + 'Expected a request with the following body:\n$_expectedBody\n but found:\n$_body' + ), _stackTrace); + } else { + completer.complete(); + } + }); + await completer.future; if (_responseError != null) { return Future.error(_responseError!); } @@ -366,16 +394,24 @@ class _FakeHttpClientRequest implements HttpClientRequest { Uri get uri => _uri; @override - void write(Object? object) { } + void write(Object? object) { + _body.addAll(utf8.encode(object.toString())); + } @override - void writeAll(Iterable objects, [String separator = '']) { } + void writeAll(Iterable objects, [String separator = '']) { + _body.addAll(utf8.encode(objects.join(separator))); + } @override - void writeCharCode(int charCode) { } + void writeCharCode(int charCode) { + _body.add(charCode); + } @override - void writeln([Object? object = '']) { } + void writeln([Object? object = '']) { + _body.addAll(utf8.encode(object.toString() + '\n')); + } } class _FakeHttpClientResponse extends Stream> implements HttpClientResponse {