Unverified Commit 8df58eb6 authored by Jonah Williams's avatar Jonah Williams Committed by GitHub

[flutter_tools] use a single fake implementation of HttpClient (#75471)

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