mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00
Android license detector in doctor, take two (#14783)
* Revert "Revert "Add android license verification to doctor and some refactoring" (#14727)"
This reverts commit d260294752
.
* Add tests, fix sdkManagerEnv and use it consistently, and rearrange Status object model
* AnsiSpinner needs to leave the cursor where it found it.
* fix tests
* Const constructor warning only shows up on windows...?
* Avoid crash if we can't find the home directory
* Make pathVarSeparator return a string in the mock
* Implement review comments
* Fix out-of-order problem on stop
This commit is contained in:
parent
97091f513a
commit
614df6949c
@ -234,7 +234,7 @@ Future<String> _doctorText() async {
|
||||
|
||||
appContext.setVariable(Logger, logger);
|
||||
|
||||
await appContext.runInZone(() => doctor.diagnose());
|
||||
await appContext.runInZone(() => doctor.diagnose(verbose: true));
|
||||
|
||||
return logger.statusText;
|
||||
} catch (error, trace) {
|
||||
|
@ -12,9 +12,11 @@ import '../base/file_system.dart';
|
||||
import '../base/io.dart' show ProcessResult;
|
||||
import '../base/os.dart';
|
||||
import '../base/platform.dart';
|
||||
import '../base/process.dart';
|
||||
import '../base/process_manager.dart';
|
||||
import '../base/version.dart';
|
||||
import '../globals.dart';
|
||||
import 'android_studio.dart' as android_studio;
|
||||
|
||||
AndroidSdk get androidSdk => context[AndroidSdk];
|
||||
|
||||
@ -63,6 +65,9 @@ class AndroidSdk {
|
||||
_init();
|
||||
}
|
||||
|
||||
static const String _kJavaHomeEnvironmentVariable = 'JAVA_HOME';
|
||||
static const String _kJavaExecutable = 'java';
|
||||
|
||||
/// The path to the Android SDK.
|
||||
final String directory;
|
||||
|
||||
@ -291,11 +296,56 @@ class AndroidSdk {
|
||||
return fs.path.join(directory, 'tools', 'bin', 'sdkmanager');
|
||||
}
|
||||
|
||||
/// First try Java bundled with Android Studio, then sniff JAVA_HOME, then fallback to PATH.
|
||||
static String findJavaBinary() {
|
||||
|
||||
if (android_studio.javaPath != null)
|
||||
return fs.path.join(android_studio.javaPath, 'bin', 'java');
|
||||
|
||||
final String javaHomeEnv = platform.environment[_kJavaHomeEnvironmentVariable];
|
||||
if (javaHomeEnv != null) {
|
||||
// Trust JAVA_HOME.
|
||||
return fs.path.join(javaHomeEnv, 'bin', 'java');
|
||||
}
|
||||
|
||||
// MacOS specific logic to avoid popping up a dialog window.
|
||||
// See: http://stackoverflow.com/questions/14292698/how-do-i-check-if-the-java-jdk-is-installed-on-mac.
|
||||
if (platform.isMacOS) {
|
||||
try {
|
||||
final String javaHomeOutput = runCheckedSync(<String>['/usr/libexec/java_home'], hideStdout: true);
|
||||
if (javaHomeOutput != null) {
|
||||
final List<String> javaHomeOutputSplit = javaHomeOutput.split('\n');
|
||||
if ((javaHomeOutputSplit != null) && (javaHomeOutputSplit.isNotEmpty)) {
|
||||
final String javaHome = javaHomeOutputSplit[0].trim();
|
||||
return fs.path.join(javaHome, 'bin', 'java');
|
||||
}
|
||||
}
|
||||
} catch (_) { /* ignore */ }
|
||||
}
|
||||
|
||||
// Fallback to PATH based lookup.
|
||||
return os.which(_kJavaExecutable)?.path;
|
||||
}
|
||||
|
||||
Map<String, String> _sdkManagerEnv;
|
||||
Map<String, String> get sdkManagerEnv {
|
||||
if (_sdkManagerEnv == null) {
|
||||
// If we can locate Java, then add it to the path used to run the Android SDK manager.
|
||||
_sdkManagerEnv = <String, String>{};
|
||||
final String javaBinary = findJavaBinary();
|
||||
if (javaBinary != null) {
|
||||
_sdkManagerEnv['PATH'] =
|
||||
fs.path.dirname(javaBinary) + os.pathVarSeparator + platform.environment['PATH'];
|
||||
}
|
||||
}
|
||||
return _sdkManagerEnv;
|
||||
}
|
||||
|
||||
/// Returns the version of the Android SDK manager tool or null if not found.
|
||||
String get sdkManagerVersion {
|
||||
if (!processManager.canRun(sdkManagerPath))
|
||||
throwToolExit('Android sdkmanager not found. Update to the latest Android SDK to resolve this.');
|
||||
final ProcessResult result = processManager.runSync(<String>[sdkManagerPath, '--version']);
|
||||
final ProcessResult result = processManager.runSync(<String>[sdkManagerPath, '--version'], environment: sdkManagerEnv);
|
||||
if (result.exitCode != 0) {
|
||||
throwToolExit('sdkmanager --version failed: ${result.exitCode}', exitCode: result.exitCode);
|
||||
}
|
||||
|
@ -178,14 +178,14 @@ class AndroidStudio implements Comparable<AndroidStudio> {
|
||||
|
||||
// Read all $HOME/.AndroidStudio*/system/.home files. There may be several
|
||||
// pointing to the same installation, so we grab only the latest one.
|
||||
for (FileSystemEntity entity in fs.directory(homeDirPath).listSync()) {
|
||||
if (entity is Directory && entity.basename.startsWith('.AndroidStudio')) {
|
||||
final AndroidStudio studio = new AndroidStudio.fromHomeDot(entity);
|
||||
if (studio != null &&
|
||||
!_hasStudioAt(studio.directory, newerThan: studio.version)) {
|
||||
studios.removeWhere(
|
||||
(AndroidStudio other) => other.directory == studio.directory);
|
||||
studios.add(studio);
|
||||
if (fs.directory(homeDirPath).existsSync()) {
|
||||
for (FileSystemEntity entity in fs.directory(homeDirPath).listSync()) {
|
||||
if (entity is Directory && entity.basename.startsWith('.AndroidStudio')) {
|
||||
final AndroidStudio studio = new AndroidStudio.fromHomeDot(entity);
|
||||
if (studio != null && !_hasStudioAt(studio.directory, newerThan: studio.version)) {
|
||||
studios.removeWhere((AndroidStudio other) => other.directory == studio.directory);
|
||||
studios.add(studio);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3,12 +3,11 @@
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
|
||||
import '../base/common.dart';
|
||||
import '../base/context.dart';
|
||||
import '../base/file_system.dart';
|
||||
import '../base/io.dart';
|
||||
import '../base/os.dart';
|
||||
import '../base/platform.dart';
|
||||
import '../base/process.dart';
|
||||
import '../base/process_manager.dart';
|
||||
@ -17,10 +16,20 @@ import '../base/version.dart';
|
||||
import '../doctor.dart';
|
||||
import '../globals.dart';
|
||||
import 'android_sdk.dart';
|
||||
import 'android_studio.dart' as android_studio;
|
||||
|
||||
AndroidWorkflow get androidWorkflow => context.putIfAbsent(AndroidWorkflow, () => new AndroidWorkflow());
|
||||
|
||||
enum LicensesAccepted {
|
||||
none,
|
||||
some,
|
||||
all,
|
||||
unknown,
|
||||
}
|
||||
|
||||
final RegExp licenseCounts = new RegExp(r'(\d+) of (\d+) SDK package licenses? not accepted.');
|
||||
final RegExp licenseNotAccepted = new RegExp(r'licenses? not accepted', caseSensitive: false);
|
||||
final RegExp licenseAccepted = new RegExp(r'All SDK package licenses accepted.');
|
||||
|
||||
class AndroidWorkflow extends DoctorValidator implements Workflow {
|
||||
AndroidWorkflow() : super('Android toolchain - develop for Android devices');
|
||||
|
||||
@ -33,41 +42,8 @@ class AndroidWorkflow extends DoctorValidator implements Workflow {
|
||||
@override
|
||||
bool get canLaunchDevices => androidSdk != null && androidSdk.validateSdkWellFormed().isEmpty;
|
||||
|
||||
static const String _kJavaHomeEnvironmentVariable = 'JAVA_HOME';
|
||||
static const String _kJavaExecutable = 'java';
|
||||
static const String _kJdkDownload = 'https://www.oracle.com/technetwork/java/javase/downloads/';
|
||||
|
||||
/// First try Java bundled with Android Studio, then sniff JAVA_HOME, then fallback to PATH.
|
||||
static String _findJavaBinary() {
|
||||
|
||||
if (android_studio.javaPath != null)
|
||||
return fs.path.join(android_studio.javaPath, 'bin', 'java');
|
||||
|
||||
final String javaHomeEnv = platform.environment[_kJavaHomeEnvironmentVariable];
|
||||
if (javaHomeEnv != null) {
|
||||
// Trust JAVA_HOME.
|
||||
return fs.path.join(javaHomeEnv, 'bin', 'java');
|
||||
}
|
||||
|
||||
// MacOS specific logic to avoid popping up a dialog window.
|
||||
// See: http://stackoverflow.com/questions/14292698/how-do-i-check-if-the-java-jdk-is-installed-on-mac.
|
||||
if (platform.isMacOS) {
|
||||
try {
|
||||
final String javaHomeOutput = runCheckedSync(<String>['/usr/libexec/java_home'], hideStdout: true);
|
||||
if (javaHomeOutput != null) {
|
||||
final List<String> javaHomeOutputSplit = javaHomeOutput.split('\n');
|
||||
if ((javaHomeOutputSplit != null) && (javaHomeOutputSplit.isNotEmpty)) {
|
||||
final String javaHome = javaHomeOutputSplit[0].trim();
|
||||
return fs.path.join(javaHome, 'bin', 'java');
|
||||
}
|
||||
}
|
||||
} catch (_) { /* ignore */ }
|
||||
}
|
||||
|
||||
// Fallback to PATH based lookup.
|
||||
return os.which(_kJavaExecutable)?.path;
|
||||
}
|
||||
|
||||
/// Returns false if we cannot determine the Java version or if the version
|
||||
/// is not compatible.
|
||||
bool _checkJavaVersion(String javaBinary, List<ValidationMessage> messages) {
|
||||
@ -154,7 +130,7 @@ class AndroidWorkflow extends DoctorValidator implements Workflow {
|
||||
}
|
||||
|
||||
// Now check for the JDK.
|
||||
final String javaBinary = _findJavaBinary();
|
||||
final String javaBinary = AndroidSdk.findJavaBinary();
|
||||
if (javaBinary == null) {
|
||||
messages.add(new ValidationMessage.error(
|
||||
'No Java Development Kit (JDK) found; You must have the environment '
|
||||
@ -169,10 +145,59 @@ class AndroidWorkflow extends DoctorValidator implements Workflow {
|
||||
return new ValidationResult(ValidationType.partial, messages, statusInfo: sdkVersionText);
|
||||
}
|
||||
|
||||
// Check for licenses.
|
||||
switch (await licensesAccepted) {
|
||||
case LicensesAccepted.all:
|
||||
messages.add(new ValidationMessage('All Android licenses accepted.'));
|
||||
break;
|
||||
case LicensesAccepted.some:
|
||||
messages.add(new ValidationMessage.hint('Some Android licenses not accepted. To resolve this, run: flutter doctor --android-licenses'));
|
||||
return new ValidationResult(ValidationType.partial, messages, statusInfo: sdkVersionText);
|
||||
case LicensesAccepted.none:
|
||||
messages.add(new ValidationMessage.error('Android licenses not accepted. To resolve this, run: flutter doctor --android-licenses'));
|
||||
return new ValidationResult(ValidationType.partial, messages, statusInfo: sdkVersionText);
|
||||
case LicensesAccepted.unknown:
|
||||
messages.add(new ValidationMessage.error('Android license status unknown.'));
|
||||
return new ValidationResult(ValidationType.partial, messages, statusInfo: sdkVersionText);
|
||||
}
|
||||
|
||||
// Success.
|
||||
return new ValidationResult(ValidationType.installed, messages, statusInfo: sdkVersionText);
|
||||
}
|
||||
|
||||
Future<LicensesAccepted> get licensesAccepted async {
|
||||
LicensesAccepted status = LicensesAccepted.unknown;
|
||||
|
||||
void _onLine(String line) {
|
||||
if (licenseAccepted.hasMatch(line)) {
|
||||
status = LicensesAccepted.all;
|
||||
} else if (licenseCounts.hasMatch(line)) {
|
||||
final Match match = licenseCounts.firstMatch(line);
|
||||
if (match.group(1) != match.group(2)) {
|
||||
status = LicensesAccepted.some;
|
||||
} else {
|
||||
status = LicensesAccepted.none;
|
||||
}
|
||||
} else if (licenseNotAccepted.hasMatch(line)) {
|
||||
// In case the format changes, a more general match will keep doctor
|
||||
// mostly working.
|
||||
status = LicensesAccepted.none;
|
||||
}
|
||||
}
|
||||
|
||||
final Process process = await runCommand(<String>[androidSdk.sdkManagerPath, '--licenses'], environment: androidSdk.sdkManagerEnv);
|
||||
process.stdin.write('n\n');
|
||||
final Future<void> output = process.stdout.transform(const Utf8Decoder(allowMalformed: true)).transform(const LineSplitter()).listen(_onLine).asFuture<void>(null);
|
||||
final Future<void> errors = process.stderr.transform(const Utf8Decoder(allowMalformed: true)).transform(const LineSplitter()).listen(_onLine).asFuture<void>(null);
|
||||
try {
|
||||
await Future.wait<void>(<Future<void>>[output, errors]).timeout(const Duration(seconds: 30));
|
||||
} catch (TimeoutException) {
|
||||
printTrace('Intentionally killing ${androidSdk.sdkManagerPath}');
|
||||
processManager.killPid(process.pid);
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
/// Run the Android SDK manager tool in order to accept SDK licenses.
|
||||
static Future<bool> runLicenseManager() async {
|
||||
if (androidSdk == null) {
|
||||
@ -180,14 +205,6 @@ class AndroidWorkflow extends DoctorValidator implements Workflow {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If we can locate Java, then add it to the path used to run the Android SDK manager.
|
||||
final Map<String, String> sdkManagerEnv = <String, String>{};
|
||||
final String javaBinary = _findJavaBinary();
|
||||
if (javaBinary != null) {
|
||||
sdkManagerEnv['PATH'] =
|
||||
fs.path.dirname(javaBinary) + os.pathVarSeparator + platform.environment['PATH'];
|
||||
}
|
||||
|
||||
if (!processManager.canRun(androidSdk.sdkManagerPath))
|
||||
throwToolExit(
|
||||
'Android sdkmanager tool not found.\n'
|
||||
@ -205,7 +222,7 @@ class AndroidWorkflow extends DoctorValidator implements Workflow {
|
||||
|
||||
final Process process = await runCommand(
|
||||
<String>[androidSdk.sdkManagerPath, '--licenses'],
|
||||
environment: sdkManagerEnv,
|
||||
environment: androidSdk.sdkManagerEnv,
|
||||
);
|
||||
|
||||
waitGroup<Null>(<Future<Null>>[
|
||||
|
@ -11,6 +11,8 @@ import 'io.dart';
|
||||
import 'terminal.dart';
|
||||
import 'utils.dart';
|
||||
|
||||
const int kDefaultStatusPadding = 59;
|
||||
|
||||
abstract class Logger {
|
||||
bool get isVerbose => false;
|
||||
|
||||
@ -47,15 +49,10 @@ abstract class Logger {
|
||||
String message, {
|
||||
String progressId,
|
||||
bool expectSlowOperation: false,
|
||||
int progressIndicatorPadding: 52,
|
||||
int progressIndicatorPadding: kDefaultStatusPadding,
|
||||
});
|
||||
}
|
||||
|
||||
class Status {
|
||||
void stop() { }
|
||||
void cancel() { }
|
||||
}
|
||||
|
||||
typedef void _FinishCallback();
|
||||
|
||||
class StdoutLogger extends Logger {
|
||||
@ -108,25 +105,19 @@ class StdoutLogger extends Logger {
|
||||
String message, {
|
||||
String progressId,
|
||||
bool expectSlowOperation: false,
|
||||
int progressIndicatorPadding: 52,
|
||||
int progressIndicatorPadding: 59,
|
||||
}) {
|
||||
if (_status != null) {
|
||||
// Ignore nested progresses; return a no-op status object.
|
||||
return new Status();
|
||||
} else {
|
||||
if (supportsColor) {
|
||||
_status = new _AnsiStatus(
|
||||
message,
|
||||
expectSlowOperation,
|
||||
() { _status = null; },
|
||||
progressIndicatorPadding,
|
||||
);
|
||||
return _status;
|
||||
} else {
|
||||
printStatus(message);
|
||||
return new Status();
|
||||
}
|
||||
return new Status()..start();
|
||||
}
|
||||
if (terminal.supportsColor) {
|
||||
_status = new AnsiStatus(message, expectSlowOperation, () { _status = null; }, progressIndicatorPadding)..start();
|
||||
} else {
|
||||
printStatus(message);
|
||||
_status = new Status()..start();
|
||||
}
|
||||
return _status;
|
||||
}
|
||||
}
|
||||
|
||||
@ -142,6 +133,7 @@ class WindowsStdoutLogger extends StdoutLogger {
|
||||
|
||||
@override
|
||||
void writeToStdOut(String message) {
|
||||
// TODO(jcollins-g): wrong abstraction layer for this, move to [Stdio].
|
||||
stdout.write(message
|
||||
.replaceAll('✗', 'X')
|
||||
.replaceAll('✓', '√')
|
||||
@ -185,7 +177,7 @@ class BufferLogger extends Logger {
|
||||
String message, {
|
||||
String progressId,
|
||||
bool expectSlowOperation: false,
|
||||
int progressIndicatorPadding: 52,
|
||||
int progressIndicatorPadding: kDefaultStatusPadding,
|
||||
}) {
|
||||
printStatus(message);
|
||||
return new Status();
|
||||
@ -235,7 +227,7 @@ class VerboseLogger extends Logger {
|
||||
String message, {
|
||||
String progressId,
|
||||
bool expectSlowOperation: false,
|
||||
int progressIndicatorPadding: 52,
|
||||
int progressIndicatorPadding: kDefaultStatusPadding,
|
||||
}) {
|
||||
printStatus(message);
|
||||
return new Status();
|
||||
@ -280,57 +272,140 @@ enum _LogType {
|
||||
trace
|
||||
}
|
||||
|
||||
class _AnsiStatus extends Status {
|
||||
_AnsiStatus(this.message, this.expectSlowOperation, this.onFinish, int padding) {
|
||||
stopwatch = new Stopwatch()..start();
|
||||
/// A [Status] class begins when start is called, and may produce progress
|
||||
/// information asynchronously.
|
||||
///
|
||||
/// When stop is called, summary information supported by this class is printed.
|
||||
/// If cancel is called, no summary information is displayed.
|
||||
/// The base class displays nothing at all.
|
||||
class Status {
|
||||
Status();
|
||||
|
||||
stdout.write('${message.padRight(padding)} ');
|
||||
stdout.write('${_progress[0]}');
|
||||
bool _isStarted = false;
|
||||
|
||||
factory Status.withSpinner() {
|
||||
if (terminal.supportsColor)
|
||||
return new AnsiSpinner()..start();
|
||||
return new Status()..start();
|
||||
}
|
||||
|
||||
/// Display summary information for this spinner; called by [stop].
|
||||
void summaryInformation() {}
|
||||
|
||||
/// Call to start spinning. Call this method via super at the beginning
|
||||
/// of a subclass [start] method.
|
||||
void start() {
|
||||
_isStarted = true;
|
||||
}
|
||||
|
||||
/// Call to stop spinning and delete the spinner. Print summary information,
|
||||
/// if applicable to the spinner.
|
||||
void stop() {
|
||||
if (_isStarted) {
|
||||
cancel();
|
||||
summaryInformation();
|
||||
}
|
||||
}
|
||||
|
||||
/// Call to cancel the spinner without printing any summary output. Call
|
||||
/// this method via super at the end of a subclass [cancel] method.
|
||||
void cancel() {
|
||||
_isStarted = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// An [AnsiSpinner] is a simple animation that does nothing but implement an
|
||||
/// ASCII spinner. When stopped or canceled, the animation erases itself.
|
||||
class AnsiSpinner extends Status {
|
||||
int ticks = 0;
|
||||
Timer timer;
|
||||
|
||||
static final List<String> _progress = <String>['-', r'\', '|', r'/'];
|
||||
|
||||
void _callback(Timer _) {
|
||||
stdout.write('\b${_progress[ticks++ % _progress.length]}');
|
||||
}
|
||||
|
||||
@override
|
||||
void start() {
|
||||
super.start();
|
||||
stdout.write(' ');
|
||||
_callback(null);
|
||||
timer = new Timer.periodic(const Duration(milliseconds: 100), _callback);
|
||||
}
|
||||
|
||||
static final List<String> _progress = <String>['-', r'\', '|', r'/', '-', r'\', '|', '/'];
|
||||
@override
|
||||
/// Clears the spinner. After cancel, the cursor will be one space right
|
||||
/// of where it was when [start] was called (assuming no other input).
|
||||
void cancel() {
|
||||
if (timer?.isActive == true) {
|
||||
timer.cancel();
|
||||
// Many terminals do not interpret backspace as deleting a character,
|
||||
// but rather just moving the cursor back one.
|
||||
stdout.write('\b \b');
|
||||
}
|
||||
super.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
/// Constructor writes [message] to [stdout] with padding, then starts as an
|
||||
/// [AnsiSpinner]. On [cancel] or [stop], will call [onFinish].
|
||||
/// On [stop], will additionally print out summary information in
|
||||
/// milliseconds if [expectSlowOperation] is false, as seconds otherwise.
|
||||
class AnsiStatus extends AnsiSpinner {
|
||||
AnsiStatus(this.message, this.expectSlowOperation, this.onFinish, this.padding);
|
||||
|
||||
final String message;
|
||||
final bool expectSlowOperation;
|
||||
final _FinishCallback onFinish;
|
||||
final int padding;
|
||||
|
||||
Stopwatch stopwatch;
|
||||
Timer timer;
|
||||
int index = 1;
|
||||
bool live = true;
|
||||
bool _finished = false;
|
||||
|
||||
void _callback(Timer timer) {
|
||||
stdout.write('\b${_progress[index]}');
|
||||
index = ++index % _progress.length;
|
||||
@override
|
||||
/// Writes [message] to [stdout] with padding, then begins spinning.
|
||||
void start() {
|
||||
stopwatch = new Stopwatch()..start();
|
||||
stdout.write('${message.padRight(padding)} ');
|
||||
assert(!_finished);
|
||||
super.start();
|
||||
}
|
||||
|
||||
@override
|
||||
/// Calls onFinish.
|
||||
void stop() {
|
||||
onFinish();
|
||||
|
||||
if (!live)
|
||||
return;
|
||||
live = false;
|
||||
|
||||
if (expectSlowOperation) {
|
||||
print('\b\b\b\b\b${getElapsedAsSeconds(stopwatch.elapsed).padLeft(5)}');
|
||||
} else {
|
||||
print('\b\b\b\b\b${getElapsedAsMilliseconds(stopwatch.elapsed).padLeft(5)}');
|
||||
if (!_finished) {
|
||||
onFinish();
|
||||
_finished = true;
|
||||
super.cancel();
|
||||
summaryInformation();
|
||||
}
|
||||
|
||||
timer.cancel();
|
||||
}
|
||||
|
||||
@override
|
||||
/// Backs up 4 characters and prints a (minimum) 5 character padded time. If
|
||||
/// [expectSlowOperation] is true, the time is in seconds; otherwise,
|
||||
/// milliseconds. Only backs up 4 characters because [super.cancel] backs
|
||||
/// up one.
|
||||
///
|
||||
/// Example: '\b\b\b\b 0.5s', '\b\b\b\b150ms', '\b\b\b\b1600ms'
|
||||
void summaryInformation() {
|
||||
if (expectSlowOperation) {
|
||||
stdout.writeln('\b\b\b\b${getElapsedAsSeconds(stopwatch.elapsed).padLeft(5)}');
|
||||
} else {
|
||||
stdout.writeln('\b\b\b\b${getElapsedAsMilliseconds(stopwatch.elapsed).padLeft(5)}');
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
/// Calls [onFinish].
|
||||
void cancel() {
|
||||
onFinish();
|
||||
|
||||
if (!live)
|
||||
return;
|
||||
live = false;
|
||||
|
||||
print('\b ');
|
||||
timer.cancel();
|
||||
if (!_finished) {
|
||||
onFinish();
|
||||
_finished = true;
|
||||
super.cancel();
|
||||
stdout.write('\n');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -17,6 +17,10 @@ typedef String StringConverter(String string);
|
||||
typedef Future<dynamic> ShutdownHook();
|
||||
|
||||
// TODO(ianh): We have way too many ways to run subprocesses in this project.
|
||||
// Convert most of these into one or more lightweight wrappers around the
|
||||
// [ProcessManager] API using named parameters for the various options.
|
||||
// See [here](https://github.com/flutter/flutter/pull/14535#discussion_r167041161)
|
||||
// for more details.
|
||||
|
||||
/// The stage in which a [ShutdownHook] will be run. All shutdown hooks within
|
||||
/// a given stage will be started in parallel and will be guaranteed to run to
|
||||
|
@ -764,7 +764,7 @@ class NotifyingLogger extends Logger {
|
||||
String message, {
|
||||
String progressId,
|
||||
bool expectSlowOperation: false,
|
||||
int progressIndicatorPadding: 52,
|
||||
int progressIndicatorPadding: kDefaultStatusPadding,
|
||||
}) {
|
||||
printStatus(message);
|
||||
return new Status();
|
||||
@ -906,13 +906,16 @@ class _AppRunLogger extends Logger {
|
||||
}
|
||||
}
|
||||
|
||||
class _AppLoggerStatus implements Status {
|
||||
class _AppLoggerStatus extends Status {
|
||||
_AppLoggerStatus(this.logger, this.id, this.progressId);
|
||||
|
||||
final _AppRunLogger logger;
|
||||
final int id;
|
||||
final String progressId;
|
||||
|
||||
@override
|
||||
void start() {}
|
||||
|
||||
@override
|
||||
void stop() {
|
||||
logger._status = null;
|
||||
|
@ -13,6 +13,7 @@ import 'artifacts.dart';
|
||||
import 'base/common.dart';
|
||||
import 'base/context.dart';
|
||||
import 'base/file_system.dart';
|
||||
import 'base/logger.dart';
|
||||
import 'base/os.dart';
|
||||
import 'base/platform.dart';
|
||||
import 'base/process_manager.dart';
|
||||
@ -27,6 +28,12 @@ import 'vscode/vscode_validator.dart';
|
||||
|
||||
Doctor get doctor => context[Doctor];
|
||||
|
||||
class ValidatorTask {
|
||||
ValidatorTask(this.validator, this.result);
|
||||
final DoctorValidator validator;
|
||||
final Future<ValidationResult> result;
|
||||
}
|
||||
|
||||
class Doctor {
|
||||
List<DoctorValidator> _validators;
|
||||
|
||||
@ -56,6 +63,16 @@ class Doctor {
|
||||
return _validators;
|
||||
}
|
||||
|
||||
/// Return a list of [ValidatorTask] objects and starts validation on all
|
||||
/// objects in [validators].
|
||||
List<ValidatorTask> startValidatorTasks() {
|
||||
final List<ValidatorTask> tasks = <ValidatorTask>[];
|
||||
for (DoctorValidator validator in validators) {
|
||||
tasks.add(new ValidatorTask(validator, validator.validate()));
|
||||
}
|
||||
return tasks;
|
||||
}
|
||||
|
||||
List<Workflow> get workflows {
|
||||
return new List<Workflow>.from(validators.where((DoctorValidator validator) => validator is Workflow));
|
||||
}
|
||||
@ -108,9 +125,14 @@ class Doctor {
|
||||
bool doctorResult = true;
|
||||
int issues = 0;
|
||||
|
||||
for (DoctorValidator validator in validators) {
|
||||
final ValidationResult result = await validator.validate();
|
||||
for (ValidatorTask validatorTask in startValidatorTasks()) {
|
||||
final DoctorValidator validator = validatorTask.validator;
|
||||
final Status status = new Status.withSpinner();
|
||||
await (validatorTask.result).then<void>((_) {
|
||||
status.stop();
|
||||
}).whenComplete(status.cancel);
|
||||
|
||||
final ValidationResult result = await validatorTask.result;
|
||||
if (result.type == ValidationType.missing) {
|
||||
doctorResult = false;
|
||||
}
|
||||
|
@ -366,7 +366,7 @@ Future<XcodeBuildResult> buildXcodeProject({
|
||||
buildSubStatus = logger.startProgress(
|
||||
line,
|
||||
expectSlowOperation: true,
|
||||
progressIndicatorPadding: 45,
|
||||
progressIndicatorPadding: kDefaultStatusPadding - 7,
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -393,7 +393,7 @@ Future<XcodeBuildResult> buildXcodeProject({
|
||||
scriptOutputPipeTempDirectory?.deleteSync(recursive: true);
|
||||
printStatus(
|
||||
'Xcode build done',
|
||||
ansiAlternative: 'Xcode build done'.padRight(53)
|
||||
ansiAlternative: 'Xcode build done'.padRight(kDefaultStatusPadding + 1)
|
||||
+ '${getElapsedAsSeconds(buildStopwatch.elapsed).padLeft(5)}',
|
||||
);
|
||||
|
||||
|
@ -73,7 +73,7 @@ void main() {
|
||||
|
||||
final AndroidSdk sdk = AndroidSdk.locateAndroidSdk();
|
||||
when(processManager.canRun(sdk.sdkManagerPath)).thenReturn(true);
|
||||
when(processManager.runSync(<String>[sdk.sdkManagerPath, '--version']))
|
||||
when(processManager.runSync(<String>[sdk.sdkManagerPath, '--version'], environment: argThat(isNotNull)))
|
||||
.thenReturn(new ProcessResult(1, 0, '26.1.1\n', ''));
|
||||
expect(sdk.sdkManagerVersion, '26.1.1');
|
||||
}, overrides: <Type, Generator>{
|
||||
@ -87,7 +87,7 @@ void main() {
|
||||
|
||||
final AndroidSdk sdk = AndroidSdk.locateAndroidSdk();
|
||||
when(processManager.canRun(sdk.sdkManagerPath)).thenReturn(true);
|
||||
when(processManager.runSync(<String>[sdk.sdkManagerPath, '--version']))
|
||||
when(processManager.runSync(<String>[sdk.sdkManagerPath, '--version'], environment: argThat(isNotNull)))
|
||||
.thenReturn(new ProcessResult(1, 1, '26.1.1\n', 'Mystery error'));
|
||||
expect(() => sdk.sdkManagerVersion, throwsToolExit(exitCode: 1));
|
||||
}, overrides: <Type, Generator>{
|
||||
|
@ -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:file/memory.dart';
|
||||
import 'package:flutter_tools/src/base/file_system.dart';
|
||||
import 'package:flutter_tools/src/base/io.dart';
|
||||
@ -14,7 +16,7 @@ import 'package:test/test.dart';
|
||||
|
||||
import '../src/common.dart';
|
||||
import '../src/context.dart';
|
||||
import '../src/mocks.dart' show MockAndroidSdk, MockProcessManager, MockStdio;
|
||||
import '../src/mocks.dart' show MockAndroidSdk, MockProcess, MockProcessManager, MockStdio;
|
||||
|
||||
void main() {
|
||||
AndroidSdk sdk;
|
||||
@ -25,12 +27,93 @@ void main() {
|
||||
setUp(() {
|
||||
sdk = new MockAndroidSdk();
|
||||
fs = new MemoryFileSystem();
|
||||
fs.directory('/home/me').createSync(recursive: true);
|
||||
processManager = new MockProcessManager();
|
||||
stdio = new MockStdio();
|
||||
});
|
||||
|
||||
MockProcess Function(List<String>) processMetaFactory(List<String> stdout) {
|
||||
final Stream<List<int>> stdoutStream = new Stream<List<int>>.fromIterable(
|
||||
stdout.map((String s) => s.codeUnits));
|
||||
return (List<String> command) => new MockProcess(stdout: stdoutStream);
|
||||
}
|
||||
|
||||
testUsingContext('licensesAccepted handles garbage/no output', () async {
|
||||
MockAndroidSdk.createSdkDirectory();
|
||||
when(sdk.sdkManagerPath).thenReturn('/foo/bar/sdkmanager');
|
||||
final AndroidWorkflow androidWorkflow = new AndroidWorkflow();
|
||||
final LicensesAccepted result = await(androidWorkflow.licensesAccepted);
|
||||
expect(result, equals(LicensesAccepted.unknown));
|
||||
expect(processManager.commands.first, equals('/foo/bar/sdkmanager'));
|
||||
expect(processManager.commands.last, equals('--licenses'));
|
||||
}, overrides: <Type, Generator>{
|
||||
AndroidSdk: () => sdk,
|
||||
FileSystem: () => fs,
|
||||
Platform: () => new FakePlatform()..environment = <String, String>{'HOME': '/home/me'},
|
||||
ProcessManager: () => processManager,
|
||||
Stdio: () => stdio,
|
||||
});
|
||||
|
||||
testUsingContext('licensesAccepted works for all licenses accepted', () async {
|
||||
MockAndroidSdk.createSdkDirectory();
|
||||
when(sdk.sdkManagerPath).thenReturn('/foo/bar/sdkmanager');
|
||||
processManager.processFactory = processMetaFactory(<String>[
|
||||
'[=======================================] 100% Computing updates... ',
|
||||
'All SDK package licenses accepted.'
|
||||
]);
|
||||
|
||||
final AndroidWorkflow androidWorkflow = new AndroidWorkflow();
|
||||
final LicensesAccepted result = await(androidWorkflow.licensesAccepted);
|
||||
expect(result, equals(LicensesAccepted.all));
|
||||
}, overrides: <Type, Generator>{
|
||||
AndroidSdk: () => sdk,
|
||||
FileSystem: () => fs,
|
||||
Platform: () => new FakePlatform()..environment = <String, String>{'HOME': '/home/me'},
|
||||
ProcessManager: () => processManager,
|
||||
Stdio: () => stdio,
|
||||
});
|
||||
|
||||
testUsingContext('licensesAccepted works for some licenses accepted', () async {
|
||||
MockAndroidSdk.createSdkDirectory();
|
||||
when(sdk.sdkManagerPath).thenReturn('/foo/bar/sdkmanager');
|
||||
processManager.processFactory = processMetaFactory(<String>[
|
||||
'[=======================================] 100% Computing updates... ',
|
||||
'2 of 5 SDK package licenses not accepted.',
|
||||
'Review licenses that have not been accepted (y/N)?',
|
||||
]);
|
||||
|
||||
final AndroidWorkflow androidWorkflow = new AndroidWorkflow();
|
||||
final LicensesAccepted result = await(androidWorkflow.licensesAccepted);
|
||||
expect(result, equals(LicensesAccepted.some));
|
||||
}, overrides: <Type, Generator>{
|
||||
AndroidSdk: () => sdk,
|
||||
FileSystem: () => fs,
|
||||
Platform: () => new FakePlatform()..environment = <String, String>{'HOME': '/home/me'},
|
||||
ProcessManager: () => processManager,
|
||||
Stdio: () => stdio,
|
||||
});
|
||||
|
||||
testUsingContext('licensesAccepted works for no licenses accepted', () async {
|
||||
MockAndroidSdk.createSdkDirectory();
|
||||
when(sdk.sdkManagerPath).thenReturn('/foo/bar/sdkmanager');
|
||||
processManager.processFactory = processMetaFactory(<String>[
|
||||
'[=======================================] 100% Computing updates... ',
|
||||
'5 of 5 SDK package licenses not accepted.',
|
||||
'Review licenses that have not been accepted (y/N)?',
|
||||
]);
|
||||
|
||||
final AndroidWorkflow androidWorkflow = new AndroidWorkflow();
|
||||
final LicensesAccepted result = await(androidWorkflow.licensesAccepted);
|
||||
expect(result, equals(LicensesAccepted.none));
|
||||
}, overrides: <Type, Generator>{
|
||||
AndroidSdk: () => sdk,
|
||||
FileSystem: () => fs,
|
||||
Platform: () => new FakePlatform()..environment = <String, String>{'HOME': '/home/me'},
|
||||
ProcessManager: () => processManager,
|
||||
Stdio: () => stdio,
|
||||
});
|
||||
|
||||
testUsingContext('runLicenseManager succeeds for version >= 26', () async {
|
||||
fs.directory('/home/me').createSync(recursive: true);
|
||||
MockAndroidSdk.createSdkDirectory();
|
||||
when(sdk.sdkManagerPath).thenReturn('/foo/bar/sdkmanager');
|
||||
when(sdk.sdkManagerVersion).thenReturn('26.0.0');
|
||||
@ -45,7 +128,6 @@ void main() {
|
||||
});
|
||||
|
||||
testUsingContext('runLicenseManager errors for version < 26', () async {
|
||||
fs.directory('/home/me').createSync(recursive: true);
|
||||
MockAndroidSdk.createSdkDirectory();
|
||||
when(sdk.sdkManagerPath).thenReturn('/foo/bar/sdkmanager');
|
||||
when(sdk.sdkManagerVersion).thenReturn('25.0.0');
|
||||
@ -60,7 +142,6 @@ void main() {
|
||||
});
|
||||
|
||||
testUsingContext('runLicenseManager errors when sdkmanager is not found', () async {
|
||||
fs.directory('/home/me').createSync(recursive: true);
|
||||
MockAndroidSdk.createSdkDirectory();
|
||||
when(sdk.sdkManagerPath).thenReturn('/foo/bar/sdkmanager');
|
||||
processManager.succeed = false;
|
||||
|
@ -2,9 +2,15 @@
|
||||
// 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/io.dart';
|
||||
import 'package:flutter_tools/src/base/logger.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
import '../src/context.dart';
|
||||
import '../src/mocks.dart';
|
||||
|
||||
void main() {
|
||||
group('AppContext', () {
|
||||
test('error', () async {
|
||||
@ -22,4 +28,138 @@ void main() {
|
||||
expect(mockLogger.errorText, matches(r'^\[ (?: {0,2}\+[0-9]{1,3} ms| )\] Helpless!\n$'));
|
||||
});
|
||||
});
|
||||
|
||||
group('Spinners', () {
|
||||
MockStdio mockStdio;
|
||||
AnsiSpinner ansiSpinner;
|
||||
AnsiStatus ansiStatus;
|
||||
int called;
|
||||
final RegExp secondDigits = new RegExp(r'[^\b]\b\b\b\b\b[0-9]+[.][0-9]+(?:s|ms)');
|
||||
|
||||
setUp(() {
|
||||
mockStdio = new MockStdio();
|
||||
ansiSpinner = new AnsiSpinner();
|
||||
called = 0;
|
||||
ansiStatus = new AnsiStatus('Hello world', true, () => called++, 20);
|
||||
});
|
||||
|
||||
List<String> outputLines() => mockStdio.writtenToStdout.join('').split('\n');
|
||||
|
||||
Future<void> doWhile(bool doThis()) async {
|
||||
return Future.doWhile(() async {
|
||||
// Future.doWhile() isn't enough by itself, because the VM never gets
|
||||
// around to scheduling the other tasks for some reason.
|
||||
await new Future<void>.delayed(const Duration(milliseconds: 0));
|
||||
return doThis();
|
||||
});
|
||||
}
|
||||
|
||||
testUsingContext('AnsiSpinner works', () async {
|
||||
ansiSpinner.start();
|
||||
await doWhile(() => ansiSpinner.ticks < 10);
|
||||
List<String> lines = outputLines();
|
||||
expect(lines[0], startsWith(' \b-\b\\\b|\b/\b-\b\\\b|\b/'));
|
||||
expect(lines[0].endsWith('\n'), isFalse);
|
||||
expect(lines.length, equals(1));
|
||||
ansiSpinner.stop();
|
||||
lines = outputLines();
|
||||
expect(lines[0], endsWith('\b \b'));
|
||||
expect(lines.length, equals(1));
|
||||
|
||||
// Verify that stopping multiple times doesn't clear multiple times.
|
||||
ansiSpinner.stop();
|
||||
lines = outputLines();
|
||||
expect(lines[0].endsWith('\b \b '), isFalse);
|
||||
expect(lines.length, equals(1));
|
||||
ansiSpinner.cancel();
|
||||
lines = outputLines();
|
||||
expect(lines[0].endsWith('\b \b '), isFalse);
|
||||
expect(lines.length, equals(1));
|
||||
}, overrides: <Type, Generator>{Stdio: () => mockStdio});
|
||||
|
||||
testUsingContext('AnsiStatus works when cancelled', () async {
|
||||
ansiStatus.start();
|
||||
await doWhile(() => ansiStatus.ticks < 10);
|
||||
List<String> lines = outputLines();
|
||||
expect(lines[0], startsWith('Hello world \b-\b\\\b|\b/\b-\b\\\b|\b/\b-'));
|
||||
expect(lines[0].endsWith('\n'), isFalse);
|
||||
expect(lines.length, equals(1));
|
||||
ansiStatus.cancel();
|
||||
lines = outputLines();
|
||||
expect(lines[0], endsWith('\b \b'));
|
||||
expect(lines.length, equals(2));
|
||||
expect(called, equals(1));
|
||||
ansiStatus.cancel();
|
||||
lines = outputLines();
|
||||
expect(lines[0].endsWith('\b \b\b \b'), isFalse);
|
||||
expect(lines.length, equals(2));
|
||||
expect(called, equals(1));
|
||||
ansiStatus.stop();
|
||||
lines = outputLines();
|
||||
expect(lines[0].endsWith('\b \b\b \b'), isFalse);
|
||||
expect(lines.length, equals(2));
|
||||
expect(called, equals(1));
|
||||
}, overrides: <Type, Generator>{Stdio: () => mockStdio});
|
||||
|
||||
testUsingContext('AnsiStatus works when stopped', () async {
|
||||
ansiStatus.start();
|
||||
await doWhile(() => ansiStatus.ticks < 10);
|
||||
List<String> lines = outputLines();
|
||||
expect(lines[0], startsWith('Hello world \b-\b\\\b|\b/\b-\b\\\b|\b/\b-'));
|
||||
expect(lines.length, equals(1));
|
||||
|
||||
// Verify a stop prints the time.
|
||||
ansiStatus.stop();
|
||||
lines = outputLines();
|
||||
List<Match> matches = secondDigits.allMatches(lines[0]).toList();
|
||||
expect(matches, isNotNull);
|
||||
expect(matches, hasLength(1));
|
||||
Match match = matches.first;
|
||||
expect(lines[0], endsWith(match.group(0)));
|
||||
final String initialTime = match.group(0);
|
||||
expect(called, equals(1));
|
||||
expect(lines.length, equals(2));
|
||||
expect(lines[1], equals(''));
|
||||
|
||||
// Verify stopping more than once generates no additional output.
|
||||
ansiStatus.stop();
|
||||
lines = outputLines();
|
||||
matches = secondDigits.allMatches(lines[0]).toList();
|
||||
expect(matches, hasLength(1));
|
||||
match = matches.first;
|
||||
expect(lines[0], endsWith(initialTime));
|
||||
expect(called, equals(1));
|
||||
expect(lines.length, equals(2));
|
||||
expect(lines[1], equals(''));
|
||||
}, overrides: <Type, Generator>{Stdio: () => mockStdio});
|
||||
|
||||
testUsingContext('AnsiStatus works when cancelled', () async {
|
||||
ansiStatus.start();
|
||||
await doWhile(() => ansiStatus.ticks < 10);
|
||||
List<String> lines = outputLines();
|
||||
expect(lines[0], startsWith('Hello world \b-\b\\\b|\b/\b-\b\\\b|\b/\b-'));
|
||||
expect(lines.length, equals(1));
|
||||
|
||||
// Verify a cancel does _not_ print the time and prints a newline.
|
||||
ansiStatus.cancel();
|
||||
lines = outputLines();
|
||||
List<Match> matches = secondDigits.allMatches(lines[0]).toList();
|
||||
expect(matches, isEmpty);
|
||||
expect(lines[0], endsWith('\b \b'));
|
||||
expect(called, equals(1));
|
||||
// TODO(jcollins-g): Consider having status objects print the newline
|
||||
// when canceled, or never printing a newline at all.
|
||||
expect(lines.length, equals(2));
|
||||
|
||||
// Verifying calling stop after cancel doesn't print anything weird.
|
||||
ansiStatus.stop();
|
||||
lines = outputLines();
|
||||
matches = secondDigits.allMatches(lines[0]).toList();
|
||||
expect(matches, isEmpty);
|
||||
expect(lines[0], endsWith('\b \b'));
|
||||
expect(called, equals(1));
|
||||
expect(lines[0], isNot(endsWith('\b \b\b \b')));
|
||||
expect(lines.length, equals(2));
|
||||
}, overrides: <Type, Generator>{Stdio: () => mockStdio});
|
||||
});
|
||||
}
|
||||
|
@ -380,9 +380,8 @@ void main() {
|
||||
final CommandRunner<Null> runner = createTestCommandRunner(command);
|
||||
|
||||
await runner.run(<String>['create', '--pub', '--offline', projectDir.path]);
|
||||
final List<String> commands = loggingProcessManager.commands;
|
||||
expect(commands, contains(matches(r'dart-sdk[\\/]bin[\\/]pub')));
|
||||
expect(commands, contains('--offline'));
|
||||
expect(loggingProcessManager.commands.first, contains(matches(r'dart-sdk[\\/]bin[\\/]pub')));
|
||||
expect(loggingProcessManager.commands.first, contains('--offline'));
|
||||
},
|
||||
timeout: allowForCreateFlutterProject,
|
||||
overrides: <Type, Generator>{
|
||||
@ -397,9 +396,8 @@ void main() {
|
||||
final CommandRunner<Null> runner = createTestCommandRunner(command);
|
||||
|
||||
await runner.run(<String>['create', '--pub', projectDir.path]);
|
||||
final List<String> commands = loggingProcessManager.commands;
|
||||
expect(commands, contains(matches(r'dart-sdk[\\/]bin[\\/]pub')));
|
||||
expect(commands, isNot(contains('--offline')));
|
||||
expect(loggingProcessManager.commands.first, contains(matches(r'dart-sdk[\\/]bin[\\/]pub')));
|
||||
expect(loggingProcessManager.commands.first, isNot(contains('--offline')));
|
||||
},
|
||||
timeout: allowForCreateFlutterProject,
|
||||
overrides: <Type, Generator>{
|
||||
@ -494,9 +492,9 @@ Future<Null> _runFlutterTest(Directory workingDir, {String target}) async {
|
||||
class MockFlutterVersion extends Mock implements FlutterVersion {}
|
||||
|
||||
/// A ProcessManager that invokes a real process manager, but keeps
|
||||
/// the last commands sent to it.
|
||||
/// track of all commands sent to it.
|
||||
class LoggingProcessManager extends LocalProcessManager {
|
||||
List<String> commands;
|
||||
List<List<String>> commands = <List<String>>[];
|
||||
|
||||
@override
|
||||
Future<Process> start(
|
||||
@ -507,7 +505,7 @@ class LoggingProcessManager extends LocalProcessManager {
|
||||
bool runInShell: false,
|
||||
ProcessStartMode mode: ProcessStartMode.NORMAL,
|
||||
}) {
|
||||
commands = command;
|
||||
commands.add(command);
|
||||
return super.start(
|
||||
command,
|
||||
workingDirectory: workingDirectory,
|
||||
|
@ -4,6 +4,7 @@
|
||||
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter_tools/src/android/android_workflow.dart';
|
||||
import 'package:flutter_tools/src/artifacts.dart';
|
||||
import 'package:flutter_tools/src/base/config.dart';
|
||||
import 'package:flutter_tools/src/base/context.dart';
|
||||
@ -199,6 +200,11 @@ class MockDeviceManager implements DeviceManager {
|
||||
Future<List<String>> getDeviceDiagnostics() async => <String>[];
|
||||
}
|
||||
|
||||
class MockAndroidWorkflowValidator extends AndroidWorkflow {
|
||||
@override
|
||||
Future<LicensesAccepted> get licensesAccepted async => LicensesAccepted.all;
|
||||
}
|
||||
|
||||
class MockDoctor extends Doctor {
|
||||
// True for testing.
|
||||
@override
|
||||
@ -207,6 +213,20 @@ class MockDoctor extends Doctor {
|
||||
// True for testing.
|
||||
@override
|
||||
bool get canLaunchAnything => true;
|
||||
|
||||
@override
|
||||
/// Replaces the android workflow with a version that overrides licensesAccepted,
|
||||
/// to prevent individual tests from having to mock out the process for
|
||||
/// the Doctor.
|
||||
List<DoctorValidator> get validators {
|
||||
final List<DoctorValidator> superValidators = super.validators;
|
||||
return superValidators.map((DoctorValidator v) {
|
||||
if (v is AndroidWorkflow) {
|
||||
return new MockAndroidWorkflowValidator();
|
||||
}
|
||||
return v;
|
||||
}).toList();
|
||||
}
|
||||
}
|
||||
|
||||
class MockSimControl extends Mock implements SimControl {
|
||||
@ -221,6 +241,9 @@ class MockOperatingSystemUtils extends Mock implements OperatingSystemUtils {
|
||||
|
||||
@override
|
||||
String get name => 'fake OS name and version';
|
||||
|
||||
@override
|
||||
String get pathVarSeparator => ';';
|
||||
}
|
||||
|
||||
class MockIOSSimulatorUtils extends Mock implements IOSSimulatorUtils {}
|
||||
|
Loading…
Reference in New Issue
Block a user