Unverified Commit d29668dd authored by Ian Hickson's avatar Ian Hickson Committed by GitHub

Improve network resources doctor check (#120417)

...and various other minor cleanup:

* Moved "FLUTTER_STORAGE_BASE_URL" into a constant throughout the code. There are other strings that we should do that to but this one was relevant to the code I was changing.

* Fixed the logger's handling of slow warnings. Previously it deleted too much text. Fixed the test for that to actually verify it entirely, too.

* Made the logger delete the slow warning when it's finished.

* Fixed 'Please choose one (To quit, press "q/Q")' message to be the cleaner 'Please choose one (or "q" to quit)'.

* Added a debug toString to ValidationResult for debugging purposes (not used).

* In http_host_validator:

  - Shortened constant names to be clearer (e.g. kPubDevHttpHost -> kPubDev).
  - Added GitHub as a tested host since when you run `flutter` we hit that immediately.
  - Renamed the check "Network resources".
  - Updated the `slowWarning` of the check to say which hosts are pending.
  - Removed all timeout logic. Timeouts violate our style guide.
  - Removed `int.parse(... ?? '10')`; passing a constant to `int.parse` is inefficient.
  - Replaced the `_HostValidationResult` class with `String?` for simplicity.
  - Improved the error messages to be more detailed.
  - Removed all checks that dependened on the stringification of exceptions. That's very brittle.
  - Added a warning specifically for HandshakeException that talks about the implications (MITM attacks).
  - Replaced exception-message-parsing logic with just calling `Uri.tryParse` and validating the result.
  - Replaced a lot of list-filtering logic with just a single for loop to check the results.
  - Replaced code that added a constant to a known-empty list with just returning a constant list.
  - Revamped the logic for deciding which hosts to check to just use a single chain of if/else blocks instead of getters, lists literals with `if` expressions, `??`, functions, etc spread over multiple places in the code.
parent 9d94a51b
......@@ -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('<host>', '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}'
......
......@@ -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();
}
......
......@@ -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;
......
......@@ -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 <deviceId>' flag, or use '-d all' to act on all devices.";
......
......@@ -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',
......
......@@ -136,7 +136,7 @@ class UpdatePackagesCommand extends FlutterCommand {
);
Future<void> _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<int>? data = await _net.fetchUrl(
coverageUri,
......
......@@ -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)));
}
......
......@@ -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.
......
......@@ -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<String> macOSRequiredHttpHosts = <String>[
'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<String> androidRequiredHttpHosts(Platform platform) {
return <String>[
// 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<Uri> _activeHosts = <Uri>{};
List<String> get _requiredHosts => <String>[
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<String?> _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<ValidationResult> validate() async {
final List<ValidationMessage> messages = <ValidationMessage>[];
final Iterable<Future<_HostValidationResult>> 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<String?> availabilityResults = <String?>[];
final List<Uri> requiredHosts = <Uri>[];
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<String?>(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<ValidationMessage> messages = <ValidationMessage>[];
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>[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;
}
......@@ -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<String, String> kTestEnvironment = <String, String>{
'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>[
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>[
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>[
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>[
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: <String,String> {
'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: <String,String> {
'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: <String,String> {
'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>[
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>[
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>[
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>[
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')),
);
});
}
......@@ -497,10 +497,7 @@ void main() {
time.elapse(timeLapse);
List<String> lines = outputStdout();
expect(
lines.join(),
contains(warningMessage),
);
expect(lines.join(), '⣽\ba warning message.⣻');
spinner.stop();
lines = outputStdout();
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment