mirror of
https://github.com/flutter/flutter
synced 2024-10-14 04:02:56 +00:00
400 lines
13 KiB
Dart
400 lines
13 KiB
Dart
// 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.
|
|
|
|
import 'dart:async';
|
|
import 'dart:convert';
|
|
|
|
import 'package:file/file.dart';
|
|
import 'package:file/memory.dart';
|
|
import 'package:platform/platform.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/net.dart';
|
|
|
|
import 'package:flutter_tools/src/globals.dart' as globals;
|
|
import 'package:quiver/testing/async.dart';
|
|
|
|
import '../../src/common.dart';
|
|
import '../../src/context.dart';
|
|
|
|
void main() {
|
|
group('successful fetch', () {
|
|
const String responseString = 'response string';
|
|
List<int> responseData;
|
|
|
|
setUp(() {
|
|
responseData = utf8.encode(responseString);
|
|
});
|
|
|
|
testUsingContext('fetchUrl() gets the data', () async {
|
|
final List<int> data = await fetchUrl(Uri.parse('http://example.invalid/'));
|
|
expect(data, equals(responseData));
|
|
}, overrides: <Type, Generator>{
|
|
HttpClientFactory: () => () => FakeHttpClient(200, data: responseString),
|
|
});
|
|
|
|
testUsingContext('fetchUrl(destFile) writes the data to a file', () async {
|
|
final File destFile = globals.fs.file('dest_file')..createSync();
|
|
final List<int> data = await fetchUrl(
|
|
Uri.parse('http://example.invalid/'),
|
|
destFile: destFile,
|
|
);
|
|
expect(data, equals(<int>[]));
|
|
expect(destFile.readAsStringSync(), equals(responseString));
|
|
}, overrides: <Type, Generator>{
|
|
FileSystem: () => MemoryFileSystem(),
|
|
HttpClientFactory: () => () => FakeHttpClient(200, data: responseString),
|
|
ProcessManager: () => FakeProcessManager.any(),
|
|
});
|
|
});
|
|
|
|
testUsingContext('retry from 500', () async {
|
|
String error;
|
|
FakeAsync().run((FakeAsync time) {
|
|
fetchUrl(Uri.parse('http://example.invalid/')).then((List<int> value) {
|
|
error = 'test completed unexpectedly';
|
|
}, onError: (dynamic exception) {
|
|
error = 'test failed unexpectedly: $exception';
|
|
});
|
|
expect(testLogger.statusText, '');
|
|
time.elapse(const Duration(milliseconds: 10000));
|
|
expect(testLogger.statusText,
|
|
'Download failed -- attempting retry 1 in 1 second...\n'
|
|
'Download failed -- attempting retry 2 in 2 seconds...\n'
|
|
'Download failed -- attempting retry 3 in 4 seconds...\n'
|
|
'Download failed -- attempting retry 4 in 8 seconds...\n',
|
|
);
|
|
});
|
|
expect(testLogger.errorText, isEmpty);
|
|
expect(error, isNull);
|
|
}, overrides: <Type, Generator>{
|
|
HttpClientFactory: () => () => FakeHttpClient(500),
|
|
});
|
|
|
|
testUsingContext('retry from network error', () async {
|
|
String error;
|
|
FakeAsync().run((FakeAsync time) {
|
|
fetchUrl(Uri.parse('http://example.invalid/')).then((List<int> value) {
|
|
error = 'test completed unexpectedly';
|
|
}, onError: (dynamic exception) {
|
|
error = 'test failed unexpectedly: $exception';
|
|
});
|
|
expect(testLogger.statusText, '');
|
|
time.elapse(const Duration(milliseconds: 10000));
|
|
expect(testLogger.statusText,
|
|
'Download failed -- attempting retry 1 in 1 second...\n'
|
|
'Download failed -- attempting retry 2 in 2 seconds...\n'
|
|
'Download failed -- attempting retry 3 in 4 seconds...\n'
|
|
'Download failed -- attempting retry 4 in 8 seconds...\n',
|
|
);
|
|
});
|
|
expect(testLogger.errorText, isEmpty);
|
|
expect(error, isNull);
|
|
}, overrides: <Type, Generator>{
|
|
HttpClientFactory: () => () => FakeHttpClient(200),
|
|
});
|
|
|
|
testUsingContext('retry from SocketException', () async {
|
|
String error;
|
|
FakeAsync().run((FakeAsync time) {
|
|
fetchUrl(Uri.parse('http://example.invalid/')).then((List<int> value) {
|
|
error = 'test completed unexpectedly';
|
|
}, onError: (dynamic exception) {
|
|
error = 'test failed unexpectedly: $exception';
|
|
});
|
|
expect(testLogger.statusText, '');
|
|
time.elapse(const Duration(milliseconds: 10000));
|
|
expect(testLogger.statusText,
|
|
'Download failed -- attempting retry 1 in 1 second...\n'
|
|
'Download failed -- attempting retry 2 in 2 seconds...\n'
|
|
'Download failed -- attempting retry 3 in 4 seconds...\n'
|
|
'Download failed -- attempting retry 4 in 8 seconds...\n',
|
|
);
|
|
});
|
|
expect(testLogger.errorText, isEmpty);
|
|
expect(error, isNull);
|
|
expect(testLogger.traceText, contains('Download error: SocketException'));
|
|
}, overrides: <Type, Generator>{
|
|
HttpClientFactory: () => () => FakeHttpClientThrowing(
|
|
const io.SocketException('test exception handling'),
|
|
),
|
|
});
|
|
|
|
testUsingContext('no retry from HandshakeException', () async {
|
|
String error;
|
|
FakeAsync().run((FakeAsync time) {
|
|
fetchUrl(Uri.parse('http://example.invalid/')).then((List<int> value) {
|
|
error = 'test completed unexpectedly';
|
|
}, onError: (dynamic exception) {
|
|
error = 'test failed: $exception';
|
|
});
|
|
expect(testLogger.statusText, '');
|
|
time.elapse(const Duration(milliseconds: 10000));
|
|
expect(testLogger.statusText, '');
|
|
});
|
|
expect(error, startsWith('test failed'));
|
|
expect(testLogger.traceText, contains('HandshakeException'));
|
|
}, overrides: <Type, Generator>{
|
|
HttpClientFactory: () => () => FakeHttpClientThrowing(
|
|
const io.HandshakeException('test exception handling'),
|
|
),
|
|
});
|
|
|
|
testUsingContext('check for bad override on ArgumentError', () async {
|
|
String error;
|
|
FakeAsync().run((FakeAsync time) {
|
|
fetchUrl(Uri.parse('example.invalid/')).then((List<int> value) {
|
|
error = 'test completed unexpectedly';
|
|
}, onError: (dynamic exception) {
|
|
error = 'test failed: $exception';
|
|
});
|
|
expect(testLogger.statusText, '');
|
|
time.elapse(const Duration(milliseconds: 10000));
|
|
expect(testLogger.statusText, '');
|
|
});
|
|
expect(error, startsWith('test failed'));
|
|
expect(testLogger.errorText, contains('Invalid argument'));
|
|
expect(error, contains('FLUTTER_STORAGE_BASE_URL'));
|
|
}, overrides: <Type, Generator>{
|
|
HttpClientFactory: () => () => FakeHttpClientThrowing(
|
|
ArgumentError('test exception handling'),
|
|
),
|
|
Platform: () => FakePlatform.fromPlatform(const LocalPlatform())
|
|
..environment = <String, String>{
|
|
'FLUTTER_STORAGE_BASE_URL': 'example.invalid',
|
|
},
|
|
});
|
|
|
|
testUsingContext('retry from HttpException', () async {
|
|
String error;
|
|
FakeAsync().run((FakeAsync time) {
|
|
fetchUrl(Uri.parse('http://example.invalid/')).then((List<int> value) {
|
|
error = 'test completed unexpectedly';
|
|
}, onError: (dynamic exception) {
|
|
error = 'test failed unexpectedly: $exception';
|
|
});
|
|
expect(testLogger.statusText, '');
|
|
time.elapse(const Duration(milliseconds: 10000));
|
|
expect(testLogger.statusText,
|
|
'Download failed -- attempting retry 1 in 1 second...\n'
|
|
'Download failed -- attempting retry 2 in 2 seconds...\n'
|
|
'Download failed -- attempting retry 3 in 4 seconds...\n'
|
|
'Download failed -- attempting retry 4 in 8 seconds...\n',
|
|
);
|
|
});
|
|
expect(testLogger.errorText, isEmpty);
|
|
expect(error, isNull);
|
|
expect(testLogger.traceText, contains('Download error: HttpException'));
|
|
}, overrides: <Type, Generator>{
|
|
HttpClientFactory: () => () => FakeHttpClientThrowing(
|
|
const io.HttpException('test exception handling'),
|
|
),
|
|
});
|
|
|
|
testUsingContext('retry from HttpException when request throws', () async {
|
|
String error;
|
|
FakeAsync().run((FakeAsync time) {
|
|
fetchUrl(Uri.parse('http://example.invalid/')).then((List<int> value) {
|
|
error = 'test completed unexpectedly';
|
|
}, onError: (dynamic exception) {
|
|
error = 'test failed unexpectedly: $exception';
|
|
});
|
|
expect(testLogger.statusText, '');
|
|
time.elapse(const Duration(milliseconds: 10000));
|
|
expect(testLogger.statusText,
|
|
'Download failed -- attempting retry 1 in 1 second...\n'
|
|
'Download failed -- attempting retry 2 in 2 seconds...\n'
|
|
'Download failed -- attempting retry 3 in 4 seconds...\n'
|
|
'Download failed -- attempting retry 4 in 8 seconds...\n',
|
|
);
|
|
});
|
|
expect(testLogger.errorText, isEmpty);
|
|
expect(error, isNull);
|
|
expect(testLogger.traceText, contains('Download error: HttpException'));
|
|
}, overrides: <Type, Generator>{
|
|
HttpClientFactory: () => () => FakeHttpClientThrowingRequest(
|
|
const io.HttpException('test exception handling'),
|
|
),
|
|
});
|
|
|
|
testUsingContext('max attempts', () async {
|
|
String error;
|
|
List<int> actualResult;
|
|
FakeAsync().run((FakeAsync time) {
|
|
fetchUrl(Uri.parse('http://example.invalid/'), maxAttempts: 3).then((List<int> value) {
|
|
actualResult = value;
|
|
}, onError: (dynamic exception) {
|
|
error = 'test failed unexpectedly: $exception';
|
|
});
|
|
expect(testLogger.statusText, '');
|
|
time.elapse(const Duration(milliseconds: 10000));
|
|
expect(testLogger.statusText,
|
|
'Download failed -- attempting retry 1 in 1 second...\n'
|
|
'Download failed -- attempting retry 2 in 2 seconds...\n'
|
|
'Download failed -- retry 3\n',
|
|
);
|
|
});
|
|
expect(testLogger.errorText, isEmpty);
|
|
expect(error, isNull);
|
|
expect(actualResult, isNull);
|
|
}, overrides: <Type, Generator>{
|
|
HttpClientFactory: () => () => FakeHttpClient(500),
|
|
});
|
|
|
|
testUsingContext('remote file non-existant', () async {
|
|
final Uri invalid = Uri.parse('http://example.invalid/');
|
|
final bool result = await doesRemoteFileExist(invalid);
|
|
expect(result, false);
|
|
}, overrides: <Type, Generator>{
|
|
HttpClientFactory: () => () => FakeHttpClient(404),
|
|
});
|
|
|
|
testUsingContext('remote file server error', () async {
|
|
final Uri valid = Uri.parse('http://example.valid/');
|
|
final bool result = await doesRemoteFileExist(valid);
|
|
expect(result, false);
|
|
}, overrides: <Type, Generator>{
|
|
HttpClientFactory: () => () => FakeHttpClient(500),
|
|
});
|
|
|
|
testUsingContext('remote file exists', () async {
|
|
final Uri valid = Uri.parse('http://example.valid/');
|
|
final bool result = await doesRemoteFileExist(valid);
|
|
expect(result, true);
|
|
}, overrides: <Type, Generator>{
|
|
HttpClientFactory: () => () => FakeHttpClient(200),
|
|
});
|
|
}
|
|
|
|
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';
|
|
}
|
|
}
|