Commit 8b5af14f authored by tonyzhao1's avatar tonyzhao1 Committed by Mehmet Fidanboylu

Use grouped validator instead of categories (#21577)

This is a cleanup PR.
parent 36b735ee
...@@ -13,7 +13,7 @@ import 'android_studio.dart'; ...@@ -13,7 +13,7 @@ import 'android_studio.dart';
class AndroidStudioValidator extends DoctorValidator { class AndroidStudioValidator extends DoctorValidator {
final AndroidStudio _studio; final AndroidStudio _studio;
AndroidStudioValidator(this._studio) : super('Android Studio', ValidatorCategory.androidStudio); AndroidStudioValidator(this._studio) : super('Android Studio');
static List<DoctorValidator> get allValidators { static List<DoctorValidator> get allValidators {
final List<DoctorValidator> validators = <DoctorValidator>[]; final List<DoctorValidator> validators = <DoctorValidator>[];
......
...@@ -46,8 +46,7 @@ class AndroidWorkflow implements Workflow { ...@@ -46,8 +46,7 @@ class AndroidWorkflow implements Workflow {
} }
class AndroidValidator extends DoctorValidator { class AndroidValidator extends DoctorValidator {
AndroidValidator(): super('Android toolchain - develop for Android devices', AndroidValidator(): super('Android toolchain - develop for Android devices',);
ValidatorCategory.androidToolchain);
static const String _jdkDownload = 'https://www.oracle.com/technetwork/java/javase/downloads/'; static const String _jdkDownload = 'https://www.oracle.com/technetwork/java/javase/downloads/';
......
...@@ -54,6 +54,7 @@ Future<T> runInContext<T>( ...@@ -54,6 +54,7 @@ Future<T> runInContext<T>(
Cache: () => Cache(), Cache: () => Cache(),
Clock: () => const Clock(), Clock: () => const Clock(),
CocoaPods: () => CocoaPods(), CocoaPods: () => CocoaPods(),
CocoaPodsValidator: () => const CocoaPodsValidator(),
Config: () => Config(), Config: () => Config(),
DevFSConfig: () => DevFSConfig(), DevFSConfig: () => DevFSConfig(),
DeviceManager: () => DeviceManager(), DeviceManager: () => DeviceManager(),
......
...@@ -51,7 +51,7 @@ class _DefaultDoctorValidatorsProvider implements DoctorValidatorsProvider { ...@@ -51,7 +51,7 @@ class _DefaultDoctorValidatorsProvider implements DoctorValidatorsProvider {
_validators.add(androidValidator); _validators.add(androidValidator);
if (iosWorkflow.appliesToHostPlatform) if (iosWorkflow.appliesToHostPlatform)
_validators.add(iosValidator); _validators.add(GroupedValidator(<DoctorValidator>[iosValidator, cocoapodsValidator]));
final List<DoctorValidator> ideValidators = <DoctorValidator>[]; final List<DoctorValidator> ideValidators = <DoctorValidator>[];
ideValidators.addAll(AndroidStudioValidator.allValidators); ideValidators.addAll(AndroidStudioValidator.allValidators);
...@@ -121,28 +121,8 @@ class Doctor { ...@@ -121,28 +121,8 @@ class Doctor {
bool allGood = true; bool allGood = true;
final Set<ValidatorCategory> finishedGroups = Set<ValidatorCategory>();
for (DoctorValidator validator in validators) { for (DoctorValidator validator in validators) {
final ValidatorCategory currentCategory = validator.category; final ValidationResult result = await validator.validate();
ValidationResult result;
if (currentCategory.isGrouped) {
if (finishedGroups.contains(currentCategory)) {
// We already handled this category via a previous validator.
continue;
}
// Skip ahead and get results for the other validators in this category.
final List<ValidationResult> results = <ValidationResult>[];
for (DoctorValidator subValidator in validators.where(
(DoctorValidator v) => v.category == currentCategory)) {
results.add(await subValidator.validate());
}
result = _mergeValidationResults(results);
finishedGroups.add(currentCategory);
} else {
result = await validator.validate();
}
buffer.write('${result.leadingBox} ${validator.title} is '); buffer.write('${result.leadingBox} ${validator.title} is ');
if (result.type == ValidationType.missing) if (result.type == ValidationType.missing)
buffer.write('not installed.'); buffer.write('not installed.');
...@@ -158,7 +138,6 @@ class Doctor { ...@@ -158,7 +138,6 @@ class Doctor {
if (result.type != ValidationType.installed) if (result.type != ValidationType.installed)
allGood = false; allGood = false;
} }
if (!allGood) { if (!allGood) {
...@@ -180,39 +159,15 @@ class Doctor { ...@@ -180,39 +159,15 @@ class Doctor {
bool doctorResult = true; bool doctorResult = true;
int issues = 0; int issues = 0;
final List<ValidatorTask> taskList = startValidatorTasks(); for (ValidatorTask validatorTask in startValidatorTasks()) {
final Set<ValidatorCategory> finishedGroups = Set<ValidatorCategory>();
for (ValidatorTask validatorTask in taskList) {
final DoctorValidator validator = validatorTask.validator; final DoctorValidator validator = validatorTask.validator;
final ValidatorCategory currentCategory = validator.category;
final Status status = Status.withSpinner(); final Status status = Status.withSpinner();
ValidationResult result; ValidationResult result;
try {
if (currentCategory.isGrouped) { result = await validatorTask.result;
if (finishedGroups.contains(currentCategory)) { } catch (exception) {
continue; status.cancel();
} rethrow;
final List<ValidationResult> results = <ValidationResult>[];
for (ValidatorTask subValidator in taskList.where(
(ValidatorTask t) => t.validator.category == currentCategory)) {
try {
results.add(await subValidator.result);
} catch (exception) {
status.cancel();
rethrow;
}
}
result = _mergeValidationResults(results);
finishedGroups.add(currentCategory);
} else {
try {
result = await validatorTask.result;
} catch (exception) {
status.cancel();
rethrow;
}
} }
status.stop(); status.stop();
...@@ -256,35 +211,6 @@ class Doctor { ...@@ -256,35 +211,6 @@ class Doctor {
return doctorResult; return doctorResult;
} }
ValidationResult _mergeValidationResults(List<ValidationResult> results) {
ValidationType mergedType = results[0].type;
final List<ValidationMessage> mergedMessages = <ValidationMessage>[];
for (ValidationResult result in results) {
switch (result.type) {
case ValidationType.installed:
if (mergedType == ValidationType.missing) {
mergedType = ValidationType.partial;
}
break;
case ValidationType.partial:
mergedType = ValidationType.partial;
break;
case ValidationType.missing:
if (mergedType == ValidationType.installed) {
mergedType = ValidationType.partial;
}
break;
default:
throw 'Unrecognized validation type: ' + result.type.toString();
}
mergedMessages.addAll(result.messages);
}
return ValidationResult(mergedType, mergedMessages,
statusInfo: results[0].statusInfo);
}
bool get canListAnything => workflows.any((Workflow workflow) => workflow.canListDevices); bool get canListAnything => workflows.any((Workflow workflow) => workflow.canListDevices);
bool get canLaunchAnything { bool get canLaunchAnything {
...@@ -292,11 +218,8 @@ class Doctor { ...@@ -292,11 +218,8 @@ class Doctor {
return true; return true;
return workflows.any((Workflow workflow) => workflow.canLaunchDevices); return workflows.any((Workflow workflow) => workflow.canLaunchDevices);
} }
} }
/// A series of tools and required install steps for a target platform (iOS or Android). /// A series of tools and required install steps for a target platform (iOS or Android).
abstract class Workflow { abstract class Workflow {
/// Whether the workflow applies to this platform (as in, should we ever try and use it). /// Whether the workflow applies to this platform (as in, should we ever try and use it).
...@@ -318,32 +241,69 @@ enum ValidationType { ...@@ -318,32 +241,69 @@ enum ValidationType {
installed installed
} }
/// Validator output is grouped by category.
class ValidatorCategory {
final String name;
// Whether we should bundle results for validators sharing this cateogry,
// or let each stand alone.
final bool isGrouped;
const ValidatorCategory(this.name, this.isGrouped);
static const ValidatorCategory androidToolchain = ValidatorCategory('androidToolchain', true);
static const ValidatorCategory androidStudio = ValidatorCategory('androidStudio', false);
static const ValidatorCategory ios = ValidatorCategory('ios', true);
static const ValidatorCategory flutter = ValidatorCategory('flutter', false);
static const ValidatorCategory ide = ValidatorCategory('ide', false);
static const ValidatorCategory device = ValidatorCategory('device', false);
static const ValidatorCategory other = ValidatorCategory('other', false);
}
abstract class DoctorValidator { abstract class DoctorValidator {
const DoctorValidator(this.title, [this.category = ValidatorCategory.other]); const DoctorValidator(this.title);
final String title; final String title;
final ValidatorCategory category;
Future<ValidationResult> validate(); Future<ValidationResult> validate();
} }
/// A validator that runs other [DoctorValidator]s and combines their output
/// into a single [ValidationResult]. It uses the title of the first validator
/// passed to the constructor and reports the statusInfo of the first validator
/// that provides one. Other titles and statusInfo strings are discarded.
class GroupedValidator extends DoctorValidator {
GroupedValidator(this.subValidators) : super(subValidators[0].title);
final List<DoctorValidator> subValidators;
@override
Future<ValidationResult> validate() async {
final List<ValidatorTask> tasks = <ValidatorTask>[];
for (DoctorValidator validator in subValidators) {
tasks.add(ValidatorTask(validator, validator.validate()));
}
final List<ValidationResult> results = <ValidationResult>[];
for (ValidatorTask subValidator in tasks) {
results.add(await subValidator.result);
}
return _mergeValidationResults(results);
}
ValidationResult _mergeValidationResults(List<ValidationResult> results) {
assert(results.isNotEmpty, 'Validation results should not be empty');
ValidationType mergedType = results[0].type;
final List<ValidationMessage> mergedMessages = <ValidationMessage>[];
String statusInfo;
for (ValidationResult result in results) {
statusInfo ??= result.statusInfo;
switch (result.type) {
case ValidationType.installed:
if (mergedType == ValidationType.missing) {
mergedType = ValidationType.partial;
}
break;
case ValidationType.partial:
mergedType = ValidationType.partial;
break;
case ValidationType.missing:
if (mergedType == ValidationType.installed) {
mergedType = ValidationType.partial;
}
break;
default:
throw 'Unrecognized validation type: ' + result.type.toString();
}
mergedMessages.addAll(result.messages);
}
return ValidationResult(mergedType, mergedMessages,
statusInfo: statusInfo);
}
}
class ValidationResult { class ValidationResult {
/// [ValidationResult.type] should only equal [ValidationResult.installed] /// [ValidationResult.type] should only equal [ValidationResult.installed]
...@@ -383,7 +343,7 @@ class ValidationMessage { ...@@ -383,7 +343,7 @@ class ValidationMessage {
} }
class _FlutterValidator extends DoctorValidator { class _FlutterValidator extends DoctorValidator {
_FlutterValidator() : super('Flutter', ValidatorCategory.flutter); _FlutterValidator() : super('Flutter');
@override @override
Future<ValidationResult> validate() async { Future<ValidationResult> validate() async {
...@@ -432,7 +392,7 @@ bool _genSnapshotRuns(String genSnapshotPath) { ...@@ -432,7 +392,7 @@ bool _genSnapshotRuns(String genSnapshotPath) {
} }
class NoIdeValidator extends DoctorValidator { class NoIdeValidator extends DoctorValidator {
NoIdeValidator() : super('Flutter IDE Support',ValidatorCategory.ide); NoIdeValidator() : super('Flutter IDE Support');
@override @override
Future<ValidationResult> validate() async { Future<ValidationResult> validate() async {
...@@ -445,7 +405,7 @@ class NoIdeValidator extends DoctorValidator { ...@@ -445,7 +405,7 @@ class NoIdeValidator extends DoctorValidator {
abstract class IntelliJValidator extends DoctorValidator { abstract class IntelliJValidator extends DoctorValidator {
final String installPath; final String installPath;
IntelliJValidator(String title, this.installPath) : super(title, ValidatorCategory.ide); IntelliJValidator(String title, this.installPath) : super(title);
String get version; String get version;
String get pluginsPath; String get pluginsPath;
...@@ -640,7 +600,7 @@ class IntelliJValidatorOnMac extends IntelliJValidator { ...@@ -640,7 +600,7 @@ class IntelliJValidatorOnMac extends IntelliJValidator {
} }
class DeviceValidator extends DoctorValidator { class DeviceValidator extends DoctorValidator {
DeviceValidator() : super('Connected devices', ValidatorCategory.device); DeviceValidator() : super('Connected devices');
@override @override
Future<ValidationResult> validate() async { Future<ValidationResult> validate() async {
......
...@@ -16,6 +16,7 @@ import 'plist_utils.dart' as plist; ...@@ -16,6 +16,7 @@ import 'plist_utils.dart' as plist;
IOSWorkflow get iosWorkflow => context[IOSWorkflow]; IOSWorkflow get iosWorkflow => context[IOSWorkflow];
IOSValidator get iosValidator => context[IOSValidator]; IOSValidator get iosValidator => context[IOSValidator];
CocoaPodsValidator get cocoapodsValidator => context[CocoaPodsValidator];
class IOSWorkflow implements Workflow { class IOSWorkflow implements Workflow {
const IOSWorkflow(); const IOSWorkflow();
...@@ -42,8 +43,7 @@ class IOSWorkflow implements Workflow { ...@@ -42,8 +43,7 @@ class IOSWorkflow implements Workflow {
class IOSValidator extends DoctorValidator { class IOSValidator extends DoctorValidator {
const IOSValidator() : super('iOS toolchain - develop for iOS devices', ValidatorCategory.ios); const IOSValidator() : super('iOS toolchain - develop for iOS devices');
Future<bool> get hasIDeviceInstaller => exitsHappyAsync(<String>['ideviceinstaller', '-h']); Future<bool> get hasIDeviceInstaller => exitsHappyAsync(<String>['ideviceinstaller', '-h']);
...@@ -175,13 +175,45 @@ class IOSValidator extends DoctorValidator { ...@@ -175,13 +175,45 @@ class IOSValidator extends DoctorValidator {
} }
} }
final CocoaPodsStatus cocoaPodsStatus = await cocoaPods.evaluateCocoaPodsInstallation; } else {
brewStatus = ValidationType.missing;
messages.add(ValidationMessage.error(
'Brew not installed; use this to install tools for iOS device development.\n'
'Download brew at https://brew.sh/.'
));
}
return ValidationResult(
<ValidationType>[xcodeStatus, brewStatus].reduce(_mergeValidationTypes),
messages,
statusInfo: xcodeVersionInfo
);
}
ValidationType _mergeValidationTypes(ValidationType t1, ValidationType t2) {
return t1 == t2 ? t1 : ValidationType.partial;
}
}
class CocoaPodsValidator extends DoctorValidator {
const CocoaPodsValidator() : super('CocoaPods subvalidator');
bool get hasHomebrew => os.which('brew') != null;
@override
Future<ValidationResult> validate() async {
final List<ValidationMessage> messages = <ValidationMessage>[];
ValidationType status = ValidationType.installed;
if (hasHomebrew) {
final CocoaPodsStatus cocoaPodsStatus = await cocoaPods
.evaluateCocoaPodsInstallation;
if (cocoaPodsStatus == CocoaPodsStatus.recommended) { if (cocoaPodsStatus == CocoaPodsStatus.recommended) {
if (await cocoaPods.isCocoaPodsInitialized) { if (await cocoaPods.isCocoaPodsInitialized) {
messages.add(ValidationMessage('CocoaPods version ${await cocoaPods.cocoaPodsVersionText}')); messages.add(ValidationMessage('CocoaPods version ${await cocoaPods.cocoaPodsVersionText}'));
} else { } else {
brewStatus = ValidationType.partial; status = ValidationType.partial;
messages.add(ValidationMessage.error( messages.add(ValidationMessage.error(
'CocoaPods installed but not initialized.\n' 'CocoaPods installed but not initialized.\n'
'$noCocoaPodsConsequence\n' '$noCocoaPodsConsequence\n'
...@@ -191,8 +223,8 @@ class IOSValidator extends DoctorValidator { ...@@ -191,8 +223,8 @@ class IOSValidator extends DoctorValidator {
)); ));
} }
} else { } else {
brewStatus = ValidationType.partial;
if (cocoaPodsStatus == CocoaPodsStatus.notInstalled) { if (cocoaPodsStatus == CocoaPodsStatus.notInstalled) {
status = ValidationType.missing;
messages.add(ValidationMessage.error( messages.add(ValidationMessage.error(
'CocoaPods not installed.\n' 'CocoaPods not installed.\n'
'$noCocoaPodsConsequence\n' '$noCocoaPodsConsequence\n'
...@@ -200,6 +232,7 @@ class IOSValidator extends DoctorValidator { ...@@ -200,6 +232,7 @@ class IOSValidator extends DoctorValidator {
'$cocoaPodsInstallInstructions' '$cocoaPodsInstallInstructions'
)); ));
} else { } else {
status = ValidationType.partial;
messages.add(ValidationMessage.hint( messages.add(ValidationMessage.hint(
'CocoaPods out of date (${cocoaPods.cocoaPodsRecommendedVersion} is recommended).\n' 'CocoaPods out of date (${cocoaPods.cocoaPodsRecommendedVersion} is recommended).\n'
'$noCocoaPodsConsequence\n' '$noCocoaPodsConsequence\n'
...@@ -209,21 +242,9 @@ class IOSValidator extends DoctorValidator { ...@@ -209,21 +242,9 @@ class IOSValidator extends DoctorValidator {
} }
} }
} else { } else {
brewStatus = ValidationType.missing; // Only set status. The main validator handles messages for missing brew.
messages.add(ValidationMessage.error( status = ValidationType.missing;
'Brew not installed; use this to install tools for iOS device development.\n'
'Download brew at https://brew.sh/.'
));
} }
return ValidationResult(status, messages);
return ValidationResult(
<ValidationType>[xcodeStatus, brewStatus].reduce(_mergeValidationTypes),
messages,
statusInfo: xcodeVersionInfo
);
}
ValidationType _mergeValidationTypes(ValidationType t1, ValidationType t2) {
return t1 == t2 ? t1 : ValidationType.partial;
} }
} }
...@@ -13,7 +13,7 @@ class VsCodeValidator extends DoctorValidator { ...@@ -13,7 +13,7 @@ class VsCodeValidator extends DoctorValidator {
'https://marketplace.visualstudio.com/items?itemName=${VsCode.extensionIdentifier}'; 'https://marketplace.visualstudio.com/items?itemName=${VsCode.extensionIdentifier}';
final VsCode _vsCode; final VsCode _vsCode;
VsCodeValidator(this._vsCode) : super(_vsCode.productName, ValidatorCategory.ide); VsCodeValidator(this._vsCode) : super(_vsCode.productName);
static Iterable<DoctorValidator> get installedValidators { static Iterable<DoctorValidator> get installedValidators {
return VsCode return VsCode
......
...@@ -184,7 +184,6 @@ void main() { ...@@ -184,7 +184,6 @@ void main() {
}); });
}); });
group('doctor with grouped validators', () { group('doctor with grouped validators', () {
testUsingContext('validate diagnose combines validator output', () async { testUsingContext('validate diagnose combines validator output', () async {
expect(await FakeGroupedDoctor().diagnose(), isTrue); expect(await FakeGroupedDoctor().diagnose(), isTrue);
...@@ -201,22 +200,24 @@ void main() { ...@@ -201,22 +200,24 @@ void main() {
)); ));
}); });
testUsingContext('validate summary combines validator output', () async { testUsingContext('validate merging assigns statusInfo and title', () async {
expect(await FakeGroupedDoctor().summaryText, equals( // There are two subvalidators. Only the second contains statusInfo.
'[✓] Category 1 is fully installed.\n' expect(await FakeGroupedDoctorWithStatus().diagnose(), isTrue);
'[!] Category 2 is partially installed; more components are available.\n' expect(testLogger.statusText, equals(
'[✓] First validator title (A status message)\n'
' • A helpful message\n'
' • A different message\n'
'\n' '\n'
'Run "flutter doctor" for information about installing additional components.\n' '• No issues found!\n'
)); ));
}); });
}); });
group('doctor merging validator results', () { group('grouped validator merging results', () {
final PassingGroupedValidator installed = PassingGroupedValidator('Category', groupedCategory1); final PassingGroupedValidator installed = PassingGroupedValidator('Category');
final PartialGroupedValidator partial = PartialGroupedValidator('Category', groupedCategory1); final PartialGroupedValidator partial = PartialGroupedValidator('Category');
final MissingGroupedValidator missing = MissingGroupedValidator('Category', groupedCategory1); final MissingGroupedValidator missing = MissingGroupedValidator('Category');
testUsingContext('validate installed + installed = installed', () async { testUsingContext('validate installed + installed = installed', () async {
expect(await FakeSmallGroupDoctor(installed, installed).diagnose(), isTrue); expect(await FakeSmallGroupDoctor(installed, installed).diagnose(), isTrue);
...@@ -405,11 +406,9 @@ class FakeDoctorValidatorsProvider implements DoctorValidatorsProvider { ...@@ -405,11 +406,9 @@ class FakeDoctorValidatorsProvider implements DoctorValidatorsProvider {
} }
ValidatorCategory groupedCategory1 = const ValidatorCategory('group 1', true);
ValidatorCategory groupedCategory2 = const ValidatorCategory('group 2', true);
class PassingGroupedValidator extends DoctorValidator { class PassingGroupedValidator extends DoctorValidator {
PassingGroupedValidator(String name, ValidatorCategory group) : super(name, group); PassingGroupedValidator(String name) : super(name);
@override @override
Future<ValidationResult> validate() async { Future<ValidationResult> validate() async {
...@@ -421,7 +420,7 @@ class PassingGroupedValidator extends DoctorValidator { ...@@ -421,7 +420,7 @@ class PassingGroupedValidator extends DoctorValidator {
} }
class MissingGroupedValidator extends DoctorValidator { class MissingGroupedValidator extends DoctorValidator {
MissingGroupedValidator(String name, ValidatorCategory group): super(name, group); MissingGroupedValidator(String name): super(name);
@override @override
Future<ValidationResult> validate() async { Future<ValidationResult> validate() async {
...@@ -432,7 +431,7 @@ class MissingGroupedValidator extends DoctorValidator { ...@@ -432,7 +431,7 @@ class MissingGroupedValidator extends DoctorValidator {
} }
class PartialGroupedValidator extends DoctorValidator { class PartialGroupedValidator extends DoctorValidator {
PartialGroupedValidator(String name, ValidatorCategory group): super(name, group); PartialGroupedValidator(String name): super(name);
@override @override
Future<ValidationResult> validate() async { Future<ValidationResult> validate() async {
...@@ -442,28 +441,56 @@ class PartialGroupedValidator extends DoctorValidator { ...@@ -442,28 +441,56 @@ class PartialGroupedValidator extends DoctorValidator {
} }
} }
/// A doctor that has two category groups of two validators each. class PassingGroupedValidatorWithStatus extends DoctorValidator {
PassingGroupedValidatorWithStatus(String name) : super(name);
@override
Future<ValidationResult> validate() async {
final List<ValidationMessage> messages = <ValidationMessage>[];
messages.add(ValidationMessage('A different message'));
return ValidationResult(ValidationType.installed, messages, statusInfo: 'A status message');
}
}
/// A doctor that has two groups of two validators each.
class FakeGroupedDoctor extends Doctor { class FakeGroupedDoctor extends Doctor {
List<DoctorValidator> _validators; List<DoctorValidator> _validators;
@override @override
List<DoctorValidator> get validators { List<DoctorValidator> get validators {
if (_validators == null) { if (_validators == null) {
_validators = <DoctorValidator>[]; _validators = <DoctorValidator>[];
_validators.add(PassingGroupedValidator('Category 1', groupedCategory1)); _validators.add(GroupedValidator(<DoctorValidator>[
_validators.add(PassingGroupedValidator('Category 1', groupedCategory1)); PassingGroupedValidator('Category 1'),
_validators.add(PassingGroupedValidator('Category 2', groupedCategory2)); PassingGroupedValidator('Category 1')
_validators.add(MissingGroupedValidator('Category 2', groupedCategory2)); ]));
_validators.add(GroupedValidator(<DoctorValidator>[
PassingGroupedValidator('Category 2'),
MissingGroupedValidator('Category 2')
]));
} }
return _validators; return _validators;
} }
} }
class FakeGroupedDoctorWithStatus extends Doctor {
List<DoctorValidator> _validators;
@override
List<DoctorValidator> get validators {
_validators ??= <DoctorValidator>[
GroupedValidator(<DoctorValidator>[
PassingGroupedValidator('First validator title'),
PassingGroupedValidatorWithStatus('Second validator title'),
])];
return _validators;
}
}
/// A doctor that takes any two validators. Used to check behavior when /// A doctor that takes any two validators. Used to check behavior when
/// merging ValidationTypes (installed, missing, partial). /// merging ValidationTypes (installed, missing, partial).
class FakeSmallGroupDoctor extends Doctor { class FakeSmallGroupDoctor extends Doctor {
List<DoctorValidator> _validators; List<DoctorValidator> _validators;
FakeSmallGroupDoctor(DoctorValidator val1, DoctorValidator val2) { FakeSmallGroupDoctor(DoctorValidator val1, DoctorValidator val2) {
_validators = <DoctorValidator>[val1, val2]; _validators = <DoctorValidator>[GroupedValidator(<DoctorValidator>[val1, val2])];
} }
@override @override
List<DoctorValidator> get validators => _validators; List<DoctorValidator> get validators => _validators;
......
...@@ -190,15 +190,13 @@ void main() { ...@@ -190,15 +190,13 @@ void main() {
CocoaPods: () => cocoaPods, CocoaPods: () => cocoaPods,
}); });
testUsingContext('Emits partial status when CocoaPods is not installed', () async { testUsingContext('Emits partial status when simctl is not installed', () async {
when(xcode.isInstalled).thenReturn(true); when(xcode.isInstalled).thenReturn(true);
when(xcode.versionText) when(xcode.versionText)
.thenReturn('Xcode 8.2.1\nBuild version 8C1002\n'); .thenReturn('Xcode 8.2.1\nBuild version 8C1002\n');
when(xcode.isInstalledAndMeetsVersionCheck).thenReturn(true); when(xcode.isInstalledAndMeetsVersionCheck).thenReturn(true);
when(xcode.eulaSigned).thenReturn(true); when(xcode.eulaSigned).thenReturn(true);
when(cocoaPods.evaluateCocoaPodsInstallation) when(xcode.isSimctlInstalled).thenReturn(false);
.thenAnswer((_) async => CocoaPodsStatus.notInstalled);
when(xcode.isSimctlInstalled).thenReturn(true);
final IOSWorkflowTestTarget workflow = IOSWorkflowTestTarget(); final IOSWorkflowTestTarget workflow = IOSWorkflowTestTarget();
final ValidationResult result = await workflow.validate(); final ValidationResult result = await workflow.validate();
expect(result.type, ValidationType.partial); expect(result.type, ValidationType.partial);
...@@ -208,35 +206,19 @@ void main() { ...@@ -208,35 +206,19 @@ void main() {
CocoaPods: () => cocoaPods, CocoaPods: () => cocoaPods,
}); });
testUsingContext('Emits partial status when CocoaPods version is too low', () async {
when(xcode.isInstalled).thenReturn(true);
when(xcode.versionText)
.thenReturn('Xcode 8.2.1\nBuild version 8C1002\n');
when(xcode.isInstalledAndMeetsVersionCheck).thenReturn(true);
when(xcode.eulaSigned).thenReturn(true);
when(cocoaPods.evaluateCocoaPodsInstallation)
.thenAnswer((_) async => CocoaPodsStatus.belowRecommendedVersion);
when(xcode.isSimctlInstalled).thenReturn(true);
final IOSWorkflowTestTarget workflow = IOSWorkflowTestTarget();
final ValidationResult result = await workflow.validate();
expect(result.type, ValidationType.partial);
}, overrides: <Type, Generator>{
IMobileDevice: () => iMobileDevice,
Xcode: () => xcode,
CocoaPods: () => cocoaPods,
});
testUsingContext('Emits partial status when CocoaPods is not initialized', () async { testUsingContext('Succeeds when all checks pass', () async {
when(xcode.isInstalled).thenReturn(true); when(xcode.isInstalled).thenReturn(true);
when(xcode.versionText) when(xcode.versionText)
.thenReturn('Xcode 8.2.1\nBuild version 8C1002\n'); .thenReturn('Xcode 8.2.1\nBuild version 8C1002\n');
when(xcode.isInstalledAndMeetsVersionCheck).thenReturn(true); when(xcode.isInstalledAndMeetsVersionCheck).thenReturn(true);
when(xcode.eulaSigned).thenReturn(true); when(xcode.eulaSigned).thenReturn(true);
when(cocoaPods.isCocoaPodsInitialized).thenAnswer((_) async => false);
when(xcode.isSimctlInstalled).thenReturn(true); when(xcode.isSimctlInstalled).thenReturn(true);
ensureDirectoryExists(fs.path.join(homeDirPath, '.cocoapods', 'repos', 'master', 'README.md'));
final ValidationResult result = await IOSWorkflowTestTarget().validate(); final ValidationResult result = await IOSWorkflowTestTarget().validate();
expect(result.type, ValidationType.partial); expect(result.type, ValidationType.installed);
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
FileSystem: () => fs, FileSystem: () => fs,
IMobileDevice: () => iMobileDevice, IMobileDevice: () => iMobileDevice,
...@@ -244,42 +226,62 @@ void main() { ...@@ -244,42 +226,62 @@ void main() {
CocoaPods: () => cocoaPods, CocoaPods: () => cocoaPods,
ProcessManager: () => processManager, ProcessManager: () => processManager,
}); });
});
testUsingContext('Emits partial status when simctl is not installed', () async { group('iOS CocoaPods validation', () {
when(xcode.isInstalled).thenReturn(true); MockCocoaPods cocoaPods;
when(xcode.versionText)
.thenReturn('Xcode 8.2.1\nBuild version 8C1002\n'); setUp(() {
when(xcode.isInstalledAndMeetsVersionCheck).thenReturn(true); cocoaPods = MockCocoaPods();
when(xcode.eulaSigned).thenReturn(true); when(cocoaPods.evaluateCocoaPodsInstallation)
when(xcode.isSimctlInstalled).thenReturn(false); .thenAnswer((_) async => CocoaPodsStatus.recommended);
final IOSWorkflowTestTarget workflow = IOSWorkflowTestTarget(); when(cocoaPods.isCocoaPodsInitialized).thenAnswer((_) async => true);
when(cocoaPods.cocoaPodsVersionText).thenAnswer((_) async => '1.8.0');
});
testUsingContext('Emits installed status when CocoaPods is installed', () async {
final CocoaPodsTestTarget workflow = CocoaPodsTestTarget();
final ValidationResult result = await workflow.validate(); final ValidationResult result = await workflow.validate();
expect(result.type, ValidationType.partial); expect(result.type, ValidationType.installed);
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
IMobileDevice: () => iMobileDevice,
Xcode: () => xcode,
CocoaPods: () => cocoaPods, CocoaPods: () => cocoaPods,
}); });
testUsingContext('Emits missing status when CocoaPods is not installed', () async {
when(cocoaPods.evaluateCocoaPodsInstallation)
.thenAnswer((_) async => CocoaPodsStatus.notInstalled);
final CocoaPodsTestTarget workflow = CocoaPodsTestTarget();
final ValidationResult result = await workflow.validate();
expect(result.type, ValidationType.missing);
}, overrides: <Type, Generator>{
CocoaPods: () => cocoaPods,
});
testUsingContext('Succeeds when all checks pass', () async { testUsingContext('Emits partial status when CocoaPods is not initialized', () async {
when(xcode.isInstalled).thenReturn(true); when(cocoaPods.isCocoaPodsInitialized).thenAnswer((_) async => false);
when(xcode.versionText) final CocoaPodsTestTarget workflow = CocoaPodsTestTarget();
.thenReturn('Xcode 8.2.1\nBuild version 8C1002\n'); final ValidationResult result = await workflow.validate();
when(xcode.isInstalledAndMeetsVersionCheck).thenReturn(true); expect(result.type, ValidationType.partial);
when(xcode.eulaSigned).thenReturn(true); }, overrides: <Type, Generator>{
when(xcode.isSimctlInstalled).thenReturn(true); CocoaPods: () => cocoaPods,
});
ensureDirectoryExists(fs.path.join(homeDirPath, '.cocoapods', 'repos', 'master', 'README.md')); testUsingContext('Emits partial status when CocoaPods version is too low', () async {
when(cocoaPods.evaluateCocoaPodsInstallation)
.thenAnswer((_) async => CocoaPodsStatus.belowRecommendedVersion);
final CocoaPodsTestTarget workflow = CocoaPodsTestTarget();
final ValidationResult result = await workflow.validate();
expect(result.type, ValidationType.partial);
}, overrides: <Type, Generator>{
CocoaPods: () => cocoaPods,
});
final ValidationResult result = await IOSWorkflowTestTarget().validate(); testUsingContext('Emits missing status when homebrew is not installed', () async {
expect(result.type, ValidationType.installed); final CocoaPodsTestTarget workflow = CocoaPodsTestTarget(hasHomebrew: false);
final ValidationResult result = await workflow.validate();
expect(result.type, ValidationType.missing);
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
FileSystem: () => fs,
IMobileDevice: () => iMobileDevice,
Xcode: () => xcode,
CocoaPods: () => cocoaPods, CocoaPods: () => cocoaPods,
ProcessManager: () => processManager,
}); });
}); });
} }
...@@ -330,3 +332,12 @@ class IOSWorkflowTestTarget extends IOSValidator { ...@@ -330,3 +332,12 @@ class IOSWorkflowTestTarget extends IOSValidator {
@override @override
final Future<bool> hasIDeviceInstaller; final Future<bool> hasIDeviceInstaller;
} }
class CocoaPodsTestTarget extends CocoaPodsValidator {
CocoaPodsTestTarget({
this.hasHomebrew = true
});
@override
final bool hasHomebrew;
}
\ No newline at end of file
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