Unverified Commit b4325b68 authored by Christopher Fujino's avatar Christopher Fujino Committed by GitHub

[flutter_tool] Have long-running validators fail (#100936)

parent 61c30eed
......@@ -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 'package:process/process.dart';
......@@ -204,13 +206,29 @@ class Doctor {
// Future returned by the asyncGuard() is not awaited, we pass an
// onError callback to it and translate errors into ValidationResults.
asyncGuard<ValidationResult>(
validator.validate,
() {
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;
......@@ -290,6 +308,11 @@ class Doctor {
return globals.cache.areRemoteArtifactsAvailable(engineVersion: engineRevision);
}
/// Maximum allowed duration for an entire validator to take.
///
/// This should only ever be reached if a process is stuck.
static const Duration doctorDuration = Duration(minutes: 10);
/// Print information about the state of installed tooling.
///
/// To exclude personally identifiable information like device names and
......@@ -316,7 +339,7 @@ class Doctor {
for (final ValidatorTask validatorTask in startedValidatorTasks ?? startValidatorTasks()) {
final DoctorValidator validator = validatorTask.validator;
final Status status = _logger.startSpinner(
timeout: const Duration(seconds: 2),
timeout: validator.slowWarningDuration,
slowWarningCallback: () => validator.slowWarning,
);
ValidationResult result;
......
......@@ -53,6 +53,11 @@ abstract class DoctorValidator {
String get slowWarning => 'This is taking an unexpectedly long time...';
static const Duration _slowWarningDuration = Duration(seconds: 10);
/// Duration before the spinner should display [slowWarning].
Duration get slowWarningDuration => _slowWarningDuration;
Future<ValidationResult> validate();
}
......
......@@ -359,6 +359,16 @@ void main() {
expect(logger.statusText, contains('#0 CrashingValidator.validate'));
});
testUsingContext('validate tool exit when exceeding timeout', () async {
FakeAsync().run<void>((FakeAsync time) {
final Doctor doctor = FakeAsyncStuckDoctor(logger);
doctor.diagnose(verbose: false);
time.elapse(Doctor.doctorDuration + const Duration(seconds: 1));
time.flushMicrotasks();
});
expect(logger.statusText, contains('Stuck validator that never completes exceeded maximum allowed duration of '));
});
testUsingContext('validate non-verbose output format for run with an async crash', () async {
final Completer<void> completer = Completer<void>();
......@@ -816,6 +826,18 @@ class NotAvailableValidator extends DoctorValidator {
}
}
class StuckValidator extends DoctorValidator {
StuckValidator() : super('Stuck validator that never completes');
@override
Future<ValidationResult> validate() {
final Completer<ValidationResult> completer = Completer<ValidationResult>();
// This future will never complete
return completer.future;
}
}
class PartialValidatorWithErrors extends DoctorValidator {
PartialValidatorWithErrors() : super('Partial Validator with Errors');
......@@ -966,6 +988,25 @@ class FakeCrashingDoctor extends Doctor {
}
}
/// A doctor with a validator that will never finish.
class FakeAsyncStuckDoctor extends Doctor {
FakeAsyncStuckDoctor(Logger logger) : super(logger: logger);
List<DoctorValidator> _validators;
@override
List<DoctorValidator> get validators {
if (_validators == null) {
_validators = <DoctorValidator>[];
_validators.add(PassingValidator('Passing Validator'));
_validators.add(PassingValidator('Another Passing Validator'));
_validators.add(StuckValidator());
_validators.add(PassingValidator('Validators are fun'));
_validators.add(PassingValidator('Four score and seven validators ago'));
}
return _validators;
}
}
/// A doctor with a validator that throws an exception.
class FakeAsyncCrashingDoctor extends Doctor {
FakeAsyncCrashingDoctor(this._time, Logger logger) : super(logger: logger);
......
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