Add validator execution times to flutter doctor --verbose
(#158124)
Should help provide more information for `flutter doctor` timeouts like we've seen in https://github.com/flutter/flutter/issues/157513
This commit is contained in:
parent
5f06c091b9
commit
b3e65358d7
@ -52,7 +52,7 @@ class AndroidStudioValidator extends DoctorValidator {
|
||||
}
|
||||
|
||||
@override
|
||||
Future<ValidationResult> validate() async {
|
||||
Future<ValidationResult> validateImpl() async {
|
||||
final List<ValidationMessage> messages = <ValidationMessage>[];
|
||||
ValidationType type = ValidationType.missing;
|
||||
|
||||
@ -125,7 +125,7 @@ class NoAndroidStudioValidator extends DoctorValidator {
|
||||
final UserMessages _userMessages;
|
||||
|
||||
@override
|
||||
Future<ValidationResult> validate() async {
|
||||
Future<ValidationResult> validateImpl() async {
|
||||
final List<ValidationMessage> messages = <ValidationMessage>[];
|
||||
|
||||
final String? cfgAndroidStudio = _config.getValue('android-studio-dir') as String?;
|
||||
|
@ -134,7 +134,7 @@ class AndroidValidator extends DoctorValidator {
|
||||
}
|
||||
|
||||
@override
|
||||
Future<ValidationResult> validate() async {
|
||||
Future<ValidationResult> validateImpl() async {
|
||||
final List<ValidationMessage> messages = <ValidationMessage>[];
|
||||
final AndroidSdk? androidSdk = _androidSdk;
|
||||
if (androidSdk == null) {
|
||||
@ -267,7 +267,7 @@ class AndroidLicenseValidator extends DoctorValidator {
|
||||
String get slowWarning => 'Checking Android licenses is taking an unexpectedly long time...';
|
||||
|
||||
@override
|
||||
Future<ValidationResult> validate() async {
|
||||
Future<ValidationResult> validateImpl() async {
|
||||
final List<ValidationMessage> messages = <ValidationMessage>[];
|
||||
|
||||
// Match pre-existing early termination behavior
|
||||
|
@ -63,9 +63,11 @@ abstract class DoctorValidatorsProvider {
|
||||
}
|
||||
|
||||
/// The singleton instance, pulled from the [AppContext].
|
||||
static DoctorValidatorsProvider get _instance => context.get<DoctorValidatorsProvider>()!;
|
||||
static DoctorValidatorsProvider get _instance =>
|
||||
context.get<DoctorValidatorsProvider>()!;
|
||||
|
||||
static final DoctorValidatorsProvider defaultInstance = _DefaultDoctorValidatorsProvider(
|
||||
static final DoctorValidatorsProvider defaultInstance =
|
||||
_DefaultDoctorValidatorsProvider(
|
||||
logger: globals.logger,
|
||||
platform: globals.platform,
|
||||
featureFlags: featureFlags,
|
||||
@ -93,7 +95,8 @@ class _DefaultDoctorValidatorsProvider implements DoctorValidatorsProvider {
|
||||
featureFlags: featureFlags,
|
||||
);
|
||||
|
||||
late final WebWorkflow webWorkflow = WebWorkflow(platform: platform, featureFlags: featureFlags);
|
||||
late final WebWorkflow webWorkflow =
|
||||
WebWorkflow(platform: platform, featureFlags: featureFlags);
|
||||
|
||||
late final MacOSWorkflow macOSWorkflow = MacOSWorkflow(
|
||||
platform: platform,
|
||||
@ -126,15 +129,16 @@ class _DefaultDoctorValidatorsProvider implements DoctorValidatorsProvider {
|
||||
processManager: globals.processManager,
|
||||
logger: _logger,
|
||||
),
|
||||
...VsCodeValidator.installedValidators(globals.fs, platform, globals.processManager),
|
||||
...VsCodeValidator.installedValidators(
|
||||
globals.fs, platform, globals.processManager),
|
||||
];
|
||||
final ProxyValidator proxyValidator = ProxyValidator(platform: platform);
|
||||
_validators = <DoctorValidator>[
|
||||
FlutterValidator(
|
||||
fileSystem: globals.fs,
|
||||
platform: globals.platform,
|
||||
flutterVersion:
|
||||
() => globals.flutterVersion.fetchTagsAndGetVersion(clock: globals.systemClock),
|
||||
flutterVersion: () => globals.flutterVersion
|
||||
.fetchTagsAndGetVersion(clock: globals.systemClock),
|
||||
devToolsVersion: () => globals.cache.devToolsVersion,
|
||||
processManager: globals.processManager,
|
||||
userMessages: globals.userMessages,
|
||||
@ -152,8 +156,10 @@ class _DefaultDoctorValidatorsProvider implements DoctorValidatorsProvider {
|
||||
),
|
||||
),
|
||||
if (androidWorkflow!.appliesToHostPlatform)
|
||||
GroupedValidator(<DoctorValidator>[androidValidator!, androidLicenseValidator!]),
|
||||
if (globals.iosWorkflow!.appliesToHostPlatform || macOSWorkflow.appliesToHostPlatform)
|
||||
GroupedValidator(
|
||||
<DoctorValidator>[androidValidator!, androidLicenseValidator!]),
|
||||
if (globals.iosWorkflow!.appliesToHostPlatform ||
|
||||
macOSWorkflow.appliesToHostPlatform)
|
||||
GroupedValidator(<DoctorValidator>[
|
||||
XcodeValidator(
|
||||
xcode: globals.xcode!,
|
||||
@ -183,7 +189,9 @@ class _DefaultDoctorValidatorsProvider implements DoctorValidatorsProvider {
|
||||
if (ideValidators.isNotEmpty) ...ideValidators else NoIdeValidator(),
|
||||
if (proxyValidator.shouldShow) proxyValidator,
|
||||
if (globals.deviceManager?.canListAnything ?? false)
|
||||
DeviceValidator(deviceManager: globals.deviceManager, userMessages: globals.userMessages),
|
||||
DeviceValidator(
|
||||
deviceManager: globals.deviceManager,
|
||||
userMessages: globals.userMessages),
|
||||
HttpHostValidator(
|
||||
platform: globals.platform,
|
||||
featureFlags: featureFlags,
|
||||
@ -208,10 +216,13 @@ class _DefaultDoctorValidatorsProvider implements DoctorValidatorsProvider {
|
||||
}
|
||||
|
||||
class Doctor {
|
||||
Doctor({required Logger logger, required SystemClock clock, Analytics? analytics})
|
||||
: _logger = logger,
|
||||
_clock = clock,
|
||||
_analytics = analytics ?? globals.analytics;
|
||||
Doctor(
|
||||
{required Logger logger,
|
||||
required SystemClock clock,
|
||||
Analytics? analytics})
|
||||
: _logger = logger,
|
||||
_clock = clock,
|
||||
_analytics = analytics ?? globals.analytics;
|
||||
|
||||
final Logger _logger;
|
||||
final SystemClock _clock;
|
||||
@ -224,39 +235,41 @@ class Doctor {
|
||||
/// Return a list of [ValidatorTask] objects and starts validation on all
|
||||
/// objects in [validators].
|
||||
List<ValidatorTask> startValidatorTasks() => <ValidatorTask>[
|
||||
for (final DoctorValidator validator in validators)
|
||||
ValidatorTask(
|
||||
validator,
|
||||
// We use an asyncGuard() here to be absolutely certain that
|
||||
// DoctorValidators do not result in an uncaught exception. Since the
|
||||
// Future returned by the asyncGuard() is not awaited, we pass an
|
||||
// onError callback to it and translate errors into ValidationResults.
|
||||
asyncGuard<ValidationResult>(
|
||||
() {
|
||||
final Completer<ValidationResult> timeoutCompleter = Completer<ValidationResult>();
|
||||
final Timer timer = Timer(doctorDuration, () {
|
||||
timeoutCompleter.completeError(
|
||||
Exception(
|
||||
'${validator.title} exceeded maximum allowed duration of $doctorDuration',
|
||||
),
|
||||
);
|
||||
});
|
||||
final Future<ValidationResult> validatorFuture = validator.validate();
|
||||
return Future.any<ValidationResult>(<Future<ValidationResult>>[
|
||||
validatorFuture,
|
||||
// This future can only complete with an error
|
||||
timeoutCompleter.future,
|
||||
]).then((ValidationResult result) async {
|
||||
timer.cancel();
|
||||
return result;
|
||||
});
|
||||
},
|
||||
onError: (Object exception, StackTrace stackTrace) {
|
||||
return ValidationResult.crash(exception, stackTrace);
|
||||
},
|
||||
),
|
||||
),
|
||||
];
|
||||
for (final DoctorValidator validator in validators)
|
||||
ValidatorTask(
|
||||
validator,
|
||||
// We use an asyncGuard() here to be absolutely certain that
|
||||
// DoctorValidators do not result in an uncaught exception. Since the
|
||||
// Future returned by the asyncGuard() is not awaited, we pass an
|
||||
// onError callback to it and translate errors into ValidationResults.
|
||||
asyncGuard<ValidationResult>(
|
||||
() {
|
||||
final Completer<ValidationResult> timeoutCompleter =
|
||||
Completer<ValidationResult>();
|
||||
final Timer timer = Timer(doctorDuration, () {
|
||||
timeoutCompleter.completeError(
|
||||
Exception(
|
||||
'${validator.title} exceeded maximum allowed duration of $doctorDuration',
|
||||
),
|
||||
);
|
||||
});
|
||||
final Future<ValidationResult> validatorFuture =
|
||||
validator.validate();
|
||||
return Future.any<ValidationResult>(<Future<ValidationResult>>[
|
||||
validatorFuture,
|
||||
// This future can only complete with an error
|
||||
timeoutCompleter.future,
|
||||
]).then((ValidationResult result) async {
|
||||
timer.cancel();
|
||||
return result;
|
||||
});
|
||||
},
|
||||
onError: (Object exception, StackTrace stackTrace) {
|
||||
return ValidationResult.crash(exception, stackTrace);
|
||||
},
|
||||
),
|
||||
),
|
||||
];
|
||||
|
||||
List<Workflow> get workflows {
|
||||
return DoctorValidatorsProvider._instance.workflows;
|
||||
@ -277,7 +290,8 @@ class Doctor {
|
||||
final StringBuffer lineBuffer = StringBuffer();
|
||||
ValidationResult result;
|
||||
try {
|
||||
result = await asyncGuard<ValidationResult>(() => validator.validate());
|
||||
result =
|
||||
await asyncGuard<ValidationResult>(() => validator.validateImpl());
|
||||
} on Exception catch (exception) {
|
||||
// We're generating a summary, so drop the stack trace.
|
||||
result = ValidationResult.crash(exception);
|
||||
@ -290,7 +304,8 @@ class Doctor {
|
||||
case ValidationType.missing:
|
||||
lineBuffer.write('is not installed.');
|
||||
case ValidationType.partial:
|
||||
lineBuffer.write('is partially installed; more components are available.');
|
||||
lineBuffer
|
||||
.write('is partially installed; more components are available.');
|
||||
case ValidationType.notAvailable:
|
||||
lineBuffer.write('is not available.');
|
||||
case ValidationType.success:
|
||||
@ -318,7 +333,8 @@ class Doctor {
|
||||
|
||||
if (sawACrash) {
|
||||
buffer.writeln();
|
||||
buffer.writeln('Run "flutter doctor" for information about why a doctor check crashed.');
|
||||
buffer.writeln(
|
||||
'Run "flutter doctor" for information about why a doctor check crashed.');
|
||||
}
|
||||
|
||||
if (missingComponent) {
|
||||
@ -332,7 +348,8 @@ class Doctor {
|
||||
}
|
||||
|
||||
Future<bool> checkRemoteArtifacts(String engineRevision) async {
|
||||
return globals.cache.areRemoteArtifactsAvailable(engineVersion: engineRevision);
|
||||
return globals.cache
|
||||
.areRemoteArtifactsAvailable(engineVersion: engineRevision);
|
||||
}
|
||||
|
||||
/// Maximum allowed duration for an entire validator to take.
|
||||
@ -360,7 +377,8 @@ class Doctor {
|
||||
}
|
||||
|
||||
if (!verbose) {
|
||||
_logger.printStatus('Doctor summary (to see all details, run flutter doctor -v):');
|
||||
_logger.printStatus(
|
||||
'Doctor summary (to see all details, run flutter doctor -v):');
|
||||
}
|
||||
bool doctorResult = true;
|
||||
int issues = 0;
|
||||
@ -369,7 +387,8 @@ class Doctor {
|
||||
// were sent for each doctor validator and its result
|
||||
final int analyticsTimestamp = _clock.now().millisecondsSinceEpoch;
|
||||
|
||||
for (final ValidatorTask validatorTask in startedValidatorTasks ?? startValidatorTasks()) {
|
||||
for (final ValidatorTask validatorTask
|
||||
in startedValidatorTasks ?? startValidatorTasks()) {
|
||||
final DoctorValidator validator = validatorTask.validator;
|
||||
final Status status = _logger.startSpinner(
|
||||
timeout: validator.slowWarningDuration,
|
||||
@ -437,29 +456,41 @@ class Doctor {
|
||||
DoctorResultEvent(validator: validator, result: result).send();
|
||||
}
|
||||
|
||||
final String leadingBox = showColor ? result.coloredLeadingBox : result.leadingBox;
|
||||
final String executionDuration = () {
|
||||
final Duration? executionTime = result.executionTime;
|
||||
if (!verbose || executionTime == null) {
|
||||
return '';
|
||||
}
|
||||
final String formatted = executionTime.inSeconds < 2
|
||||
? getElapsedAsMilliseconds(executionTime)
|
||||
: getElapsedAsSeconds(executionTime);
|
||||
return ' [$formatted]';
|
||||
}();
|
||||
|
||||
final String leadingBox =
|
||||
showColor ? result.coloredLeadingBox : result.leadingBox;
|
||||
if (result.statusInfo != null) {
|
||||
_logger.printStatus(
|
||||
'$leadingBox ${validator.title} (${result.statusInfo})',
|
||||
hangingIndent: result.leadingBox.length + 1,
|
||||
);
|
||||
'$leadingBox ${validator.title} (${result.statusInfo})$executionDuration',
|
||||
hangingIndent: result.leadingBox.length + 1);
|
||||
} else {
|
||||
_logger.printStatus(
|
||||
'$leadingBox ${validator.title}',
|
||||
hangingIndent: result.leadingBox.length + 1,
|
||||
);
|
||||
_logger.printStatus('$leadingBox ${validator.title}$executionDuration',
|
||||
hangingIndent: result.leadingBox.length + 1);
|
||||
}
|
||||
|
||||
for (final ValidationMessage message in result.messages) {
|
||||
if (!message.isInformation || verbose) {
|
||||
int hangingIndent = 2;
|
||||
int indent = 4;
|
||||
final String indicator = showColor ? message.coloredIndicator : message.indicator;
|
||||
final String indicator =
|
||||
showColor ? message.coloredIndicator : message.indicator;
|
||||
for (final String line
|
||||
in '$indicator ${showPii ? message.message : message.piiStrippedMessage}'.split(
|
||||
'\n',
|
||||
)) {
|
||||
_logger.printStatus(line, hangingIndent: hangingIndent, indent: indent, emphasis: true);
|
||||
in '$indicator ${showPii ? message.message : message.piiStrippedMessage}'
|
||||
.split(
|
||||
'\n',
|
||||
)) {
|
||||
_logger.printStatus(line,
|
||||
hangingIndent: hangingIndent, indent: indent, emphasis: true);
|
||||
// Only do hanging indent for the first line.
|
||||
hangingIndent = 0;
|
||||
indent = 6;
|
||||
@ -501,7 +532,8 @@ class Doctor {
|
||||
return doctorResult;
|
||||
}
|
||||
|
||||
bool get canListAnything => workflows.any((Workflow workflow) => workflow.canListDevices);
|
||||
bool get canListAnything =>
|
||||
workflows.any((Workflow workflow) => workflow.canListDevices);
|
||||
|
||||
bool get canLaunchAnything {
|
||||
if (FlutterTesterDevices.showFlutterTesterDevice) {
|
||||
@ -527,16 +559,16 @@ class FlutterValidator extends DoctorValidator {
|
||||
required ProcessManager processManager,
|
||||
required String Function() flutterRoot,
|
||||
required OperatingSystemUtils operatingSystemUtils,
|
||||
}) : _flutterVersion = flutterVersion,
|
||||
_devToolsVersion = devToolsVersion,
|
||||
_platform = platform,
|
||||
_userMessages = userMessages,
|
||||
_fileSystem = fileSystem,
|
||||
_artifacts = artifacts,
|
||||
_processManager = processManager,
|
||||
_flutterRoot = flutterRoot,
|
||||
_operatingSystemUtils = operatingSystemUtils,
|
||||
super('Flutter');
|
||||
}) : _flutterVersion = flutterVersion,
|
||||
_devToolsVersion = devToolsVersion,
|
||||
_platform = platform,
|
||||
_userMessages = userMessages,
|
||||
_fileSystem = fileSystem,
|
||||
_artifacts = artifacts,
|
||||
_processManager = processManager,
|
||||
_flutterRoot = flutterRoot,
|
||||
_operatingSystemUtils = operatingSystemUtils,
|
||||
super('Flutter');
|
||||
|
||||
final Platform _platform;
|
||||
final FlutterVersion Function() _flutterVersion;
|
||||
@ -549,7 +581,7 @@ class FlutterValidator extends DoctorValidator {
|
||||
final OperatingSystemUtils _operatingSystemUtils;
|
||||
|
||||
@override
|
||||
Future<ValidationResult> validate() async {
|
||||
Future<ValidationResult> validateImpl() async {
|
||||
final List<ValidationMessage> messages = <ValidationMessage>[];
|
||||
String? versionChannel;
|
||||
String? frameworkVersion;
|
||||
@ -561,7 +593,8 @@ class FlutterValidator extends DoctorValidator {
|
||||
frameworkVersion = version.frameworkVersion;
|
||||
|
||||
final String flutterRoot = _flutterRoot();
|
||||
messages.add(_getFlutterVersionMessage(frameworkVersion, versionChannel, flutterRoot));
|
||||
messages.add(_getFlutterVersionMessage(
|
||||
frameworkVersion, versionChannel, flutterRoot));
|
||||
|
||||
_validateRequiredBinaries(flutterRoot).forEach(messages.add);
|
||||
messages.add(_getFlutterUpstreamMessage(version));
|
||||
@ -577,16 +610,21 @@ class FlutterValidator extends DoctorValidator {
|
||||
),
|
||||
),
|
||||
);
|
||||
messages.add(ValidationMessage(_userMessages.engineRevision(version.engineRevisionShort)));
|
||||
messages.add(ValidationMessage(_userMessages.dartRevision(version.dartSdkVersion)));
|
||||
messages.add(ValidationMessage(_userMessages.devToolsVersion(_devToolsVersion())));
|
||||
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[kPubDevOverride];
|
||||
if (pubUrl != null) {
|
||||
messages.add(ValidationMessage(_userMessages.pubMirrorURL(pubUrl)));
|
||||
}
|
||||
final String? storageBaseUrl = _platform.environment[kFlutterStorageBaseUrl];
|
||||
final String? storageBaseUrl =
|
||||
_platform.environment[kFlutterStorageBaseUrl];
|
||||
if (storageBaseUrl != null) {
|
||||
messages.add(ValidationMessage(_userMessages.flutterMirrorURL(storageBaseUrl)));
|
||||
messages.add(
|
||||
ValidationMessage(_userMessages.flutterMirrorURL(storageBaseUrl)));
|
||||
}
|
||||
} on VersionCheckError catch (e) {
|
||||
messages.add(ValidationMessage.error(e.message));
|
||||
@ -595,8 +633,10 @@ class FlutterValidator extends DoctorValidator {
|
||||
// Check that the binaries we downloaded for this platform actually run on it.
|
||||
// If the binaries are not downloaded (because android is not enabled), then do
|
||||
// not run this check.
|
||||
final String genSnapshotPath = _artifacts.getArtifactPath(Artifact.genSnapshot);
|
||||
if (_fileSystem.file(genSnapshotPath).existsSync() && !_genSnapshotRuns(genSnapshotPath)) {
|
||||
final String genSnapshotPath =
|
||||
_artifacts.getArtifactPath(Artifact.genSnapshot);
|
||||
if (_fileSystem.file(genSnapshotPath).existsSync() &&
|
||||
!_genSnapshotRuns(genSnapshotPath)) {
|
||||
final StringBuffer buffer = StringBuffer();
|
||||
buffer.writeln(_userMessages.flutterBinariesDoNotRun);
|
||||
if (_platform.isLinux) {
|
||||
@ -606,7 +646,8 @@ class FlutterValidator extends DoctorValidator {
|
||||
buffer.writeln(
|
||||
'Flutter requires the Rosetta translation environment on ARM Macs. Try running:',
|
||||
);
|
||||
buffer.writeln(' sudo softwareupdate --install-rosetta --agree-to-license');
|
||||
buffer.writeln(
|
||||
' sudo softwareupdate --install-rosetta --agree-to-license');
|
||||
}
|
||||
messages.add(ValidationMessage.error(buffer.toString()));
|
||||
}
|
||||
@ -619,7 +660,8 @@ class FlutterValidator extends DoctorValidator {
|
||||
// in that case, make it clear that it is fine to continue, but freshness check/upgrades
|
||||
// won't be supported.
|
||||
valid = ValidationType.partial;
|
||||
messages.add(ValidationMessage(_userMessages.flutterValidatorErrorIntentional));
|
||||
messages.add(
|
||||
ValidationMessage(_userMessages.flutterValidatorErrorIntentional));
|
||||
}
|
||||
|
||||
return ValidationResult(
|
||||
@ -653,17 +695,21 @@ class FlutterValidator extends DoctorValidator {
|
||||
return ValidationMessage(flutterVersionMessage);
|
||||
}
|
||||
if (versionChannel == kUserBranch) {
|
||||
flutterVersionMessage = '$flutterVersionMessage\n${_userMessages.flutterUnknownChannel}';
|
||||
flutterVersionMessage =
|
||||
'$flutterVersionMessage\n${_userMessages.flutterUnknownChannel}';
|
||||
}
|
||||
if (frameworkVersion == '0.0.0-unknown') {
|
||||
flutterVersionMessage = '$flutterVersionMessage\n${_userMessages.flutterUnknownVersion}';
|
||||
flutterVersionMessage =
|
||||
'$flutterVersionMessage\n${_userMessages.flutterUnknownVersion}';
|
||||
}
|
||||
return ValidationMessage.hint(flutterVersionMessage);
|
||||
}
|
||||
|
||||
List<ValidationMessage> _validateRequiredBinaries(String flutterRoot) {
|
||||
final ValidationMessage? flutterWarning = _validateSdkBinary('flutter', flutterRoot);
|
||||
final ValidationMessage? dartWarning = _validateSdkBinary('dart', flutterRoot);
|
||||
final ValidationMessage? flutterWarning =
|
||||
_validateSdkBinary('flutter', flutterRoot);
|
||||
final ValidationMessage? dartWarning =
|
||||
_validateSdkBinary('dart', flutterRoot);
|
||||
return <ValidationMessage>[
|
||||
if (flutterWarning != null) flutterWarning,
|
||||
if (dartWarning != null) dartWarning,
|
||||
@ -684,8 +730,7 @@ class FlutterValidator extends DoctorValidator {
|
||||
}
|
||||
final String resolvedFlutterPath = flutterBin.resolveSymbolicLinksSync();
|
||||
if (!_filePathContainsDirPath(flutterRoot, resolvedFlutterPath)) {
|
||||
final String hint =
|
||||
'Warning: `$binary` on your path resolves to '
|
||||
final String hint = 'Warning: `$binary` on your path resolves to '
|
||||
'$resolvedFlutterPath, which is not inside your current Flutter '
|
||||
'SDK checkout at $flutterRoot. Consider adding $flutterBinDir to '
|
||||
'the front of your path.';
|
||||
@ -697,9 +742,8 @@ class FlutterValidator extends DoctorValidator {
|
||||
bool _filePathContainsDirPath(String directory, String file) {
|
||||
// calling .canonicalize() will normalize for alphabetic case and path
|
||||
// separators
|
||||
return _fileSystem.path
|
||||
.canonicalize(file)
|
||||
.startsWith(_fileSystem.path.canonicalize(directory) + _fileSystem.path.separator);
|
||||
return _fileSystem.path.canonicalize(file).startsWith(
|
||||
_fileSystem.path.canonicalize(directory) + _fileSystem.path.separator);
|
||||
}
|
||||
|
||||
ValidationMessage _getFlutterUpstreamMessage(FlutterVersion version) {
|
||||
@ -710,11 +754,14 @@ class FlutterValidator extends DoctorValidator {
|
||||
// VersionUpstreamValidator can produce an error if repositoryUrl is null
|
||||
if (upstreamValidationError != null) {
|
||||
final String errorMessage = upstreamValidationError.message;
|
||||
if (errorMessage.contains('could not determine the remote upstream which is being tracked')) {
|
||||
return ValidationMessage.hint(_userMessages.flutterUpstreamRepositoryUnknown);
|
||||
if (errorMessage.contains(
|
||||
'could not determine the remote upstream which is being tracked')) {
|
||||
return ValidationMessage.hint(
|
||||
_userMessages.flutterUpstreamRepositoryUnknown);
|
||||
}
|
||||
// At this point, repositoryUrl must not be null
|
||||
if (errorMessage.contains('Flutter SDK is tracking a non-standard remote')) {
|
||||
if (errorMessage
|
||||
.contains('Flutter SDK is tracking a non-standard remote')) {
|
||||
return ValidationMessage.hint(
|
||||
_userMessages.flutterUpstreamRepositoryUrlNonStandard(repositoryUrl!),
|
||||
);
|
||||
@ -727,13 +774,15 @@ class FlutterValidator extends DoctorValidator {
|
||||
);
|
||||
}
|
||||
}
|
||||
return ValidationMessage(_userMessages.flutterUpstreamRepositoryUrl(repositoryUrl!));
|
||||
return ValidationMessage(
|
||||
_userMessages.flutterUpstreamRepositoryUrl(repositoryUrl!));
|
||||
}
|
||||
|
||||
bool _genSnapshotRuns(String genSnapshotPath) {
|
||||
const int kExpectedExitCode = 255;
|
||||
try {
|
||||
return _processManager.runSync(<String>[genSnapshotPath]).exitCode == kExpectedExitCode;
|
||||
return _processManager.runSync(<String>[genSnapshotPath]).exitCode ==
|
||||
kExpectedExitCode;
|
||||
} on Exception {
|
||||
return false;
|
||||
}
|
||||
@ -743,9 +792,9 @@ class FlutterValidator extends DoctorValidator {
|
||||
class DeviceValidator extends DoctorValidator {
|
||||
// TODO(jmagman): Make required once g3 rolls and is updated.
|
||||
DeviceValidator({DeviceManager? deviceManager, UserMessages? userMessages})
|
||||
: _deviceManager = deviceManager ?? globals.deviceManager!,
|
||||
_userMessages = userMessages ?? globals.userMessages,
|
||||
super('Connected device');
|
||||
: _deviceManager = deviceManager ?? globals.deviceManager!,
|
||||
_userMessages = userMessages ?? globals.userMessages,
|
||||
super('Connected device');
|
||||
|
||||
final DeviceManager _deviceManager;
|
||||
final UserMessages _userMessages;
|
||||
@ -754,25 +803,27 @@ class DeviceValidator extends DoctorValidator {
|
||||
String get slowWarning => 'Scanning for devices is taking a long time...';
|
||||
|
||||
@override
|
||||
Future<ValidationResult> validate() async {
|
||||
Future<ValidationResult> validateImpl() async {
|
||||
final List<Device> devices = await _deviceManager.refreshAllDevices(
|
||||
timeout: DeviceManager.minimumWirelessDeviceDiscoveryTimeout,
|
||||
);
|
||||
List<ValidationMessage> installedMessages = <ValidationMessage>[];
|
||||
if (devices.isNotEmpty) {
|
||||
installedMessages =
|
||||
(await Device.descriptions(
|
||||
devices,
|
||||
)).map<ValidationMessage>((String msg) => ValidationMessage(msg)).toList();
|
||||
installedMessages = (await Device.descriptions(
|
||||
devices,
|
||||
))
|
||||
.map<ValidationMessage>((String msg) => ValidationMessage(msg))
|
||||
.toList();
|
||||
}
|
||||
|
||||
List<ValidationMessage> diagnosticMessages = <ValidationMessage>[];
|
||||
final List<String> diagnostics = await _deviceManager.getDeviceDiagnostics();
|
||||
final List<String> diagnostics =
|
||||
await _deviceManager.getDeviceDiagnostics();
|
||||
if (diagnostics.isNotEmpty) {
|
||||
diagnosticMessages =
|
||||
diagnostics
|
||||
.map<ValidationMessage>((String message) => ValidationMessage.hint(message))
|
||||
.toList();
|
||||
diagnosticMessages = diagnostics
|
||||
.map<ValidationMessage>(
|
||||
(String message) => ValidationMessage.hint(message))
|
||||
.toList();
|
||||
} else if (devices.isEmpty) {
|
||||
diagnosticMessages = <ValidationMessage>[
|
||||
ValidationMessage.hint(_userMessages.devicesMissing),
|
||||
@ -800,9 +851,11 @@ class DeviceValidator extends DoctorValidator {
|
||||
|
||||
/// Wrapper for doctor to run multiple times with PII and without, running the validators only once.
|
||||
class DoctorText {
|
||||
DoctorText(BufferLogger logger, {SystemClock? clock, @visibleForTesting Doctor? doctor})
|
||||
: _doctor = doctor ?? Doctor(logger: logger, clock: clock ?? globals.systemClock),
|
||||
_logger = logger;
|
||||
DoctorText(BufferLogger logger,
|
||||
{SystemClock? clock, @visibleForTesting Doctor? doctor})
|
||||
: _doctor = doctor ??
|
||||
Doctor(logger: logger, clock: clock ?? globals.systemClock),
|
||||
_logger = logger;
|
||||
|
||||
final BufferLogger _logger;
|
||||
final Doctor _doctor;
|
||||
@ -812,7 +865,8 @@ class DoctorText {
|
||||
late final Future<String> piiStrippedText = _runDiagnosis(false);
|
||||
|
||||
// Start the validator tasks only once.
|
||||
late final List<ValidatorTask> _validatorTasks = _doctor.startValidatorTasks();
|
||||
late final List<ValidatorTask> _validatorTasks =
|
||||
_doctor.startValidatorTasks();
|
||||
|
||||
Future<String> _runDiagnosis(bool showPii) async {
|
||||
try {
|
||||
|
@ -2,6 +2,8 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:meta/meta.dart';
|
||||
|
||||
import 'base/async_guard.dart';
|
||||
@ -36,7 +38,7 @@ enum ValidationType { crash, missing, partial, notAvailable, success }
|
||||
enum ValidationMessageType { error, hint, information }
|
||||
|
||||
abstract class DoctorValidator {
|
||||
const DoctorValidator(this.title);
|
||||
DoctorValidator(this.title);
|
||||
|
||||
/// This is displayed in the CLI.
|
||||
final String title;
|
||||
@ -48,7 +50,19 @@ abstract class DoctorValidator {
|
||||
/// Duration before the spinner should display [slowWarning].
|
||||
Duration get slowWarningDuration => _slowWarningDuration;
|
||||
|
||||
Future<ValidationResult> validate();
|
||||
/// Performs validation by invoking [validateImpl].
|
||||
///
|
||||
/// Tracks time taken to execute the validation step.
|
||||
Future<ValidationResult> validate() async {
|
||||
final Stopwatch stopwatch = Stopwatch()..start();
|
||||
final ValidationResult result = await validateImpl();
|
||||
stopwatch.stop();
|
||||
result._executionTime = stopwatch.elapsed;
|
||||
return result;
|
||||
}
|
||||
|
||||
/// Validation implementation.
|
||||
Future<ValidationResult> validateImpl();
|
||||
}
|
||||
|
||||
/// A validator that runs other [DoctorValidator]s and combines their output
|
||||
@ -74,10 +88,11 @@ class GroupedValidator extends DoctorValidator {
|
||||
String _currentSlowWarning = 'Initializing...';
|
||||
|
||||
@override
|
||||
Future<ValidationResult> validate() async {
|
||||
Future<ValidationResult> validateImpl() async {
|
||||
final List<ValidatorTask> tasks = <ValidatorTask>[
|
||||
for (final DoctorValidator validator in subValidators)
|
||||
ValidatorTask(validator, asyncGuard<ValidationResult>(() => validator.validate())),
|
||||
ValidatorTask(validator,
|
||||
asyncGuard<ValidationResult>(() => validator.validate())),
|
||||
];
|
||||
|
||||
final List<ValidationResult> results = <ValidationResult>[];
|
||||
@ -123,24 +138,26 @@ class GroupedValidator extends DoctorValidator {
|
||||
}
|
||||
}
|
||||
|
||||
@immutable
|
||||
class ValidationResult {
|
||||
/// [ValidationResult.type] should only equal [ValidationResult.success]
|
||||
/// if no [messages] are hints or errors.
|
||||
const ValidationResult(this.type, this.messages, {this.statusInfo});
|
||||
ValidationResult(this.type, this.messages, {this.statusInfo});
|
||||
|
||||
factory ValidationResult.crash(Object error, [StackTrace? stackTrace]) {
|
||||
return ValidationResult(ValidationType.crash, <ValidationMessage>[
|
||||
const ValidationMessage.error(
|
||||
'Due to an error, the doctor check did not complete. '
|
||||
'If the error message below is not helpful, '
|
||||
'please let us know about this issue at https://github.com/flutter/flutter/issues.',
|
||||
),
|
||||
ValidationMessage.error('$error'),
|
||||
if (stackTrace != null)
|
||||
// Stacktrace is informational. Printed in verbose mode only.
|
||||
ValidationMessage('$stackTrace'),
|
||||
], statusInfo: 'the doctor check crashed');
|
||||
return ValidationResult(
|
||||
ValidationType.crash,
|
||||
<ValidationMessage>[
|
||||
const ValidationMessage.error(
|
||||
'Due to an error, the doctor check did not complete. '
|
||||
'If the error message below is not helpful, '
|
||||
'please let us know about this issue at https://github.com/flutter/flutter/issues.',
|
||||
),
|
||||
ValidationMessage.error('$error'),
|
||||
if (stackTrace != null)
|
||||
// Stacktrace is informational. Printed in verbose mode only.
|
||||
ValidationMessage('$stackTrace'),
|
||||
],
|
||||
statusInfo: 'the doctor check crashed');
|
||||
}
|
||||
|
||||
final ValidationType type;
|
||||
@ -149,28 +166,36 @@ class ValidationResult {
|
||||
final List<ValidationMessage> messages;
|
||||
|
||||
String get leadingBox => switch (type) {
|
||||
ValidationType.crash => '[☠]',
|
||||
ValidationType.missing => '[✗]',
|
||||
ValidationType.success => '[✓]',
|
||||
ValidationType.notAvailable || ValidationType.partial => '[!]',
|
||||
};
|
||||
ValidationType.crash => '[☠]',
|
||||
ValidationType.missing => '[✗]',
|
||||
ValidationType.success => '[✓]',
|
||||
ValidationType.notAvailable || ValidationType.partial => '[!]',
|
||||
};
|
||||
|
||||
/// The time taken to perform the validation, set by [DoctorValidator.validate].
|
||||
Duration? get executionTime => _executionTime;
|
||||
Duration? _executionTime;
|
||||
|
||||
String get coloredLeadingBox {
|
||||
return globals.terminal.color(leadingBox, switch (type) {
|
||||
ValidationType.success => TerminalColor.green,
|
||||
ValidationType.crash || ValidationType.missing => TerminalColor.red,
|
||||
ValidationType.notAvailable || ValidationType.partial => TerminalColor.yellow,
|
||||
});
|
||||
return globals.terminal.color(
|
||||
leadingBox,
|
||||
switch (type) {
|
||||
ValidationType.success => TerminalColor.green,
|
||||
ValidationType.crash || ValidationType.missing => TerminalColor.red,
|
||||
ValidationType.notAvailable ||
|
||||
ValidationType.partial =>
|
||||
TerminalColor.yellow,
|
||||
});
|
||||
}
|
||||
|
||||
/// The string representation of the type.
|
||||
String get typeStr => switch (type) {
|
||||
ValidationType.crash => 'crash',
|
||||
ValidationType.missing => 'missing',
|
||||
ValidationType.success => 'installed',
|
||||
ValidationType.notAvailable => 'notAvailable',
|
||||
ValidationType.partial => 'partial',
|
||||
};
|
||||
ValidationType.crash => 'crash',
|
||||
ValidationType.missing => 'missing',
|
||||
ValidationType.success => 'installed',
|
||||
ValidationType.notAvailable => 'notAvailable',
|
||||
ValidationType.partial => 'partial',
|
||||
};
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
@ -192,22 +217,23 @@ class ValidationMessage {
|
||||
///
|
||||
/// The [contextUrl] may be supplied to link to external resources. This
|
||||
/// is displayed after the informative message in verbose modes.
|
||||
const ValidationMessage(this.message, {this.contextUrl, String? piiStrippedMessage})
|
||||
: type = ValidationMessageType.information,
|
||||
piiStrippedMessage = piiStrippedMessage ?? message;
|
||||
const ValidationMessage(this.message,
|
||||
{this.contextUrl, String? piiStrippedMessage})
|
||||
: type = ValidationMessageType.information,
|
||||
piiStrippedMessage = piiStrippedMessage ?? message;
|
||||
|
||||
/// Create a validation message with information for a failing validator.
|
||||
const ValidationMessage.error(this.message, {String? piiStrippedMessage})
|
||||
: type = ValidationMessageType.error,
|
||||
piiStrippedMessage = piiStrippedMessage ?? message,
|
||||
contextUrl = null;
|
||||
: type = ValidationMessageType.error,
|
||||
piiStrippedMessage = piiStrippedMessage ?? message,
|
||||
contextUrl = null;
|
||||
|
||||
/// Create a validation message with information for a partially failing
|
||||
/// validator.
|
||||
const ValidationMessage.hint(this.message, {String? piiStrippedMessage})
|
||||
: type = ValidationMessageType.hint,
|
||||
piiStrippedMessage = piiStrippedMessage ?? message,
|
||||
contextUrl = null;
|
||||
: type = ValidationMessageType.hint,
|
||||
piiStrippedMessage = piiStrippedMessage ?? message,
|
||||
contextUrl = null;
|
||||
|
||||
final ValidationMessageType type;
|
||||
final String? contextUrl;
|
||||
@ -223,17 +249,19 @@ class ValidationMessage {
|
||||
bool get isInformation => type == ValidationMessageType.information;
|
||||
|
||||
String get indicator => switch (type) {
|
||||
ValidationMessageType.error => '✗',
|
||||
ValidationMessageType.hint => '!',
|
||||
ValidationMessageType.information => '•',
|
||||
};
|
||||
ValidationMessageType.error => '✗',
|
||||
ValidationMessageType.hint => '!',
|
||||
ValidationMessageType.information => '•',
|
||||
};
|
||||
|
||||
String get coloredIndicator {
|
||||
return globals.terminal.color(indicator, switch (type) {
|
||||
ValidationMessageType.error => TerminalColor.red,
|
||||
ValidationMessageType.hint => TerminalColor.yellow,
|
||||
ValidationMessageType.information => TerminalColor.green,
|
||||
});
|
||||
return globals.terminal.color(
|
||||
indicator,
|
||||
switch (type) {
|
||||
ValidationMessageType.error => TerminalColor.red,
|
||||
ValidationMessageType.hint => TerminalColor.yellow,
|
||||
ValidationMessageType.information => TerminalColor.green,
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
@ -255,7 +283,7 @@ class NoIdeValidator extends DoctorValidator {
|
||||
NoIdeValidator() : super('Flutter IDE Support');
|
||||
|
||||
@override
|
||||
Future<ValidationResult> validate() async {
|
||||
Future<ValidationResult> validateImpl() async {
|
||||
return ValidationResult(
|
||||
// Info hint to user they do not have a supported IDE installed
|
||||
ValidationType.notAvailable,
|
||||
@ -273,5 +301,5 @@ class ValidatorWithResult extends DoctorValidator {
|
||||
final ValidationResult result;
|
||||
|
||||
@override
|
||||
Future<ValidationResult> validate() async => result;
|
||||
Future<ValidationResult> validateImpl() async => result;
|
||||
}
|
||||
|
@ -18,7 +18,8 @@ const String kMaven = 'https://maven.google.com/';
|
||||
const String kPubDev = 'https://pub.dev/';
|
||||
|
||||
// Overridable environment variables.
|
||||
const String kPubDevOverride = 'PUB_HOSTED_URL'; // https://dart.dev/tools/pub/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 {
|
||||
@ -26,10 +27,10 @@ class HttpHostValidator extends DoctorValidator {
|
||||
required Platform platform,
|
||||
required FeatureFlags featureFlags,
|
||||
required HttpClient httpClient,
|
||||
}) : _platform = platform,
|
||||
_featureFlags = featureFlags,
|
||||
_httpClient = httpClient,
|
||||
super('Network resources');
|
||||
}) : _platform = platform,
|
||||
_featureFlags = featureFlags,
|
||||
_httpClient = httpClient,
|
||||
super('Network resources');
|
||||
|
||||
final Platform _platform;
|
||||
final FeatureFlags _featureFlags;
|
||||
@ -82,7 +83,7 @@ class HttpHostValidator extends DoctorValidator {
|
||||
}
|
||||
|
||||
@override
|
||||
Future<ValidationResult> validate() async {
|
||||
Future<ValidationResult> validateImpl() async {
|
||||
final List<String?> availabilityResults = <String?>[];
|
||||
|
||||
final List<Uri> requiredHosts = <Uri>[];
|
||||
@ -100,7 +101,8 @@ class HttpHostValidator extends DoctorValidator {
|
||||
requiredHosts.add(Uri.parse(kPubDev));
|
||||
}
|
||||
if (_platform.environment.containsKey(kFlutterStorageBaseUrl)) {
|
||||
final Uri? url = _parseUrl(_platform.environment[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'
|
||||
@ -141,9 +143,12 @@ class HttpHostValidator extends DoctorValidator {
|
||||
if (failures == 0) {
|
||||
assert(successes > 0);
|
||||
assert(messages.isEmpty);
|
||||
return const ValidationResult(ValidationType.success, <ValidationMessage>[
|
||||
ValidationMessage('All expected network resources are available.'),
|
||||
]);
|
||||
return ValidationResult(
|
||||
ValidationType.success,
|
||||
const <ValidationMessage>[
|
||||
ValidationMessage('All expected network resources are available.')
|
||||
],
|
||||
);
|
||||
}
|
||||
assert(messages.isNotEmpty);
|
||||
return ValidationResult(
|
||||
|
@ -91,7 +91,7 @@ abstract class IntelliJValidator extends DoctorValidator {
|
||||
}
|
||||
|
||||
@override
|
||||
Future<ValidationResult> validate() async {
|
||||
Future<ValidationResult> validateImpl() async {
|
||||
final List<ValidationMessage> messages = <ValidationMessage>[];
|
||||
|
||||
if (pluginsPath == null) {
|
||||
|
@ -54,7 +54,7 @@ class LinuxDoctorValidator extends DoctorValidator {
|
||||
final List<String> _requiredGtkLibraries = <String>['gtk+-3.0', 'glib-2.0', 'gio-2.0'];
|
||||
|
||||
@override
|
||||
Future<ValidationResult> validate() async {
|
||||
Future<ValidationResult> validateImpl() async {
|
||||
ValidationType validationType = ValidationType.success;
|
||||
final List<ValidationMessage> messages = <ValidationMessage>[];
|
||||
|
||||
|
@ -20,7 +20,7 @@ class CocoaPodsValidator extends DoctorValidator {
|
||||
final UserMessages _userMessages;
|
||||
|
||||
@override
|
||||
Future<ValidationResult> validate() async {
|
||||
Future<ValidationResult> validateImpl() async {
|
||||
final List<ValidationMessage> messages = <ValidationMessage>[];
|
||||
|
||||
final CocoaPodsStatus cocoaPodsStatus = await _cocoaPods.evaluateCocoaPodsInstallation;
|
||||
|
@ -32,7 +32,7 @@ class XcodeValidator extends DoctorValidator {
|
||||
final UserMessages _userMessages;
|
||||
|
||||
@override
|
||||
Future<ValidationResult> validate() async {
|
||||
Future<ValidationResult> validateImpl() async {
|
||||
final List<ValidationMessage> messages = <ValidationMessage>[];
|
||||
ValidationType xcodeStatus = ValidationType.missing;
|
||||
String? xcodeVersionInfo;
|
||||
|
@ -12,10 +12,10 @@ import 'doctor_validator.dart';
|
||||
/// validated along with `NO_PROXY`.
|
||||
class ProxyValidator extends DoctorValidator {
|
||||
ProxyValidator({required Platform platform})
|
||||
: shouldShow = _getEnv('HTTP_PROXY', platform).isNotEmpty,
|
||||
_httpProxy = _getEnv('HTTP_PROXY', platform),
|
||||
_noProxy = _getEnv('NO_PROXY', platform),
|
||||
super('Proxy Configuration');
|
||||
: shouldShow = _getEnv('HTTP_PROXY', platform).isNotEmpty,
|
||||
_httpProxy = _getEnv('HTTP_PROXY', platform),
|
||||
_noProxy = _getEnv('NO_PROXY', platform),
|
||||
super('Proxy Configuration');
|
||||
|
||||
final bool shouldShow;
|
||||
final String _httpProxy;
|
||||
@ -30,9 +30,10 @@ class ProxyValidator extends DoctorValidator {
|
||||
'';
|
||||
|
||||
@override
|
||||
Future<ValidationResult> validate() async {
|
||||
Future<ValidationResult> validateImpl() async {
|
||||
if (_httpProxy.isEmpty) {
|
||||
return const ValidationResult(ValidationType.success, <ValidationMessage>[]);
|
||||
return ValidationResult(
|
||||
ValidationType.success, const <ValidationMessage>[]);
|
||||
}
|
||||
|
||||
final List<ValidationMessage> messages = <ValidationMessage>[
|
||||
@ -49,13 +50,16 @@ class ProxyValidator extends DoctorValidator {
|
||||
],
|
||||
];
|
||||
|
||||
final bool hasIssues = messages.any((ValidationMessage msg) => msg.isHint || msg.isError);
|
||||
final bool hasIssues =
|
||||
messages.any((ValidationMessage msg) => msg.isHint || msg.isError);
|
||||
|
||||
return ValidationResult(hasIssues ? ValidationType.partial : ValidationType.success, messages);
|
||||
return ValidationResult(
|
||||
hasIssues ? ValidationType.partial : ValidationType.success, messages);
|
||||
}
|
||||
|
||||
Future<List<String>> _getLoopbackAddresses() async {
|
||||
final List<NetworkInterface> networkInterfaces = await listNetworkInterfaces(
|
||||
final List<NetworkInterface> networkInterfaces =
|
||||
await listNetworkInterfaces(
|
||||
includeLinkLocal: true,
|
||||
includeLoopback: true,
|
||||
);
|
||||
@ -63,7 +67,8 @@ class ProxyValidator extends DoctorValidator {
|
||||
return <String>[
|
||||
'localhost',
|
||||
for (final NetworkInterface networkInterface in networkInterfaces)
|
||||
for (final InternetAddress internetAddress in networkInterface.addresses)
|
||||
for (final InternetAddress internetAddress
|
||||
in networkInterface.addresses)
|
||||
if (internetAddress.isLoopback) internetAddress.address,
|
||||
];
|
||||
}
|
||||
|
@ -27,16 +27,17 @@ class VsCodeValidator extends DoctorValidator {
|
||||
}
|
||||
|
||||
@override
|
||||
Future<ValidationResult> validate() async {
|
||||
final List<ValidationMessage> validationMessages = List<ValidationMessage>.from(
|
||||
_vsCode.validationMessages,
|
||||
);
|
||||
Future<ValidationResult> validateImpl() async {
|
||||
final List<ValidationMessage> validationMessages =
|
||||
List<ValidationMessage>.from(_vsCode.validationMessages);
|
||||
|
||||
final String vsCodeVersionText =
|
||||
_vsCode.version == null ? 'version unknown' : 'version ${_vsCode.version}';
|
||||
final String vsCodeVersionText = _vsCode.version == null
|
||||
? 'version unknown'
|
||||
: 'version ${_vsCode.version}';
|
||||
|
||||
if (_vsCode.version == null) {
|
||||
validationMessages.add(const ValidationMessage.error('Unable to determine VS Code version.'));
|
||||
validationMessages.add(const ValidationMessage.error(
|
||||
'Unable to determine VS Code version.'));
|
||||
}
|
||||
|
||||
return ValidationResult(
|
||||
|
@ -8,14 +8,14 @@ import 'chrome.dart';
|
||||
|
||||
/// A validator for Chromium-based browsers.
|
||||
abstract class ChromiumValidator extends DoctorValidator {
|
||||
const ChromiumValidator(super.title);
|
||||
ChromiumValidator(super.title);
|
||||
|
||||
Platform get _platform;
|
||||
ChromiumLauncher get _chromiumLauncher;
|
||||
String get _name;
|
||||
|
||||
@override
|
||||
Future<ValidationResult> validate() async {
|
||||
Future<ValidationResult> validateImpl() async {
|
||||
final bool canRunChromium = _chromiumLauncher.canFindExecutable();
|
||||
final String chromiumSearchLocation = _chromiumLauncher.findExecutable();
|
||||
final List<ValidationMessage> messages = <ValidationMessage>[
|
||||
@ -45,10 +45,12 @@ abstract class ChromiumValidator extends DoctorValidator {
|
||||
|
||||
/// A validator that checks whether Chrome is installed and can run.
|
||||
class ChromeValidator extends ChromiumValidator {
|
||||
const ChromeValidator({required Platform platform, required ChromiumLauncher chromiumLauncher})
|
||||
: _platform = platform,
|
||||
_chromiumLauncher = chromiumLauncher,
|
||||
super('Chrome - develop for the web');
|
||||
ChromeValidator({
|
||||
required Platform platform,
|
||||
required ChromiumLauncher chromiumLauncher,
|
||||
}) : _platform = platform,
|
||||
_chromiumLauncher = chromiumLauncher,
|
||||
super('Chrome - develop for the web');
|
||||
|
||||
@override
|
||||
final Platform _platform;
|
||||
@ -62,10 +64,12 @@ class ChromeValidator extends ChromiumValidator {
|
||||
|
||||
/// A validator that checks whether Edge is installed and can run.
|
||||
class EdgeValidator extends ChromiumValidator {
|
||||
const EdgeValidator({required Platform platform, required ChromiumLauncher chromiumLauncher})
|
||||
: _platform = platform,
|
||||
_chromiumLauncher = chromiumLauncher,
|
||||
super('Edge - develop for the web');
|
||||
EdgeValidator({
|
||||
required Platform platform,
|
||||
required ChromiumLauncher chromiumLauncher,
|
||||
}) : _platform = platform,
|
||||
_chromiumLauncher = chromiumLauncher,
|
||||
super('Edge - develop for the web');
|
||||
|
||||
@override
|
||||
final Platform _platform;
|
||||
|
@ -10,7 +10,7 @@ import 'visual_studio.dart';
|
||||
VisualStudioValidator? get visualStudioValidator => context.get<VisualStudioValidator>();
|
||||
|
||||
class VisualStudioValidator extends DoctorValidator {
|
||||
const VisualStudioValidator({
|
||||
VisualStudioValidator({
|
||||
required VisualStudio visualStudio,
|
||||
required UserMessages userMessages,
|
||||
}) : _visualStudio = visualStudio,
|
||||
@ -21,7 +21,7 @@ class VisualStudioValidator extends DoctorValidator {
|
||||
final UserMessages _userMessages;
|
||||
|
||||
@override
|
||||
Future<ValidationResult> validate() async {
|
||||
Future<ValidationResult> validateImpl() async {
|
||||
final List<ValidationMessage> messages = <ValidationMessage>[];
|
||||
ValidationType status = ValidationType.missing;
|
||||
String? versionInfo;
|
||||
|
@ -14,7 +14,8 @@ const List<String> kUnsupportedVersions = <String>['6', '7', '8'];
|
||||
|
||||
/// Regex pattern for identifying line from systeminfo stdout with windows version
|
||||
/// (ie. 10.0.22631.4037)
|
||||
const String kWindowsOSVersionSemVerPattern = r'([0-9]+)\.([0-9]+)\.([0-9]+)\.?([0-9\.]+)?';
|
||||
const String kWindowsOSVersionSemVerPattern =
|
||||
r'([0-9]+)\.([0-9]+)\.([0-9]+)\.?([0-9\.]+)?';
|
||||
|
||||
/// Regex pattern for identifying a running instance of the Topaz OFD process.
|
||||
/// This is a known process that interferes with the build toolchain.
|
||||
@ -23,14 +24,14 @@ const String kCoreProcessPattern = r'Topaz\s+OFD\\Warsaw\\core\.exe';
|
||||
|
||||
/// Validator for supported Windows host machine operating system version.
|
||||
class WindowsVersionValidator extends DoctorValidator {
|
||||
const WindowsVersionValidator({
|
||||
WindowsVersionValidator({
|
||||
required OperatingSystemUtils operatingSystemUtils,
|
||||
required ProcessLister processLister,
|
||||
required WindowsVersionExtractor versionExtractor,
|
||||
}) : _operatingSystemUtils = operatingSystemUtils,
|
||||
_processLister = processLister,
|
||||
_versionExtractor = versionExtractor,
|
||||
super('Windows Version');
|
||||
}) : _operatingSystemUtils = operatingSystemUtils,
|
||||
_processLister = processLister,
|
||||
_versionExtractor = versionExtractor,
|
||||
super('Windows Version');
|
||||
|
||||
// See https://learn.microsoft.com/en-us/windows/release-health/windows11-release-information
|
||||
static const int _lowestWindows11BuildNumber = 22000;
|
||||
@ -41,35 +42,47 @@ class WindowsVersionValidator extends DoctorValidator {
|
||||
|
||||
Future<ValidationResult> _topazScan() async {
|
||||
if (!_processLister.canRunPowershell()) {
|
||||
return const ValidationResult(ValidationType.missing, <ValidationMessage>[
|
||||
ValidationMessage.hint(
|
||||
'Failed to find ${ProcessLister.powershell} or ${ProcessLister.pwsh} on PATH',
|
||||
),
|
||||
]);
|
||||
return ValidationResult(
|
||||
ValidationType.missing,
|
||||
const <ValidationMessage>[
|
||||
ValidationMessage.hint(
|
||||
'Failed to find ${ProcessLister.powershell} or ${ProcessLister.pwsh} on PATH'),
|
||||
],
|
||||
);
|
||||
}
|
||||
final ProcessResult getProcessesResult = await _processLister.getProcessesWithPath();
|
||||
final ProcessResult getProcessesResult =
|
||||
await _processLister.getProcessesWithPath();
|
||||
if (getProcessesResult.exitCode != 0) {
|
||||
return const ValidationResult(ValidationType.missing, <ValidationMessage>[
|
||||
ValidationMessage.hint('Get-Process failed to complete'),
|
||||
]);
|
||||
return ValidationResult(
|
||||
ValidationType.missing,
|
||||
const <ValidationMessage>[
|
||||
ValidationMessage.hint('Get-Process failed to complete'),
|
||||
],
|
||||
);
|
||||
}
|
||||
final RegExp topazRegex = RegExp(kCoreProcessPattern, caseSensitive: false, multiLine: true);
|
||||
final RegExp topazRegex =
|
||||
RegExp(kCoreProcessPattern, caseSensitive: false, multiLine: true);
|
||||
final String processes = getProcessesResult.stdout as String;
|
||||
final bool topazFound = topazRegex.hasMatch(processes);
|
||||
if (topazFound) {
|
||||
return const ValidationResult(ValidationType.missing, <ValidationMessage>[
|
||||
ValidationMessage.hint(
|
||||
'The Topaz OFD Security Module was detected on your machine. '
|
||||
'You may need to disable it to build Flutter applications.',
|
||||
),
|
||||
]);
|
||||
return ValidationResult(
|
||||
ValidationType.missing,
|
||||
const <ValidationMessage>[
|
||||
ValidationMessage.hint(
|
||||
'The Topaz OFD Security Module was detected on your machine. '
|
||||
'You may need to disable it to build Flutter applications.',
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
return const ValidationResult(ValidationType.success, <ValidationMessage>[]);
|
||||
return ValidationResult(
|
||||
ValidationType.success, const <ValidationMessage>[]);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<ValidationResult> validate() async {
|
||||
final RegExp regex = RegExp(kWindowsOSVersionSemVerPattern, multiLine: true);
|
||||
Future<ValidationResult> validateImpl() async {
|
||||
final RegExp regex =
|
||||
RegExp(kWindowsOSVersionSemVerPattern, multiLine: true);
|
||||
final String commandResult = _operatingSystemUtils.name;
|
||||
final Iterable<RegExpMatch> matches = regex.allMatches(commandResult);
|
||||
|
||||
@ -78,13 +91,15 @@ class WindowsVersionValidator extends DoctorValidator {
|
||||
ValidationType windowsVersionStatus;
|
||||
final List<ValidationMessage> messages = <ValidationMessage>[];
|
||||
String statusInfo;
|
||||
if (matches.length == 1 && !kUnsupportedVersions.contains(matches.elementAt(0).group(1))) {
|
||||
if (matches.length == 1 &&
|
||||
!kUnsupportedVersions.contains(matches.elementAt(0).group(1))) {
|
||||
windowsVersionStatus = ValidationType.success;
|
||||
final WindowsVersionExtractionResult details = await _versionExtractor.getDetails();
|
||||
final WindowsVersionExtractionResult details =
|
||||
await _versionExtractor.getDetails();
|
||||
String? caption = details.caption;
|
||||
if (caption == null || caption.isEmpty) {
|
||||
final bool isWindows11 =
|
||||
int.parse(matches.elementAt(0).group(3)!) > _lowestWindows11BuildNumber;
|
||||
final bool isWindows11 = int.parse(matches.elementAt(0).group(3)!) >
|
||||
_lowestWindows11BuildNumber;
|
||||
if (isWindows11) {
|
||||
caption = 'Windows 11 or higher';
|
||||
} else {
|
||||
@ -99,7 +114,9 @@ class WindowsVersionValidator extends DoctorValidator {
|
||||
|
||||
// Check if the Topaz OFD security module is running, and warn the user if it is.
|
||||
// See https://github.com/flutter/flutter/issues/121366
|
||||
final List<ValidationResult> subResults = <ValidationResult>[await _topazScan()];
|
||||
final List<ValidationResult> subResults = <ValidationResult>[
|
||||
await _topazScan()
|
||||
];
|
||||
for (final ValidationResult subResult in subResults) {
|
||||
if (subResult.type != ValidationType.success) {
|
||||
statusInfo = 'Problem detected with Windows installation';
|
||||
@ -109,10 +126,12 @@ class WindowsVersionValidator extends DoctorValidator {
|
||||
}
|
||||
} else {
|
||||
windowsVersionStatus = ValidationType.missing;
|
||||
statusInfo = 'Unable to determine Windows version (command `ver` returned $commandResult)';
|
||||
statusInfo =
|
||||
'Unable to determine Windows version (command `ver` returned $commandResult)';
|
||||
}
|
||||
|
||||
return ValidationResult(windowsVersionStatus, messages, statusInfo: statusInfo);
|
||||
return ValidationResult(windowsVersionStatus, messages,
|
||||
statusInfo: statusInfo);
|
||||
}
|
||||
}
|
||||
|
||||
@ -147,9 +166,10 @@ class ProcessLister {
|
||||
/// the edition and the processor architecture), releaseId and displayVersion and are returned via the
|
||||
/// [WindowsVersionExtractionResult] class.
|
||||
class WindowsVersionExtractor {
|
||||
WindowsVersionExtractor({required ProcessManager processManager, required Logger logger})
|
||||
: _logger = logger,
|
||||
_processManager = processManager;
|
||||
WindowsVersionExtractor(
|
||||
{required ProcessManager processManager, required Logger logger})
|
||||
: _logger = logger,
|
||||
_processManager = processManager;
|
||||
|
||||
final ProcessManager _processManager;
|
||||
final Logger _logger;
|
||||
@ -169,12 +189,16 @@ class WindowsVersionExtractor {
|
||||
if (output != null) {
|
||||
final List<String> parts = output.split('\n');
|
||||
if (parts.length >= 2) {
|
||||
caption = parts[1].replaceAll('Microsoft Windows', '').replaceAll(' ', ' ').trim();
|
||||
caption = parts[1]
|
||||
.replaceAll('Microsoft Windows', '')
|
||||
.replaceAll(' ', ' ')
|
||||
.trim();
|
||||
}
|
||||
}
|
||||
}
|
||||
} on ProcessException catch (e) {
|
||||
_logger.printTrace('Failed to get Caption and OSArchitecture from WMI: $e');
|
||||
_logger
|
||||
.printTrace('Failed to get Caption and OSArchitecture from WMI: $e');
|
||||
}
|
||||
|
||||
try {
|
||||
@ -190,9 +214,13 @@ class WindowsVersionExtractor {
|
||||
final String? output = osDetails.stdout as String?;
|
||||
if (output != null) {
|
||||
final Map<String, String> data = Map<String, String>.fromEntries(
|
||||
output.split('\n').where((String line) => line.contains('REG_SZ')).map((String line) {
|
||||
output
|
||||
.split('\n')
|
||||
.where((String line) => line.contains('REG_SZ'))
|
||||
.map((String line) {
|
||||
final List<String> parts = line.split('REG_SZ');
|
||||
return MapEntry<String, String>(parts.first.trim(), parts.last.trim());
|
||||
return MapEntry<String, String>(
|
||||
parts.first.trim(), parts.last.trim());
|
||||
}),
|
||||
);
|
||||
releaseId = data['ReleaseId'];
|
||||
@ -200,7 +228,8 @@ class WindowsVersionExtractor {
|
||||
}
|
||||
}
|
||||
} on ProcessException catch (e) {
|
||||
_logger.printTrace('Failed to get ReleaseId and DisplayVersion from registry: $e');
|
||||
_logger.printTrace(
|
||||
'Failed to get ReleaseId and DisplayVersion from registry: $e');
|
||||
}
|
||||
|
||||
return WindowsVersionExtractionResult(
|
||||
@ -221,7 +250,8 @@ final class WindowsVersionExtractionResult {
|
||||
});
|
||||
|
||||
factory WindowsVersionExtractionResult.empty() {
|
||||
return WindowsVersionExtractionResult(caption: null, releaseId: null, displayVersion: null);
|
||||
return WindowsVersionExtractionResult(
|
||||
caption: null, releaseId: null, displayVersion: null);
|
||||
}
|
||||
|
||||
final String? caption;
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -162,7 +162,7 @@ class FakeDoctorValidator extends DoctorValidator {
|
||||
FakeDoctorValidator(super.title);
|
||||
|
||||
@override
|
||||
Future<ValidationResult> validate() async {
|
||||
Future<ValidationResult> validateImpl() async {
|
||||
return ValidationResult.crash(Object());
|
||||
}
|
||||
}
|
||||
|
@ -14,15 +14,20 @@ import '../src/context.dart';
|
||||
import '../src/fake_process_manager.dart';
|
||||
|
||||
/// Fake [_WindowsUtils] to use for testing
|
||||
class FakeValidOperatingSystemUtils extends Fake implements OperatingSystemUtils {
|
||||
FakeValidOperatingSystemUtils([this.name = 'Microsoft Windows [Version 11.0.22621.963]']);
|
||||
class FakeValidOperatingSystemUtils extends Fake
|
||||
implements OperatingSystemUtils {
|
||||
FakeValidOperatingSystemUtils(
|
||||
[this.name = 'Microsoft Windows [Version 11.0.22621.963]']);
|
||||
|
||||
@override
|
||||
final String name;
|
||||
}
|
||||
|
||||
class FakeProcessLister extends Fake implements ProcessLister {
|
||||
FakeProcessLister({required this.result, this.exitCode = 0, this.powershellAvailable = true});
|
||||
FakeProcessLister(
|
||||
{required this.result,
|
||||
this.exitCode = 0,
|
||||
this.powershellAvailable = true});
|
||||
final String result;
|
||||
final int exitCode;
|
||||
final bool powershellAvailable;
|
||||
@ -37,11 +42,13 @@ class FakeProcessLister extends Fake implements ProcessLister {
|
||||
}
|
||||
|
||||
FakeProcessLister ofdRunning() {
|
||||
return FakeProcessLister(result: r'Path: "C:\Program Files\Topaz OFD\Warsaw\core.exe"');
|
||||
return FakeProcessLister(
|
||||
result: r'Path: "C:\Program Files\Topaz OFD\Warsaw\core.exe"');
|
||||
}
|
||||
|
||||
FakeProcessLister ofdNotRunning() {
|
||||
return FakeProcessLister(result: r'Path: "C:\Program Files\Google\Chrome\Application\chrome.exe');
|
||||
return FakeProcessLister(
|
||||
result: r'Path: "C:\Program Files\Google\Chrome\Application\chrome.exe');
|
||||
}
|
||||
|
||||
FakeProcessLister failure() {
|
||||
@ -57,51 +64,58 @@ FakeProcessLister powershellUnavailable() {
|
||||
|
||||
/// The expected validation result object for
|
||||
/// a passing windows version test
|
||||
const ValidationResult validWindows11ValidationResult = ValidationResult(
|
||||
ValidationResult validWindows11ValidationResult = ValidationResult(
|
||||
ValidationType.success,
|
||||
<ValidationMessage>[],
|
||||
const <ValidationMessage>[],
|
||||
statusInfo: '11 Pro 64-bit, 23H2, 2009',
|
||||
);
|
||||
|
||||
/// The expected validation result object for
|
||||
/// a passing windows version test
|
||||
const ValidationResult invalidWindowsValidationResult = ValidationResult(
|
||||
ValidationResult invalidWindowsValidationResult = ValidationResult(
|
||||
ValidationType.missing,
|
||||
<ValidationMessage>[],
|
||||
const <ValidationMessage>[],
|
||||
statusInfo: 'Unable to confirm if installed Windows version is 10 or greater',
|
||||
);
|
||||
|
||||
const ValidationResult ofdFoundRunning =
|
||||
ValidationResult(ValidationType.partial, <ValidationMessage>[
|
||||
ValidationMessage.hint(
|
||||
'The Topaz OFD Security Module was detected on your machine. '
|
||||
'You may need to disable it to build Flutter applications.',
|
||||
),
|
||||
], statusInfo: 'Problem detected with Windows installation');
|
||||
|
||||
const ValidationResult powershellUnavailableResult =
|
||||
ValidationResult(ValidationType.partial, <ValidationMessage>[
|
||||
ValidationMessage.hint(
|
||||
'Failed to find ${ProcessLister.powershell} or ${ProcessLister.pwsh} on PATH',
|
||||
),
|
||||
], statusInfo: 'Problem detected with Windows installation');
|
||||
|
||||
const ValidationResult getProcessFailed = ValidationResult(
|
||||
ValidationResult ofdFoundRunning = ValidationResult(
|
||||
ValidationType.partial,
|
||||
<ValidationMessage>[ValidationMessage.hint('Get-Process failed to complete')],
|
||||
const <ValidationMessage>[
|
||||
ValidationMessage.hint(
|
||||
'The Topaz OFD Security Module was detected on your machine. '
|
||||
'You may need to disable it to build Flutter applications.',
|
||||
),
|
||||
],
|
||||
statusInfo: 'Problem detected with Windows installation',
|
||||
);
|
||||
|
||||
ValidationResult powershellUnavailableResult = ValidationResult(
|
||||
ValidationType.partial,
|
||||
const <ValidationMessage>[
|
||||
ValidationMessage.hint(
|
||||
'Failed to find ${ProcessLister.powershell} or ${ProcessLister.pwsh} on PATH'),
|
||||
],
|
||||
statusInfo: 'Problem detected with Windows installation',
|
||||
);
|
||||
|
||||
ValidationResult getProcessFailed = ValidationResult(
|
||||
ValidationType.partial,
|
||||
const <ValidationMessage>[
|
||||
ValidationMessage.hint('Get-Process failed to complete'),
|
||||
],
|
||||
statusInfo: 'Problem detected with Windows installation',
|
||||
);
|
||||
|
||||
class FakeVersionExtractor extends Fake implements WindowsVersionExtractor {
|
||||
FakeVersionExtractor({required this.mockData});
|
||||
FakeVersionExtractor.win11ProX64()
|
||||
: this(
|
||||
mockData: WindowsVersionExtractionResult(
|
||||
caption: '11 Pro 64-bit',
|
||||
releaseId: '2009',
|
||||
displayVersion: '23H2',
|
||||
),
|
||||
);
|
||||
: this(
|
||||
mockData: WindowsVersionExtractionResult(
|
||||
caption: '11 Pro 64-bit',
|
||||
releaseId: '2009',
|
||||
displayVersion: '23H2',
|
||||
),
|
||||
);
|
||||
final WindowsVersionExtractionResult mockData;
|
||||
|
||||
@override
|
||||
@ -111,8 +125,10 @@ class FakeVersionExtractor extends Fake implements WindowsVersionExtractor {
|
||||
}
|
||||
|
||||
void main() {
|
||||
testWithoutContext('Successfully running windows version check on windows 10', () async {
|
||||
final WindowsVersionValidator windowsVersionValidator = WindowsVersionValidator(
|
||||
testWithoutContext('Successfully running windows version check on windows 10',
|
||||
() async {
|
||||
final WindowsVersionValidator windowsVersionValidator =
|
||||
WindowsVersionValidator(
|
||||
operatingSystemUtils: FakeValidOperatingSystemUtils(),
|
||||
processLister: ofdNotRunning(),
|
||||
versionExtractor: FakeVersionExtractor.win11ProX64(),
|
||||
@ -132,8 +148,11 @@ void main() {
|
||||
);
|
||||
});
|
||||
|
||||
testWithoutContext('Successfully running windows version check on windows 10 for BR', () async {
|
||||
final WindowsVersionValidator windowsVersionValidator = WindowsVersionValidator(
|
||||
testWithoutContext(
|
||||
'Successfully running windows version check on windows 10 for BR',
|
||||
() async {
|
||||
final WindowsVersionValidator windowsVersionValidator =
|
||||
WindowsVersionValidator(
|
||||
operatingSystemUtils: FakeValidOperatingSystemUtils(
|
||||
'Microsoft Windows [versão 10.0.22621.1105]',
|
||||
),
|
||||
@ -156,7 +175,8 @@ void main() {
|
||||
});
|
||||
|
||||
testWithoutContext('Identifying a windows version before 10', () async {
|
||||
final WindowsVersionValidator windowsVersionValidator = WindowsVersionValidator(
|
||||
final WindowsVersionValidator windowsVersionValidator =
|
||||
WindowsVersionValidator(
|
||||
operatingSystemUtils: FakeValidOperatingSystemUtils(
|
||||
'Microsoft Windows [Version 8.0.22621.1105]',
|
||||
),
|
||||
@ -185,13 +205,16 @@ OS Version: .0.19044 N/A Build 19044
|
||||
OS 版本: 10.0.22621 暂缺 Build 22621
|
||||
''';
|
||||
|
||||
final RegExp regex = RegExp(kWindowsOSVersionSemVerPattern, multiLine: true);
|
||||
final RegExp regex =
|
||||
RegExp(kWindowsOSVersionSemVerPattern, multiLine: true);
|
||||
final Iterable<RegExpMatch> matches = regex.allMatches(testStr);
|
||||
|
||||
expect(matches.length, 5, reason: 'There should be only 5 matches for the pattern provided');
|
||||
expect(matches.length, 5,
|
||||
reason: 'There should be only 5 matches for the pattern provided');
|
||||
});
|
||||
|
||||
testWithoutContext('Successfully checks for Topaz OFD when it is running', () async {
|
||||
testWithoutContext('Successfully checks for Topaz OFD when it is running',
|
||||
() async {
|
||||
final WindowsVersionValidator validator = WindowsVersionValidator(
|
||||
operatingSystemUtils: FakeValidOperatingSystemUtils(),
|
||||
processLister: ofdRunning(),
|
||||
@ -253,7 +276,8 @@ OS 版本: 10.0.22621 暂缺 Build 22621
|
||||
final WindowsVersionValidator validator = WindowsVersionValidator(
|
||||
operatingSystemUtils: FakeValidOperatingSystemUtils(),
|
||||
processLister: failure(),
|
||||
versionExtractor: FakeVersionExtractor(mockData: WindowsVersionExtractionResult.empty()),
|
||||
versionExtractor: FakeVersionExtractor(
|
||||
mockData: WindowsVersionExtractionResult.empty()),
|
||||
);
|
||||
final ValidationResult result = await validator.validate();
|
||||
expect(
|
||||
@ -278,14 +302,20 @@ OS 版本: 10.0.22621 暂缺 Build 22621
|
||||
);
|
||||
});
|
||||
|
||||
testWithoutContext('getProcessesWithPath successfully runs with powershell', () async {
|
||||
testWithoutContext('getProcessesWithPath successfully runs with powershell',
|
||||
() async {
|
||||
final ProcessLister processLister = ProcessLister(
|
||||
FakeProcessManager.list(<FakeCommand>[
|
||||
const FakeCommand(
|
||||
command: <String>[ProcessLister.powershell, '-command', 'Get-Process | Format-List Path'],
|
||||
command: <String>[
|
||||
ProcessLister.powershell,
|
||||
'-command',
|
||||
'Get-Process | Format-List Path'
|
||||
],
|
||||
stdout: ProcessLister.powershell,
|
||||
),
|
||||
])..excludedExecutables.add(ProcessLister.pwsh),
|
||||
])
|
||||
..excludedExecutables.add(ProcessLister.pwsh),
|
||||
);
|
||||
|
||||
try {
|
||||
@ -303,10 +333,15 @@ OS 版本: 10.0.22621 暂缺 Build 22621
|
||||
final ProcessLister processLister = ProcessLister(
|
||||
FakeProcessManager.list(<FakeCommand>[
|
||||
const FakeCommand(
|
||||
command: <String>[ProcessLister.pwsh, '-command', 'Get-Process | Format-List Path'],
|
||||
command: <String>[
|
||||
ProcessLister.pwsh,
|
||||
'-command',
|
||||
'Get-Process | Format-List Path'
|
||||
],
|
||||
stdout: ProcessLister.pwsh,
|
||||
),
|
||||
])..excludedExecutables.add(ProcessLister.powershell),
|
||||
])
|
||||
..excludedExecutables.add(ProcessLister.powershell),
|
||||
);
|
||||
|
||||
try {
|
||||
@ -324,7 +359,8 @@ OS 版本: 10.0.22621 暂缺 Build 22621
|
||||
() async {
|
||||
final ProcessLister processLister = ProcessLister(
|
||||
FakeProcessManager.empty()
|
||||
..excludedExecutables.addAll(<String>[ProcessLister.powershell, ProcessLister.pwsh]),
|
||||
..excludedExecutables
|
||||
.addAll(<String>[ProcessLister.powershell, ProcessLister.pwsh]),
|
||||
);
|
||||
|
||||
try {
|
||||
@ -342,7 +378,8 @@ OS 版本: 10.0.22621 暂缺 Build 22621
|
||||
testWithoutContext(
|
||||
'Parses Caption, OSArchitecture, releaseId, and CurrentVersion from the OS',
|
||||
() async {
|
||||
final FakeProcessManager processManager = FakeProcessManager.list(<FakeCommand>[
|
||||
final FakeProcessManager processManager =
|
||||
FakeProcessManager.list(<FakeCommand>[
|
||||
const FakeCommand(
|
||||
command: <Pattern>['wmic', 'os', 'get', 'Caption,OSArchitecture'],
|
||||
stdout: '''
|
||||
@ -402,7 +439,8 @@ End of search: 22 match(es) found.
|
||||
},
|
||||
);
|
||||
|
||||
testWithoutContext('Differentiates Windows 11 from 10 when wmic call fails', () async {
|
||||
testWithoutContext('Differentiates Windows 11 from 10 when wmic call fails',
|
||||
() async {
|
||||
const String windows10RegQueryOutput = r'''
|
||||
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion
|
||||
SystemRoot REG_SZ C:\WINDOWS
|
||||
@ -430,8 +468,14 @@ HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion
|
||||
End of search: 21 match(es) found.
|
||||
|
||||
''';
|
||||
const List<String> wmicCommand = <String>['wmic', 'os', 'get', 'Caption,OSArchitecture'];
|
||||
final FakeProcessManager processManager = FakeProcessManager.list(<FakeCommand>[
|
||||
const List<String> wmicCommand = <String>[
|
||||
'wmic',
|
||||
'os',
|
||||
'get',
|
||||
'Caption,OSArchitecture'
|
||||
];
|
||||
final FakeProcessManager processManager =
|
||||
FakeProcessManager.list(<FakeCommand>[
|
||||
FakeCommand(
|
||||
command: wmicCommand,
|
||||
exception: ProcessException(wmicCommand[0], wmicCommand.sublist(1)),
|
||||
@ -462,7 +506,12 @@ End of search: 21 match(es) found.
|
||||
});
|
||||
|
||||
testWithoutContext('Handles reg call failing', () async {
|
||||
const List<String> wmicCommand = <String>['wmic', 'os', 'get', 'Caption,OSArchitecture'];
|
||||
const List<String> wmicCommand = <String>[
|
||||
'wmic',
|
||||
'os',
|
||||
'get',
|
||||
'Caption,OSArchitecture'
|
||||
];
|
||||
const List<String> regCommand = <String>[
|
||||
'reg',
|
||||
'query',
|
||||
@ -470,7 +519,8 @@ End of search: 21 match(es) found.
|
||||
'/t',
|
||||
'REG_SZ',
|
||||
];
|
||||
final FakeProcessManager processManager = FakeProcessManager.list(<FakeCommand>[
|
||||
final FakeProcessManager processManager =
|
||||
FakeProcessManager.list(<FakeCommand>[
|
||||
const FakeCommand(
|
||||
command: wmicCommand,
|
||||
stdout: r'''
|
||||
|
Loading…
x
Reference in New Issue
Block a user