flutter/packages/flutter_tools/test/commands/doctor_test.dart
tonyzhao1 58d98ce31e Create categories for doctor validators (#20758)
* First step in Flutter Doctor refactor. Assigns categories to all validators.

* Revert "Roll engine e54bc4ea1832..a84b210b3d26 (6 commits) (#20453)"

This reverts commit 05c2880a17.

* Split iOS and Android workflows into workflow and validator classes.

* Change ValidatorCategory to handle standalone validators that share a
category (e.g. IntelliJ).

Also make Android Studio and Android toolchain use separate categories.

At this stage, flutter doctor output matches what it was previously.
(The summary() method itself has not yet been changed )

* Change doctor summary code to support validator categories.

Output is still unchanged.

* Handle small formatting issues.

* Flip Flutter category's isGroup field to false until it's actually
needed.

* Revert auto-generated formatting changes to keep those lines from
muddying the pull.

* Small fixes pointed out by analyzer.

* Properly fix analyzer issues around const constructors.

* Small changes to address comments.

* Add tests to verify grouped validator behavior and validationtype
merging.

* Update doctor.dart

* Add comments for clarification.
2018-09-04 19:36:47 -07:00

488 lines
20 KiB
Dart

// Copyright 2015 The Chromium Authors. All rights reserved.
// 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:flutter_tools/src/base/file_system.dart';
import 'package:flutter_tools/src/doctor.dart';
import 'package:flutter_tools/src/vscode/vscode.dart';
import 'package:flutter_tools/src/vscode/vscode_validator.dart';
import '../src/common.dart';
import '../src/context.dart';
void main() {
group('doctor', () {
testUsingContext('intellij validator', () async {
const String installPath = '/path/to/intelliJ';
final ValidationResult result = await new IntelliJValidatorTestTarget('Test', installPath).validate();
expect(result.type, ValidationType.partial);
expect(result.statusInfo, 'version test.test.test');
expect(result.messages, hasLength(4));
ValidationMessage message = result.messages
.firstWhere((ValidationMessage m) => m.message.startsWith('IntelliJ '));
expect(message.message, 'IntelliJ at $installPath');
message = result.messages
.firstWhere((ValidationMessage m) => m.message.startsWith('Dart '));
expect(message.message, 'Dart plugin version 162.2485');
message = result.messages
.firstWhere((ValidationMessage m) => m.message.startsWith('Flutter '));
expect(message.message, contains('Flutter plugin version 0.1.3'));
expect(message.message, contains('recommended minimum version'));
});
testUsingContext('vs code validator when both installed', () async {
final ValidationResult result = await VsCodeValidatorTestTargets.installedWithExtension.validate();
expect(result.type, ValidationType.installed);
expect(result.statusInfo, 'version 1.2.3');
expect(result.messages, hasLength(2));
ValidationMessage message = result.messages
.firstWhere((ValidationMessage m) => m.message.startsWith('VS Code '));
expect(message.message, 'VS Code at ${VsCodeValidatorTestTargets.validInstall}');
message = result.messages
.firstWhere((ValidationMessage m) => m.message.startsWith('Flutter '));
expect(message.message, 'Flutter extension version 4.5.6');
});
testUsingContext('vs code validator when 64bit installed', () async {
expect(VsCodeValidatorTestTargets.installedWithExtension64bit.title, 'VS Code, 64-bit edition');
final ValidationResult result = await VsCodeValidatorTestTargets.installedWithExtension64bit.validate();
expect(result.type, ValidationType.installed);
expect(result.statusInfo, 'version 1.2.3');
expect(result.messages, hasLength(2));
ValidationMessage message = result.messages
.firstWhere((ValidationMessage m) => m.message.startsWith('VS Code '));
expect(message.message, 'VS Code at ${VsCodeValidatorTestTargets.validInstall}');
message = result.messages
.firstWhere((ValidationMessage m) => m.message.startsWith('Flutter '));
expect(message.message, 'Flutter extension version 4.5.6');
});
testUsingContext('vs code validator when extension missing', () async {
final ValidationResult result = await VsCodeValidatorTestTargets.installedWithoutExtension.validate();
expect(result.type, ValidationType.partial);
expect(result.statusInfo, 'version 1.2.3');
expect(result.messages, hasLength(2));
ValidationMessage message = result.messages
.firstWhere((ValidationMessage m) => m.message.startsWith('VS Code '));
expect(message.message, 'VS Code at ${VsCodeValidatorTestTargets.validInstall}');
message = result.messages
.firstWhere((ValidationMessage m) => m.message.startsWith('Flutter '));
expect(message.message, startsWith('Flutter extension not installed'));
});
});
group('doctor with overriden validators', () {
testUsingContext('validate non-verbose output format for run without issues', () async {
expect(await doctor.diagnose(verbose: false), isTrue);
expect(testLogger.statusText, equals(
'Doctor summary (to see all details, run flutter doctor -v):\n'
'[✓] Passing Validator (with statusInfo)\n'
'[✓] Another Passing Validator (with statusInfo)\n'
'[✓] Providing validators is fun (with statusInfo)\n'
'\n'
'• No issues found!\n'
));
}, overrides: <Type, Generator>{
DoctorValidatorsProvider: () => new FakeDoctorValidatorsProvider()
});
});
group('doctor with fake validators', () {
testUsingContext('validate non-verbose output format for run without issues', () async {
expect(await new FakeQuietDoctor().diagnose(verbose: false), isTrue);
expect(testLogger.statusText, equals(
'Doctor summary (to see all details, run flutter doctor -v):\n'
'[✓] Passing Validator (with statusInfo)\n'
'[✓] Another Passing Validator (with statusInfo)\n'
'[✓] Validators are fun (with statusInfo)\n'
'[✓] Four score and seven validators ago (with statusInfo)\n'
'\n'
'• No issues found!\n'
));
});
testUsingContext('validate non-verbose output format when only one category fails', () async {
expect(await new FakeSinglePassingDoctor().diagnose(verbose: false), isTrue);
expect(testLogger.statusText, equals(
'Doctor summary (to see all details, run flutter doctor -v):\n'
'[!] Partial Validator with only a Hint\n'
' ! There is a hint here\n'
'\n'
'! Doctor found issues in 1 category.\n'
));
});
testUsingContext('validate non-verbose output format for a passing run', () async {
expect(await new FakePassingDoctor().diagnose(verbose: false), isTrue);
expect(testLogger.statusText, equals(
'Doctor summary (to see all details, run flutter doctor -v):\n'
'[✓] Passing Validator (with statusInfo)\n'
'[!] Partial Validator with only a Hint\n'
' ! There is a hint here\n'
'[!] Partial Validator with Errors\n'
' ✗ A error message indicating partial installation\n'
' ! Maybe a hint will help the user\n'
'[✓] Another Passing Validator (with statusInfo)\n'
'\n'
'! Doctor found issues in 2 categories.\n'
));
});
testUsingContext('validate non-verbose output format', () async {
expect(await new FakeDoctor().diagnose(verbose: false), isFalse);
expect(testLogger.statusText, equals(
'Doctor summary (to see all details, run flutter doctor -v):\n'
'[✓] Passing Validator (with statusInfo)\n'
'[✗] Missing Validator\n'
' ✗ A useful error message\n'
' ! A hint message\n'
'[!] Partial Validator with only a Hint\n'
' ! There is a hint here\n'
'[!] Partial Validator with Errors\n'
' ✗ A error message indicating partial installation\n'
' ! Maybe a hint will help the user\n'
'\n'
'! Doctor found issues in 3 categories.\n'
));
});
testUsingContext('validate verbose output format', () async {
expect(await new FakeDoctor().diagnose(verbose: true), isFalse);
expect(testLogger.statusText, equals(
'[✓] Passing Validator (with statusInfo)\n'
' • A helpful message\n'
' • A second, somewhat longer helpful message\n'
'\n'
'[✗] Missing Validator\n'
' ✗ A useful error message\n'
' • A message that is not an error\n'
' ! A hint message\n'
'\n'
'[!] Partial Validator with only a Hint\n'
' ! There is a hint here\n'
' • But there is no error\n'
'\n'
'[!] Partial Validator with Errors\n'
' ✗ A error message indicating partial installation\n'
' ! Maybe a hint will help the user\n'
' • An extra message with some verbose details\n'
'\n'
'! Doctor found issues in 3 categories.\n'
));
});
});
group('doctor with grouped validators', () {
testUsingContext('validate diagnose combines validator output', () async {
expect(await new FakeGroupedDoctor().diagnose(), isTrue);
expect(testLogger.statusText, equals(
'[✓] Category 1\n'
' • A helpful message\n'
' • A helpful message\n'
'\n'
'[!] Category 2\n'
' • A helpful message\n'
' ✗ A useful error message\n'
'\n'
'! Doctor found issues in 1 category.\n'
));
});
testUsingContext('validate summary combines validator output', () async {
expect(await new FakeGroupedDoctor().summaryText, equals(
'[✓] Category 1 is fully installed.\n'
'[!] Category 2 is partially installed; more components are available.\n'
'\n'
'Run "flutter doctor" for information about installing additional components.\n'
));
});
});
group('doctor merging validator results', () {
final PassingGroupedValidator installed = new PassingGroupedValidator('Category', groupedCategory1);
final PartialGroupedValidator partial = new PartialGroupedValidator('Category', groupedCategory1);
final MissingGroupedValidator missing = new MissingGroupedValidator('Category', groupedCategory1);
testUsingContext('validate installed + installed = installed', () async {
expect(await new FakeSmallGroupDoctor(installed, installed).diagnose(), isTrue);
expect(testLogger.statusText, startsWith('[✓]'));
});
testUsingContext('validate installed + partial = partial', () async {
expect(await new FakeSmallGroupDoctor(installed, partial).diagnose(), isTrue);
expect(testLogger.statusText, startsWith('[!]'));
});
testUsingContext('validate installed + missing = partial', () async {
expect(await new FakeSmallGroupDoctor(installed, missing).diagnose(), isTrue);
expect(testLogger.statusText, startsWith('[!]'));
});
testUsingContext('validate partial + installed = partial', () async {
expect(await new FakeSmallGroupDoctor(partial, installed).diagnose(), isTrue);
expect(testLogger.statusText, startsWith('[!]'));
});
testUsingContext('validate partial + partial = partial', () async {
expect(await new FakeSmallGroupDoctor(partial, partial).diagnose(), isTrue);
expect(testLogger.statusText, startsWith('[!]'));
});
testUsingContext('validate partial + missing = partial', () async {
expect(await new FakeSmallGroupDoctor(partial, missing).diagnose(), isTrue);
expect(testLogger.statusText, startsWith('[!]'));
});
testUsingContext('validate missing + installed = partial', () async {
expect(await new FakeSmallGroupDoctor(missing, installed).diagnose(), isTrue);
expect(testLogger.statusText, startsWith('[!]'));
});
testUsingContext('validate missing + partial = partial', () async {
expect(await new FakeSmallGroupDoctor(missing, partial).diagnose(), isTrue);
expect(testLogger.statusText, startsWith('[!]'));
});
testUsingContext('validate missing + missing = missing', () async {
expect(await new FakeSmallGroupDoctor(missing, missing).diagnose(), isFalse);
expect(testLogger.statusText, startsWith('[✗]'));
});
});
}
class IntelliJValidatorTestTarget extends IntelliJValidator {
IntelliJValidatorTestTarget(String title, String installPath) : super(title, installPath);
@override
String get pluginsPath => fs.path.join('test', 'data', 'intellij', 'plugins');
@override
String get version => 'test.test.test';
}
class PassingValidator extends DoctorValidator {
PassingValidator(String name) : super(name);
@override
Future<ValidationResult> validate() async {
final List<ValidationMessage> messages = <ValidationMessage>[];
messages.add(new ValidationMessage('A helpful message'));
messages.add(new ValidationMessage('A second, somewhat longer helpful message'));
return new ValidationResult(ValidationType.installed, messages, statusInfo: 'with statusInfo');
}
}
class MissingValidator extends DoctorValidator {
MissingValidator(): super('Missing Validator');
@override
Future<ValidationResult> validate() async {
final List<ValidationMessage> messages = <ValidationMessage>[];
messages.add(new ValidationMessage.error('A useful error message'));
messages.add(new ValidationMessage('A message that is not an error'));
messages.add(new ValidationMessage.hint('A hint message'));
return new ValidationResult(ValidationType.missing, messages);
}
}
class PartialValidatorWithErrors extends DoctorValidator {
PartialValidatorWithErrors() : super('Partial Validator with Errors');
@override
Future<ValidationResult> validate() async {
final List<ValidationMessage> messages = <ValidationMessage>[];
messages.add(new ValidationMessage.error('A error message indicating partial installation'));
messages.add(new ValidationMessage.hint('Maybe a hint will help the user'));
messages.add(new ValidationMessage('An extra message with some verbose details'));
return new ValidationResult(ValidationType.partial, messages);
}
}
class PartialValidatorWithHintsOnly extends DoctorValidator {
PartialValidatorWithHintsOnly() : super('Partial Validator with only a Hint');
@override
Future<ValidationResult> validate() async {
final List<ValidationMessage> messages = <ValidationMessage>[];
messages.add(new ValidationMessage.hint('There is a hint here'));
messages.add(new ValidationMessage('But there is no error'));
return new ValidationResult(ValidationType.partial, messages);
}
}
/// A doctor that fails with a missing [ValidationResult].
class FakeDoctor extends Doctor {
List<DoctorValidator> _validators;
@override
List<DoctorValidator> get validators {
if (_validators == null) {
_validators = <DoctorValidator>[];
_validators.add(new PassingValidator('Passing Validator'));
_validators.add(new MissingValidator());
_validators.add(new PartialValidatorWithHintsOnly());
_validators.add(new PartialValidatorWithErrors());
}
return _validators;
}
}
/// A doctor that should pass, but still has issues in some categories.
class FakePassingDoctor extends Doctor {
List<DoctorValidator> _validators;
@override
List<DoctorValidator> get validators {
if (_validators == null) {
_validators = <DoctorValidator>[];
_validators.add(new PassingValidator('Passing Validator'));
_validators.add(new PartialValidatorWithHintsOnly());
_validators.add(new PartialValidatorWithErrors());
_validators.add(new PassingValidator('Another Passing Validator'));
}
return _validators;
}
}
/// A doctor that should pass, but still has 1 issue to test the singular of
/// categories.
class FakeSinglePassingDoctor extends Doctor {
List<DoctorValidator> _validators;
@override
List<DoctorValidator> get validators {
if (_validators == null) {
_validators = <DoctorValidator>[];
_validators.add(new PartialValidatorWithHintsOnly());
}
return _validators;
}
}
/// A doctor that passes and has no issues anywhere.
class FakeQuietDoctor extends Doctor {
List<DoctorValidator> _validators;
@override
List<DoctorValidator> get validators {
if (_validators == null) {
_validators = <DoctorValidator>[];
_validators.add(new PassingValidator('Passing Validator'));
_validators.add(new PassingValidator('Another Passing Validator'));
_validators.add(new PassingValidator('Validators are fun'));
_validators.add(new PassingValidator('Four score and seven validators ago'));
}
return _validators;
}
}
/// A DoctorValidatorsProvider that overrides the default validators without
/// overriding the doctor.
class FakeDoctorValidatorsProvider implements DoctorValidatorsProvider {
@override
List<DoctorValidator> get validators {
return <DoctorValidator>[
new PassingValidator('Passing Validator'),
new PassingValidator('Another Passing Validator'),
new PassingValidator('Providing validators is fun')
];
}
@override
List<Workflow> get workflows => <Workflow>[];
}
ValidatorCategory groupedCategory1 = const ValidatorCategory('group 1', true);
ValidatorCategory groupedCategory2 = const ValidatorCategory('group 2', true);
class PassingGroupedValidator extends DoctorValidator {
PassingGroupedValidator(String name, ValidatorCategory group) : super(name, group);
@override
Future<ValidationResult> validate() async {
final List<ValidationMessage> messages = <ValidationMessage>[];
messages.add(new ValidationMessage('A helpful message'));
return new ValidationResult(ValidationType.installed, messages);
}
}
class MissingGroupedValidator extends DoctorValidator {
MissingGroupedValidator(String name, ValidatorCategory group): super(name, group);
@override
Future<ValidationResult> validate() async {
final List<ValidationMessage> messages = <ValidationMessage>[];
messages.add(new ValidationMessage.error('A useful error message'));
return new ValidationResult(ValidationType.missing, messages);
}
}
class PartialGroupedValidator extends DoctorValidator {
PartialGroupedValidator(String name, ValidatorCategory group): super(name, group);
@override
Future<ValidationResult> validate() async {
final List<ValidationMessage> messages = <ValidationMessage>[];
messages.add(new ValidationMessage.error('An error message for partial installation'));
return new ValidationResult(ValidationType.partial, messages);
}
}
/// A doctor that has two category groups of two validators each.
class FakeGroupedDoctor extends Doctor {
List<DoctorValidator> _validators;
@override
List<DoctorValidator> get validators {
if (_validators == null) {
_validators = <DoctorValidator>[];
_validators.add(new PassingGroupedValidator('Category 1', groupedCategory1));
_validators.add(new PassingGroupedValidator('Category 1', groupedCategory1));
_validators.add(new PassingGroupedValidator('Category 2', groupedCategory2));
_validators.add(new MissingGroupedValidator('Category 2', groupedCategory2));
}
return _validators;
}
}
/// A doctor that takes any two validators. Used to check behavior when
/// merging ValidationTypes (installed, missing, partial).
class FakeSmallGroupDoctor extends Doctor {
List<DoctorValidator> _validators;
FakeSmallGroupDoctor(DoctorValidator val1, DoctorValidator val2) {
_validators = <DoctorValidator>[val1, val2];
}
@override
List<DoctorValidator> get validators => _validators;
}
class VsCodeValidatorTestTargets extends VsCodeValidator {
static final String validInstall = fs.path.join('test', 'data', 'vscode', 'application');
static final String validExtensions = fs.path.join('test', 'data', 'vscode', 'extensions');
static final String missingExtensions = fs.path.join('test', 'data', 'vscode', 'notExtensions');
VsCodeValidatorTestTargets._(String installDirectory, String extensionDirectory, {String edition})
: super(new VsCode.fromDirectory(installDirectory, extensionDirectory, edition: edition));
static VsCodeValidatorTestTargets get installedWithExtension =>
new VsCodeValidatorTestTargets._(validInstall, validExtensions);
static VsCodeValidatorTestTargets get installedWithExtension64bit =>
new VsCodeValidatorTestTargets._(validInstall, validExtensions, edition: '64-bit edition');
static VsCodeValidatorTestTargets get installedWithoutExtension =>
new VsCodeValidatorTestTargets._(validInstall, missingExtensions);
}