diff --git a/packages/flutter_tools/lib/src/base/net.dart b/packages/flutter_tools/lib/src/base/net.dart index 45c1074c4d..536ea9b9a4 100644 --- a/packages/flutter_tools/lib/src/base/net.dart +++ b/packages/flutter_tools/lib/src/base/net.dart @@ -48,6 +48,7 @@ class Net { Future> fetchUrl(Uri url, { int maxAttempts, File destFile, + @visibleForTesting Duration durationOverride, }) async { int attempts = 0; int durationSeconds = 1; @@ -78,7 +79,7 @@ class Net { 'Download failed -- attempting retry $attempts in ' '$durationSeconds second${ durationSeconds == 1 ? "" : "s"}...', ); - await Future.delayed(Duration(seconds: durationSeconds)); + await Future.delayed(durationOverride ?? Duration(seconds: durationSeconds)); if (durationSeconds < 64) { durationSeconds *= 2; } @@ -173,8 +174,6 @@ class Net { } } - - /// An IOSink that collects whatever is written to it. class _MemoryIOSink implements IOSink { @override diff --git a/packages/flutter_tools/test/commands.shard/hermetic/attach_test.dart b/packages/flutter_tools/test/commands.shard/hermetic/attach_test.dart index 92773934aa..8e2f771c23 100644 --- a/packages/flutter_tools/test/commands.shard/hermetic/attach_test.dart +++ b/packages/flutter_tools/test/commands.shard/hermetic/attach_test.dart @@ -78,7 +78,6 @@ void main() { MockPortForwarder portForwarder; MockDartDevelopmentService mockDds; MockAndroidDevice device; - MockHttpClient httpClient; setUp(() { fakeLogReader = FakeDeviceLogReader(); @@ -98,13 +97,6 @@ void main() { when(mockDds.startDartDevelopmentService(any, any, false, any)).thenReturn(null); when(mockDds.uri).thenReturn(Uri.parse('http://localhost:8181')); when(mockDds.done).thenAnswer((_) => noopCompleter.future); - final HttpClientRequest httpClientRequest = MockHttpClientRequest(); - httpClient = MockHttpClient(); - when(httpClient.putUrl(any)) - .thenAnswer((_) => Future.value(httpClientRequest)); - when(httpClientRequest.headers).thenReturn(MockHttpHeaders()); - when(httpClientRequest.close()) - .thenAnswer((_) => Future.value(MockHttpClientResponse())); // We cannot add the device to a device manager because that is // only enabled by the context of each testUsingContext call. @@ -925,6 +917,3 @@ class TestHotRunnerFactory extends HotRunnerFactory { } class MockDartDevelopmentService extends Mock implements DartDevelopmentService {} -class MockHttpClientRequest extends Mock implements HttpClientRequest {} -class MockHttpClientResponse extends Mock implements HttpClientResponse {} -class MockHttpHeaders extends Mock implements HttpHeaders {} diff --git a/packages/flutter_tools/test/commands.shard/permeable/create_test.dart b/packages/flutter_tools/test/commands.shard/permeable/create_test.dart index 4db25b0832..1e5ce26347 100755 --- a/packages/flutter_tools/test/commands.shard/permeable/create_test.dart +++ b/packages/flutter_tools/test/commands.shard/permeable/create_test.dart @@ -6,7 +6,6 @@ import 'dart:async'; import 'dart:convert'; -import 'dart:typed_data'; import 'package:args/command_runner.dart'; import 'package:file_testing/file_testing.dart'; @@ -31,6 +30,7 @@ import 'package:pubspec_parse/pubspec_parse.dart'; import '../../src/common.dart'; import '../../src/context.dart'; +import '../../src/fake_http_client.dart'; import '../../src/pubspec_schema.dart'; import '../../src/testbed.dart'; @@ -1627,7 +1627,16 @@ void main() { expect(projectDir.childDirectory('lib').childFile('main.dart').readAsStringSync(), contains('void main() {}')); }, overrides: { - HttpClientFactory: () => () => MockHttpClient(200, result: 'void main() {}'), + HttpClientFactory: () { + return () { + return FakeHttpClient.list([ + FakeRequest( + Uri.parse('https://master-api.flutter.dev/snippets/foo.bar.Baz.dart'), + response: FakeResponse(body: utf8.encode('void main() {}')), + ) + ]); + }; + }, }); testUsingContext('null-safe sample-based project have no analyzer errors', () async { @@ -1641,7 +1650,16 @@ void main() { contains('String?'), // uses null-safe syntax ); }, overrides: { - HttpClientFactory: () => () => MockHttpClient(200, result: 'void main() { String? foo; print(foo); }'), + HttpClientFactory: () { + return () { + return FakeHttpClient.list([ + FakeRequest( + Uri.parse('https://master-api.flutter.dev/snippets/foo.bar.Baz.dart'), + response: FakeResponse(body: utf8.encode('void main() { String? foo; print(foo); }')), + ) + ]); + }; + }, }); testUsingContext('can write samples index to disk', () async { @@ -1659,8 +1677,16 @@ void main() { expect(expectedFile, exists); expect(expectedFile.readAsStringSync(), equals(samplesIndexJson)); }, overrides: { - HttpClientFactory: () => - () => MockHttpClient(200, result: samplesIndexJson), + HttpClientFactory: () { + return () { + return FakeHttpClient.list([ + FakeRequest( + Uri.parse('https://master-api.flutter.dev/snippets/index.json'), + response: FakeResponse(body: utf8.encode(samplesIndexJson)), + ) + ]); + }; + }, }); testUsingContext('Throws tool exit on empty samples index', () async { @@ -1680,8 +1706,15 @@ void main() { message: 'Unable to download samples', )); }, overrides: { - HttpClientFactory: () => - () => MockHttpClient(200, result: ''), + HttpClientFactory: () { + return () { + return FakeHttpClient.list([ + FakeRequest( + Uri.parse('https://master-api.flutter.dev/snippets/index.json'), + ) + ]); + }; + }, }); testUsingContext('provides an error to the user if samples json download fails', () async { @@ -1697,8 +1730,16 @@ void main() { await expectLater(runner.run(args), throwsToolExit(exitCode: 2, message: 'Failed to write samples')); expect(globals.fs.file(outputFile), isNot(exists)); }, overrides: { - HttpClientFactory: () => - () => MockHttpClient(404, result: 'not found'), + HttpClientFactory: () { + return () { + return FakeHttpClient.list([ + FakeRequest( + Uri.parse('https://master-api.flutter.dev/snippets/index.json'), + response: const FakeResponse(statusCode: HttpStatus.notFound), + ) + ]); + }; + }, }); testUsingContext('plugin does not support any platform by default', () async { @@ -2655,76 +2696,3 @@ class LoggingProcessManager extends LocalProcessManager { ); } } - -class MockHttpClient implements HttpClient { - MockHttpClient(this.statusCode, {this.result}); - - final int statusCode; - final String result; - - @override - Future getUrl(Uri url) async { - return MockHttpClientRequest(statusCode, result: result); - } - - @override - dynamic noSuchMethod(Invocation invocation) { - throw 'io.HttpClient - $invocation'; - } -} - -class MockHttpClientRequest implements HttpClientRequest { - MockHttpClientRequest(this.statusCode, {this.result}); - - final int statusCode; - final String result; - - @override - Future close() async { - return MockHttpClientResponse(statusCode, result: result); - } - - @override - dynamic noSuchMethod(Invocation invocation) { - throw 'io.HttpClientRequest - $invocation'; - } -} - -class MockHttpClientResponse implements HttpClientResponse { - MockHttpClientResponse(this.statusCode, {this.result}); - - @override - final int statusCode; - - final String result; - - @override - String get reasonPhrase => ''; - - @override - HttpClientResponseCompressionState get compressionState { - return HttpClientResponseCompressionState.decompressed; - } - - @override - StreamSubscription listen( - void onData(Uint8List event), { - Function onError, - void onDone(), - bool cancelOnError, - }) { - return Stream.fromIterable([Uint8List.fromList(result.codeUnits)]) - .listen(onData, onError: onError, onDone: onDone, cancelOnError: cancelOnError); - } - - @override - Future forEach(void Function(Uint8List element) action) { - action(Uint8List.fromList(result.codeUnits)); - return Future.value(); - } - - @override - dynamic noSuchMethod(Invocation invocation) { - throw 'io.HttpClientResponse - $invocation'; - } -} diff --git a/packages/flutter_tools/test/general.shard/artifact_updater_test.dart b/packages/flutter_tools/test/general.shard/artifact_updater_test.dart index a75fb3ecee..506a741d0a 100644 --- a/packages/flutter_tools/test/general.shard/artifact_updater_test.dart +++ b/packages/flutter_tools/test/general.shard/artifact_updater_test.dart @@ -18,6 +18,7 @@ import 'package:flutter_tools/src/cache.dart'; import 'package:mockito/mockito.dart'; import '../src/common.dart'; +import '../src/fake_http_client.dart'; import '../src/fakes.dart'; final Platform testPlatform = FakePlatform(environment: const {}); @@ -32,7 +33,7 @@ void main() { logger: logger, operatingSystemUtils: operatingSystemUtils, platform: testPlatform, - httpClient: MockHttpClient(), + httpClient: FakeHttpClient.any(), tempStorage: fileSystem.currentDirectory.childDirectory('temp') ..createSync(), ); @@ -55,7 +56,7 @@ void main() { logger: logger, operatingSystemUtils: operatingSystemUtils, platform: testPlatform, - httpClient: MockHttpClient(), + httpClient: FakeHttpClient.any(), tempStorage: fileSystem.currentDirectory.childDirectory('temp') ..createSync(), ); @@ -80,17 +81,19 @@ void main() { final MockOperatingSystemUtils operatingSystemUtils = MockOperatingSystemUtils(); final MemoryFileSystem fileSystem = MemoryFileSystem.test(); final BufferLogger logger = BufferLogger.test(); - final MockHttpClient client = MockHttpClient(); - client.testRequest.testResponse.headers = FakeHttpHeaders(>{ - 'x-goog-hash': [], - }); final ArtifactUpdater artifactUpdater = ArtifactUpdater( fileSystem: fileSystem, logger: logger, operatingSystemUtils: operatingSystemUtils, platform: testPlatform, - httpClient: client, + httpClient: FakeHttpClient.list([ + FakeRequest(Uri.parse('http:///test.zip'), response: const FakeResponse( + headers: >{ + 'x-goog-hash': [], + } + )), + ]), tempStorage: fileSystem.currentDirectory.childDirectory('temp') ..createSync(), ); @@ -109,20 +112,23 @@ void main() { final MockOperatingSystemUtils operatingSystemUtils = MockOperatingSystemUtils(); final MemoryFileSystem fileSystem = MemoryFileSystem.test(); final BufferLogger logger = BufferLogger.test(); - final MockHttpClient client = MockHttpClient(); - client.testRequest.testResponse.headers = FakeHttpHeaders(>{ - 'x-goog-hash': [ - 'foo-bar-baz', - 'md5=k7iFrf4NoInN9jSQT9WfcQ==' - ], - }); final ArtifactUpdater artifactUpdater = ArtifactUpdater( fileSystem: fileSystem, logger: logger, operatingSystemUtils: operatingSystemUtils, platform: testPlatform, - httpClient: client, + httpClient: FakeHttpClient.list([ + FakeRequest(Uri.parse('http:///test.zip'), response: const FakeResponse( + body: [0], + headers: >{ + 'x-goog-hash': [ + 'foo-bar-baz', + 'md5=k7iFrf4NoInN9jSQT9WfcQ==' + ], + } + )), + ]), tempStorage: fileSystem.currentDirectory.childDirectory('temp') ..createSync(), ); @@ -141,20 +147,31 @@ void main() { final MockOperatingSystemUtils operatingSystemUtils = MockOperatingSystemUtils(); final MemoryFileSystem fileSystem = MemoryFileSystem.test(); final BufferLogger logger = BufferLogger.test(); - final MockHttpClient client = MockHttpClient(); - client.testRequest.testResponse.headers = FakeHttpHeaders(>{ - 'x-goog-hash': [ - 'foo-bar-baz', - 'md5=k7iFrf4SQT9WfcQ==' - ], - }); final ArtifactUpdater artifactUpdater = ArtifactUpdater( fileSystem: fileSystem, logger: logger, operatingSystemUtils: operatingSystemUtils, platform: testPlatform, - httpClient: client, + httpClient: FakeHttpClient.list([ + FakeRequest(Uri.parse('http:///test.zip'), response: const FakeResponse( + body: [0], + headers: >{ + 'x-goog-hash': [ + 'foo-bar-baz', + 'md5=k7iFrf4SQT9WfcQ==' + ], + } + )), + FakeRequest(Uri.parse('http:///test.zip'), response: const FakeResponse( + headers: >{ + 'x-goog-hash': [ + 'foo-bar-baz', + 'md5=k7iFrf4SQT9WfcQ==' + ], + } + )), + ]), tempStorage: fileSystem.currentDirectory.childDirectory('temp') ..createSync(), ); @@ -179,7 +196,10 @@ void main() { logger: logger, operatingSystemUtils: operatingSystemUtils, platform: testPlatform, - httpClient: MockHttpClient()..exceptionOnFirstRun = true, + httpClient: FakeHttpClient.list([ + FakeRequest(Uri.parse('http:///test.zip'), responseError: const HttpException('')), + FakeRequest(Uri.parse('http:///test.zip')), + ]), tempStorage: fileSystem.currentDirectory.childDirectory('temp') ..createSync(), ); @@ -197,14 +217,16 @@ void main() { final MockOperatingSystemUtils operatingSystemUtils = MockOperatingSystemUtils(); final MemoryFileSystem fileSystem = MemoryFileSystem.test(); final BufferLogger logger = BufferLogger.test(); - final MockHttpClient client = MockHttpClient(); - client.testRequest.testResponse.statusCode = HttpStatus.preconditionFailed; + final ArtifactUpdater artifactUpdater = ArtifactUpdater( fileSystem: fileSystem, logger: logger, operatingSystemUtils: operatingSystemUtils, platform: testPlatform, - httpClient: client, + httpClient: FakeHttpClient.list([ + FakeRequest(Uri.parse('http:///test.zip'), response: const FakeResponse(statusCode: HttpStatus.preconditionFailed)), + FakeRequest(Uri.parse('http:///test.zip'), response: const FakeResponse(statusCode: HttpStatus.preconditionFailed)), + ]), tempStorage: fileSystem.currentDirectory.childDirectory('temp') ..createSync(), ); @@ -215,7 +237,6 @@ void main() { fileSystem.currentDirectory.childDirectory('out'), ), throwsToolExit()); - expect(client.attempts, 2); expect(logger.statusText, contains('test message')); expect(fileSystem.file('out/test'), isNot(exists)); }); @@ -224,8 +245,6 @@ void main() { final MockOperatingSystemUtils operatingSystemUtils = MockOperatingSystemUtils(); final MemoryFileSystem fileSystem = MemoryFileSystem.test(); final BufferLogger logger = BufferLogger.test(); - final MockHttpClient client = MockHttpClient(); - client.argumentError = true; final ArtifactUpdater artifactUpdater = ArtifactUpdater( fileSystem: fileSystem, logger: logger, @@ -235,7 +254,9 @@ void main() { 'FLUTTER_STORAGE_BASE_URL': 'foo-bar' }, ), - httpClient: client, + httpClient: FakeHttpClient.list([ + FakeRequest(Uri.parse('http:///foo-bar/test.zip'), responseError: ArgumentError()) + ]), tempStorage: fileSystem.currentDirectory.childDirectory('temp') ..createSync(), ); @@ -246,7 +267,6 @@ void main() { fileSystem.currentDirectory.childDirectory('out'), ), throwsToolExit()); - expect(client.attempts, 1); expect(logger.statusText, contains('test message')); expect(fileSystem.file('out/test'), isNot(exists)); }); @@ -255,14 +275,14 @@ void main() { final MockOperatingSystemUtils operatingSystemUtils = MockOperatingSystemUtils(); final MemoryFileSystem fileSystem = MemoryFileSystem.test(); final BufferLogger logger = BufferLogger.test(); - final MockHttpClient client = MockHttpClient(); - client.argumentError = true; final ArtifactUpdater artifactUpdater = ArtifactUpdater( fileSystem: fileSystem, logger: logger, operatingSystemUtils: operatingSystemUtils, platform: testPlatform, - httpClient: client, + httpClient: FakeHttpClient.list([ + FakeRequest(Uri.parse('http:///test.zip'), responseError: ArgumentError()), + ]), tempStorage: fileSystem.currentDirectory.childDirectory('temp') ..createSync(), ); @@ -273,7 +293,6 @@ void main() { fileSystem.currentDirectory.childDirectory('out'), ), throwsA(isA())); - expect(client.attempts, 1); expect(logger.statusText, contains('test message')); expect(fileSystem.file('out/test'), isNot(exists)); }); @@ -287,7 +306,7 @@ void main() { logger: logger, operatingSystemUtils: operatingSystemUtils, platform: testPlatform, - httpClient: MockHttpClient(), + httpClient: FakeHttpClient.any(), tempStorage: fileSystem.currentDirectory.childDirectory('temp') ..createSync(), ); @@ -311,7 +330,7 @@ void main() { logger: logger, operatingSystemUtils: operatingSystemUtils, platform: testPlatform, - httpClient: MockHttpClient(), + httpClient: FakeHttpClient.any(), tempStorage: fileSystem.currentDirectory.childDirectory('temp') ..createSync(), ); @@ -335,7 +354,7 @@ void main() { logger: logger, operatingSystemUtils: operatingSystemUtils, platform: testPlatform, - httpClient: MockHttpClient(), + httpClient: FakeHttpClient.any(), tempStorage: fileSystem.currentDirectory.childDirectory('temp') ..createSync(), ); @@ -359,7 +378,7 @@ void main() { logger: logger, operatingSystemUtils: operatingSystemUtils, platform: testPlatform, - httpClient: MockHttpClient(), + httpClient: FakeHttpClient.any(), tempStorage: fileSystem.currentDirectory.childDirectory('temp') ..createSync(), ); @@ -383,7 +402,7 @@ void main() { logger: logger, operatingSystemUtils: operatingSystemUtils, platform: testPlatform, - httpClient: MockHttpClient(), + httpClient: FakeHttpClient.any(), tempStorage: fileSystem.currentDirectory.childDirectory('temp') ..createSync(), ); @@ -405,7 +424,7 @@ void main() { logger: logger, operatingSystemUtils: operatingSystemUtils, platform: testPlatform, - httpClient: MockHttpClient(), + httpClient: FakeHttpClient.any(), tempStorage: fileSystem.currentDirectory.childDirectory('temp') ..createSync(), ); @@ -448,57 +467,3 @@ class MockOperatingSystemUtils extends Mock implements OperatingSystemUtils { .createSync(); } } - -class MockHttpClient extends Mock implements HttpClient { - int attempts = 0; - bool argumentError = false; - bool exceptionOnFirstRun = false; - final MockHttpClientRequest testRequest = MockHttpClientRequest(); - - @override - Future getUrl(Uri url) async { - if (exceptionOnFirstRun && attempts == 0) { - attempts += 1; - throw Exception(); - } - attempts += 1; - if (argumentError) { - throw ArgumentError(); - } - return testRequest; - } -} - -class MockHttpClientRequest extends Mock implements HttpClientRequest { - final MockHttpClientResponse testResponse = MockHttpClientResponse(); - - @override - Future close() async { - return testResponse; - } -} - -class MockHttpClientResponse extends Mock implements HttpClientResponse { - @override - int statusCode = HttpStatus.ok; - - @override - HttpHeaders headers = FakeHttpHeaders(>{}); - - @override - Future forEach(void Function(List element) action) async { - action([0]); - return; - } -} - -class FakeHttpHeaders extends Fake implements HttpHeaders { - FakeHttpHeaders(this.values); - - final Map> values; - - @override - List operator [](String key) { - return values[key]; - } -} diff --git a/packages/flutter_tools/test/general.shard/base/bot_detector_test.dart b/packages/flutter_tools/test/general.shard/base/bot_detector_test.dart index f8fe5fbd84..cab0c9e1bb 100644 --- a/packages/flutter_tools/test/general.shard/base/bot_detector_test.dart +++ b/packages/flutter_tools/test/general.shard/base/bot_detector_test.dart @@ -4,45 +4,32 @@ // @dart = 2.8 -import 'dart:async'; - import 'package:file/memory.dart'; import 'package:flutter_tools/src/base/bot_detector.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/persistent_tool_state.dart'; -import 'package:mockito/mockito.dart'; -import 'package:fake_async/fake_async.dart'; import '../../src/common.dart'; +import '../../src/fake_http_client.dart'; import '../../src/fakes.dart'; +final Uri azureUrl = Uri.parse('http://169.254.169.254/metadata/instance'); + void main() { group('BotDetector', () { FakePlatform fakePlatform; FakeStdio fakeStdio; - MockHttpClient mockHttpClient; - MockHttpClientRequest mockHttpClientRequest; - MockHttpHeaders mockHttpHeaders; - BotDetector botDetector; PersistentToolState persistentToolState; setUp(() { fakePlatform = FakePlatform()..environment = {}; fakeStdio = FakeStdio(); - mockHttpClient = MockHttpClient(); - mockHttpClientRequest = MockHttpClientRequest(); - mockHttpHeaders = MockHttpHeaders(); persistentToolState = PersistentToolState.test( directory: MemoryFileSystem.test().currentDirectory, logger: BufferLogger.test(), ); - botDetector = BotDetector( - platform: fakePlatform, - httpClientFactory: () => mockHttpClient, - persistentToolState: persistentToolState, - ); }); group('isRunningOnBot', () { @@ -50,6 +37,12 @@ void main() { fakePlatform.environment['BOT'] = 'false'; fakePlatform.environment['TRAVIS'] = 'true'; + final BotDetector botDetector = BotDetector( + platform: fakePlatform, + httpClientFactory: () => FakeHttpClient.any(), + persistentToolState: persistentToolState, + ); + expect(await botDetector.isRunningOnBot, isFalse); expect(persistentToolState.isRunningOnBot, isFalse); }); @@ -58,14 +51,25 @@ void main() { fakePlatform.environment['FLUTTER_HOST'] = 'foo'; fakePlatform.environment['TRAVIS'] = 'true'; + final BotDetector botDetector = BotDetector( + platform: fakePlatform, + httpClientFactory: () => FakeHttpClient.any(), + persistentToolState: persistentToolState, + ); + expect(await botDetector.isRunningOnBot, isFalse); expect(persistentToolState.isRunningOnBot, isFalse); }); testWithoutContext('returns false with and without a terminal attached', () async { - when(mockHttpClient.getUrl(any)).thenAnswer((_) { - throw const SocketException('HTTP connection timed out'); - }); + final BotDetector botDetector = BotDetector( + platform: fakePlatform, + httpClientFactory: () => FakeHttpClient.list([ + FakeRequest(azureUrl, responseError: const SocketException('HTTP connection timed out')), + ]), + persistentToolState: persistentToolState, + ); + fakeStdio.stdout.hasTerminal = true; expect(await botDetector.isRunningOnBot, isFalse); fakeStdio.stdout.hasTerminal = false; @@ -76,38 +80,52 @@ void main() { testWithoutContext('can test analytics outputs on bots when outputting to a file', () async { fakePlatform.environment['TRAVIS'] = 'true'; fakePlatform.environment['FLUTTER_ANALYTICS_LOG_FILE'] = '/some/file'; + + final BotDetector botDetector = BotDetector( + platform: fakePlatform, + httpClientFactory: () => FakeHttpClient.any(), + persistentToolState: persistentToolState, + ); + expect(await botDetector.isRunningOnBot, isFalse); expect(persistentToolState.isRunningOnBot, isFalse); }); testWithoutContext('returns true when azure metadata is reachable', () async { - when(mockHttpClient.getUrl(any)).thenAnswer((_) { - return Future.value(mockHttpClientRequest); - }); - when(mockHttpClientRequest.headers).thenReturn(mockHttpHeaders); + final BotDetector botDetector = BotDetector( + platform: fakePlatform, + httpClientFactory: () => FakeHttpClient.any(), + persistentToolState: persistentToolState, + ); expect(await botDetector.isRunningOnBot, isTrue); expect(persistentToolState.isRunningOnBot, isTrue); }); testWithoutContext('caches azure bot detection results across instances', () async { - when(mockHttpClient.getUrl(any)).thenAnswer((_) { - return Future.value(mockHttpClientRequest); - }); - when(mockHttpClientRequest.headers).thenReturn(mockHttpHeaders); + final BotDetector botDetector = BotDetector( + platform: fakePlatform, + httpClientFactory: () => FakeHttpClient.any(), + persistentToolState: persistentToolState, + ); expect(await botDetector.isRunningOnBot, isTrue); expect(await BotDetector( platform: fakePlatform, - httpClientFactory: () => mockHttpClient, + httpClientFactory: () => FakeHttpClient.list([]), persistentToolState: persistentToolState, ).isRunningOnBot, isTrue); - verify(mockHttpClient.getUrl(any)).called(1); }); testWithoutContext('returns true when running on borg', () async { fakePlatform.environment['BORG_ALLOC_DIR'] = 'true'; + final BotDetector botDetector = BotDetector( + platform: fakePlatform, + httpClientFactory: () => FakeHttpClient.any(), + persistentToolState: persistentToolState, + ); + expect(await botDetector.isRunningOnBot, isTrue); expect(persistentToolState.isRunningOnBot, isTrue); }); @@ -115,60 +133,34 @@ void main() { }); group('AzureDetector', () { - AzureDetector azureDetector; - MockHttpClient mockHttpClient; - MockHttpClientRequest mockHttpClientRequest; - MockHttpHeaders mockHttpHeaders; - - setUp(() { - mockHttpClient = MockHttpClient(); - mockHttpClientRequest = MockHttpClientRequest(); - mockHttpHeaders = MockHttpHeaders(); - azureDetector = AzureDetector( - httpClientFactory: () => mockHttpClient, - ); - }); - testWithoutContext('isRunningOnAzure returns false when connection times out', () async { - when(mockHttpClient.getUrl(any)).thenAnswer((_) { - throw const SocketException('HTTP connection timed out'); - }); + final AzureDetector azureDetector = AzureDetector( + httpClientFactory: () => FakeHttpClient.list([ + FakeRequest(azureUrl, responseError: const SocketException('HTTP connection timed out')), + ], + )); expect(await azureDetector.isRunningOnAzure, isFalse); }); - testWithoutContext('isRunningOnAzure returns false when the http request times out', () { - FakeAsync().run((FakeAsync time) async { - when(mockHttpClient.getUrl(any)).thenAnswer((_) { - final Completer completer = Completer(); - return completer.future; // Never completed to test timeout behavior. - }); - final Future onBot = azureDetector.isRunningOnAzure; - time.elapse(const Duration(seconds: 2)); - - expect(await onBot, isFalse); - }); - }); - testWithoutContext('isRunningOnAzure returns false when OsError is thrown', () async { - when(mockHttpClient.getUrl(any)).thenAnswer((_) { - throw const OSError('Connection Refused', 111); - }); + final AzureDetector azureDetector = AzureDetector( + httpClientFactory: () => FakeHttpClient.list([ + FakeRequest(azureUrl, responseError: const OSError('Connection Refused', 111)), + ], + )); expect(await azureDetector.isRunningOnAzure, isFalse); }); testWithoutContext('isRunningOnAzure returns true when azure metadata is reachable', () async { - when(mockHttpClient.getUrl(any)).thenAnswer((_) { - return Future.value(mockHttpClientRequest); - }); - when(mockHttpClientRequest.headers).thenReturn(mockHttpHeaders); + final AzureDetector azureDetector = AzureDetector( + httpClientFactory: () => FakeHttpClient.list([ + FakeRequest(azureUrl), + ], + )); expect(await azureDetector.isRunningOnAzure, isTrue); }); }); } - -class MockHttpClient extends Mock implements HttpClient {} -class MockHttpClientRequest extends Mock implements HttpClientRequest {} -class MockHttpHeaders extends Mock implements HttpHeaders {} diff --git a/packages/flutter_tools/test/general.shard/base/net_test.dart b/packages/flutter_tools/test/general.shard/base/net_test.dart index dc4eee7c94..d5fa4c640e 100644 --- a/packages/flutter_tools/test/general.shard/base/net_test.dart +++ b/packages/flutter_tools/test/general.shard/base/net_test.dart @@ -4,19 +4,20 @@ // @dart = 2.8 -import 'dart:async'; import 'dart:convert'; import 'package:file/file.dart'; import 'package:file/memory.dart'; import 'package:flutter_tools/src/base/file_system.dart'; import 'package:flutter_tools/src/base/io.dart' as io; +import 'package:flutter_tools/src/base/io.dart'; import 'package:flutter_tools/src/base/logger.dart'; import 'package:flutter_tools/src/base/net.dart'; import 'package:flutter_tools/src/base/platform.dart'; import 'package:fake_async/fake_async.dart'; import '../../src/common.dart'; +import '../../src/fake_http_client.dart'; void main() { BufferLogger testLogger; @@ -42,75 +43,93 @@ void main() { }); testWithoutContext('fetchUrl() gets the data', () async { - final Net net = createNet(FakeHttpClient(200, data: responseString)); + final Net net = createNet( + FakeHttpClient.list([ + FakeRequest(Uri.parse('http://example.invalid/'), response: FakeResponse( + body: utf8.encode(responseString), + )), + ]) + ); + final List data = await net.fetchUrl(Uri.parse('http://example.invalid/')); expect(data, equals(responseData)); }); testWithoutContext('fetchUrl(destFile) writes the data to a file', () async { - final Net net = createNet(FakeHttpClient(200, data: responseString)); - final MemoryFileSystem fs = MemoryFileSystem.test(); - final File destFile = fs.file('dest_file')..createSync(); + final Net net = createNet( + FakeHttpClient.list([ + FakeRequest(Uri.parse('http://example.invalid/'), response: FakeResponse( + body: utf8.encode(responseString), + )), + ]) + ); + final MemoryFileSystem fileSystem = MemoryFileSystem.test(); + final File destFile = fileSystem.file('dest_file')..createSync(); final List data = await net.fetchUrl( Uri.parse('http://example.invalid/'), destFile: destFile, ); expect(data, equals([])); - expect(destFile.readAsStringSync(), equals(responseString)); + expect(destFile.readAsStringSync(), responseString); }); }); testWithoutContext('retry from 500', () async { - final Net net = createNet(FakeHttpClient(500)); - String error; - FakeAsync().run((FakeAsync time) { - net.fetchUrl(Uri.parse('http://example.invalid/')).then((List value) { - error = 'test completed unexpectedly'; - }, onError: (dynamic exception) { - error = 'test failed unexpectedly: $exception'; - }); - expect(testLogger.statusText, ''); - time.elapse(const Duration(milliseconds: 10000)); - expect(testLogger.statusText, - 'Download failed -- attempting retry 1 in 1 second...\n' - 'Download failed -- attempting retry 2 in 2 seconds...\n' - 'Download failed -- attempting retry 3 in 4 seconds...\n' - 'Download failed -- attempting retry 4 in 8 seconds...\n', - ); - }); + final Net net = createNet( + FakeHttpClient.list([ + FakeRequest(Uri.parse('http://example.invalid/'), response: const FakeResponse(statusCode: io.HttpStatus.internalServerError)), + FakeRequest(Uri.parse('http://example.invalid/'), response: const FakeResponse(statusCode: io.HttpStatus.internalServerError)), + FakeRequest(Uri.parse('http://example.invalid/'), response: const FakeResponse(statusCode: io.HttpStatus.internalServerError)), + FakeRequest(Uri.parse('http://example.invalid/'), response: const FakeResponse(statusCode: io.HttpStatus.internalServerError)), + ]) + ); + + await net.fetchUrl(Uri.parse('http://example.invalid/'), maxAttempts: 4, durationOverride: Duration.zero); + + expect(testLogger.statusText, + 'Download failed -- attempting retry 1 in 1 second...\n' + 'Download failed -- attempting retry 2 in 2 seconds...\n' + 'Download failed -- attempting retry 3 in 4 seconds...\n' + 'Download failed -- retry 4\n', + ); expect(testLogger.errorText, isEmpty); - expect(error, isNull); }); testWithoutContext('retry from network error', () async { - final Net net = createNet(FakeHttpClient(200)); - String error; - FakeAsync().run((FakeAsync time) { - net.fetchUrl(Uri.parse('http://example.invalid/')).then((List value) { - error = 'test completed unexpectedly'; - }, onError: (dynamic exception) { - error = 'test failed unexpectedly: $exception'; - }); - expect(testLogger.statusText, ''); - time.elapse(const Duration(milliseconds: 10000)); - expect(testLogger.statusText, - 'Download failed -- attempting retry 1 in 1 second...\n' - 'Download failed -- attempting retry 2 in 2 seconds...\n' - 'Download failed -- attempting retry 3 in 4 seconds...\n' - 'Download failed -- attempting retry 4 in 8 seconds...\n', - ); - }); + final Uri invalid = Uri.parse('http://example.invalid/'); + final Net net = createNet( + FakeHttpClient.list([ + FakeRequest(invalid, responseError: const io.SocketException('test')), + FakeRequest(invalid, responseError: const io.SocketException('test')), + FakeRequest(invalid, responseError: const io.SocketException('test')), + FakeRequest(invalid, responseError: const io.SocketException('test')), + ]) + ); + + await net.fetchUrl(Uri.parse('http://example.invalid/'), maxAttempts: 4, durationOverride: Duration.zero); + + expect(testLogger.statusText, + 'Download failed -- attempting retry 1 in 1 second...\n' + 'Download failed -- attempting retry 2 in 2 seconds...\n' + 'Download failed -- attempting retry 3 in 4 seconds...\n' + 'Download failed -- retry 4\n', + ); expect(testLogger.errorText, isEmpty); - expect(error, isNull); }); testWithoutContext('retry from SocketException', () async { - final Net net = createNet(FakeHttpClientThrowing( - const io.SocketException('test exception handling'), - )); + final Uri invalid = Uri.parse('http://example.invalid/'); + final Net net = createNet( + FakeHttpClient.list([ + FakeRequest(invalid, responseError: const io.SocketException('')), + FakeRequest(invalid, responseError: const io.SocketException('')), + FakeRequest(invalid, responseError: const io.SocketException('')), + FakeRequest(invalid, responseError: const io.SocketException('')), + ]) + ); String error; FakeAsync().run((FakeAsync time) { - net.fetchUrl(Uri.parse('http://example.invalid/')).then((List value) { + net.fetchUrl(invalid).then((List value) { error = 'test completed unexpectedly'; }, onError: (dynamic exception) { error = 'test failed unexpectedly: $exception'; @@ -130,12 +149,18 @@ void main() { }); testWithoutContext('no retry from HandshakeException', () async { - final Net net = createNet(FakeHttpClientThrowing( - const io.HandshakeException('test exception handling'), - )); + final Uri invalid = Uri.parse('http://example.invalid/'); + final Net net = createNet( + FakeHttpClient.list([ + FakeRequest(invalid, responseError: const io.HandshakeException('')), + FakeRequest(invalid, responseError: const io.HandshakeException('')), + FakeRequest(invalid, responseError: const io.HandshakeException('')), + FakeRequest(invalid, responseError: const io.HandshakeException('')), + ]) + ); String error; FakeAsync().run((FakeAsync time) { - net.fetchUrl(Uri.parse('http://example.invalid/')).then((List value) { + net.fetchUrl(invalid).then((List value) { error = 'test completed unexpectedly'; }, onError: (dynamic exception) { error = 'test failed: $exception'; @@ -149,10 +174,13 @@ void main() { }); testWithoutContext('check for bad override on ArgumentError', () async { + final Uri invalid = Uri.parse('example.invalid/'); final Net net = Net( - httpClientFactory: () => FakeHttpClientThrowing( - ArgumentError('test exception handling'), - ), + httpClientFactory: () { + return FakeHttpClient.list([ + FakeRequest(invalid, responseError: ArgumentError()), + ]); + }, logger: testLogger, platform: FakePlatform( environment: { @@ -177,12 +205,18 @@ void main() { }); testWithoutContext('retry from HttpException', () async { - final Net net = createNet(FakeHttpClientThrowing( - const io.HttpException('test exception handling'), - )); + final Uri invalid = Uri.parse('http://example.invalid/'); + final Net net = createNet( + FakeHttpClient.list([ + FakeRequest(invalid, responseError: const io.HttpException('')), + FakeRequest(invalid, responseError: const io.HttpException('')), + FakeRequest(invalid, responseError: const io.HttpException('')), + FakeRequest(invalid, responseError: const io.HttpException('')), + ]) + ); String error; FakeAsync().run((FakeAsync time) { - net.fetchUrl(Uri.parse('http://example.invalid/')).then((List value) { + net.fetchUrl(invalid).then((List value) { error = 'test completed unexpectedly'; }, onError: (dynamic exception) { error = 'test failed unexpectedly: $exception'; @@ -202,12 +236,18 @@ void main() { }); testWithoutContext('retry from HttpException when request throws', () async { - final Net net = createNet(FakeHttpClientThrowingRequest( - const io.HttpException('test exception handling'), - )); + final Uri invalid = Uri.parse('http://example.invalid/'); + final Net net = createNet( + FakeHttpClient.list([ + FakeRequest(invalid, responseError: const io.HttpException('')), + FakeRequest(invalid, responseError: const io.HttpException('')), + FakeRequest(invalid, responseError: const io.HttpException('')), + FakeRequest(invalid, responseError: const io.HttpException('')), + ]) + ); String error; FakeAsync().run((FakeAsync time) { - net.fetchUrl(Uri.parse('http://example.invalid/')).then((List value) { + net.fetchUrl(invalid).then((List value) { error = 'test completed unexpectedly'; }, onError: (dynamic exception) { error = 'test failed unexpectedly: $exception'; @@ -227,11 +267,24 @@ void main() { }); testWithoutContext('max attempts', () async { - final Net net = createNet(FakeHttpClient(500)); + final Uri invalid = Uri.parse('http://example.invalid/'); + final Net net = createNet( + FakeHttpClient.list([ + FakeRequest(invalid, response: const FakeResponse( + statusCode: HttpStatus.internalServerError, + )), + FakeRequest(invalid, response: const FakeResponse( + statusCode: HttpStatus.internalServerError, + )), + FakeRequest(invalid, response: const FakeResponse( + statusCode: HttpStatus.internalServerError, + )), + ]) + ); String error; List actualResult; FakeAsync().run((FakeAsync time) { - net.fetchUrl(Uri.parse('http://example.invalid/'), maxAttempts: 3).then((List value) { + net.fetchUrl(invalid, maxAttempts: 3).then((List value) { actualResult = value; }, onError: (dynamic exception) { error = 'test failed unexpectedly: $exception'; @@ -249,155 +302,40 @@ void main() { expect(actualResult, isNull); }); - testWithoutContext('remote file non-existant', () async { - final Net net = createNet(FakeHttpClient(404)); + testWithoutContext('remote file non-existent', () async { final Uri invalid = Uri.parse('http://example.invalid/'); + final Net net = createNet( + FakeHttpClient.list([ + FakeRequest(invalid, method: HttpMethod.head, response: const FakeResponse( + statusCode: HttpStatus.notFound, + )), + ]) + ); final bool result = await net.doesRemoteFileExist(invalid); expect(result, false); }); testWithoutContext('remote file server error', () async { - final Net net = createNet(FakeHttpClient(500)); final Uri valid = Uri.parse('http://example.valid/'); + final Net net = createNet( + FakeHttpClient.list([ + FakeRequest(valid, method: HttpMethod.head, response: const FakeResponse( + statusCode: HttpStatus.internalServerError, + )), + ]) + ); final bool result = await net.doesRemoteFileExist(valid); expect(result, false); }); testWithoutContext('remote file exists', () async { - final Net net = createNet(FakeHttpClient(200)); final Uri valid = Uri.parse('http://example.valid/'); + final Net net = createNet( + FakeHttpClient.list([ + FakeRequest(valid, method: HttpMethod.head), + ]) + ); final bool result = await net.doesRemoteFileExist(valid); expect(result, true); }); } - -class FakeHttpClientThrowing implements io.HttpClient { - FakeHttpClientThrowing(this.exception); - - final Object exception; - - @override - Future getUrl(Uri url) async { - throw exception; - } - - @override - dynamic noSuchMethod(Invocation invocation) { - throw 'io.HttpClient - $invocation'; - } -} - -class FakeHttpClient implements io.HttpClient { - FakeHttpClient(this.statusCode, { this.data }); - - final int statusCode; - final String data; - - @override - Future getUrl(Uri url) async { - return FakeHttpClientRequest(statusCode, data: data); - } - - @override - Future headUrl(Uri url) async { - return FakeHttpClientRequest(statusCode); - } - - @override - dynamic noSuchMethod(Invocation invocation) { - throw 'io.HttpClient - $invocation'; - } -} - -class FakeHttpClientThrowingRequest implements io.HttpClient { - FakeHttpClientThrowingRequest(this.exception); - - final Object exception; - - @override - Future getUrl(Uri url) async { - return FakeHttpClientRequestThrowing(exception); - } - - @override - dynamic noSuchMethod(Invocation invocation) { - throw 'io.HttpClient - $invocation'; - } -} - -class FakeHttpClientRequest implements io.HttpClientRequest { - FakeHttpClientRequest(this.statusCode, { this.data }); - - final int statusCode; - final String data; - - @override - Future close() async { - return FakeHttpClientResponse(statusCode, data: data); - } - - @override - dynamic noSuchMethod(Invocation invocation) { - throw 'io.HttpClientRequest - $invocation'; - } -} - -class FakeHttpClientRequestThrowing implements io.HttpClientRequest { - FakeHttpClientRequestThrowing(this.exception); - - final Object exception; - - @override - Future close() async { - throw exception; - } - - @override - dynamic noSuchMethod(Invocation invocation) { - throw 'io.HttpClientRequest - $invocation'; - } -} - -class FakeHttpClientResponse implements io.HttpClientResponse { - FakeHttpClientResponse(this.statusCode, { this.data }); - - @override - final int statusCode; - - final String data; - - @override - String get reasonPhrase => ''; - - @override - StreamSubscription> listen( - void onData(List event), { - Function onError, - void onDone(), - bool cancelOnError, - }) { - if (data == null) { - return Stream>.fromFuture(Future>.error( - const io.SocketException('test'), - )).listen(onData, onError: onError, onDone: onDone, cancelOnError: cancelOnError); - } else { - return Stream>.fromFuture(Future>.value( - utf8.encode(data), - )).listen(onData, onError: onError, onDone: onDone, cancelOnError: cancelOnError); - } - } - - @override - Future forEach(void Function(List element) action) async { - if (data == null) { - return Future.error(const io.SocketException('test')); - } else { - return Future.microtask(() => action(utf8.encode(data))); - } - } - - @override - dynamic noSuchMethod(Invocation invocation) { - throw 'io.HttpClientResponse - $invocation'; - } -} diff --git a/packages/flutter_tools/test/general.shard/crash_reporting_test.dart b/packages/flutter_tools/test/general.shard/crash_reporting_test.dart index dce763163a..dcc6618296 100644 --- a/packages/flutter_tools/test/general.shard/crash_reporting_test.dart +++ b/packages/flutter_tools/test/general.shard/crash_reporting_test.dart @@ -20,7 +20,7 @@ import 'package:http/testing.dart'; import '../src/common.dart'; import '../src/context.dart'; -import '../src/testbed.dart'; +import '../src/fake_http_client.dart'; void main() { BufferLogger logger; @@ -79,7 +79,7 @@ void main() { fileSystem: fs, logger: logger, flutterProjectFactory: FlutterProjectFactory(fileSystem: fs, logger: logger), - client: FakeHttpClient(), + client: FakeHttpClient.any(), ); final File file = fs.file('flutter_00.log'); diff --git a/packages/flutter_tools/test/general.shard/devfs_test.dart b/packages/flutter_tools/test/general.shard/devfs_test.dart index f8a8b8d8a3..7220b4bb0b 100644 --- a/packages/flutter_tools/test/general.shard/devfs_test.dart +++ b/packages/flutter_tools/test/general.shard/devfs_test.dart @@ -22,6 +22,7 @@ import 'package:package_config/package_config.dart'; import '../src/common.dart'; import '../src/context.dart'; +import '../src/fake_http_client.dart'; final FakeVmServiceRequest createDevFSRequest = FakeVmServiceRequest( method: '_createDevFS', @@ -110,7 +111,6 @@ void main() { }); testWithoutContext('DevFS create throws a DevFSException when vmservice disconnects unexpectedly', () async { - final HttpClient httpClient = MockHttpClient(); final FileSystem fileSystem = MemoryFileSystem.test(); final OperatingSystemUtils osUtils = MockOperatingSystemUtils(); final FakeVmServiceHost fakeVmServiceHost = FakeVmServiceHost( @@ -118,16 +118,6 @@ void main() { ); setHttpAddress(Uri.parse('http://localhost'), fakeVmServiceHost.vmService); - final MockHttpClientRequest httpRequest = MockHttpClientRequest(); - when(httpRequest.headers).thenReturn(MockHttpHeaders()); - when(httpClient.putUrl(any)).thenAnswer((Invocation invocation) { - return Future.value(httpRequest); - }); - final MockHttpClientResponse httpClientResponse = MockHttpClientResponse(); - when(httpRequest.close()).thenAnswer((Invocation invocation) { - return Future.value(httpClientResponse); - }); - final DevFS devFS = DevFS( fakeVmServiceHost.vmService, 'test', @@ -135,13 +125,12 @@ void main() { osUtils: osUtils, fileSystem: fileSystem, logger: BufferLogger.test(), - httpClient: httpClient, + httpClient: FakeHttpClient.any(), ); expect(() async => await devFS.create(), throwsA(isA())); }); - testWithoutContext('DevFS destroy is resiliant to vmservice disconnection', () async { - final HttpClient httpClient = MockHttpClient(); + testWithoutContext('DevFS destroy is resilient to vmservice disconnection', () async { final FileSystem fileSystem = MemoryFileSystem.test(); final OperatingSystemUtils osUtils = MockOperatingSystemUtils(); final FakeVmServiceHost fakeVmServiceHost = FakeVmServiceHost( @@ -152,15 +141,6 @@ void main() { ); setHttpAddress(Uri.parse('http://localhost'), fakeVmServiceHost.vmService); - final MockHttpClientRequest httpRequest = MockHttpClientRequest(); - when(httpRequest.headers).thenReturn(MockHttpHeaders()); - when(httpClient.putUrl(any)).thenAnswer((Invocation invocation) { - return Future.value(httpRequest); - }); - final MockHttpClientResponse httpClientResponse = MockHttpClientResponse(); - when(httpRequest.close()).thenAnswer((Invocation invocation) { - return Future.value(httpClientResponse); - }); final DevFS devFS = DevFS( fakeVmServiceHost.vmService, @@ -169,7 +149,7 @@ void main() { osUtils: osUtils, fileSystem: fileSystem, logger: BufferLogger.test(), - httpClient: httpClient, + httpClient: FakeHttpClient.any(), ); expect(await devFS.create(), isNotNull); @@ -177,7 +157,6 @@ void main() { }); testWithoutContext('DevFS retries uploads when connection reset by peer', () async { - final HttpClient httpClient = MockHttpClient(); final FileSystem fileSystem = MemoryFileSystem.test(); final OperatingSystemUtils osUtils = MockOperatingSystemUtils(); final MockResidentCompiler residentCompiler = MockResidentCompiler(); @@ -186,21 +165,6 @@ void main() { ); setHttpAddress(Uri.parse('http://localhost'), fakeVmServiceHost.vmService); - final MockHttpClientRequest httpRequest = MockHttpClientRequest(); - when(httpRequest.headers).thenReturn(MockHttpHeaders()); - when(httpClient.putUrl(any)).thenAnswer((Invocation invocation) { - return Future.value(httpRequest); - }); - final MockHttpClientResponse httpClientResponse = MockHttpClientResponse(); - int nRequest = 0; - const int kFailedAttempts = 5; - when(httpRequest.close()).thenAnswer((Invocation invocation) { - if (nRequest++ < kFailedAttempts) { - throw const OSError('Connection Reset by peer'); - } - return Future.value(httpClientResponse); - }); - when(residentCompiler.recompile( any, any, @@ -220,7 +184,14 @@ void main() { osUtils: osUtils, fileSystem: fileSystem, logger: BufferLogger.test(), - httpClient: httpClient, + httpClient: FakeHttpClient.list([ + 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, 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) + ]), uploadRetryThrottle: Duration.zero, ); await devFS.create(); @@ -237,9 +208,7 @@ void main() { expect(report.syncedBytes, 5); expect(report.success, isTrue); - verify(httpClient.putUrl(any)).called(kFailedAttempts + 1); - verify(httpRequest.close()).called(kFailedAttempts + 1); - verify(osUtils.gzipLevel1Stream(any)).called(kFailedAttempts + 1); + verify(osUtils.gzipLevel1Stream(any)).called(6); }); testWithoutContext('DevFS reports unsuccessful compile when errors are returned', () async { @@ -255,7 +224,7 @@ void main() { fileSystem: fileSystem, logger: BufferLogger.test(), osUtils: FakeOperatingSystemUtils(), - httpClient: MockHttpClient(), + httpClient: FakeHttpClient.any(), ); await devFS.create(); @@ -290,16 +259,6 @@ void main() { final FakeVmServiceHost fakeVmServiceHost = FakeVmServiceHost( requests: [createDevFSRequest], ); - final HttpClient httpClient = MockHttpClient(); - final MockHttpClientRequest httpRequest = MockHttpClientRequest(); - when(httpRequest.headers).thenReturn(MockHttpHeaders()); - when(httpClient.putUrl(any)).thenAnswer((Invocation invocation) { - return Future.value(httpRequest); - }); - final MockHttpClientResponse httpClientResponse = MockHttpClientResponse(); - when(httpRequest.close()).thenAnswer((Invocation invocation) async { - return httpClientResponse; - }); final DevFS devFS = DevFS( fakeVmServiceHost.vmService, @@ -308,7 +267,7 @@ void main() { fileSystem: fileSystem, logger: BufferLogger.test(), osUtils: FakeOperatingSystemUtils(), - httpClient: httpClient, + httpClient: FakeHttpClient.any(), ); await devFS.create(); @@ -407,7 +366,7 @@ void main() { fileSystem: fileSystem, logger: BufferLogger.test(), osUtils: FakeOperatingSystemUtils(), - httpClient: MockHttpClient(), + httpClient: FakeHttpClient.any(), ); await devFS.create(); @@ -469,9 +428,6 @@ void main() { }); } -class MockHttpClientRequest extends Mock implements HttpClientRequest {} -class MockHttpHeaders extends Mock implements HttpHeaders {} -class MockHttpClientResponse extends Mock implements HttpClientResponse {} class MockOperatingSystemUtils extends Mock implements OperatingSystemUtils {} class MockResidentCompiler extends Mock implements ResidentCompiler {} class MockFile extends Mock implements File {} diff --git a/packages/flutter_tools/test/general.shard/github_template_test.dart b/packages/flutter_tools/test/general.shard/github_template_test.dart index ae66086e4c..a777435095 100644 --- a/packages/flutter_tools/test/general.shard/github_template_test.dart +++ b/packages/flutter_tools/test/general.shard/github_template_test.dart @@ -14,6 +14,7 @@ import 'package:flutter_tools/src/reporting/reporting.dart'; import '../src/common.dart'; import '../src/context.dart'; +import '../src/fake_http_client.dart'; import '../src/testbed.dart'; const String _kShortURL = 'https://www.example.com/short'; @@ -161,7 +162,14 @@ void main() { final GitHubTemplateCreator creator = GitHubTemplateCreator( fileSystem: fs, logger: logger, - client: SuccessShortenURLFakeHttpClient(), + client: FakeHttpClient.list([ + FakeRequest(Uri.parse('https://git.io'), method: HttpMethod.post, response: const FakeResponse( + statusCode: 201, + headers: >{ + HttpHeaders.locationHeader: [_kShortURL], + } + )) + ]), flutterProjectFactory: FlutterProjectFactory( fileSystem: fs, logger: logger, @@ -180,7 +188,11 @@ void main() { final GitHubTemplateCreator creator = GitHubTemplateCreator( fileSystem: fs, logger: logger, - client: FakeHttpClient(), + client: FakeHttpClient.list([ + FakeRequest(Uri.parse('https://git.io'), method: HttpMethod.post, response: const FakeResponse( + statusCode: 500, + )) + ]), flutterProjectFactory: FlutterProjectFactory( fileSystem: fs, logger: logger, @@ -206,7 +218,7 @@ void main() { final GitHubTemplateCreator creator = GitHubTemplateCreator( fileSystem: fs, logger: logger, - client: FakeHttpClient(), + client: FakeHttpClient.any(), flutterProjectFactory: FlutterProjectFactory( fileSystem: fs, logger: logger, @@ -292,37 +304,7 @@ device_info-0.4.1+4 }); } - -class SuccessFakeHttpHeaders extends FakeHttpHeaders { - @override - List operator [](String name) => [_kShortURL]; -} - -class SuccessFakeHttpClientResponse extends FakeHttpClientResponse { - @override - int get statusCode => 201; - - @override - HttpHeaders get headers { - return SuccessFakeHttpHeaders(); - } -} - -class SuccessFakeHttpClientRequest extends FakeHttpClientRequest { - @override - Future close() async { - return SuccessFakeHttpClientResponse(); - } -} - -class SuccessShortenURLFakeHttpClient extends FakeHttpClient { - @override - Future postUrl(Uri url) async { - return SuccessFakeHttpClientRequest(); - } -} - -class FakeError implements Error { +class FakeError extends Error { @override StackTrace get stackTrace => StackTrace.fromString(''' #0 _File.open. (dart:io/file_impl.dart:366:9) diff --git a/packages/flutter_tools/test/general.shard/testbed_test.dart b/packages/flutter_tools/test/general.shard/testbed_test.dart index cc70986eb1..297bd0eae3 100644 --- a/packages/flutter_tools/test/general.shard/testbed_test.dart +++ b/packages/flutter_tools/test/general.shard/testbed_test.dart @@ -69,7 +69,7 @@ void main() { final HttpClientRequest request = await client.getUrl(null); final HttpClientResponse response = await request.close(); - expect(response.statusCode, HttpStatus.badRequest); + expect(response.statusCode, HttpStatus.ok); expect(response.contentLength, 0); }); }); diff --git a/packages/flutter_tools/test/src/context.dart b/packages/flutter_tools/test/src/context.dart index 71a5a538cd..47a99a3309 100644 --- a/packages/flutter_tools/test/src/context.dart +++ b/packages/flutter_tools/test/src/context.dart @@ -37,6 +37,7 @@ import 'package:meta/meta.dart'; import 'package:mockito/mockito.dart'; import 'common.dart'; +import 'fake_http_client.dart'; import 'fake_process_manager.dart'; import 'fakes.dart'; import 'throwing_pub.dart'; @@ -112,7 +113,7 @@ void testUsingContext( DeviceManager: () => FakeDeviceManager(), Doctor: () => FakeDoctor(globals.logger), FlutterVersion: () => MockFlutterVersion(), - HttpClient: () => MockHttpClient(), + HttpClient: () => FakeHttpClient.any(), IOSSimulatorUtils: () { final MockIOSSimulatorUtils mock = MockIOSSimulatorUtils(); when(mock.getAttachedDevices()).thenAnswer((Invocation _) async => []); @@ -378,8 +379,6 @@ class FakeXcodeProjectInterpreter implements XcodeProjectInterpreter { class MockFlutterVersion extends Mock implements FlutterVersion {} -class MockHttpClient extends Mock implements HttpClient {} - class MockCrashReporter extends Mock implements CrashReporter {} class LocalFileSystemBlockingSetCurrentDirectory extends LocalFileSystem { diff --git a/packages/flutter_tools/test/src/fake_http_client.dart b/packages/flutter_tools/test/src/fake_http_client.dart new file mode 100644 index 0000000000..b5c27f915b --- /dev/null +++ b/packages/flutter_tools/test/src/fake_http_client.dart @@ -0,0 +1,488 @@ +// 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 'dart:convert'; + +import 'dart:io'; + +import 'package:flutter_tools/src/base/io.dart'; + +/// The HTTP verb for a [FakeRequest]. +enum HttpMethod { + get, + put, + delete, + post, + patch, + head, +} + +HttpMethod _fromMethodString(String value) { + final String name = value.toLowerCase(); + switch (name) { + case 'get': + return HttpMethod.get; + case 'put': + return HttpMethod.put; + case 'delete': + return HttpMethod.delete; + case 'post': + return HttpMethod.post; + case 'patch': + return HttpMethod.patch; + case 'head': + return HttpMethod.head; + default: + throw StateError('Unrecognized HTTP method $value'); + } +} + +String _toMethodString(HttpMethod method) { + switch (method) { + case HttpMethod.get: + return 'GET'; + case HttpMethod.put: + return 'PUT'; + case HttpMethod.delete: + return 'DELETE'; + case HttpMethod.post: + return 'POST'; + case HttpMethod.patch: + return 'PATCH'; + case HttpMethod.head: + return 'HEAD'; + } + assert(false); + return null; +} + +/// Override the creation of all [HttpClient] objects with a zone injection. +/// +/// This should only be used when the http client cannot be set directly, such as +/// when testing `package:http` code. +Future overrideHttpClients(Future Function() callback, FakeHttpClient httpClient) async { + final HttpOverrides overrides = _FakeHttpClientOverrides(httpClient); + await HttpOverrides.runWithHttpOverrides(callback, overrides); +} + +class _FakeHttpClientOverrides extends HttpOverrides { + _FakeHttpClientOverrides(this.httpClient); + + final FakeHttpClient httpClient; + + @override + HttpClient createHttpClient(SecurityContext context) { + return httpClient; + } +} + +/// Create a fake request that configures the [FakeHttpClient] to respond +/// with the provided [response]. +/// +/// By default, returns a response with a 200 OK status code and an +/// empty response. If [responseError] is non-null, will throw this instead +/// of returning the response when closing the request. +class FakeRequest { + const FakeRequest(this.uri, { + this.method = HttpMethod.get, + this.response = FakeResponse.empty, + this.responseError, + }); + + final Uri uri; + final HttpMethod method; + final FakeResponse response; + final dynamic responseError; + + @override + String toString() => 'Request{${_toMethodString(method)}, $uri}'; +} + +/// The response the server will create for a given [FakeRequest]. +class FakeResponse { + const FakeResponse({ + this.statusCode = HttpStatus.ok, + this.body = const [], + this.headers = const >{}, + }); + + static const FakeResponse empty = FakeResponse(); + + final int statusCode; + final List body; + final Map> headers; +} + +/// A fake implementation of the HttpClient used for testing. +/// +/// This does not fully implement the HttpClient. If an additional method +/// is actually needed by the test script, then it should be added here +/// instead of in another fake. +class FakeHttpClient implements HttpClient { + /// Creates an HTTP client that responses to each provided + /// fake request with the provided fake response. + /// + /// This does not enforce any order on the requests, but if multiple + /// requests match then the first will be selected; + FakeHttpClient.list(List requests) + : _requests = requests.toList(); + + /// Creates an HTTP client that always returns an empty 200 request. + FakeHttpClient.any() : _any = true, _requests = []; + + bool _any = false; + final List _requests; + + @override + bool autoUncompress; + + @override + Duration connectionTimeout; + + @override + Duration idleTimeout; + + @override + int maxConnectionsPerHost; + + @override + String userAgent; + + @override + void addCredentials(Uri url, String realm, HttpClientCredentials credentials) { + throw UnimplementedError(); + } + + @override + void addProxyCredentials(String host, int port, String realm, HttpClientCredentials credentials) { + throw UnimplementedError(); + } + + @override + set authenticate(Future Function(Uri url, String scheme, String realm) f) { + throw UnimplementedError(); + } + + @override + set authenticateProxy(Future Function(String host, int port, String scheme, String realm) f) { + throw UnimplementedError(); + } + + @override + set badCertificateCallback(bool Function(X509Certificate cert, String host, int port) callback) { + throw UnimplementedError(); + } + + @override + void close({bool force = false}) { } + + @override + Future delete(String host, int port, String path) { + final Uri uri = Uri(host: host, port: port, path: path); + return deleteUrl(uri); + } + + @override + Future deleteUrl(Uri url) async { + return _findRequest(HttpMethod.delete, url); + } + + @override + set findProxy(String Function(Uri url) f) { } + + @override + Future get(String host, int port, String path) { + final Uri uri = Uri(host: host, port: port, path: path); + return getUrl(uri); + } + + @override + Future getUrl(Uri url) async { + return _findRequest(HttpMethod.get, url); + } + + @override + Future head(String host, int port, String path) { + final Uri uri = Uri(host: host, port: port, path: path); + return headUrl(uri); + } + + @override + Future headUrl(Uri url) async { + return _findRequest(HttpMethod.head, url); + } + + @override + Future open(String method, String host, int port, String path) { + final Uri uri = Uri(host: host, port: port, path: path); + return openUrl(method, uri); + } + + @override + Future openUrl(String method, Uri url) async { + return _findRequest(_fromMethodString(method), url); + } + + @override + Future patch(String host, int port, String path) { + final Uri uri = Uri(host: host, port: port, path: path); + return patchUrl(uri); + } + + @override + Future patchUrl(Uri url) async { + return _findRequest(HttpMethod.patch, url); + } + + @override + Future post(String host, int port, String path) { + final Uri uri = Uri(host: host, port: port, path: path); + return postUrl(uri); + } + + @override + Future postUrl(Uri url) async { + return _findRequest(HttpMethod.post, url); + } + + @override + Future put(String host, int port, String path) { + final Uri uri = Uri(host: host, port: port, path: path); + return putUrl(uri); + } + + @override + Future putUrl(Uri url) async { + return _findRequest(HttpMethod.put, url); + } + + int _requestCount = 0; + + _FakeHttpClientRequest _findRequest(HttpMethod method, Uri uri) { + final String methodString = _toMethodString(method); + if (_any) { + return _FakeHttpClientRequest( + FakeResponse.empty, + uri, + methodString, + null, + ); + } + FakeRequest matchedRequest; + for (final FakeRequest request in _requests) { + if (request.method == method && request.uri.toString() == uri.toString()) { + matchedRequest = request; + break; + } + } + if (matchedRequest == null) { + throw StateError( + 'Unexpected request for $method to $uri after $_requestCount requests.\n' + 'Pending requests: ${_requests.join(',')}' + ); + } + _requestCount += 1; + _requests.remove(matchedRequest); + return _FakeHttpClientRequest( + matchedRequest.response, + uri, + methodString, + matchedRequest.responseError, + ); + } +} + +class _FakeHttpClientRequest implements HttpClientRequest { + _FakeHttpClientRequest(this._response, this._uri, this._method, this._responseError); + + final FakeResponse _response; + final String _method; + final Uri _uri; + final dynamic _responseError; + + @override + bool bufferOutput; + + @override + int contentLength = 0; + + @override + Encoding encoding; + + @override + bool followRedirects; + + @override + int maxRedirects; + + @override + bool persistentConnection; + + @override + void abort([Object exception, StackTrace stackTrace]) { + throw UnimplementedError(); + } + + @override + void add(List data) { } + + @override + void addError(Object error, [StackTrace stackTrace]) { } + + @override + Future addStream(Stream> stream) async { } + + @override + Future close() async { + if (_responseError != null) { + return Future.error(_responseError); + } + return _FakeHttpClientResponse(_response); + } + + @override + HttpConnectionInfo get connectionInfo => throw UnimplementedError(); + + @override + List get cookies => throw UnimplementedError(); + + @override + Future get done => throw UnimplementedError(); + + @override + Future flush() async { } + + @override + final HttpHeaders headers = _FakeHttpHeaders(>{}); + + @override + String get method => _method; + + @override + Uri get uri => _uri; + + @override + void write(Object object) { } + + @override + void writeAll(Iterable objects, [String separator = '']) { } + + @override + void writeCharCode(int charCode) { } + + @override + void writeln([Object object = '']) { } +} + +class _FakeHttpClientResponse extends Stream> implements HttpClientResponse { + _FakeHttpClientResponse(this._response) + : headers = _FakeHttpHeaders(Map>.from(_response.headers)); + + final FakeResponse _response; + + @override + X509Certificate get certificate => throw UnimplementedError(); + + @override + HttpClientResponseCompressionState get compressionState => throw UnimplementedError(); + + @override + HttpConnectionInfo get connectionInfo => throw UnimplementedError(); + + @override + int get contentLength => _response.body.length; + + @override + List get cookies => throw UnimplementedError(); + + @override + Future detachSocket() { + throw UnimplementedError(); + } + + @override + final HttpHeaders headers; + + @override + bool get isRedirect => throw UnimplementedError(); + + @override + StreamSubscription> listen( + void Function(List event) onData, { + Function onError, + void Function() onDone, + bool cancelOnError, + }) { + final Stream> response = Stream>.fromIterable(>[ + _response.body, + ]); + return response.listen(onData, onError: onError, onDone: onDone, cancelOnError: cancelOnError); + } + + @override + bool get persistentConnection => throw UnimplementedError(); + + @override + String get reasonPhrase => 'OK'; + + @override + Future redirect([String method, Uri url, bool followLoops]) { + throw UnimplementedError(); + } + + @override + List get redirects => throw UnimplementedError(); + + @override + int get statusCode => _response.statusCode; +} + +class _FakeHttpHeaders extends HttpHeaders { + _FakeHttpHeaders(this._backingData); + + final Map> _backingData; + + @override + List operator [](String name) => _backingData[name]; + + @override + void add(String name, Object value, {bool preserveHeaderCase = false}) { + _backingData[name] ??= []; + _backingData[name].add(value.toString()); + } + + @override + void clear() { + _backingData.clear(); + } + + @override + void forEach(void Function(String name, List values) action) { } + + @override + void noFolding(String name) { } + + @override + void remove(String name, Object value) { + _backingData[name]?.remove(value.toString()); + } + + @override + void removeAll(String name) { + _backingData.remove(name); + } + + @override + void set(String name, Object value, {bool preserveHeaderCase = false}) { + _backingData[name] = [value.toString()]; + } + + @override + String value(String name) { + return _backingData[name]?.join('; '); + } +} diff --git a/packages/flutter_tools/test/src/testbed.dart b/packages/flutter_tools/test/src/testbed.dart index 6edc90a55d..e6c555b936 100644 --- a/packages/flutter_tools/test/src/testbed.dart +++ b/packages/flutter_tools/test/src/testbed.dart @@ -5,7 +5,6 @@ // @dart = 2.8 import 'dart:async'; -import 'dart:convert'; import 'dart:io'; import 'package:file/memory.dart'; @@ -29,6 +28,7 @@ import 'package:process/process.dart'; import 'common.dart' as tester; import 'context.dart'; +import 'fake_http_client.dart'; import 'throwing_pub.dart'; export 'package:flutter_tools/src/base/context.dart' show Generator; @@ -154,7 +154,7 @@ class Testbed { return null; }); }); - }, createHttpClient: (SecurityContext c) => FakeHttpClient()); + }, createHttpClient: (SecurityContext c) => FakeHttpClient.any()); } } @@ -197,457 +197,6 @@ class NoOpUsage implements Usage { void sendTiming(String category, String variableName, Duration duration, { String label }) {} } -class FakeHttpClient implements HttpClient { - @override - bool autoUncompress; - - @override - Duration connectionTimeout; - - @override - Duration idleTimeout; - - @override - int maxConnectionsPerHost; - - @override - String userAgent; - - @override - void addCredentials(Uri url, String realm, HttpClientCredentials credentials) {} - - @override - void addProxyCredentials(String host, int port, String realm, HttpClientCredentials credentials) {} - - @override - set authenticate(Future Function(Uri url, String scheme, String realm) f) {} - - @override - set authenticateProxy(Future Function(String host, int port, String scheme, String realm) f) {} - - @override - set badCertificateCallback(bool Function(X509Certificate cert, String host, int port) callback) {} - - @override - void close({bool force = false}) {} - - @override - Future delete(String host, int port, String path) async { - return FakeHttpClientRequest(); - } - - @override - Future deleteUrl(Uri url) async { - return FakeHttpClientRequest(); - } - - @override - set findProxy(String Function(Uri url) f) {} - - @override - Future get(String host, int port, String path) async { - return FakeHttpClientRequest(); - } - - @override - Future getUrl(Uri url) async { - return FakeHttpClientRequest(); - } - - @override - Future head(String host, int port, String path) async { - return FakeHttpClientRequest(); - } - - @override - Future headUrl(Uri url) async { - return FakeHttpClientRequest(); - } - - @override - Future open(String method, String host, int port, String path) async { - return FakeHttpClientRequest(); - } - - @override - Future openUrl(String method, Uri url) async { - return FakeHttpClientRequest(); - } - - @override - Future patch(String host, int port, String path) async { - return FakeHttpClientRequest(); - } - - @override - Future patchUrl(Uri url) async { - return FakeHttpClientRequest(); - } - - @override - Future post(String host, int port, String path) async { - return FakeHttpClientRequest(); - } - - @override - Future postUrl(Uri url) async { - return FakeHttpClientRequest(); - } - - @override - Future put(String host, int port, String path) async { - return FakeHttpClientRequest(); - } - - @override - Future putUrl(Uri url) async { - return FakeHttpClientRequest(); - } -} - -class FakeHttpClientRequest implements HttpClientRequest { - FakeHttpClientRequest(); - - @override - bool bufferOutput; - - @override - int contentLength; - - @override - Encoding encoding; - - @override - bool followRedirects; - - @override - int maxRedirects; - - @override - bool persistentConnection; - - @override - void add(List data) {} - - @override - void addError(Object error, [StackTrace stackTrace]) {} - - @override - Future addStream(Stream> stream) async {} - - @override - Future close() async { - return FakeHttpClientResponse(); - } - - @override - HttpConnectionInfo get connectionInfo => null; - - @override - List get cookies => []; - - @override - Future get done => null; - - @override - Future flush() { - return Future.value(); - } - - @override - HttpHeaders get headers => FakeHttpHeaders(); - - @override - String get method => null; - - @override - Uri get uri => null; - - @override - void write(Object obj) {} - - @override - void writeAll(Iterable objects, [String separator = '']) {} - - @override - void writeCharCode(int charCode) {} - - @override - void writeln([Object obj = '']) {} - - // TODO(zichangguo): remove the ignore after the change in dart:io lands. - @override - // ignore: override_on_non_overriding_member - void abort([Object exception, StackTrace stackTrace]) {} -} - -class FakeHttpClientResponse implements HttpClientResponse { - final Stream> _delegate = Stream>.fromIterable(const Iterable>.empty()); - - @override - final HttpHeaders headers = FakeHttpHeaders(); - - @override - X509Certificate get certificate => null; - - @override - HttpConnectionInfo get connectionInfo => null; - - @override - int get contentLength => 0; - - @override - HttpClientResponseCompressionState get compressionState { - return HttpClientResponseCompressionState.decompressed; - } - - @override - List get cookies => null; - - @override - Future detachSocket() { - return Future.error(UnsupportedError('Mocked response')); - } - - @override - bool get isRedirect => false; - - @override - StreamSubscription> listen(void Function(List event) onData, { Function onError, void Function() onDone, bool cancelOnError }) { - return const Stream>.empty().listen(onData, onError: onError, onDone: onDone, cancelOnError: cancelOnError); - } - - @override - bool get persistentConnection => null; - - @override - String get reasonPhrase => null; - - @override - Future redirect([ String method, Uri url, bool followLoops ]) { - return Future.error(UnsupportedError('Mocked response')); - } - - @override - List get redirects => []; - - @override - int get statusCode => 400; - - @override - Future any(bool Function(List element) test) { - return _delegate.any(test); - } - - @override - Stream> asBroadcastStream({ - void Function(StreamSubscription> subscription) onListen, - void Function(StreamSubscription> subscription) onCancel, - }) { - return _delegate.asBroadcastStream(onListen: onListen, onCancel: onCancel); - } - - @override - Stream asyncExpand(Stream Function(List event) convert) { - return _delegate.asyncExpand(convert); - } - - @override - Stream asyncMap(FutureOr Function(List event) convert) { - return _delegate.asyncMap(convert); - } - - @override - Stream cast() { - return _delegate.cast(); - } - - @override - Future contains(Object needle) { - return _delegate.contains(needle); - } - - @override - Stream> distinct([bool Function(List previous, List next) equals]) { - return _delegate.distinct(equals); - } - - @override - Future drain([E futureValue]) { - return _delegate.drain(futureValue); - } - - @override - Future> elementAt(int index) { - return _delegate.elementAt(index); - } - - @override - Future every(bool Function(List element) test) { - return _delegate.every(test); - } - - @override - Stream expand(Iterable Function(List element) convert) { - return _delegate.expand(convert); - } - - @override - Future> get first => _delegate.first; - - @override - Future> firstWhere( - bool Function(List element) test, { - List Function() orElse, - }) { - return _delegate.firstWhere(test, orElse: orElse); - } - - @override - Future fold(S initialValue, S Function(S previous, List element) combine) { - return _delegate.fold(initialValue, combine); - } - - @override - Future forEach(void Function(List element) action) { - return _delegate.forEach(action); - } - - @override - Stream> handleError( - Function onError, { - bool Function(dynamic error) test, - }) { - return _delegate.handleError(onError, test: test); - } - - @override - bool get isBroadcast => _delegate.isBroadcast; - - @override - Future get isEmpty => _delegate.isEmpty; - - @override - Future join([String separator = '']) { - return _delegate.join(separator); - } - - @override - Future> get last => _delegate.last; - - @override - Future> lastWhere( - bool Function(List element) test, { - List Function() orElse, - }) { - return _delegate.lastWhere(test, orElse: orElse); - } - - @override - Future get length => _delegate.length; - - @override - Stream map(S Function(List event) convert) { - return _delegate.map(convert); - } - - @override - Future pipe(StreamConsumer> streamConsumer) { - return _delegate.pipe(streamConsumer); - } - - @override - Future> reduce(List Function(List previous, List element) combine) { - return _delegate.reduce(combine); - } - - @override - Future> get single => _delegate.single; - - @override - Future> singleWhere(bool Function(List element) test, {List Function() orElse}) { - return _delegate.singleWhere(test, orElse: orElse); - } - - @override - Stream> skip(int count) { - return _delegate.skip(count); - } - - @override - Stream> skipWhile(bool Function(List element) test) { - return _delegate.skipWhile(test); - } - - @override - Stream> take(int count) { - return _delegate.take(count); - } - - @override - Stream> takeWhile(bool Function(List element) test) { - return _delegate.takeWhile(test); - } - - @override - Stream> timeout( - Duration timeLimit, { - void Function(EventSink> sink) onTimeout, - }) { - return _delegate.timeout(timeLimit, onTimeout: onTimeout); - } - - @override - Future>> toList() { - return _delegate.toList(); - } - - @override - Future>> toSet() { - return _delegate.toSet(); - } - - @override - Stream transform(StreamTransformer, S> streamTransformer) { - return _delegate.transform(streamTransformer); - } - - @override - Stream> where(bool Function(List event) test) { - return _delegate.where(test); - } -} - -/// A fake [HttpHeaders] that ignores all writes. -class FakeHttpHeaders extends HttpHeaders { - @override - List operator [](String name) => []; - - @override - void add(String name, Object value, {bool preserveHeaderCase = false}) { } - - @override - void clear() { } - - @override - void forEach(void Function(String name, List values) f) { } - - @override - void noFolding(String name) { } - - @override - void remove(String name, Object value) { } - - @override - void removeAll(String name) { } - - @override - void set(String name, Object value, {bool preserveHeaderCase = false}) { } - - @override - String value(String name) => null; -} - class FakeFlutterVersion implements FlutterVersion { @override void fetchTagsAndUpdate() { }