diff --git a/packages/flutter_tools/lib/src/base/net.dart b/packages/flutter_tools/lib/src/base/net.dart index 38fa19ccf673e..9731ec872a6ca 100644 --- a/packages/flutter_tools/lib/src/base/net.dart +++ b/packages/flutter_tools/lib/src/base/net.dart @@ -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 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> 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(); - } +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> 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() ?? []; - } + final bool result = await _attempt( + url, + destSink: sink, + ); + if (result) { + return memorySink?.writes?.takeBytes() ?? []; + } - 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.delayed(Duration(seconds: durationSeconds)); - if (durationSeconds < 64) { - durationSeconds *= 2; + 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.delayed(Duration(seconds: durationSeconds)); + if (durationSeconds < 64) { + durationSeconds *= 2; + } } } -} -/// Check if the given URL points to a valid endpoint. -Future doesRemoteFileExist(Uri url) async => await _attempt(url, onlyHeaders: true); - -// Returns true on success and false on failure. -Future _attempt(Uri url, { - IOSink destSink, - bool onlyHeaders = false, -}) async { - assert(onlyHeaders || destSink != null); - globals.printTrace('Downloading: $url'); - HttpClient httpClient; - if (context.get() != null) { - httpClient = context.get()(); - } else { - httpClient = HttpClient(); - } - HttpClientRequest request; - HttpClientResponse response; - try { - if (onlyHeaders) { - request = await httpClient.headUrl(url); + /// Check if the given URL points to a valid endpoint. + Future doesRemoteFileExist(Uri url) => _attempt(url, onlyHeaders: true); + + // Returns true on success and false on failure. + Future _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 { - 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()); - 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,); + httpClient = HttpClient(); } - 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) { + 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( - '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 diff --git a/packages/flutter_tools/lib/src/cache.dart b/packages/flutter_tools/lib/src/cache.dart index 9c4805a91590b..ef963235cccbb 100644 --- a/packages/flutter_tools/lib/src/cache.dart +++ b/packages/flutter_tools/lib/src/cache.dart @@ -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 _hostsBlockedInChina = [ '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 downloadFile(Uri url, File location) async { + _ensureExists(location.parent); + await _net.fetchUrl(url, destFile: location); + } + + Future 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 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 _downloadFile(Uri url, File location) async { - _ensureExists(location.parent); - await fetchUrl(url, destFile: location); -} - -Future _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()) { diff --git a/packages/flutter_tools/lib/src/commands/create.dart b/packages/flutter_tools/lib/src/commands/create.dart index 9d7c122674cb6..1faa98b361cab 100644 --- a/packages/flutter_tools/lib/src/commands/create.dart +++ b/packages/flutter_tools/lib/src/commands/create.dart @@ -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'; @@ -166,6 +168,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() ?? () => 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 @@ -230,13 +240,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 _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 diff --git a/packages/flutter_tools/lib/src/commands/update_packages.dart b/packages/flutter_tools/lib/src/commands/update_packages.dart index 7e398cb759474..30d01e4b25a2e 100644 --- a/packages/flutter_tools/lib/src/commands/update_packages.dart +++ b/packages/flutter_tools/lib/src/commands/update_packages.dart @@ -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() ?? () => HttpClient(), + logger: globals.logger, + platform: globals.platform, + ); + Future _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 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 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); diff --git a/packages/flutter_tools/test/general.shard/base/net_test.dart b/packages/flutter_tools/test/general.shard/base/net_test.dart index 7f4e78f2e194d..ec69406edf875 100644 --- a/packages/flutter_tools/test/general.shard/base/net_test.dart +++ b/packages/flutter_tools/test/general.shard/base/net_test.dart @@ -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 responseData; @@ -27,32 +47,30 @@ void main() { responseData = utf8.encode(responseString); }); - testUsingContext('fetchUrl() gets the data', () async { - final List data = await fetchUrl(Uri.parse('http://example.invalid/')); + testWithoutContext('fetchUrl() gets the data', () async { + final Net net = createNet(FakeHttpClient(200, data: responseString)); + final List data = await net.fetchUrl(Uri.parse('http://example.invalid/')); expect(data, equals(responseData)); - }, overrides: { - 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 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 data = await net.fetchUrl( Uri.parse('http://example.invalid/'), destFile: destFile, ); expect(data, equals([])); expect(destFile.readAsStringSync(), equals(responseString)); - }, overrides: { - 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 value) { + net.fetchUrl(Uri.parse('http://example.invalid/')).then((List 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: { - 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 value) { + net.fetchUrl(Uri.parse('http://example.invalid/')).then((List 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: { - 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 value) { + net.fetchUrl(Uri.parse('http://example.invalid/')).then((List 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: { - 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 value) { + net.fetchUrl(Uri.parse('http://example.invalid/')).then((List 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: { - 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 = { + 'FLUTTER_STORAGE_BASE_URL': 'example.invalid', + }, + ); String error; FakeAsync().run((FakeAsync time) { - fetchUrl(Uri.parse('example.invalid/')).then((List value) { + net.fetchUrl(Uri.parse('example.invalid/')).then((List 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: { - HttpClientFactory: () => () => FakeHttpClientThrowing( - ArgumentError('test exception handling'), - ), - Platform: () => FakePlatform.fromPlatform(const LocalPlatform()) - ..environment = { - '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 value) { + net.fetchUrl(Uri.parse('http://example.invalid/')).then((List 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: { - 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 value) { + net.fetchUrl(Uri.parse('http://example.invalid/')).then((List 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: { - 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 actualResult; FakeAsync().run((FakeAsync time) { - fetchUrl(Uri.parse('http://example.invalid/'), maxAttempts: 3).then((List value) { + net.fetchUrl(Uri.parse('http://example.invalid/'), maxAttempts: 3).then((List 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: { - 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: { - 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: { - 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: { - HttpClientFactory: () => () => FakeHttpClient(200), }); } diff --git a/packages/flutter_tools/test/src/testbed.dart b/packages/flutter_tools/test/src/testbed.dart index 5934d282142ee..111ca6207ff40 100644 --- a/packages/flutter_tools/test/src/testbed.dart +++ b/packages/flutter_tools/test/src/testbed.dart @@ -918,4 +918,13 @@ class FakeCache implements Cache { @override Future updateAll(Set requiredArtifacts) async { } + + @override + Future downloadFile(Uri url, File location) async { + } + + @override + Future doesRemoteExist(String message, Uri url) async { + return true; + } }