Introduce DoctorValidatorsProvider to improve extensibility of flutter_tools (#16918)

DoctorValidatorsProvider is injected into Doctor to allow
overriding of DoctorValidators without needing to override
the whole Doctor instance.
diff --git a/packages/flutter_tools/lib/src/context_runner.dart b/packages/flutter_tools/lib/src/context_runner.dart
index 8b41406..28d2035 100644
--- a/packages/flutter_tools/lib/src/context_runner.dart
+++ b/packages/flutter_tools/lib/src/context_runner.dart
@@ -56,7 +56,8 @@
       Config: () => new Config(),
       DevFSConfig: () => new DevFSConfig(),
       DeviceManager: () => new DeviceManager(),
-      Doctor: () => new Doctor(),
+      Doctor: () => const Doctor(),
+      DoctorValidatorsProvider: () => DoctorValidatorsProvider.defaultInstance,
       Flags: () => const EmptyFlags(),
       FlutterVersion: () => new FlutterVersion(const Clock()),
       GenSnapshot: () => const GenSnapshot(),
diff --git a/packages/flutter_tools/lib/src/doctor.dart b/packages/flutter_tools/lib/src/doctor.dart
index 8b31a4d..a988918 100644
--- a/packages/flutter_tools/lib/src/doctor.dart
+++ b/packages/flutter_tools/lib/src/doctor.dart
@@ -26,15 +26,19 @@
 
 Doctor get doctor => context[Doctor];
 
-class ValidatorTask {
-  ValidatorTask(this.validator, this.result);
-  final DoctorValidator validator;
-  final Future<ValidationResult> result;
+abstract class DoctorValidatorsProvider {
+  /// The singleton instance, pulled from the [AppContext].
+  static DoctorValidatorsProvider get instance => context[DoctorValidatorsProvider];
+
+  static final DoctorValidatorsProvider defaultInstance = new _DefaultDoctorValidatorsProvider();
+
+  List<DoctorValidator> get validators;
 }
 
-class Doctor {
+class _DefaultDoctorValidatorsProvider implements DoctorValidatorsProvider {
   List<DoctorValidator> _validators;
 
+  @override
   List<DoctorValidator> get validators {
     if (_validators == null) {
       _validators = <DoctorValidator>[];
@@ -60,6 +64,20 @@
     }
     return _validators;
   }
+}
+
+class ValidatorTask {
+  ValidatorTask(this.validator, this.result);
+  final DoctorValidator validator;
+  final Future<ValidationResult> result;
+}
+
+class Doctor {
+  const Doctor();
+
+  List<DoctorValidator> get validators {
+    return DoctorValidatorsProvider.instance.validators;
+  }
 
   /// Return a list of [ValidatorTask] objects and starts validation on all
   /// objects in [validators].
diff --git a/packages/flutter_tools/test/commands/doctor_test.dart b/packages/flutter_tools/test/commands/doctor_test.dart
index 4d40ede..e94d355 100644
--- a/packages/flutter_tools/test/commands/doctor_test.dart
+++ b/packages/flutter_tools/test/commands/doctor_test.dart
@@ -82,7 +82,24 @@
     });
   });
 
-  group('doctor with fake validators', () {
+  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(
@@ -291,6 +308,19 @@
   }
 }
 
+/// 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')
+    ];
+  }
+}
+
 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');