diff --git a/packages/flutter_tools/lib/src/android/gradle.dart b/packages/flutter_tools/lib/src/android/gradle.dart index dbcfea4d0a9b1..012a16463392c 100644 --- a/packages/flutter_tools/lib/src/android/gradle.dart +++ b/packages/flutter_tools/lib/src/android/gradle.dart @@ -16,6 +16,7 @@ import '../base/deferred_component.dart'; import '../base/file_system.dart'; import '../base/io.dart'; import '../base/logger.dart'; +import '../base/net.dart'; import '../base/platform.dart'; import '../base/process.dart'; import '../base/terminal.dart'; @@ -696,7 +697,7 @@ void printHowToConsumeAar({ 1. Open ${fileSystem.path.join('', 'app', 'build.gradle')} 2. Ensure you have the repositories configured, otherwise add them: - String storageUrl = System.env.FLUTTER_STORAGE_BASE_URL ?: "https://storage.googleapis.com" + String storageUrl = System.env.$kFlutterStorageBaseUrl ?: "https://storage.googleapis.com" repositories { maven { url '${repoDirectory.path}' diff --git a/packages/flutter_tools/lib/src/base/logger.dart b/packages/flutter_tools/lib/src/base/logger.dart index 099b6368a841c..49aa2a7bffb86 100644 --- a/packages/flutter_tools/lib/src/base/logger.dart +++ b/packages/flutter_tools/lib/src/base/logger.dart @@ -1360,7 +1360,9 @@ class AnonymousSpinnerStatus extends Status { if (seemsSlow) { if (!timedOut) { timedOut = true; - _clear(_currentLineLength); + if (_currentLineLength > _lastAnimationFrameLength) { + _clear(_currentLineLength - _lastAnimationFrameLength); + } } if (_slowWarning == '' && slowWarningCallback != null) { _slowWarning = slowWarningCallback!(); @@ -1398,7 +1400,8 @@ class AnonymousSpinnerStatus extends Status { assert(timer!.isActive); timer?.cancel(); timer = null; - _clear(_lastAnimationFrameLength); + _clear(_lastAnimationFrameLength + _slowWarning.length); + _slowWarning = ''; _lastAnimationFrameLength = 0; super.finish(); } diff --git a/packages/flutter_tools/lib/src/base/net.dart b/packages/flutter_tools/lib/src/base/net.dart index 882845d682aa3..0eef86f99d4fb 100644 --- a/packages/flutter_tools/lib/src/base/net.dart +++ b/packages/flutter_tools/lib/src/base/net.dart @@ -14,6 +14,7 @@ import 'logger.dart'; import 'platform.dart'; const int kNetworkProblemExitCode = 50; +const String kFlutterStorageBaseUrl = 'FLUTTER_STORAGE_BASE_URL'; typedef HttpClientFactory = HttpClient Function(); @@ -106,15 +107,16 @@ class Net { } response = await request.close(); } on ArgumentError catch (error) { - final String? overrideUrl = _platform.environment['FLUTTER_STORAGE_BASE_URL']; + final String? overrideUrl = _platform.environment[kFlutterStorageBaseUrl]; if (overrideUrl != null && url.toString().contains(overrideUrl)) { _logger.printError(error.toString()); throwToolExit( - 'The value of FLUTTER_STORAGE_BASE_URL ($overrideUrl) could not be ' + 'The value of $kFlutterStorageBaseUrl ($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,); + exitCode: kNetworkProblemExitCode, + ); } _logger.printError(error.toString()); rethrow; diff --git a/packages/flutter_tools/lib/src/base/user_messages.dart b/packages/flutter_tools/lib/src/base/user_messages.dart index d8236e3c33e80..11ec3340211aa 100644 --- a/packages/flutter_tools/lib/src/base/user_messages.dart +++ b/packages/flutter_tools/lib/src/base/user_messages.dart @@ -274,7 +274,7 @@ class UserMessages { 'Found $count devices with name or id matching $deviceId:'; String get flutterMultipleDevicesFound => 'Multiple devices found:'; String flutterChooseDevice(int option, String name, String deviceId) => '[$option]: $name ($deviceId)'; - String get flutterChooseOne => 'Please choose one (To quit, press "q/Q")'; + String get flutterChooseOne => 'Please choose one (or "q" to quit)'; String get flutterSpecifyDeviceWithAllOption => 'More than one device connected; please specify a device with ' "the '-d ' flag, or use '-d all' to act on all devices."; diff --git a/packages/flutter_tools/lib/src/cache.dart b/packages/flutter_tools/lib/src/cache.dart index c4ecd3def3477..f027848f36697 100644 --- a/packages/flutter_tools/lib/src/cache.dart +++ b/packages/flutter_tools/lib/src/cache.dart @@ -107,8 +107,8 @@ class DevelopmentArtifact { /// /// To enable Flutter users in these environments, the Flutter tool supports /// custom artifact mirrors that the administrators of such environments may -/// provide. To use an artifact mirror, the user defines the -/// `FLUTTER_STORAGE_BASE_URL` environment variable that points to the mirror. +/// provide. To use an artifact mirror, the user defines the [kFlutterStorageBaseUrl] +/// (`FLUTTER_STORAGE_BASE_URL`) environment variable that points to the mirror. /// Flutter tool reads this variable and uses it instead of the default URLs. /// /// For more details on specific URLs used to download artifacts, see @@ -450,15 +450,15 @@ class Cache { /// during the installation of the Flutter SDK. /// /// By default the base URL is https://storage.googleapis.com. However, if - /// `FLUTTER_STORAGE_BASE_URL` environment variable is provided, the - /// environment variable value is returned instead. + /// `FLUTTER_STORAGE_BASE_URL` environment variable ([kFlutterStorageBaseUrl]) + /// is provided, the environment variable value is returned instead. /// /// See also: /// /// * [cipdBaseUrl], which determines how CIPD artifacts are fetched. /// * [Cache] class-level dartdocs that explain how artifact mirrors work. String get storageBaseUrl { - final String? overrideUrl = _platform.environment['FLUTTER_STORAGE_BASE_URL']; + final String? overrideUrl = _platform.environment[kFlutterStorageBaseUrl]; if (overrideUrl == null) { return 'https://storage.googleapis.com'; } @@ -466,7 +466,7 @@ class Cache { try { Uri.parse(overrideUrl); } on FormatException catch (err) { - throwToolExit('"FLUTTER_STORAGE_BASE_URL" contains an invalid URI:\n$err'); + throwToolExit('"$kFlutterStorageBaseUrl" contains an invalid URL:\n$err'); } _maybeWarnAboutStorageOverride(overrideUrl); return overrideUrl; @@ -479,8 +479,8 @@ class Cache { /// from [storageBaseUrl]. /// /// By default the base URL is https://chrome-infra-packages.appspot.com/dl. - /// However, if `FLUTTER_STORAGE_BASE_URL` environment variable is provided, - /// then the following value is used: + /// However, if `FLUTTER_STORAGE_BASE_URL` environment variable is provided + /// ([kFlutterStorageBaseUrl]), then the following value is used: /// /// FLUTTER_STORAGE_BASE_URL/flutter_infra_release/cipd /// @@ -492,7 +492,7 @@ class Cache { /// which contains information about CIPD. /// * [Cache] class-level dartdocs that explain how artifact mirrors work. String get cipdBaseUrl { - final String? overrideUrl = _platform.environment['FLUTTER_STORAGE_BASE_URL']; + final String? overrideUrl = _platform.environment[kFlutterStorageBaseUrl]; if (overrideUrl == null) { return 'https://chrome-infra-packages.appspot.com/dl'; } @@ -501,7 +501,7 @@ class Cache { try { original = Uri.parse(overrideUrl); } on FormatException catch (err) { - throwToolExit('"FLUTTER_STORAGE_BASE_URL" contains an invalid URI:\n$err'); + throwToolExit('"$kFlutterStorageBaseUrl" contains an invalid URL:\n$err'); } final String cipdOverride = original.replace( @@ -1065,11 +1065,11 @@ class ArtifactUpdater { } continue; } on ArgumentError catch (error) { - final String? overrideUrl = _platform.environment['FLUTTER_STORAGE_BASE_URL']; + final String? overrideUrl = _platform.environment[kFlutterStorageBaseUrl]; if (overrideUrl != null && url.toString().contains(overrideUrl)) { _logger.printError(error.toString()); throwToolExit( - 'The value of FLUTTER_STORAGE_BASE_URL ($overrideUrl) could not be ' + 'The value of $kFlutterStorageBaseUrl ($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', diff --git a/packages/flutter_tools/lib/src/commands/update_packages.dart b/packages/flutter_tools/lib/src/commands/update_packages.dart index 94a66b34e39ec..bccc99b99ebef 100644 --- a/packages/flutter_tools/lib/src/commands/update_packages.dart +++ b/packages/flutter_tools/lib/src/commands/update_packages.dart @@ -136,7 +136,7 @@ class UpdatePackagesCommand extends FlutterCommand { ); Future _downloadCoverageData() async { - final String urlBase = globals.platform.environment['FLUTTER_STORAGE_BASE_URL'] ?? 'https://storage.googleapis.com'; + final String urlBase = globals.platform.environment[kFlutterStorageBaseUrl] ?? 'https://storage.googleapis.com'; final Uri coverageUri = Uri.parse('$urlBase/flutter_infra_release/flutter/coverage/lcov.info'); final List? data = await _net.fetchUrl( coverageUri, diff --git a/packages/flutter_tools/lib/src/doctor.dart b/packages/flutter_tools/lib/src/doctor.dart index 60b8b4e08a77b..c9a608366d69a 100644 --- a/packages/flutter_tools/lib/src/doctor.dart +++ b/packages/flutter_tools/lib/src/doctor.dart @@ -15,6 +15,7 @@ import 'base/context.dart'; import 'base/file_system.dart'; import 'base/io.dart'; import 'base/logger.dart'; +import 'base/net.dart'; import 'base/os.dart'; import 'base/platform.dart'; import 'base/terminal.dart'; @@ -528,11 +529,11 @@ class FlutterValidator extends DoctorValidator { messages.add(ValidationMessage(_userMessages.engineRevision(version.engineRevisionShort))); messages.add(ValidationMessage(_userMessages.dartRevision(version.dartSdkVersion))); messages.add(ValidationMessage(_userMessages.devToolsVersion(_devToolsVersion()))); - final String? pubUrl = _platform.environment['PUB_HOSTED_URL']; + final String? pubUrl = _platform.environment[kPubDevOverride]; if (pubUrl != null) { messages.add(ValidationMessage(_userMessages.pubMirrorURL(pubUrl))); } - final String? storageBaseUrl = _platform.environment['FLUTTER_STORAGE_BASE_URL']; + final String? storageBaseUrl = _platform.environment[kFlutterStorageBaseUrl]; if (storageBaseUrl != null) { messages.add(ValidationMessage(_userMessages.flutterMirrorURL(storageBaseUrl))); } diff --git a/packages/flutter_tools/lib/src/doctor_validator.dart b/packages/flutter_tools/lib/src/doctor_validator.dart index c49e7f9d1d0c1..2736be9dc68c3 100644 --- a/packages/flutter_tools/lib/src/doctor_validator.dart +++ b/packages/flutter_tools/lib/src/doctor_validator.dart @@ -207,6 +207,11 @@ class ValidationResult { return 'partial'; } } + + @override + String toString() { + return '$runtimeType($type, $messages, $statusInfo)'; + } } /// A status line for the flutter doctor validation to display. diff --git a/packages/flutter_tools/lib/src/http_host_validator.dart b/packages/flutter_tools/lib/src/http_host_validator.dart index 6e6c68d110cf0..37a7b7bdf1d20 100644 --- a/packages/flutter_tools/lib/src/http_host_validator.dart +++ b/packages/flutter_tools/lib/src/http_host_validator.dart @@ -5,32 +5,20 @@ import 'dart:async'; import 'base/io.dart'; +import 'base/net.dart'; import 'base/platform.dart'; import 'doctor_validator.dart'; import 'features.dart'; -// Overridable environment variables -const String kEnvPubHostedUrl = 'PUB_HOSTED_URL'; -const String kEnvCloudUrl = 'FLUTTER_STORAGE_BASE_URL'; -const String kDoctorHostTimeout = 'FLUTTER_DOCTOR_HOST_TIMEOUT'; - /// Common Flutter HTTP hosts. -const String kPubDevHttpHost = 'https://pub.dev/'; -const String kgCloudHttpHost = 'https://cloud.google.com/'; - -/// MacOS specific required HTTP hosts. -const List macOSRequiredHttpHosts = [ - 'https://cocoapods.org/', -]; +const String kCloudHost = 'https://storage.googleapis.com/'; +const String kCocoaPods = 'https://cocoapods.org/'; +const String kGitHub = 'https://github.com/'; +const String kMaven = 'https://maven.google.com/'; +const String kPubDev = 'https://pub.dev/'; -/// Android specific required HTTP hosts. -List androidRequiredHttpHosts(Platform platform) { - return [ - // If kEnvCloudUrl is set, it will be used as the maven host - if (!platform.environment.containsKey(kEnvCloudUrl)) - 'https://maven.google.com/', - ]; -} +// Overridable environment variables. +const String kPubDevOverride = 'PUB_HOSTED_URL'; // https://dart.dev/tools/pub/environment-variables // Validator that checks all provided hosts are reachable and responsive class HttpHostValidator extends DoctorValidator { @@ -41,103 +29,121 @@ class HttpHostValidator extends DoctorValidator { }) : _platform = platform, _featureFlags = featureFlags, _httpClient = httpClient, - super('HTTP Host Availability'); + super('Network resources'); final Platform _platform; final FeatureFlags _featureFlags; final HttpClient _httpClient; - @override - String get slowWarning => 'HTTP Host availability check is taking a long time...'; + final Set _activeHosts = {}; - List get _requiredHosts => [ - if (_featureFlags.isMacOSEnabled) ...macOSRequiredHttpHosts, - if (_featureFlags.isAndroidEnabled) ...androidRequiredHttpHosts(_platform), - _platform.environment[kEnvPubHostedUrl] ?? kPubDevHttpHost, - _platform.environment[kEnvCloudUrl] ?? kgCloudHttpHost, - ]; + @override + String get slowWarning { + if (_activeHosts.isEmpty) { + return 'Network resources check is taking a long time...'; + } + return 'Attempting to reach ${_activeHosts.map((Uri url) => url.host).join(", ")}...'; + } /// Make a head request to the HTTP host for checking availability - Future<_HostValidationResult> _checkHostAvailability(String host) async { - late final int timeout; + Future _checkHostAvailability(Uri host) async { try { - timeout = int.parse(_platform.environment[kDoctorHostTimeout] ?? '10'); - final HttpClientRequest req = await _httpClient.headUrl(Uri.parse(host)); - await req.close().timeout(Duration(seconds: timeout)); - // HTTP host is available if no exception happened - return _HostValidationResult.success(host); - } on TimeoutException { - return _HostValidationResult.fail(host, 'Failed to connect to host in $timeout second${timeout == 1 ? '': 's'}'); - } on SocketException catch (e) { - return _HostValidationResult.fail(host, 'An error occurred while checking the HTTP host: ${e.message}'); - } on HttpException catch (e) { - return _HostValidationResult.fail(host, 'An error occurred while checking the HTTP host: ${e.message}'); - } on HandshakeException catch (e) { - return _HostValidationResult.fail(host, 'An error occurred while checking the HTTP host: ${e.message}'); - } on OSError catch (e) { - return _HostValidationResult.fail(host, 'An error occurred while checking the HTTP host: ${e.message}'); - } on FormatException catch (e) { - if (e.message.contains('Invalid radix-10 number')) { - return _HostValidationResult.fail(host, 'The value of $kDoctorHostTimeout(${_platform.environment[kDoctorHostTimeout]}) is not a valid duration in seconds'); - } else if (e.message.contains('Invalid empty scheme')){ - // Check if the invalid host is kEnvPubHostedUrl, else it must be kEnvCloudUrl - final String? pubHostedUrl = _platform.environment[kEnvPubHostedUrl]; - if (pubHostedUrl != null && host == pubHostedUrl) { - return _HostValidationResult.fail(host, 'The value of $kEnvPubHostedUrl(${_platform.environment[kEnvPubHostedUrl]}) could not be parsed as a valid url'); - } - return _HostValidationResult.fail(host, 'The value of $kEnvCloudUrl(${_platform.environment[kEnvCloudUrl]}) could not be parsed as a valid url'); - } - return _HostValidationResult.fail(host, 'An error occurred while checking the HTTP host: ${e.message}'); - } on ArgumentError catch (e) { - final String exceptionMessage = e.message.toString(); - if (exceptionMessage.contains('No host specified')) { - // Check if the invalid host is kEnvPubHostedUrl, else it must be kEnvCloudUrl - final String? pubHostedUrl = _platform.environment[kEnvPubHostedUrl]; - if (pubHostedUrl != null && host == pubHostedUrl) { - return _HostValidationResult.fail(host, 'The value of $kEnvPubHostedUrl(${_platform.environment[kEnvPubHostedUrl]}) is not a valid host'); - } - return _HostValidationResult.fail(host, 'The value of $kEnvCloudUrl(${_platform.environment[kEnvCloudUrl]}) is not a valid host'); - } - return _HostValidationResult.fail(host, 'An error occurred while checking the HTTP host: $exceptionMessage'); + assert(!_activeHosts.contains(host)); + _activeHosts.add(host); + final HttpClientRequest req = await _httpClient.headUrl(host); + await req.close(); + // HTTP host is available if no exception happened. + return null; + } on SocketException catch (error) { + return 'A network error occurred while checking "$host": ${error.message}'; + } on HttpException catch (error) { + return 'An HTTP error occurred while checking "$host": ${error.message}'; + } on HandshakeException catch (error) { + return 'A crytographic error occurred while checking "$host": ${error.message}\n' + 'You may be experiencing a man-in-the-middle attack, your network may be ' + 'compromised, or you may have malware installed on your computer.'; + } on OSError catch (error) { + return 'An error occurred while checking "$host": ${error.message}'; + } finally { + _activeHosts.remove(host); } } + static Uri? _parseUrl(String value) { + final Uri? url = Uri.tryParse(value); + if (url == null || !url.hasScheme || !url.hasAuthority || (!url.hasEmptyPath && !url.hasAbsolutePath) || url.hasFragment) { + return null; + } + return url; + } + @override Future validate() async { - final List messages = []; - final Iterable> availabilityResultFutures = _requiredHosts.map(_checkHostAvailability); - final List<_HostValidationResult> availabilityResults = await Future.wait(availabilityResultFutures); - - if (availabilityResults.every((_HostValidationResult result) => result.available)) { - return ValidationResult( - ValidationType.success, - messages..add(const ValidationMessage('All required HTTP hosts are available')), - ); + final List availabilityResults = []; + + final List requiredHosts = []; + if (_platform.environment.containsKey(kPubDevOverride)) { + final Uri? url = _parseUrl(_platform.environment[kPubDevOverride]!); + if (url == null) { + availabilityResults.add( + 'Environment variable $kPubDevOverride does not specify a valid URL: "${_platform.environment[kPubDevOverride]}"\n' + 'Please see https://flutter.dev/community/china for an example of how to use it.' + ); + } else { + requiredHosts.add(url); + } + } else { + requiredHosts.add(Uri.parse(kPubDev)); } + if (_platform.environment.containsKey(kFlutterStorageBaseUrl)) { + final Uri? url = _parseUrl(_platform.environment[kFlutterStorageBaseUrl]!); + if (url == null) { + availabilityResults.add( + 'Environment variable $kFlutterStorageBaseUrl does not specify a valid URL: "${_platform.environment[kFlutterStorageBaseUrl]}"\n' + 'Please see https://flutter.dev/community/china for an example of how to use it.' + ); + } else { + requiredHosts.add(url); + } + } else { + requiredHosts.add(Uri.parse(kCloudHost)); + if (_featureFlags.isAndroidEnabled) { + // if kFlutterStorageBaseUrl is set it is used instead of Maven + requiredHosts.add(Uri.parse(kMaven)); + } + } + if (_featureFlags.isMacOSEnabled) { + requiredHosts.add(Uri.parse(kCocoaPods)); + } + requiredHosts.add(Uri.parse(kGitHub)); - availabilityResults.removeWhere((_HostValidationResult result) => result.available); + // Check all the hosts simultaneously. + availabilityResults.addAll(await Future.wait(requiredHosts.map(_checkHostAvailability))); - for (final _HostValidationResult result in availabilityResults) { - messages.add(ValidationMessage.error('HTTP host "${result.host}" is not reachable. Reason: ${result.failResultInfo}')); + int failures = 0; + int successes = 0; + final List messages = []; + for (final String? message in availabilityResults) { + if (message == null) { + successes += 1; + } else { + failures += 1; + messages.add(ValidationMessage.error(message)); + } } + if (failures == 0) { + assert(successes > 0); + assert(messages.isEmpty); + return const ValidationResult( + ValidationType.success, + [ValidationMessage('All expected network resources are available.')], + ); + } + assert(messages.isNotEmpty); return ValidationResult( - availabilityResults.length == _requiredHosts.length - ? ValidationType.notAvailable - : ValidationType.partial, + successes == 0 ? ValidationType.notAvailable : ValidationType.partial, messages, ); } } - -class _HostValidationResult { - _HostValidationResult.success(this.host) - : failResultInfo = '', - available = true; - - _HostValidationResult.fail(this.host, this.failResultInfo) : available = false; - - final String failResultInfo; - final String host; - final bool available; -} diff --git a/packages/flutter_tools/test/commands.shard/hermetic/http_host_validator_test.dart b/packages/flutter_tools/test/commands.shard/hermetic/http_host_validator_test.dart index 8fa25e423cbb0..d1a5f80b1a6a4 100644 --- a/packages/flutter_tools/test/commands.shard/hermetic/http_host_validator_test.dart +++ b/packages/flutter_tools/test/commands.shard/hermetic/http_host_validator_test.dart @@ -2,7 +2,6 @@ // 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:io'; import 'package:flutter_tools/src/base/platform.dart'; @@ -19,7 +18,6 @@ const String kTestEnvGCloudHost = 'https://storage.flutter-io.cn'; const Map kTestEnvironment = { 'PUB_HOSTED_URL': kTestEnvPubHost, 'FLUTTER_STORAGE_BASE_URL': kTestEnvGCloudHost, - 'FLUTTER_DOCTOR_HOST_TIMEOUT': '1', }; void main() { @@ -31,7 +29,7 @@ void main() { final FakeHttpClient mockClient = FakeHttpClient.any(); // Run the check for all operating systems one by one - for(final String os in osTested) { + for (final String os in osTested) { final HttpHostValidator httpHostValidator = HttpHostValidator( platform: FakePlatform(operatingSystem: os), featureFlags: TestFeatureFlags(), @@ -48,16 +46,17 @@ void main() { testWithoutContext('all http hosts are not available', () async { // Run the check for all operating systems one by one - for(final String os in osTested) { + for (final String os in osTested) { final Platform platform = FakePlatform(operatingSystem: os); final HttpHostValidator httpHostValidator = HttpHostValidator( platform: platform, featureFlags: TestFeatureFlags(), httpClient: FakeHttpClient.list([ - FakeRequest(Uri.parse(kgCloudHttpHost), method: HttpMethod.head, responseError: const OSError('Name or service not known', -2)), - FakeRequest(Uri.parse(androidRequiredHttpHosts(platform)[0]), method: HttpMethod.head, responseError: const OSError('Name or service not known', -2)), - FakeRequest(Uri.parse(kPubDevHttpHost), method: HttpMethod.head, responseError: const OSError('Name or service not known', -2)), - FakeRequest(Uri.parse(macOSRequiredHttpHosts[0]), method: HttpMethod.head, responseError: const OSError('Name or service not known', -2)), + FakeRequest(Uri.parse(kCloudHost), method: HttpMethod.head, responseError: const OSError('Name or service not known', -2)), + FakeRequest(Uri.parse(kCocoaPods), method: HttpMethod.head, responseError: const OSError('Name or service not known', -2)), + FakeRequest(Uri.parse(kGitHub), method: HttpMethod.head, responseError: const OSError('Name or service not known', -2)), + FakeRequest(Uri.parse(kMaven), method: HttpMethod.head, responseError: const OSError('Name or service not known', -2)), + FakeRequest(Uri.parse(kPubDev), method: HttpMethod.head, responseError: const OSError('Name or service not known', -2)), ]), ); @@ -71,16 +70,17 @@ void main() { testWithoutContext('one http host is not available', () async { // Run the check for all operating systems one by one - for(final String os in osTested) { + for (final String os in osTested) { final Platform platform = FakePlatform(operatingSystem: os); final HttpHostValidator httpHostValidator = HttpHostValidator( platform: platform, featureFlags: TestFeatureFlags(), httpClient: FakeHttpClient.list([ - FakeRequest(Uri.parse(kgCloudHttpHost), method: HttpMethod.head, responseError: const OSError('Name or service not known', -2)), - FakeRequest(Uri.parse(androidRequiredHttpHosts(platform)[0]), method: HttpMethod.head), - FakeRequest(Uri.parse(kPubDevHttpHost), method: HttpMethod.head), - FakeRequest(Uri.parse(macOSRequiredHttpHosts[0]), method: HttpMethod.head), + FakeRequest(Uri.parse(kCloudHost), method: HttpMethod.head, responseError: const OSError('Name or service not known', -2)), + FakeRequest(Uri.parse(kCocoaPods), method: HttpMethod.head), + FakeRequest(Uri.parse(kGitHub), method: HttpMethod.head), + FakeRequest(Uri.parse(kMaven), method: HttpMethod.head), + FakeRequest(Uri.parse(kPubDev), method: HttpMethod.head), ]), ); @@ -98,7 +98,7 @@ void main() { final FakeHttpClient mockClient = FakeHttpClient.any(); // Run the check for all operating systems one by one - for(final String os in osTested) { + for (final String os in osTested) { final HttpHostValidator httpHostValidator = HttpHostValidator( platform: FakePlatform(operatingSystem: os, environment: kTestEnvironment), featureFlags: TestFeatureFlags(), @@ -115,15 +115,16 @@ void main() { testWithoutContext('all http hosts are not available', () async { // Run the check for all operating systems one by one - for(final String os in osTested) { + for (final String os in osTested) { final Platform platform = FakePlatform(operatingSystem: os, environment: kTestEnvironment); final HttpHostValidator httpHostValidator = HttpHostValidator( platform: platform, featureFlags: TestFeatureFlags(), httpClient: FakeHttpClient.list([ + FakeRequest(Uri.parse(kCocoaPods), method: HttpMethod.head, responseError: const OSError('Name or service not known', -2)), + FakeRequest(Uri.parse(kGitHub), method: HttpMethod.head, responseError: const OSError('Name or service not known', -2)), FakeRequest(Uri.parse(kTestEnvGCloudHost), method: HttpMethod.head, responseError: const OSError('Name or service not known', -2)), FakeRequest(Uri.parse(kTestEnvPubHost), method: HttpMethod.head, responseError: const OSError('Name or service not known', -2)), - FakeRequest(Uri.parse(macOSRequiredHttpHosts[0]), method: HttpMethod.head, responseError: const OSError('Name or service not known', -2)), ]), ); @@ -137,15 +138,16 @@ void main() { testWithoutContext('one http host is not available', () async { // Run the check for all operating systems one by one - for(final String os in osTested) { + for (final String os in osTested) { final Platform platform = FakePlatform(operatingSystem: os, environment: kTestEnvironment); final HttpHostValidator httpHostValidator = HttpHostValidator( platform: platform, featureFlags: TestFeatureFlags(), httpClient: FakeHttpClient.list([ + FakeRequest(Uri.parse(kCocoaPods), method: HttpMethod.head), + FakeRequest(Uri.parse(kGitHub), method: HttpMethod.head, responseError: const OSError('Name or service not known', -2)), FakeRequest(Uri.parse(kTestEnvGCloudHost), method: HttpMethod.head, responseError: const OSError('Name or service not known', -2)), FakeRequest(Uri.parse(kTestEnvPubHost), method: HttpMethod.head), - FakeRequest(Uri.parse(macOSRequiredHttpHosts[0]), method: HttpMethod.head), ]), ); @@ -157,39 +159,12 @@ void main() { } }); - testWithoutContext('does not throw on invalid user-defined timeout', () async { - final HttpHostValidator httpHostValidator = HttpHostValidator( - platform: FakePlatform( - environment: { - 'PUB_HOSTED_URL': kTestEnvPubHost, - 'FLUTTER_STORAGE_BASE_URL': kTestEnvGCloudHost, - 'FLUTTER_DOCTOR_HOST_TIMEOUT' : 'deadbeef', - }, - ), - featureFlags: TestFeatureFlags(isAndroidEnabled: false), - httpClient: FakeHttpClient.any(), - ); - - // Run the validation check and get the results - final ValidationResult result = await httpHostValidator.validate(); - - expect(result.type, equals(ValidationType.notAvailable)); - expect( - result.messages, - contains(const ValidationMessage.error( - 'HTTP host "$kTestEnvPubHost" is not reachable. ' - 'Reason: The value of FLUTTER_DOCTOR_HOST_TIMEOUT(deadbeef) is not a valid duration in seconds', - )), - ); - }); - testWithoutContext('does not throw on unparseable user-defined host uri', () async { final HttpHostValidator httpHostValidator = HttpHostValidator( platform: FakePlatform( environment: { 'PUB_HOSTED_URL': '::Not A Uri::', 'FLUTTER_STORAGE_BASE_URL': kTestEnvGCloudHost, - 'FLUTTER_DOCTOR_HOST_TIMEOUT' : '1', }, ), featureFlags: TestFeatureFlags(isAndroidEnabled: false), @@ -203,8 +178,8 @@ void main() { expect( result.messages, contains(const ValidationMessage.error( - 'HTTP host "::Not A Uri::" is not reachable. ' - 'Reason: The value of PUB_HOSTED_URL(::Not A Uri::) could not be parsed as a valid url', + 'Environment variable PUB_HOSTED_URL does not specify a valid URL: "::Not A Uri::"\n' + 'Please see https://flutter.dev/community/china for an example of how to use it.', )), ); }); @@ -215,7 +190,6 @@ void main() { environment: { 'PUB_HOSTED_URL': kTestEnvPubHost, 'FLUTTER_STORAGE_BASE_URL': '', - 'FLUTTER_DOCTOR_HOST_TIMEOUT' : '1', }, ), featureFlags: TestFeatureFlags(isAndroidEnabled: false), @@ -229,8 +203,8 @@ void main() { expect( result.messages, contains(const ValidationMessage.error( - 'HTTP host "" is not reachable. ' - 'Reason: The value of FLUTTER_STORAGE_BASE_URL() is not a valid host', + 'Environment variable FLUTTER_STORAGE_BASE_URL does not specify a valid URL: ""\n' + 'Please see https://flutter.dev/community/china for an example of how to use it.' )), ); }); @@ -239,14 +213,15 @@ void main() { group('specific os disabled', () { testWithoutContext('all http hosts are available - android disabled', () async { // Run the check for all operating systems one by one - for(final String os in osTested) { + for (final String os in osTested) { final HttpHostValidator httpHostValidator = HttpHostValidator( platform: FakePlatform(operatingSystem: os), featureFlags: TestFeatureFlags(isAndroidEnabled: false), httpClient: FakeHttpClient.list([ - FakeRequest(Uri.parse(kgCloudHttpHost), method: HttpMethod.head), - FakeRequest(Uri.parse(kPubDevHttpHost), method: HttpMethod.head), - FakeRequest(Uri.parse(macOSRequiredHttpHosts[0]), method: HttpMethod.head), + FakeRequest(Uri.parse(kCloudHost), method: HttpMethod.head), + FakeRequest(Uri.parse(kCocoaPods), method: HttpMethod.head), + FakeRequest(Uri.parse(kGitHub), method: HttpMethod.head), + FakeRequest(Uri.parse(kPubDev), method: HttpMethod.head), ]), ); @@ -260,15 +235,16 @@ void main() { testWithoutContext('all http hosts are available - iOS disabled', () async { // Run the check for all operating systems one by one - for(final String os in osTested) { + for (final String os in osTested) { final Platform platform = FakePlatform(operatingSystem: os); final HttpHostValidator httpHostValidator = HttpHostValidator( platform: platform, featureFlags: TestFeatureFlags(isIOSEnabled: false), httpClient: FakeHttpClient.list([ - FakeRequest(Uri.parse(kgCloudHttpHost), method: HttpMethod.head), - FakeRequest(Uri.parse(kPubDevHttpHost), method: HttpMethod.head), - FakeRequest(Uri.parse(androidRequiredHttpHosts(platform)[0]), method: HttpMethod.head), + FakeRequest(Uri.parse(kCloudHost), method: HttpMethod.head), + FakeRequest(Uri.parse(kGitHub), method: HttpMethod.head), + FakeRequest(Uri.parse(kMaven), method: HttpMethod.head), + FakeRequest(Uri.parse(kPubDev), method: HttpMethod.head), ]), ); @@ -282,13 +258,14 @@ void main() { testWithoutContext('all http hosts are available - android, iOS disabled', () async { // Run the check for all operating systems one by one - for(final String os in osTested) { + for (final String os in osTested) { final HttpHostValidator httpHostValidator = HttpHostValidator( platform: FakePlatform(operatingSystem: os), featureFlags: TestFeatureFlags(isAndroidEnabled: false, isIOSEnabled: false), httpClient: FakeHttpClient.list([ - FakeRequest(Uri.parse(kgCloudHttpHost), method: HttpMethod.head), - FakeRequest(Uri.parse(kPubDevHttpHost), method: HttpMethod.head), + FakeRequest(Uri.parse(kCloudHost), method: HttpMethod.head), + FakeRequest(Uri.parse(kGitHub), method: HttpMethod.head), + FakeRequest(Uri.parse(kPubDev), method: HttpMethod.head), ]), ); @@ -320,6 +297,7 @@ Handshake error in client (OS Error: responseError: const HandshakeException(handshakeMessage), ), FakeRequest(Uri.parse(kTestEnvGCloudHost), method: HttpMethod.head), + FakeRequest(Uri.parse(kGitHub), method: HttpMethod.head), ]), ); @@ -335,24 +313,4 @@ Handshake error in client (OS Error: ), ); }); - - testWithoutContext('Http host validator timeout message includes timeout duration.', () async { - final HttpHostValidator httpHostValidator = HttpHostValidator( - platform: FakePlatform(environment: kTestEnvironment), - featureFlags: TestFeatureFlags(isAndroidEnabled: false), - httpClient: FakeHttpClient.list([ - FakeRequest(Uri.parse(kTestEnvPubHost), method: HttpMethod.head, responseError: TimeoutException('Timeout error')), - FakeRequest(Uri.parse(kTestEnvGCloudHost), method: HttpMethod.head), - ]), - ); - - // Run the validation check and get the results - final ValidationResult result = await httpHostValidator.validate(); - - // Timeout duration for tests is set to 1 second - expect( - result.messages, - contains(const ValidationMessage.error('HTTP host "$kTestEnvPubHost" is not reachable. Reason: Failed to connect to host in 1 second')), - ); - }); } diff --git a/packages/flutter_tools/test/general.shard/base/logger_test.dart b/packages/flutter_tools/test/general.shard/base/logger_test.dart index d48389d2b730e..f8f8dc08c019c 100644 --- a/packages/flutter_tools/test/general.shard/base/logger_test.dart +++ b/packages/flutter_tools/test/general.shard/base/logger_test.dart @@ -497,10 +497,7 @@ void main() { time.elapse(timeLapse); List lines = outputStdout(); - expect( - lines.join(), - contains(warningMessage), - ); + expect(lines.join(), '⣽\ba warning message.⣻'); spinner.stop(); lines = outputStdout();