Unverified Commit 59fe5d0f authored by jcollins-g's avatar jcollins-g Committed by GitHub

Add android license verification to doctor and some refactoring (#14535)

* Initial version, seems to work

* Unit test for android license checker

* Cleanups

* Windows analyzer wants const.

* Refinements to timeout

* review comments

* Forgot a nit
parent e9163a1a
...@@ -233,7 +233,7 @@ Future<String> _doctorText() async { ...@@ -233,7 +233,7 @@ Future<String> _doctorText() async {
appContext.setVariable(Logger, logger); appContext.setVariable(Logger, logger);
await appContext.runInZone(() => doctor.diagnose()); await appContext.runInZone(() => doctor.diagnose(verbose: true));
return logger.statusText; return logger.statusText;
} catch (error, trace) { } catch (error, trace) {
......
...@@ -12,9 +12,11 @@ import '../base/file_system.dart'; ...@@ -12,9 +12,11 @@ import '../base/file_system.dart';
import '../base/io.dart' show ProcessResult; import '../base/io.dart' show ProcessResult;
import '../base/os.dart'; import '../base/os.dart';
import '../base/platform.dart'; import '../base/platform.dart';
import '../base/process.dart';
import '../base/process_manager.dart'; import '../base/process_manager.dart';
import '../base/version.dart'; import '../base/version.dart';
import '../globals.dart'; import '../globals.dart';
import 'android_studio.dart' as android_studio;
AndroidSdk get androidSdk => context[AndroidSdk]; AndroidSdk get androidSdk => context[AndroidSdk];
...@@ -63,6 +65,9 @@ class AndroidSdk { ...@@ -63,6 +65,9 @@ class AndroidSdk {
_init(); _init();
} }
static const String _kJavaHomeEnvironmentVariable = 'JAVA_HOME';
static const String _kJavaExecutable = 'java';
/// The path to the Android SDK. /// The path to the Android SDK.
final String directory; final String directory;
...@@ -291,6 +296,51 @@ class AndroidSdk { ...@@ -291,6 +296,51 @@ class AndroidSdk {
return fs.path.join(directory, 'tools', 'bin', 'sdkmanager'); 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.
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'];
}
}
return _sdkManagerEnv;
}
/// Returns the version of the Android SDK manager tool or null if not found. /// Returns the version of the Android SDK manager tool or null if not found.
String get sdkManagerVersion { String get sdkManagerVersion {
if (!processManager.canRun(sdkManagerPath)) if (!processManager.canRun(sdkManagerPath))
......
...@@ -3,12 +3,11 @@ ...@@ -3,12 +3,11 @@
// found in the LICENSE file. // found in the LICENSE file.
import 'dart:async'; import 'dart:async';
import 'dart:convert';
import '../base/common.dart'; import '../base/common.dart';
import '../base/context.dart'; import '../base/context.dart';
import '../base/file_system.dart';
import '../base/io.dart'; import '../base/io.dart';
import '../base/os.dart';
import '../base/platform.dart'; import '../base/platform.dart';
import '../base/process.dart'; import '../base/process.dart';
import '../base/process_manager.dart'; import '../base/process_manager.dart';
...@@ -17,10 +16,20 @@ import '../base/version.dart'; ...@@ -17,10 +16,20 @@ import '../base/version.dart';
import '../doctor.dart'; import '../doctor.dart';
import '../globals.dart'; import '../globals.dart';
import 'android_sdk.dart'; import 'android_sdk.dart';
import 'android_studio.dart' as android_studio;
AndroidWorkflow get androidWorkflow => context.putIfAbsent(AndroidWorkflow, () => new AndroidWorkflow()); 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 { class AndroidWorkflow extends DoctorValidator implements Workflow {
AndroidWorkflow() : super('Android toolchain - develop for Android devices'); AndroidWorkflow() : super('Android toolchain - develop for Android devices');
...@@ -33,41 +42,8 @@ class AndroidWorkflow extends DoctorValidator implements Workflow { ...@@ -33,41 +42,8 @@ class AndroidWorkflow extends DoctorValidator implements Workflow {
@override @override
bool get canLaunchDevices => androidSdk != null && androidSdk.validateSdkWellFormed().isEmpty; 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/'; 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 /// Returns false if we cannot determine the Java version or if the version
/// is not compatible. /// is not compatible.
bool _checkJavaVersion(String javaBinary, List<ValidationMessage> messages) { bool _checkJavaVersion(String javaBinary, List<ValidationMessage> messages) {
...@@ -154,7 +130,7 @@ class AndroidWorkflow extends DoctorValidator implements Workflow { ...@@ -154,7 +130,7 @@ class AndroidWorkflow extends DoctorValidator implements Workflow {
} }
// Now check for the JDK. // Now check for the JDK.
final String javaBinary = _findJavaBinary(); final String javaBinary = AndroidSdk.findJavaBinary();
if (javaBinary == null) { if (javaBinary == null) {
messages.add(new ValidationMessage.error( messages.add(new ValidationMessage.error(
'No Java Development Kit (JDK) found; You must have the environment ' 'No Java Development Kit (JDK) found; You must have the environment '
...@@ -169,10 +145,59 @@ class AndroidWorkflow extends DoctorValidator implements Workflow { ...@@ -169,10 +145,59 @@ class AndroidWorkflow extends DoctorValidator implements Workflow {
return new ValidationResult(ValidationType.partial, messages, statusInfo: sdkVersionText); 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. // Success.
return new ValidationResult(ValidationType.installed, messages, statusInfo: sdkVersionText); 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 runDetachedWithIO(<String>[androidSdk.sdkManagerPath, '--licenses']);
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. /// Run the Android SDK manager tool in order to accept SDK licenses.
static Future<bool> runLicenseManager() async { static Future<bool> runLicenseManager() async {
if (androidSdk == null) { if (androidSdk == null) {
...@@ -180,14 +205,6 @@ class AndroidWorkflow extends DoctorValidator implements Workflow { ...@@ -180,14 +205,6 @@ class AndroidWorkflow extends DoctorValidator implements Workflow {
return false; 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)) if (!processManager.canRun(androidSdk.sdkManagerPath))
throwToolExit( throwToolExit(
'Android sdkmanager tool not found.\n' 'Android sdkmanager tool not found.\n'
...@@ -205,7 +222,7 @@ class AndroidWorkflow extends DoctorValidator implements Workflow { ...@@ -205,7 +222,7 @@ class AndroidWorkflow extends DoctorValidator implements Workflow {
final Process process = await runCommand( final Process process = await runCommand(
<String>[androidSdk.sdkManagerPath, '--licenses'], <String>[androidSdk.sdkManagerPath, '--licenses'],
environment: sdkManagerEnv, environment: androidSdk.sdkManagerEnv,
); );
waitGroup<Null>(<Future<Null>>[ waitGroup<Null>(<Future<Null>>[
......
...@@ -43,10 +43,10 @@ abstract class Logger { ...@@ -43,10 +43,10 @@ abstract class Logger {
Status startProgress(String message, { String progressId, bool expectSlowOperation: false }); Status startProgress(String message, { String progressId, bool expectSlowOperation: false });
} }
class Status { /// A [Status] object includes functionality of a [Spinner], but may also display
void stop() { } /// diagnostic information like how long the spinner remained running between
void cancel() { } /// [start] and [stop] (or [cancel]).
} class Status extends Spinner {}
typedef void _FinishCallback(); typedef void _FinishCallback();
...@@ -252,30 +252,67 @@ enum _LogType { ...@@ -252,30 +252,67 @@ enum _LogType {
trace trace
} }
class _AnsiStatus extends Status {
_AnsiStatus(this.message, this.expectSlowOperation, this.onFinish) {
stopwatch = new Stopwatch()..start();
stdout.write('${message.padRight(52)} '); /// A [Spinner] is a simple animation that does nothing but implement an ASCII
stdout.write('${_progress[0]}'); /// spinner. When stopped, the animation erases itself.
class Spinner {
Spinner();
/// Use this factory to generate AnsiSpinner or Spinner as necessary, and
/// start them.
factory Spinner.forContextTerminal() {
if (terminal.supportsColor)
return new AnsiSpinner()..start();
return new Spinner()..start();
}
void start() {}
void stop() {}
void cancel() {}
}
/// Just a spinner, nothing more, nothing less.
class AnsiSpinner extends Spinner {
int index = 0;
bool live = true;
Timer timer;
static final List<String> _progress = <String>['-', r'\', '|', r'/', '-', r'\', '|', '/'];
void _callback(Timer _) {
stdout.write('\b${_progress[index]}');
index = ++index % _progress.length;
}
@override
void start() {
stdout.write(' ');
_callback(null);
timer = new Timer.periodic(const Duration(milliseconds: 100), _callback); timer = new Timer.periodic(const Duration(milliseconds: 100), _callback);
} }
static final List<String> _progress = <String>['-', r'\', '|', r'/', '-', r'\', '|', '/']; @override
void stop() {
if (!live)
return;
live = false;
timer.cancel();
stdout.write('\b');
}
@override
void cancel() => stop();
}
class _AnsiStatus extends Status with AnsiSpinner {
_AnsiStatus(this.message, this.expectSlowOperation, this.onFinish) {
stopwatch = new Stopwatch()..start();
stdout.write('${message.padRight(52)} ');
start();
}
Stopwatch stopwatch;
final String message; final String message;
final bool expectSlowOperation; final bool expectSlowOperation;
final _FinishCallback onFinish; final _FinishCallback onFinish;
Stopwatch stopwatch;
Timer timer;
int index = 1;
bool live = true;
void _callback(Timer timer) {
stdout.write('\b${_progress[index]}');
index = ++index % _progress.length;
}
@override @override
void stop() { void stop() {
...@@ -283,15 +320,13 @@ class _AnsiStatus extends Status { ...@@ -283,15 +320,13 @@ class _AnsiStatus extends Status {
if (!live) if (!live)
return; return;
live = false; super.stop();
if (expectSlowOperation) { if (expectSlowOperation) {
print('\b\b\b\b\b${getElapsedAsSeconds(stopwatch.elapsed).padLeft(5)}'); print('\b\b\b\b\b${getElapsedAsSeconds(stopwatch.elapsed).padLeft(5)}');
} else { } else {
print('\b\b\b\b\b${getElapsedAsMilliseconds(stopwatch.elapsed).padLeft(5)}'); print('\b\b\b\b\b${getElapsedAsMilliseconds(stopwatch.elapsed).padLeft(5)}');
} }
timer.cancel();
} }
@override @override
...@@ -300,9 +335,8 @@ class _AnsiStatus extends Status { ...@@ -300,9 +335,8 @@ class _AnsiStatus extends Status {
if (!live) if (!live)
return; return;
live = false; super.cancel();
print('\b '); print(' ');
timer.cancel();
} }
} }
...@@ -17,6 +17,10 @@ typedef String StringConverter(String string); ...@@ -17,6 +17,10 @@ typedef String StringConverter(String string);
typedef Future<dynamic> ShutdownHook(); typedef Future<dynamic> ShutdownHook();
// TODO(ianh): We have way too many ways to run subprocesses in this project. // 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 /// 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 /// a given stage will be started in parallel and will be guaranteed to run to
...@@ -211,6 +215,16 @@ Future<Process> runDetached(List<String> cmd) { ...@@ -211,6 +215,16 @@ Future<Process> runDetached(List<String> cmd) {
return proc; return proc;
} }
Future<Process> runDetachedWithIO(List<String> cmd, {
Map<String, String> environment
}) async {
_traceCommand(cmd);
return await processManager.start(
cmd,
mode: ProcessStartMode.DETACHED_WITH_STDIO,
);
}
Future<RunResult> runAsync(List<String> cmd, { Future<RunResult> runAsync(List<String> cmd, {
String workingDirectory, String workingDirectory,
bool allowReentrantFlutter: false, bool allowReentrantFlutter: false,
......
...@@ -906,6 +906,9 @@ class _AppLoggerStatus implements Status { ...@@ -906,6 +906,9 @@ class _AppLoggerStatus implements Status {
final int id; final int id;
final String progressId; final String progressId;
@override
void start() {}
@override @override
void stop() { void stop() {
logger._status = null; logger._status = null;
......
...@@ -13,6 +13,7 @@ import 'artifacts.dart'; ...@@ -13,6 +13,7 @@ import 'artifacts.dart';
import 'base/common.dart'; import 'base/common.dart';
import 'base/context.dart'; import 'base/context.dart';
import 'base/file_system.dart'; import 'base/file_system.dart';
import 'base/logger.dart';
import 'base/os.dart'; import 'base/os.dart';
import 'base/platform.dart'; import 'base/platform.dart';
import 'base/process_manager.dart'; import 'base/process_manager.dart';
...@@ -27,6 +28,12 @@ import 'vscode/vscode_validator.dart'; ...@@ -27,6 +28,12 @@ import 'vscode/vscode_validator.dart';
Doctor get doctor => context[Doctor]; Doctor get doctor => context[Doctor];
class ValidatorTask {
ValidatorTask(this.validator, this.result);
final DoctorValidator validator;
final Future<ValidationResult> result;
}
class Doctor { class Doctor {
List<DoctorValidator> _validators; List<DoctorValidator> _validators;
...@@ -56,6 +63,16 @@ class Doctor { ...@@ -56,6 +63,16 @@ class Doctor {
return _validators; 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 { List<Workflow> get workflows {
return new List<Workflow>.from(validators.where((DoctorValidator validator) => validator is Workflow)); return new List<Workflow>.from(validators.where((DoctorValidator validator) => validator is Workflow));
} }
...@@ -108,9 +125,14 @@ class Doctor { ...@@ -108,9 +125,14 @@ class Doctor {
bool doctorResult = true; bool doctorResult = true;
int issues = 0; int issues = 0;
for (DoctorValidator validator in validators) { for (ValidatorTask validatorTask in startValidatorTasks()) {
final ValidationResult result = await validator.validate(); final DoctorValidator validator = validatorTask.validator;
final Spinner status = new Spinner.forContextTerminal();
await (validatorTask.result).then<void>((_) {
status.stop();
}).whenComplete(status.cancel);
final ValidationResult result = await validatorTask.result;
if (result.type == ValidationType.missing) { if (result.type == ValidationType.missing) {
doctorResult = false; doctorResult = false;
} }
......
...@@ -2,6 +2,8 @@ ...@@ -2,6 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
import 'dart:async';
import 'package:file/memory.dart'; import 'package:file/memory.dart';
import 'package:flutter_tools/src/base/file_system.dart'; import 'package:flutter_tools/src/base/file_system.dart';
import 'package:flutter_tools/src/base/io.dart'; import 'package:flutter_tools/src/base/io.dart';
...@@ -14,7 +16,7 @@ import 'package:test/test.dart'; ...@@ -14,7 +16,7 @@ import 'package:test/test.dart';
import '../src/common.dart'; import '../src/common.dart';
import '../src/context.dart'; import '../src/context.dart';
import '../src/mocks.dart' show MockAndroidSdk, MockProcessManager, MockStdio; import '../src/mocks.dart' show MockAndroidSdk, MockProcess, MockProcessManager, MockStdio;
void main() { void main() {
AndroidSdk sdk; AndroidSdk sdk;
...@@ -25,12 +27,93 @@ void main() { ...@@ -25,12 +27,93 @@ void main() {
setUp(() { setUp(() {
sdk = new MockAndroidSdk(); sdk = new MockAndroidSdk();
fs = new MemoryFileSystem(); fs = new MemoryFileSystem();
fs.directory('/home/me').createSync(recursive: true);
processManager = new MockProcessManager(); processManager = new MockProcessManager();
stdio = new MockStdio(); 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 { testUsingContext('runLicenseManager succeeds for version >= 26', () async {
fs.directory('/home/me').createSync(recursive: true);
MockAndroidSdk.createSdkDirectory(); MockAndroidSdk.createSdkDirectory();
when(sdk.sdkManagerPath).thenReturn('/foo/bar/sdkmanager'); when(sdk.sdkManagerPath).thenReturn('/foo/bar/sdkmanager');
when(sdk.sdkManagerVersion).thenReturn('26.0.0'); when(sdk.sdkManagerVersion).thenReturn('26.0.0');
...@@ -45,7 +128,6 @@ void main() { ...@@ -45,7 +128,6 @@ void main() {
}); });
testUsingContext('runLicenseManager errors for version < 26', () async { testUsingContext('runLicenseManager errors for version < 26', () async {
fs.directory('/home/me').createSync(recursive: true);
MockAndroidSdk.createSdkDirectory(); MockAndroidSdk.createSdkDirectory();
when(sdk.sdkManagerPath).thenReturn('/foo/bar/sdkmanager'); when(sdk.sdkManagerPath).thenReturn('/foo/bar/sdkmanager');
when(sdk.sdkManagerVersion).thenReturn('25.0.0'); when(sdk.sdkManagerVersion).thenReturn('25.0.0');
...@@ -60,7 +142,6 @@ void main() { ...@@ -60,7 +142,6 @@ void main() {
}); });
testUsingContext('runLicenseManager errors when sdkmanager is not found', () async { testUsingContext('runLicenseManager errors when sdkmanager is not found', () async {
fs.directory('/home/me').createSync(recursive: true);
MockAndroidSdk.createSdkDirectory(); MockAndroidSdk.createSdkDirectory();
when(sdk.sdkManagerPath).thenReturn('/foo/bar/sdkmanager'); when(sdk.sdkManagerPath).thenReturn('/foo/bar/sdkmanager');
processManager.succeed = false; processManager.succeed = false;
......
...@@ -374,9 +374,8 @@ void main() { ...@@ -374,9 +374,8 @@ void main() {
final CommandRunner<Null> runner = createTestCommandRunner(command); final CommandRunner<Null> runner = createTestCommandRunner(command);
await runner.run(<String>['create', '--pub', '--offline', projectDir.path]); await runner.run(<String>['create', '--pub', '--offline', projectDir.path]);
final List<String> commands = loggingProcessManager.commands; expect(loggingProcessManager.commands.first, contains(matches(r'dart-sdk[\\/]bin[\\/]pub')));
expect(commands, contains(matches(r'dart-sdk[\\/]bin[\\/]pub'))); expect(loggingProcessManager.commands.first, contains('--offline'));
expect(commands, contains('--offline'));
}, },
timeout: allowForCreateFlutterProject, timeout: allowForCreateFlutterProject,
overrides: <Type, Generator>{ overrides: <Type, Generator>{
...@@ -391,9 +390,8 @@ void main() { ...@@ -391,9 +390,8 @@ void main() {
final CommandRunner<Null> runner = createTestCommandRunner(command); final CommandRunner<Null> runner = createTestCommandRunner(command);
await runner.run(<String>['create', '--pub', projectDir.path]); await runner.run(<String>['create', '--pub', projectDir.path]);
final List<String> commands = loggingProcessManager.commands; expect(loggingProcessManager.commands.first, contains(matches(r'dart-sdk[\\/]bin[\\/]pub')));
expect(commands, contains(matches(r'dart-sdk[\\/]bin[\\/]pub'))); expect(loggingProcessManager.commands.first, isNot(contains('--offline')));
expect(commands, isNot(contains('--offline')));
}, },
timeout: allowForCreateFlutterProject, timeout: allowForCreateFlutterProject,
overrides: <Type, Generator>{ overrides: <Type, Generator>{
...@@ -488,9 +486,9 @@ Future<Null> _runFlutterTest(Directory workingDir, {String target}) async { ...@@ -488,9 +486,9 @@ Future<Null> _runFlutterTest(Directory workingDir, {String target}) async {
class MockFlutterVersion extends Mock implements FlutterVersion {} class MockFlutterVersion extends Mock implements FlutterVersion {}
/// A ProcessManager that invokes a real process manager, but keeps /// 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 { class LoggingProcessManager extends LocalProcessManager {
List<String> commands; List<List<String>> commands = <List<String>>[];
@override @override
Future<Process> start( Future<Process> start(
...@@ -501,7 +499,7 @@ class LoggingProcessManager extends LocalProcessManager { ...@@ -501,7 +499,7 @@ class LoggingProcessManager extends LocalProcessManager {
bool runInShell: false, bool runInShell: false,
ProcessStartMode mode: ProcessStartMode.NORMAL, ProcessStartMode mode: ProcessStartMode.NORMAL,
}) { }) {
commands = command; commands.add(command);
return super.start( return super.start(
command, command,
workingDirectory: workingDirectory, workingDirectory: workingDirectory,
......
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