[flutter_tool] Make base/net.dart context free (#49575)
This commit is contained in:
parent
9980d6e550
commit
74c1be6ff1
@ -4,12 +4,14 @@
|
||||
|
||||
import 'dart:async';
|
||||
|
||||
import '../base/context.dart';
|
||||
import 'package:meta/meta.dart';
|
||||
import 'package:platform/platform.dart';
|
||||
|
||||
import '../convert.dart';
|
||||
import '../globals.dart' as globals;
|
||||
import 'common.dart';
|
||||
import 'file_system.dart';
|
||||
import 'io.dart';
|
||||
import 'logger.dart';
|
||||
|
||||
const int kNetworkProblemExitCode = 50;
|
||||
|
||||
@ -17,139 +19,160 @@ typedef HttpClientFactory = HttpClient Function();
|
||||
|
||||
typedef UrlTunneller = Future<String> Function(String url);
|
||||
|
||||
/// Download a file from the given URL.
|
||||
///
|
||||
/// If a destination file is not provided, returns the bytes.
|
||||
///
|
||||
/// If a destination file is provided, streams the bytes to that file and
|
||||
/// returns an empty list.
|
||||
///
|
||||
/// If [maxAttempts] is exceeded, returns null.
|
||||
Future<List<int>> fetchUrl(Uri url, {
|
||||
int maxAttempts,
|
||||
File destFile,
|
||||
}) async {
|
||||
int attempts = 0;
|
||||
int durationSeconds = 1;
|
||||
while (true) {
|
||||
attempts += 1;
|
||||
_MemoryIOSink memorySink;
|
||||
IOSink sink;
|
||||
if (destFile == null) {
|
||||
memorySink = _MemoryIOSink();
|
||||
sink = memorySink;
|
||||
class Net {
|
||||
Net({
|
||||
HttpClientFactory httpClientFactory,
|
||||
@required Logger logger,
|
||||
@required Platform platform,
|
||||
}) :
|
||||
_httpClientFactory = httpClientFactory,
|
||||
_logger = logger,
|
||||
_platform = platform;
|
||||
|
||||
final HttpClientFactory _httpClientFactory;
|
||||
|
||||
final Logger _logger;
|
||||
|
||||
final Platform _platform;
|
||||
|
||||
/// Download a file from the given URL.
|
||||
///
|
||||
/// If a destination file is not provided, returns the bytes.
|
||||
///
|
||||
/// If a destination file is provided, streams the bytes to that file and
|
||||
/// returns an empty list.
|
||||
///
|
||||
/// If [maxAttempts] is exceeded, returns null.
|
||||
Future<List<int>> fetchUrl(Uri url, {
|
||||
int maxAttempts,
|
||||
File destFile,
|
||||
}) async {
|
||||
int attempts = 0;
|
||||
int durationSeconds = 1;
|
||||
while (true) {
|
||||
attempts += 1;
|
||||
_MemoryIOSink memorySink;
|
||||
IOSink sink;
|
||||
if (destFile == null) {
|
||||
memorySink = _MemoryIOSink();
|
||||
sink = memorySink;
|
||||
} else {
|
||||
sink = destFile.openWrite();
|
||||
}
|
||||
|
||||
final bool result = await _attempt(
|
||||
url,
|
||||
destSink: sink,
|
||||
);
|
||||
if (result) {
|
||||
return memorySink?.writes?.takeBytes() ?? <int>[];
|
||||
}
|
||||
|
||||
if (maxAttempts != null && attempts >= maxAttempts) {
|
||||
_logger.printStatus('Download failed -- retry $attempts');
|
||||
return null;
|
||||
}
|
||||
_logger.printStatus(
|
||||
'Download failed -- attempting retry $attempts in '
|
||||
'$durationSeconds second${ durationSeconds == 1 ? "" : "s"}...',
|
||||
);
|
||||
await Future<void>.delayed(Duration(seconds: durationSeconds));
|
||||
if (durationSeconds < 64) {
|
||||
durationSeconds *= 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if the given URL points to a valid endpoint.
|
||||
Future<bool> doesRemoteFileExist(Uri url) => _attempt(url, onlyHeaders: true);
|
||||
|
||||
// Returns true on success and false on failure.
|
||||
Future<bool> _attempt(Uri url, {
|
||||
IOSink destSink,
|
||||
bool onlyHeaders = false,
|
||||
}) async {
|
||||
assert(onlyHeaders || destSink != null);
|
||||
_logger.printTrace('Downloading: $url');
|
||||
HttpClient httpClient;
|
||||
if (_httpClientFactory != null) {
|
||||
httpClient = _httpClientFactory();
|
||||
} else {
|
||||
sink = destFile.openWrite();
|
||||
httpClient = HttpClient();
|
||||
}
|
||||
|
||||
final bool result = await _attempt(
|
||||
url,
|
||||
destSink: sink,
|
||||
);
|
||||
if (result) {
|
||||
return memorySink?.writes?.takeBytes() ?? <int>[];
|
||||
}
|
||||
|
||||
if (maxAttempts != null && attempts >= maxAttempts) {
|
||||
globals.printStatus('Download failed -- retry $attempts');
|
||||
return null;
|
||||
}
|
||||
globals.printStatus('Download failed -- attempting retry $attempts in '
|
||||
'$durationSeconds second${ durationSeconds == 1 ? "" : "s"}...');
|
||||
await Future<void>.delayed(Duration(seconds: durationSeconds));
|
||||
if (durationSeconds < 64) {
|
||||
durationSeconds *= 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if the given URL points to a valid endpoint.
|
||||
Future<bool> doesRemoteFileExist(Uri url) async => await _attempt(url, onlyHeaders: true);
|
||||
|
||||
// Returns true on success and false on failure.
|
||||
Future<bool> _attempt(Uri url, {
|
||||
IOSink destSink,
|
||||
bool onlyHeaders = false,
|
||||
}) async {
|
||||
assert(onlyHeaders || destSink != null);
|
||||
globals.printTrace('Downloading: $url');
|
||||
HttpClient httpClient;
|
||||
if (context.get<HttpClientFactory>() != null) {
|
||||
httpClient = context.get<HttpClientFactory>()();
|
||||
} else {
|
||||
httpClient = HttpClient();
|
||||
}
|
||||
HttpClientRequest request;
|
||||
HttpClientResponse response;
|
||||
try {
|
||||
if (onlyHeaders) {
|
||||
request = await httpClient.headUrl(url);
|
||||
} else {
|
||||
request = await httpClient.getUrl(url);
|
||||
}
|
||||
response = await request.close();
|
||||
} on ArgumentError catch (error) {
|
||||
final String overrideUrl = globals.platform.environment['FLUTTER_STORAGE_BASE_URL'];
|
||||
if (overrideUrl != null && url.toString().contains(overrideUrl)) {
|
||||
globals.printError(error.toString());
|
||||
HttpClientRequest request;
|
||||
HttpClientResponse response;
|
||||
try {
|
||||
if (onlyHeaders) {
|
||||
request = await httpClient.headUrl(url);
|
||||
} else {
|
||||
request = await httpClient.getUrl(url);
|
||||
}
|
||||
response = await request.close();
|
||||
} on ArgumentError catch (error) {
|
||||
final String overrideUrl = _platform.environment['FLUTTER_STORAGE_BASE_URL'];
|
||||
if (overrideUrl != null && url.toString().contains(overrideUrl)) {
|
||||
_logger.printError(error.toString());
|
||||
throwToolExit(
|
||||
'The value of FLUTTER_STORAGE_BASE_URL ($overrideUrl) could not be '
|
||||
'parsed as a valid url. Please see https://flutter.dev/community/china '
|
||||
'for an example of how to use it.\n'
|
||||
'Full URL: $url',
|
||||
exitCode: kNetworkProblemExitCode,);
|
||||
}
|
||||
_logger.printError(error.toString());
|
||||
rethrow;
|
||||
} on HandshakeException catch (error) {
|
||||
_logger.printTrace(error.toString());
|
||||
throwToolExit(
|
||||
'The value of FLUTTER_STORAGE_BASE_URL ($overrideUrl) could not be '
|
||||
'parsed as a valid url. Please see https://flutter.dev/community/china '
|
||||
'for an example of how to use it.\n'
|
||||
'Full URL: $url',
|
||||
exitCode: kNetworkProblemExitCode,);
|
||||
}
|
||||
globals.printError(error.toString());
|
||||
rethrow;
|
||||
} on HandshakeException catch (error) {
|
||||
globals.printTrace(error.toString());
|
||||
throwToolExit(
|
||||
'Could not authenticate download server. You may be experiencing a man-in-the-middle attack,\n'
|
||||
'your network may be compromised, or you may have malware installed on your computer.\n'
|
||||
'URL: $url',
|
||||
exitCode: kNetworkProblemExitCode,
|
||||
);
|
||||
} on SocketException catch (error) {
|
||||
globals.printTrace('Download error: $error');
|
||||
return false;
|
||||
} on HttpException catch (error) {
|
||||
globals.printTrace('Download error: $error');
|
||||
return false;
|
||||
}
|
||||
assert(response != null);
|
||||
|
||||
// If we're making a HEAD request, we're only checking to see if the URL is
|
||||
// valid.
|
||||
if (onlyHeaders) {
|
||||
return response.statusCode == HttpStatus.ok;
|
||||
}
|
||||
if (response.statusCode != HttpStatus.ok) {
|
||||
if (response.statusCode > 0 && response.statusCode < 500) {
|
||||
throwToolExit(
|
||||
'Download failed.\n'
|
||||
'URL: $url\n'
|
||||
'Error: ${response.statusCode} ${response.reasonPhrase}',
|
||||
'Could not authenticate download server. You may be experiencing a man-in-the-middle attack,\n'
|
||||
'your network may be compromised, or you may have malware installed on your computer.\n'
|
||||
'URL: $url',
|
||||
exitCode: kNetworkProblemExitCode,
|
||||
);
|
||||
} on SocketException catch (error) {
|
||||
_logger.printTrace('Download error: $error');
|
||||
return false;
|
||||
} on HttpException catch (error) {
|
||||
_logger.printTrace('Download error: $error');
|
||||
return false;
|
||||
}
|
||||
assert(response != null);
|
||||
|
||||
// If we're making a HEAD request, we're only checking to see if the URL is
|
||||
// valid.
|
||||
if (onlyHeaders) {
|
||||
return response.statusCode == HttpStatus.ok;
|
||||
}
|
||||
if (response.statusCode != HttpStatus.ok) {
|
||||
if (response.statusCode > 0 && response.statusCode < 500) {
|
||||
throwToolExit(
|
||||
'Download failed.\n'
|
||||
'URL: $url\n'
|
||||
'Error: ${response.statusCode} ${response.reasonPhrase}',
|
||||
exitCode: kNetworkProblemExitCode,
|
||||
);
|
||||
}
|
||||
// 5xx errors are server errors and we can try again
|
||||
_logger.printTrace('Download error: ${response.statusCode} ${response.reasonPhrase}');
|
||||
return false;
|
||||
}
|
||||
_logger.printTrace('Received response from server, collecting bytes...');
|
||||
try {
|
||||
assert(destSink != null);
|
||||
await response.forEach(destSink.add);
|
||||
return true;
|
||||
} on IOException catch (error) {
|
||||
_logger.printTrace('Download error: $error');
|
||||
return false;
|
||||
} finally {
|
||||
await destSink?.flush();
|
||||
await destSink?.close();
|
||||
}
|
||||
// 5xx errors are server errors and we can try again
|
||||
globals.printTrace('Download error: ${response.statusCode} ${response.reasonPhrase}');
|
||||
return false;
|
||||
}
|
||||
globals.printTrace('Received response from server, collecting bytes...');
|
||||
try {
|
||||
assert(destSink != null);
|
||||
await response.forEach(destSink.add);
|
||||
return true;
|
||||
} on IOException catch (error) {
|
||||
globals.printTrace('Download error: $error');
|
||||
return false;
|
||||
} finally {
|
||||
await destSink?.flush();
|
||||
await destSink?.close();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// An IOSink that collects whatever is written to it.
|
||||
class _MemoryIOSink implements IOSink {
|
||||
@override
|
||||
|
@ -103,6 +103,9 @@ class Cache {
|
||||
_fileSystem = fileSystem ?? globals.fs,
|
||||
_platform = platform ?? globals.platform ,
|
||||
_osUtils = osUtils ?? os {
|
||||
// TODO(zra): Move to initializer list once logger and platform parameters
|
||||
// are required.
|
||||
_net = Net(logger: _logger, platform: _platform);
|
||||
if (artifacts == null) {
|
||||
_artifacts.add(MaterialFonts(this));
|
||||
|
||||
@ -135,6 +138,8 @@ class Cache {
|
||||
final FileSystem _fileSystem;
|
||||
final OperatingSystemUtils _osUtils;
|
||||
|
||||
Net _net;
|
||||
|
||||
static const List<String> _hostsBlockedInChina = <String> [
|
||||
'storage.googleapis.com',
|
||||
];
|
||||
@ -386,7 +391,7 @@ class Cache {
|
||||
final File cachedFile = _fileSystem.file(_fileSystem.path.join(serviceDir.path, url.pathSegments.last));
|
||||
if (!cachedFile.existsSync()) {
|
||||
try {
|
||||
await _downloadFile(url, cachedFile);
|
||||
await downloadFile(url, cachedFile);
|
||||
} catch (e) {
|
||||
throwToolExit('Failed to fetch third-party artifact $url: $e');
|
||||
}
|
||||
@ -439,6 +444,26 @@ class Cache {
|
||||
this.includeAllPlatforms = includeAllPlatformsState;
|
||||
return allAvailible;
|
||||
}
|
||||
|
||||
/// Download a file from the given [url] and write it to [location].
|
||||
Future<void> downloadFile(Uri url, File location) async {
|
||||
_ensureExists(location.parent);
|
||||
await _net.fetchUrl(url, destFile: location);
|
||||
}
|
||||
|
||||
Future<bool> doesRemoteExist(String message, Uri url) async {
|
||||
final Status status = globals.logger.startProgress(
|
||||
message,
|
||||
timeout: timeoutConfiguration.slowOperation,
|
||||
);
|
||||
bool exists;
|
||||
try {
|
||||
exists = await _net.doesRemoteFileExist(url);
|
||||
} finally {
|
||||
status.stop();
|
||||
}
|
||||
return exists;
|
||||
}
|
||||
}
|
||||
|
||||
/// Representation of a set of artifacts used by the tool.
|
||||
@ -559,7 +584,7 @@ abstract class CachedArtifact extends ArtifactSet {
|
||||
if (!verifier(tempFile)) {
|
||||
final Status status = globals.logger.startProgress(message, timeout: timeoutConfiguration.slowOperation);
|
||||
try {
|
||||
await _downloadFile(url, tempFile);
|
||||
await cache.downloadFile(url, tempFile);
|
||||
status.stop();
|
||||
} catch (exception) {
|
||||
status.cancel();
|
||||
@ -741,7 +766,7 @@ abstract class EngineCachedArtifact extends CachedArtifact {
|
||||
|
||||
bool exists = false;
|
||||
for (final String pkgName in getPackageDirs()) {
|
||||
exists = await _doesRemoteExist('Checking package $pkgName is available...',
|
||||
exists = await cache.doesRemoteExist('Checking package $pkgName is available...',
|
||||
Uri.parse(url + pkgName + '.zip'));
|
||||
if (!exists) {
|
||||
return false;
|
||||
@ -751,7 +776,7 @@ abstract class EngineCachedArtifact extends CachedArtifact {
|
||||
for (final List<String> toolsDir in getBinaryDirs()) {
|
||||
final String cacheDir = toolsDir[0];
|
||||
final String urlPath = toolsDir[1];
|
||||
exists = await _doesRemoteExist('Checking $cacheDir tools are available...',
|
||||
exists = await cache.doesRemoteExist('Checking $cacheDir tools are available...',
|
||||
Uri.parse(url + urlPath));
|
||||
if (!exists) {
|
||||
return false;
|
||||
@ -1305,19 +1330,6 @@ String flattenNameSubdirs(Uri url) {
|
||||
return globals.fs.path.joinAll(convertedPieces);
|
||||
}
|
||||
|
||||
/// Download a file from the given [url] and write it to [location].
|
||||
Future<void> _downloadFile(Uri url, File location) async {
|
||||
_ensureExists(location.parent);
|
||||
await fetchUrl(url, destFile: location);
|
||||
}
|
||||
|
||||
Future<bool> _doesRemoteExist(String message, Uri url) async {
|
||||
final Status status = globals.logger.startProgress(message, timeout: timeoutConfiguration.slowOperation);
|
||||
final bool exists = await doesRemoteFileExist(url);
|
||||
status.stop();
|
||||
return exists;
|
||||
}
|
||||
|
||||
/// Create the given [directory] and parents, as necessary.
|
||||
void _ensureExists(Directory directory) {
|
||||
if (!directory.existsSync()) {
|
||||
|
@ -11,7 +11,9 @@ import '../android/android.dart' as android;
|
||||
import '../android/android_sdk.dart' as android_sdk;
|
||||
import '../android/gradle_utils.dart' as gradle;
|
||||
import '../base/common.dart';
|
||||
import '../base/context.dart';
|
||||
import '../base/file_system.dart';
|
||||
import '../base/io.dart';
|
||||
import '../base/net.dart';
|
||||
import '../base/os.dart';
|
||||
import '../base/utils.dart';
|
||||
@ -165,6 +167,14 @@ class CreateCommand extends FlutterCommand {
|
||||
};
|
||||
}
|
||||
|
||||
// Lazy-initialize the net utilities with values from the context.
|
||||
Net _cachedNet;
|
||||
Net get _net => _cachedNet ??= Net(
|
||||
httpClientFactory: context.get<HttpClientFactory>() ?? () => HttpClient(),
|
||||
logger: globals.logger,
|
||||
platform: globals.platform,
|
||||
);
|
||||
|
||||
// If it has a .metadata file with the project_type in it, use that.
|
||||
// If it has an android dir and an android/app dir, it's a legacy app
|
||||
// If it has an ios dir and an ios/Flutter dir, it's a legacy app
|
||||
@ -229,13 +239,14 @@ class CreateCommand extends FlutterCommand {
|
||||
'documentation and try again.');
|
||||
}
|
||||
|
||||
return utf8.decode(await fetchUrl(Uri.https(_snippetsHost, 'snippets/$sampleId.dart')));
|
||||
final Uri snippetsUri = Uri.https(_snippetsHost, 'snippets/$sampleId.dart');
|
||||
return utf8.decode(await _net.fetchUrl(snippetsUri));
|
||||
}
|
||||
|
||||
/// Fetches the samples index file from the Flutter docs website.
|
||||
Future<String> _fetchSamplesIndexFromServer() async {
|
||||
return utf8.decode(
|
||||
await fetchUrl(Uri.https(_snippetsHost, 'snippets/index.json'), maxAttempts: 2));
|
||||
final Uri snippetsUri = Uri.https(_snippetsHost, 'snippets/index.json');
|
||||
return utf8.decode(await _net.fetchUrl(snippetsUri, maxAttempts: 2));
|
||||
}
|
||||
|
||||
/// Fetches the samples index file from the server and writes it to
|
||||
|
@ -8,7 +8,9 @@ import 'dart:collection';
|
||||
import 'package:meta/meta.dart';
|
||||
|
||||
import '../base/common.dart';
|
||||
import '../base/context.dart';
|
||||
import '../base/file_system.dart';
|
||||
import '../base/io.dart';
|
||||
import '../base/logger.dart';
|
||||
import '../base/net.dart';
|
||||
import '../cache.dart';
|
||||
@ -88,14 +90,27 @@ class UpdatePackagesCommand extends FlutterCommand {
|
||||
@override
|
||||
final bool hidden;
|
||||
|
||||
|
||||
// Lazy-initialize the net utilities with values from the context.
|
||||
Net _cachedNet;
|
||||
Net get _net => _cachedNet ??= Net(
|
||||
httpClientFactory: context.get<HttpClientFactory>() ?? () => HttpClient(),
|
||||
logger: globals.logger,
|
||||
platform: globals.platform,
|
||||
);
|
||||
|
||||
Future<void> _downloadCoverageData() async {
|
||||
final Status status = globals.logger.startProgress(
|
||||
'Downloading lcov data for package:flutter...',
|
||||
timeout: timeoutConfiguration.slowOperation,
|
||||
);
|
||||
final String urlBase = globals.platform.environment['FLUTTER_STORAGE_BASE_URL'] ?? 'https://storage.googleapis.com';
|
||||
final List<int> data = await fetchUrl(Uri.parse('$urlBase/flutter_infra/flutter/coverage/lcov.info'));
|
||||
final String coverageDir = globals.fs.path.join(Cache.flutterRoot, 'packages/flutter/coverage');
|
||||
final Uri coverageUri = Uri.parse('$urlBase/flutter_infra/flutter/coverage/lcov.info');
|
||||
final List<int> data = await _net.fetchUrl(coverageUri);
|
||||
final String coverageDir = globals.fs.path.join(
|
||||
Cache.flutterRoot,
|
||||
'packages/flutter/coverage',
|
||||
);
|
||||
globals.fs.file(globals.fs.path.join(coverageDir, 'lcov.base.info'))
|
||||
..createSync(recursive: true)
|
||||
..writeAsBytesSync(data, flush: true);
|
||||
|
@ -7,18 +7,38 @@ 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/logger.dart';
|
||||
import 'package:flutter_tools/src/base/net.dart';
|
||||
|
||||
import 'package:flutter_tools/src/globals.dart' as globals;
|
||||
import 'package:flutter_tools/src/base/terminal.dart';
|
||||
import 'package:platform/platform.dart';
|
||||
import 'package:quiver/testing/async.dart';
|
||||
|
||||
import '../../src/common.dart';
|
||||
import '../../src/context.dart';
|
||||
import '../../src/mocks.dart' show MockStdio;
|
||||
|
||||
void main() {
|
||||
BufferLogger testLogger;
|
||||
|
||||
setUp(() {
|
||||
testLogger = BufferLogger(
|
||||
terminal: AnsiTerminal(
|
||||
stdio: MockStdio(),
|
||||
platform: FakePlatform.fromPlatform(const LocalPlatform())..stdoutSupportsAnsi = false,
|
||||
),
|
||||
outputPreferences: OutputPreferences.test(),
|
||||
);
|
||||
});
|
||||
|
||||
Net createNet(io.HttpClient client) {
|
||||
return Net(
|
||||
httpClientFactory: () => client,
|
||||
logger: testLogger,
|
||||
platform: FakePlatform.fromPlatform(const LocalPlatform()),
|
||||
);
|
||||
}
|
||||
|
||||
group('successful fetch', () {
|
||||
const String responseString = 'response string';
|
||||
List<int> responseData;
|
||||
@ -27,32 +47,30 @@ void main() {
|
||||
responseData = utf8.encode(responseString);
|
||||
});
|
||||
|
||||
testUsingContext('fetchUrl() gets the data', () async {
|
||||
final List<int> data = await fetchUrl(Uri.parse('http://example.invalid/'));
|
||||
testWithoutContext('fetchUrl() gets the data', () async {
|
||||
final Net net = createNet(FakeHttpClient(200, data: responseString));
|
||||
final List<int> data = await net.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(
|
||||
testWithoutContext('fetchUrl(destFile) writes the data to a file', () async {
|
||||
final Net net = createNet(FakeHttpClient(200, data: responseString));
|
||||
final MemoryFileSystem fs = MemoryFileSystem();
|
||||
final File destFile = fs.file('dest_file')..createSync();
|
||||
final List<int> data = await net.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 {
|
||||
testWithoutContext('retry from 500', () async {
|
||||
final Net net = createNet(FakeHttpClient(500));
|
||||
String error;
|
||||
FakeAsync().run((FakeAsync time) {
|
||||
fetchUrl(Uri.parse('http://example.invalid/')).then((List<int> value) {
|
||||
net.fetchUrl(Uri.parse('http://example.invalid/')).then((List<int> value) {
|
||||
error = 'test completed unexpectedly';
|
||||
}, onError: (dynamic exception) {
|
||||
error = 'test failed unexpectedly: $exception';
|
||||
@ -68,14 +86,13 @@ void main() {
|
||||
});
|
||||
expect(testLogger.errorText, isEmpty);
|
||||
expect(error, isNull);
|
||||
}, overrides: <Type, Generator>{
|
||||
HttpClientFactory: () => () => FakeHttpClient(500),
|
||||
});
|
||||
|
||||
testUsingContext('retry from network error', () async {
|
||||
testWithoutContext('retry from network error', () async {
|
||||
final Net net = createNet(FakeHttpClient(200));
|
||||
String error;
|
||||
FakeAsync().run((FakeAsync time) {
|
||||
fetchUrl(Uri.parse('http://example.invalid/')).then((List<int> value) {
|
||||
net.fetchUrl(Uri.parse('http://example.invalid/')).then((List<int> value) {
|
||||
error = 'test completed unexpectedly';
|
||||
}, onError: (dynamic exception) {
|
||||
error = 'test failed unexpectedly: $exception';
|
||||
@ -91,14 +108,15 @@ void main() {
|
||||
});
|
||||
expect(testLogger.errorText, isEmpty);
|
||||
expect(error, isNull);
|
||||
}, overrides: <Type, Generator>{
|
||||
HttpClientFactory: () => () => FakeHttpClient(200),
|
||||
});
|
||||
|
||||
testUsingContext('retry from SocketException', () async {
|
||||
testWithoutContext('retry from SocketException', () async {
|
||||
final Net net = createNet(FakeHttpClientThrowing(
|
||||
const io.SocketException('test exception handling'),
|
||||
));
|
||||
String error;
|
||||
FakeAsync().run((FakeAsync time) {
|
||||
fetchUrl(Uri.parse('http://example.invalid/')).then((List<int> value) {
|
||||
net.fetchUrl(Uri.parse('http://example.invalid/')).then((List<int> value) {
|
||||
error = 'test completed unexpectedly';
|
||||
}, onError: (dynamic exception) {
|
||||
error = 'test failed unexpectedly: $exception';
|
||||
@ -115,16 +133,15 @@ void main() {
|
||||
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 {
|
||||
testWithoutContext('no retry from HandshakeException', () async {
|
||||
final Net net = createNet(FakeHttpClientThrowing(
|
||||
const io.HandshakeException('test exception handling'),
|
||||
));
|
||||
String error;
|
||||
FakeAsync().run((FakeAsync time) {
|
||||
fetchUrl(Uri.parse('http://example.invalid/')).then((List<int> value) {
|
||||
net.fetchUrl(Uri.parse('http://example.invalid/')).then((List<int> value) {
|
||||
error = 'test completed unexpectedly';
|
||||
}, onError: (dynamic exception) {
|
||||
error = 'test failed: $exception';
|
||||
@ -135,16 +152,22 @@ void main() {
|
||||
});
|
||||
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 {
|
||||
testWithoutContext('check for bad override on ArgumentError', () async {
|
||||
final Net net = Net(
|
||||
httpClientFactory: () => FakeHttpClientThrowing(
|
||||
ArgumentError('test exception handling'),
|
||||
),
|
||||
logger: testLogger,
|
||||
platform: FakePlatform.fromPlatform(const LocalPlatform())
|
||||
..environment = <String, String>{
|
||||
'FLUTTER_STORAGE_BASE_URL': 'example.invalid',
|
||||
},
|
||||
);
|
||||
String error;
|
||||
FakeAsync().run((FakeAsync time) {
|
||||
fetchUrl(Uri.parse('example.invalid/')).then((List<int> value) {
|
||||
net.fetchUrl(Uri.parse('example.invalid/')).then((List<int> value) {
|
||||
error = 'test completed unexpectedly';
|
||||
}, onError: (dynamic exception) {
|
||||
error = 'test failed: $exception';
|
||||
@ -156,20 +179,15 @@ void main() {
|
||||
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 {
|
||||
testWithoutContext('retry from HttpException', () async {
|
||||
final Net net = createNet(FakeHttpClientThrowing(
|
||||
const io.HttpException('test exception handling'),
|
||||
));
|
||||
String error;
|
||||
FakeAsync().run((FakeAsync time) {
|
||||
fetchUrl(Uri.parse('http://example.invalid/')).then((List<int> value) {
|
||||
net.fetchUrl(Uri.parse('http://example.invalid/')).then((List<int> value) {
|
||||
error = 'test completed unexpectedly';
|
||||
}, onError: (dynamic exception) {
|
||||
error = 'test failed unexpectedly: $exception';
|
||||
@ -186,16 +204,15 @@ void main() {
|
||||
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 {
|
||||
testWithoutContext('retry from HttpException when request throws', () async {
|
||||
final Net net = createNet(FakeHttpClientThrowingRequest(
|
||||
const io.HttpException('test exception handling'),
|
||||
));
|
||||
String error;
|
||||
FakeAsync().run((FakeAsync time) {
|
||||
fetchUrl(Uri.parse('http://example.invalid/')).then((List<int> value) {
|
||||
net.fetchUrl(Uri.parse('http://example.invalid/')).then((List<int> value) {
|
||||
error = 'test completed unexpectedly';
|
||||
}, onError: (dynamic exception) {
|
||||
error = 'test failed unexpectedly: $exception';
|
||||
@ -212,17 +229,14 @@ void main() {
|
||||
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 {
|
||||
testWithoutContext('max attempts', () async {
|
||||
final Net net = createNet(FakeHttpClient(500));
|
||||
String error;
|
||||
List<int> actualResult;
|
||||
FakeAsync().run((FakeAsync time) {
|
||||
fetchUrl(Uri.parse('http://example.invalid/'), maxAttempts: 3).then((List<int> value) {
|
||||
net.fetchUrl(Uri.parse('http://example.invalid/'), maxAttempts: 3).then((List<int> value) {
|
||||
actualResult = value;
|
||||
}, onError: (dynamic exception) {
|
||||
error = 'test failed unexpectedly: $exception';
|
||||
@ -238,32 +252,27 @@ void main() {
|
||||
expect(testLogger.errorText, isEmpty);
|
||||
expect(error, isNull);
|
||||
expect(actualResult, isNull);
|
||||
}, overrides: <Type, Generator>{
|
||||
HttpClientFactory: () => () => FakeHttpClient(500),
|
||||
});
|
||||
|
||||
testUsingContext('remote file non-existant', () async {
|
||||
testWithoutContext('remote file non-existant', () async {
|
||||
final Net net = createNet(FakeHttpClient(404));
|
||||
final Uri invalid = Uri.parse('http://example.invalid/');
|
||||
final bool result = await doesRemoteFileExist(invalid);
|
||||
final bool result = await net.doesRemoteFileExist(invalid);
|
||||
expect(result, false);
|
||||
}, overrides: <Type, Generator>{
|
||||
HttpClientFactory: () => () => FakeHttpClient(404),
|
||||
});
|
||||
|
||||
testUsingContext('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 bool result = await doesRemoteFileExist(valid);
|
||||
final bool result = await net.doesRemoteFileExist(valid);
|
||||
expect(result, false);
|
||||
}, overrides: <Type, Generator>{
|
||||
HttpClientFactory: () => () => FakeHttpClient(500),
|
||||
});
|
||||
|
||||
testUsingContext('remote file exists', () async {
|
||||
testWithoutContext('remote file exists', () async {
|
||||
final Net net = createNet(FakeHttpClient(200));
|
||||
final Uri valid = Uri.parse('http://example.valid/');
|
||||
final bool result = await doesRemoteFileExist(valid);
|
||||
final bool result = await net.doesRemoteFileExist(valid);
|
||||
expect(result, true);
|
||||
}, overrides: <Type, Generator>{
|
||||
HttpClientFactory: () => () => FakeHttpClient(200),
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -922,4 +922,13 @@ class FakeCache implements Cache {
|
||||
@override
|
||||
Future<void> updateAll(Set<DevelopmentArtifact> requiredArtifacts) async {
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> downloadFile(Uri url, File location) async {
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> doesRemoteExist(String message, Uri url) async {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user