Unverified Commit 9bc533c9 authored by Jonah Williams's avatar Jonah Williams Committed by GitHub

[flutter_tools] do not error flutter doctor on missing AS/intellij plugins (#66782)

In cases where the Intellij/AS plugins are not located, display links to where they can be downloaded but do not surface an error. This should generally reduce confusion about whether the plugins are required for every installed IDE. For example, frequently users may only install AS so that they can install the Android SDK - or they may have multiple copies of Intellij installed.

For example: #66762
parent e8812c40
...@@ -2,26 +2,33 @@ ...@@ -2,26 +2,33 @@
// 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 'package:meta/meta.dart';
import '../base/config.dart';
import '../base/file_system.dart';
import '../base/platform.dart';
import '../base/user_messages.dart'; import '../base/user_messages.dart';
import '../base/version.dart'; import '../base/version.dart';
import '../doctor.dart'; import '../doctor.dart';
import '../globals.dart' as globals;
import '../intellij/intellij.dart'; import '../intellij/intellij.dart';
import 'android_studio.dart'; import 'android_studio.dart';
class AndroidStudioValidator extends DoctorValidator { class AndroidStudioValidator extends DoctorValidator {
AndroidStudioValidator(this._studio) : super('Android Studio'); AndroidStudioValidator(this._studio, { @required FileSystem fileSystem })
: _fileSystem = fileSystem,
super('Android Studio');
final AndroidStudio _studio; final AndroidStudio _studio;
final FileSystem _fileSystem;
static List<DoctorValidator> get allValidators { static List<DoctorValidator> allValidators(Config config, Platform platform, FileSystem fileSystem, UserMessages userMessages) {
final List<AndroidStudio> studios = AndroidStudio.allInstalled(); final List<AndroidStudio> studios = AndroidStudio.allInstalled();
return <DoctorValidator>[ return <DoctorValidator>[
if (studios.isEmpty) if (studios.isEmpty)
NoAndroidStudioValidator() NoAndroidStudioValidator(config: config, platform: platform, userMessages: userMessages)
else else
...studios.map<DoctorValidator>( ...studios.map<DoctorValidator>(
(AndroidStudio studio) => AndroidStudioValidator(studio) (AndroidStudio studio) => AndroidStudioValidator(studio, fileSystem: fileSystem)
), ),
]; ];
} }
...@@ -38,14 +45,20 @@ class AndroidStudioValidator extends DoctorValidator { ...@@ -38,14 +45,20 @@ class AndroidStudioValidator extends DoctorValidator {
userMessages.androidStudioLocation(_studio.directory), userMessages.androidStudioLocation(_studio.directory),
)); ));
final IntelliJPlugins plugins = IntelliJPlugins(_studio.pluginsPath); final IntelliJPlugins plugins = IntelliJPlugins(_studio.pluginsPath, fileSystem: _fileSystem);
plugins.validatePackage( plugins.validatePackage(
messages, messages,
<String>['flutter-intellij', 'flutter-intellij.jar'], <String>['flutter-intellij', 'flutter-intellij.jar'],
'Flutter', 'Flutter',
IntelliJPlugins.kIntellijFlutterPluginUrl,
minVersion: IntelliJPlugins.kMinFlutterPluginVersion, minVersion: IntelliJPlugins.kMinFlutterPluginVersion,
); );
plugins.validatePackage(messages, <String>['Dart'], 'Dart'); plugins.validatePackage(
messages,
<String>['Dart'],
'Dart',
IntelliJPlugins.kIntellijDartPluginUrl,
);
if (_studio.isValid) { if (_studio.isValid) {
type = _hasIssues(messages) type = _hasIssues(messages)
...@@ -74,21 +87,32 @@ class AndroidStudioValidator extends DoctorValidator { ...@@ -74,21 +87,32 @@ class AndroidStudioValidator extends DoctorValidator {
} }
class NoAndroidStudioValidator extends DoctorValidator { class NoAndroidStudioValidator extends DoctorValidator {
NoAndroidStudioValidator() : super('Android Studio'); NoAndroidStudioValidator({
@required Config config,
@required Platform platform,
@required UserMessages userMessages,
}) : _config = config,
_platform = platform,
_userMessages = userMessages,
super('Android Studio');
final Config _config;
final Platform _platform;
final UserMessages _userMessages;
@override @override
Future<ValidationResult> validate() async { Future<ValidationResult> validate() async {
final List<ValidationMessage> messages = <ValidationMessage>[]; final List<ValidationMessage> messages = <ValidationMessage>[];
final String cfgAndroidStudio = globals.config.getValue( final String cfgAndroidStudio = _config.getValue(
'android-studio-dir', 'android-studio-dir',
) as String; ) as String;
if (cfgAndroidStudio != null) { if (cfgAndroidStudio != null) {
messages.add(ValidationMessage.error( messages.add(ValidationMessage.error(
userMessages.androidStudioMissing(cfgAndroidStudio), _userMessages.androidStudioMissing(cfgAndroidStudio),
)); ));
} }
messages.add(ValidationMessage(userMessages.androidStudioInstallation(globals.platform))); messages.add(ValidationMessage(_userMessages.androidStudioInstallation(_platform)));
return ValidationResult( return ValidationResult(
ValidationType.notAvailable, ValidationType.notAvailable,
......
...@@ -11,6 +11,7 @@ import 'base/async_guard.dart'; ...@@ -11,6 +11,7 @@ import 'base/async_guard.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/logger.dart';
import 'base/platform.dart';
import 'base/process.dart'; import 'base/process.dart';
import 'base/terminal.dart'; import 'base/terminal.dart';
import 'base/user_messages.dart'; import 'base/user_messages.dart';
...@@ -75,8 +76,8 @@ class _DefaultDoctorValidatorsProvider implements DoctorValidatorsProvider { ...@@ -75,8 +76,8 @@ class _DefaultDoctorValidatorsProvider implements DoctorValidatorsProvider {
} }
final List<DoctorValidator> ideValidators = <DoctorValidator>[ final List<DoctorValidator> ideValidators = <DoctorValidator>[
...AndroidStudioValidator.allValidators, ...AndroidStudioValidator.allValidators(globals.config, globals.platform, globals.fs, globals.userMessages),
...IntelliJValidator.installedValidators, ...IntelliJValidator.installedValidators(globals.fs, globals.platform),
...VsCodeValidator.installedValidators, ...VsCodeValidator.installedValidators,
]; ];
final ProxyValidator proxyValidator = ProxyValidator(platform: globals.platform); final ProxyValidator proxyValidator = ProxyValidator(platform: globals.platform);
...@@ -342,6 +343,9 @@ class Doctor { ...@@ -342,6 +343,9 @@ class Doctor {
hangingIndent = 0; hangingIndent = 0;
indent = 6; indent = 6;
} }
if (message.contextUrl != null) {
_logger.printStatus('🔨 ${message.contextUrl}', hangingIndent: hangingIndent, indent: indent, emphasis: true);
}
} }
} }
if (verbose) { if (verbose) {
...@@ -573,16 +577,40 @@ class ValidationResult { ...@@ -573,16 +577,40 @@ class ValidationResult {
} }
} }
/// A status line for the flutter doctor validation to display.
///
/// The [message] is required and represents either an informational statement
/// about the particular doctor validation that passed, or more context
/// on the cause and/or solution to the validation failure.
@immutable @immutable
class ValidationMessage { class ValidationMessage {
const ValidationMessage(this.message) : type = ValidationMessageType.information; /// Create a validation message with information for a passing validatior.
const ValidationMessage.error(this.message) : type = ValidationMessageType.error; ///
const ValidationMessage.hint(this.message) : type = ValidationMessageType.hint; /// By default this is not displayed unless the doctor is run in
/// verbose mode.
///
/// The [contextUrl] may be supplied to link to external resources. This
/// is displayed after the informative message in verbose modes.
const ValidationMessage(this.message, {this.contextUrl}) : type = ValidationMessageType.information;
/// Create a validation message with information for a failing validator.
const ValidationMessage.error(this.message)
: type = ValidationMessageType.error,
contextUrl = null;
/// Create a validation message with information for a partially failing
/// validator.
const ValidationMessage.hint(this.message)
: type = ValidationMessageType.hint,
contextUrl = null;
final ValidationMessageType type; final ValidationMessageType type;
final String contextUrl;
final String message;
bool get isError => type == ValidationMessageType.error; bool get isError => type == ValidationMessageType.error;
bool get isHint => type == ValidationMessageType.hint; bool get isHint => type == ValidationMessageType.hint;
final String message;
String get indicator { String get indicator {
switch (type) { switch (type) {
...@@ -613,16 +641,14 @@ class ValidationMessage { ...@@ -613,16 +641,14 @@ class ValidationMessage {
@override @override
bool operator ==(Object other) { bool operator ==(Object other) {
if (other.runtimeType != runtimeType) {
return false;
}
return other is ValidationMessage return other is ValidationMessage
&& other.message == message && other.message == message
&& other.type == type; && other.type == type
&& other.contextUrl == contextUrl;
} }
@override @override
int get hashCode => type.hashCode ^ message.hashCode; int get hashCode => type.hashCode ^ message.hashCode ^ contextUrl.hashCode;
} }
class FlutterValidator extends DoctorValidator { class FlutterValidator extends DoctorValidator {
...@@ -709,10 +735,15 @@ class NoIdeValidator extends DoctorValidator { ...@@ -709,10 +735,15 @@ class NoIdeValidator extends DoctorValidator {
} }
} }
/// A doctor validator for both Intellij and Android Studio.
abstract class IntelliJValidator extends DoctorValidator { abstract class IntelliJValidator extends DoctorValidator {
IntelliJValidator(String title, this.installPath) : super(title); IntelliJValidator(String title, this.installPath, {
@required FileSystem fileSystem,
}) : _fileSystem = fileSystem,
super(title);
final String installPath; final String installPath;
final FileSystem _fileSystem;
String get version; String get version;
String get pluginsPath; String get pluginsPath;
...@@ -724,12 +755,12 @@ abstract class IntelliJValidator extends DoctorValidator { ...@@ -724,12 +755,12 @@ abstract class IntelliJValidator extends DoctorValidator {
static final Version kMinIdeaVersion = Version(2017, 1, 0); static final Version kMinIdeaVersion = Version(2017, 1, 0);
static Iterable<DoctorValidator> get installedValidators { static Iterable<DoctorValidator> installedValidators(FileSystem fileSystem, Platform platform) {
if (globals.platform.isLinux || globals.platform.isWindows) { if (platform.isLinux || platform.isWindows) {
return IntelliJValidatorOnLinuxAndWindows.installed; return IntelliJValidatorOnLinuxAndWindows.installed(fileSystem);
} }
if (globals.platform.isMacOS) { if (platform.isMacOS) {
return IntelliJValidatorOnMac.installed; return IntelliJValidatorOnMac.installed(fileSystem);
} }
return <DoctorValidator>[]; return <DoctorValidator>[];
} }
...@@ -743,10 +774,20 @@ abstract class IntelliJValidator extends DoctorValidator { ...@@ -743,10 +774,20 @@ abstract class IntelliJValidator extends DoctorValidator {
} else { } else {
messages.add(ValidationMessage(userMessages.intellijLocation(installPath))); messages.add(ValidationMessage(userMessages.intellijLocation(installPath)));
final IntelliJPlugins plugins = IntelliJPlugins(pluginsPath); final IntelliJPlugins plugins = IntelliJPlugins(pluginsPath, fileSystem: _fileSystem);
plugins.validatePackage(messages, <String>['flutter-intellij', 'flutter-intellij.jar'], plugins.validatePackage(
'Flutter', minVersion: IntelliJPlugins.kMinFlutterPluginVersion); messages,
plugins.validatePackage(messages, <String>['Dart'], 'Dart'); <String>['flutter-intellij', 'flutter-intellij.jar'],
'Flutter',
IntelliJPlugins.kIntellijFlutterPluginUrl,
minVersion: IntelliJPlugins.kMinFlutterPluginVersion,
);
plugins.validatePackage(
messages,
<String>['Dart'],
'Dart',
IntelliJPlugins.kIntellijDartPluginUrl,
);
if (_hasIssues(messages)) { if (_hasIssues(messages)) {
messages.add(ValidationMessage(userMessages.intellijPluginInfo)); messages.add(ValidationMessage(userMessages.intellijPluginInfo));
...@@ -758,7 +799,8 @@ abstract class IntelliJValidator extends DoctorValidator { ...@@ -758,7 +799,8 @@ abstract class IntelliJValidator extends DoctorValidator {
return ValidationResult( return ValidationResult(
_hasIssues(messages) ? ValidationType.partial : ValidationType.installed, _hasIssues(messages) ? ValidationType.partial : ValidationType.installed,
messages, messages,
statusInfo: userMessages.intellijStatusInfo(version)); statusInfo: userMessages.intellijStatusInfo(version),
);
} }
bool _hasIssues(List<ValidationMessage> messages) { bool _hasIssues(List<ValidationMessage> messages) {
...@@ -782,8 +824,11 @@ abstract class IntelliJValidator extends DoctorValidator { ...@@ -782,8 +824,11 @@ abstract class IntelliJValidator extends DoctorValidator {
} }
} }
/// A linux and windows specific implementation of the intellij validator.
class IntelliJValidatorOnLinuxAndWindows extends IntelliJValidator { class IntelliJValidatorOnLinuxAndWindows extends IntelliJValidator {
IntelliJValidatorOnLinuxAndWindows(String title, this.version, String installPath, this.pluginsPath) : super(title, installPath); IntelliJValidatorOnLinuxAndWindows(String title, this.version, String installPath, this.pluginsPath, {
@required FileSystem fileSystem,
}) : super(title, installPath, fileSystem: fileSystem);
@override @override
final String version; final String version;
...@@ -791,7 +836,7 @@ class IntelliJValidatorOnLinuxAndWindows extends IntelliJValidator { ...@@ -791,7 +836,7 @@ class IntelliJValidatorOnLinuxAndWindows extends IntelliJValidator {
@override @override
final String pluginsPath; final String pluginsPath;
static Iterable<DoctorValidator> get installed { static Iterable<DoctorValidator> installed(FileSystem fileSystem) {
final List<DoctorValidator> validators = <DoctorValidator>[]; final List<DoctorValidator> validators = <DoctorValidator>[];
if (globals.fsUtils.homeDirPath == null) { if (globals.fsUtils.homeDirPath == null) {
return validators; return validators;
...@@ -799,7 +844,7 @@ class IntelliJValidatorOnLinuxAndWindows extends IntelliJValidator { ...@@ -799,7 +844,7 @@ class IntelliJValidatorOnLinuxAndWindows extends IntelliJValidator {
void addValidator(String title, String version, String installPath, String pluginsPath) { void addValidator(String title, String version, String installPath, String pluginsPath) {
final IntelliJValidatorOnLinuxAndWindows validator = final IntelliJValidatorOnLinuxAndWindows validator =
IntelliJValidatorOnLinuxAndWindows(title, version, installPath, pluginsPath); IntelliJValidatorOnLinuxAndWindows(title, version, installPath, pluginsPath, fileSystem: fileSystem);
for (int index = 0; index < validators.length; ++index) { for (int index = 0; index < validators.length; ++index) {
final DoctorValidator other = validators[index]; final DoctorValidator other = validators[index];
if (other is IntelliJValidatorOnLinuxAndWindows && validator.installPath == other.installPath) { if (other is IntelliJValidatorOnLinuxAndWindows && validator.installPath == other.installPath) {
...@@ -835,8 +880,11 @@ class IntelliJValidatorOnLinuxAndWindows extends IntelliJValidator { ...@@ -835,8 +880,11 @@ class IntelliJValidatorOnLinuxAndWindows extends IntelliJValidator {
} }
} }
/// A macOS specific implementation of the intellij validator.
class IntelliJValidatorOnMac extends IntelliJValidator { class IntelliJValidatorOnMac extends IntelliJValidator {
IntelliJValidatorOnMac(String title, this.id, String installPath) : super(title, installPath); IntelliJValidatorOnMac(String title, this.id, String installPath, {
@required FileSystem fileSystem,
}) : super(title, installPath, fileSystem: fileSystem);
final String id; final String id;
...@@ -846,26 +894,26 @@ class IntelliJValidatorOnMac extends IntelliJValidator { ...@@ -846,26 +894,26 @@ class IntelliJValidatorOnMac extends IntelliJValidator {
'IntelliJ IDEA CE.app': 'IdeaIC', 'IntelliJ IDEA CE.app': 'IdeaIC',
}; };
static Iterable<DoctorValidator> get installed { static Iterable<DoctorValidator> installed(FileSystem fileSystem) {
final List<DoctorValidator> validators = <DoctorValidator>[]; final List<DoctorValidator> validators = <DoctorValidator>[];
final List<String> installPaths = <String>[ final List<String> installPaths = <String>[
'/Applications', '/Applications',
globals.fs.path.join(globals.fsUtils.homeDirPath, 'Applications'), fileSystem.path.join(globals.fsUtils.homeDirPath, 'Applications'),
]; ];
void checkForIntelliJ(Directory dir) { void checkForIntelliJ(Directory dir) {
final String name = globals.fs.path.basename(dir.path); final String name = fileSystem.path.basename(dir.path);
_dirNameToId.forEach((String dirName, String id) { _dirNameToId.forEach((String dirName, String id) {
if (name == dirName) { if (name == dirName) {
final String title = IntelliJValidator._idToTitle[id]; final String title = IntelliJValidator._idToTitle[id];
validators.add(IntelliJValidatorOnMac(title, id, dir.path)); validators.add(IntelliJValidatorOnMac(title, id, dir.path, fileSystem: fileSystem));
} }
}); });
} }
try { try {
final Iterable<Directory> installDirs = installPaths final Iterable<Directory> installDirs = installPaths
.map<Directory>((String installPath) => globals.fs.directory(installPath)) .map(fileSystem.directory)
.map<List<FileSystemEntity>>((Directory dir) => dir.existsSync() ? dir.listSync() : <FileSystemEntity>[]) .map<List<FileSystemEntity>>((Directory dir) => dir.existsSync() ? dir.listSync() : <FileSystemEntity>[])
.expand<FileSystemEntity>((List<FileSystemEntity> mappedDirs) => mappedDirs) .expand<FileSystemEntity>((List<FileSystemEntity> mappedDirs) => mappedDirs)
.whereType<Directory>(); .whereType<Directory>();
...@@ -892,7 +940,7 @@ class IntelliJValidatorOnMac extends IntelliJValidator { ...@@ -892,7 +940,7 @@ class IntelliJValidatorOnMac extends IntelliJValidator {
@visibleForTesting @visibleForTesting
String get plistFile { String get plistFile {
_plistFile ??= globals.fs.path.join(installPath, 'Contents', 'Info.plist'); _plistFile ??= _fileSystem.path.join(installPath, 'Contents', 'Info.plist');
return _plistFile; return _plistFile;
} }
String _plistFile; String _plistFile;
......
...@@ -3,23 +3,30 @@ ...@@ -3,23 +3,30 @@
// found in the LICENSE file. // found in the LICENSE file.
import 'package:archive/archive.dart'; import 'package:archive/archive.dart';
import 'package:meta/meta.dart';
import '../base/file_system.dart';
import '../base/version.dart'; import '../base/version.dart';
import '../convert.dart'; import '../convert.dart';
import '../doctor.dart'; import '../doctor.dart';
import '../globals.dart' as globals;
class IntelliJPlugins { class IntelliJPlugins {
IntelliJPlugins(this.pluginsPath); IntelliJPlugins(this.pluginsPath, {
@required FileSystem fileSystem
}) : _fileSystem = fileSystem;
final FileSystem _fileSystem;
final String pluginsPath; final String pluginsPath;
static final Version kMinFlutterPluginVersion = Version(16, 0, 0); static final Version kMinFlutterPluginVersion = Version(16, 0, 0);
static const String kIntellijDartPluginUrl = 'https://plugins.jetbrains.com/plugin/6351-dart';
static const String kIntellijFlutterPluginUrl = 'https://plugins.jetbrains.com/plugin/9212-flutter';
void validatePackage( void validatePackage(
List<ValidationMessage> messages, List<ValidationMessage> messages,
List<String> packageNames, List<String> packageNames,
String title, { String title,
String url, {
Version minVersion, Version minVersion,
}) { }) {
for (final String packageName in packageNames) { for (final String packageName in packageNames) {
...@@ -31,36 +38,36 @@ class IntelliJPlugins { ...@@ -31,36 +38,36 @@ class IntelliJPlugins {
final Version version = Version.parse(versionText); final Version version = Version.parse(versionText);
if (version != null && minVersion != null && version < minVersion) { if (version != null && minVersion != null && version < minVersion) {
messages.add(ValidationMessage.error( messages.add(ValidationMessage.error(
'$title plugin version $versionText - the recommended minimum version is $minVersion')); '$title plugin version $versionText - the recommended minimum version is $minVersion'),
);
} else { } else {
messages.add(ValidationMessage( messages.add(ValidationMessage(
'$title plugin ${version != null ? "version $version" : "installed"}')); '$title plugin ${version != null ? "version $version" : "installed"}'),
);
} }
return; return;
} }
messages.add(ValidationMessage(
messages.add(ValidationMessage.error( '$title plugin can be installed from:',
'$title plugin not installed; this adds $title specific functionality.')); contextUrl: url,
));
} }
bool _hasPackage(String packageName) { bool _hasPackage(String packageName) {
final String packagePath = globals.fs.path.join(pluginsPath, packageName); final String packagePath = _fileSystem.path.join(pluginsPath, packageName);
if (packageName.endsWith('.jar')) { if (packageName.endsWith('.jar')) {
return globals.fs.isFileSync(packagePath); return _fileSystem.isFileSync(packagePath);
} }
return globals.fs.isDirectorySync(packagePath); return _fileSystem.isDirectorySync(packagePath);
} }
String _readPackageVersion(String packageName) { String _readPackageVersion(String packageName) {
final String jarPath = packageName.endsWith('.jar') final String jarPath = packageName.endsWith('.jar')
? globals.fs.path.join(pluginsPath, packageName) ? _fileSystem.path.join(pluginsPath, packageName)
: globals.fs.path.join(pluginsPath, packageName, 'lib', '$packageName.jar'); : _fileSystem.path.join(pluginsPath, packageName, 'lib', '$packageName.jar');
// TODO(danrubel): look for a better way to extract a single 2K file from the zip
// rather than reading the entire file into memory.
try { try {
final Archive archive = final Archive archive =
ZipDecoder().decodeBytes(globals.fs.file(jarPath).readAsBytesSync()); ZipDecoder().decodeBytes(_fileSystem.file(jarPath).readAsBytesSync());
final ArchiveFile file = archive.findFile('META-INF/plugin.xml'); final ArchiveFile file = archive.findFile('META-INF/plugin.xml');
final String content = utf8.decode(file.content as List<int>); final String content = utf8.decode(file.content as List<int>);
const String versionStartTag = '<version>'; const String versionStartTag = '<version>';
......
...@@ -27,6 +27,7 @@ import 'package:flutter_tools/src/version.dart'; ...@@ -27,6 +27,7 @@ import 'package:flutter_tools/src/version.dart';
import 'package:flutter_tools/src/vscode/vscode.dart'; import 'package:flutter_tools/src/vscode/vscode.dart';
import 'package:flutter_tools/src/vscode/vscode_validator.dart'; import 'package:flutter_tools/src/vscode/vscode_validator.dart';
import 'package:flutter_tools/src/web/workflow.dart'; import 'package:flutter_tools/src/web/workflow.dart';
import 'package:meta/meta.dart';
import 'package:mockito/mockito.dart'; import 'package:mockito/mockito.dart';
import 'package:process/process.dart'; import 'package:process/process.dart';
import 'package:fake_async/fake_async.dart'; import 'package:fake_async/fake_async.dart';
...@@ -61,6 +62,16 @@ void main() { ...@@ -61,6 +62,16 @@ void main() {
logger = BufferLogger.test(); logger = BufferLogger.test();
}); });
testWithoutContext('ValidationMessage equality and hashCode includes contextUrl', () {
const ValidationMessage messageA = ValidationMessage('ab', contextUrl: 'a');
const ValidationMessage messageB = ValidationMessage('ab', contextUrl: 'b');
expect(messageB, isNot(messageA));
expect(messageB.hashCode, isNot(messageA.hashCode));
expect(messageA, isNot(messageB));
expect(messageA.hashCode, isNot(messageB.hashCode));
});
group('doctor', () { group('doctor', () {
MockPlistParser mockPlistParser; MockPlistParser mockPlistParser;
MemoryFileSystem fileSystem; MemoryFileSystem fileSystem;
...@@ -72,7 +83,8 @@ void main() { ...@@ -72,7 +83,8 @@ void main() {
testUsingContext('intellij validator', () async { testUsingContext('intellij validator', () async {
const String installPath = '/path/to/intelliJ'; const String installPath = '/path/to/intelliJ';
final ValidationResult result = await IntelliJValidatorTestTarget('Test', installPath).validate(); // Uses real filesystem
final ValidationResult result = await IntelliJValidatorTestTarget('Test', installPath, fileSystem: globals.fs).validate();
expect(result.type, ValidationType.partial); expect(result.type, ValidationType.partial);
expect(result.statusInfo, 'version test.test.test'); expect(result.statusInfo, 'version test.test.test');
expect(result.messages, hasLength(4)); expect(result.messages, hasLength(4));
...@@ -96,7 +108,7 @@ void main() { ...@@ -96,7 +108,7 @@ void main() {
final Directory pluginsDirectory = fileSystem.directory('/foo/bar/Library/Application Support/JetBrains/TestID2020.10/plugins') final Directory pluginsDirectory = fileSystem.directory('/foo/bar/Library/Application Support/JetBrains/TestID2020.10/plugins')
..createSync(recursive: true); ..createSync(recursive: true);
final IntelliJValidatorOnMac validator = IntelliJValidatorOnMac('Test', 'TestID', '/path/to/app'); final IntelliJValidatorOnMac validator = IntelliJValidatorOnMac('Test', 'TestID', '/path/to/app', fileSystem: fileSystem);
expect(validator.plistFile, '/path/to/app/Contents/Info.plist'); expect(validator.plistFile, '/path/to/app/Contents/Info.plist');
expect(validator.pluginsPath, pluginsDirectory.path); expect(validator.pluginsPath, pluginsDirectory.path);
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
...@@ -113,7 +125,7 @@ void main() { ...@@ -113,7 +125,7 @@ void main() {
testUsingContext('legacy intellij plugins path checking on mac', () async { testUsingContext('legacy intellij plugins path checking on mac', () async {
when(mockPlistParser.getValueFromFile(any, PlistParser.kCFBundleShortVersionStringKey)).thenReturn('2020.10'); when(mockPlistParser.getValueFromFile(any, PlistParser.kCFBundleShortVersionStringKey)).thenReturn('2020.10');
final IntelliJValidatorOnMac validator = IntelliJValidatorOnMac('Test', 'TestID', '/foo'); final IntelliJValidatorOnMac validator = IntelliJValidatorOnMac('Test', 'TestID', '/foo', fileSystem: fileSystem);
expect(validator.pluginsPath, '/foo/bar/Library/Application Support/TestID2020.10'); expect(validator.pluginsPath, '/foo/bar/Library/Application Support/TestID2020.10');
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
Platform: () => macPlatform, Platform: () => macPlatform,
...@@ -129,7 +141,7 @@ void main() { ...@@ -129,7 +141,7 @@ void main() {
testUsingContext('intellij plugins path checking on mac with override', () async { testUsingContext('intellij plugins path checking on mac with override', () async {
when(mockPlistParser.getValueFromFile(any, 'JetBrainsToolboxApp')).thenReturn('/path/to/JetBrainsToolboxApp'); when(mockPlistParser.getValueFromFile(any, 'JetBrainsToolboxApp')).thenReturn('/path/to/JetBrainsToolboxApp');
final IntelliJValidatorOnMac validator = IntelliJValidatorOnMac('Test', 'TestID', '/foo'); final IntelliJValidatorOnMac validator = IntelliJValidatorOnMac('Test', 'TestID', '/foo', fileSystem: fileSystem);
expect(validator.pluginsPath, '/path/to/JetBrainsToolboxApp.plugins'); expect(validator.pluginsPath, '/path/to/JetBrainsToolboxApp.plugins');
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
PlistParser: () => mockPlistParser, PlistParser: () => mockPlistParser,
...@@ -873,8 +885,10 @@ class NoOpDoctor implements Doctor { ...@@ -873,8 +885,10 @@ class NoOpDoctor implements Doctor {
class MockUsage extends Mock implements Usage {} class MockUsage extends Mock implements Usage {}
class IntelliJValidatorTestTarget extends IntelliJValidator { class IntelliJValidatorTestTarget extends IntelliJValidator {
IntelliJValidatorTestTarget(String title, String installPath) : super(title, installPath); IntelliJValidatorTestTarget(String title, String installPath, {@required FileSystem fileSystem})
: super(title, installPath, fileSystem: fileSystem);
// Warning: requires real test data.
@override @override
String get pluginsPath => globals.fs.path.join('test', 'data', 'intellij', 'plugins'); String get pluginsPath => globals.fs.path.join('test', 'data', 'intellij', 'plugins');
......
...@@ -4,9 +4,12 @@ ...@@ -4,9 +4,12 @@
import 'package:file/memory.dart'; import 'package:file/memory.dart';
import 'package:flutter_tools/src/android/android_studio_validator.dart'; import 'package:flutter_tools/src/android/android_studio_validator.dart';
import 'package:flutter_tools/src/base/config.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';
import 'package:flutter_tools/src/base/logger.dart';
import 'package:flutter_tools/src/base/platform.dart'; import 'package:flutter_tools/src/base/platform.dart';
import 'package:flutter_tools/src/base/user_messages.dart';
import 'package:flutter_tools/src/doctor.dart'; import 'package:flutter_tools/src/doctor.dart';
import 'package:flutter_tools/src/globals.dart' as globals; import 'package:flutter_tools/src/globals.dart' as globals;
import 'package:mockito/mockito.dart'; import 'package:mockito/mockito.dart';
...@@ -29,13 +32,19 @@ void main() { ...@@ -29,13 +32,19 @@ void main() {
fileSystem = MemoryFileSystem.test(); fileSystem = MemoryFileSystem.test();
}); });
group('NoAndroidStudioValidator', () { testWithoutContext('NoAndroidStudioValidator shows Android Studio as "not available" when not available.', () async {
testUsingContext('shows Android Studio as "not available" when not available.', () async { final Config config = Config.test(
final NoAndroidStudioValidator validator = NoAndroidStudioValidator(); 'test',
directory: fileSystem.currentDirectory,
logger: BufferLogger.test(),
);
final NoAndroidStudioValidator validator = NoAndroidStudioValidator(
config: config,
platform: linuxPlatform,
userMessages: UserMessages(),
);
expect((await validator.validate()).type, equals(ValidationType.notAvailable)); expect((await validator.validate()).type, equals(ValidationType.notAvailable));
}, overrides: <Type, Generator>{
Platform: () => linuxPlatform,
});
}); });
testUsingContext('AndroidStudioValidator gives doctor error on java crash', () async { testUsingContext('AndroidStudioValidator gives doctor error on java crash', () async {
...@@ -53,7 +62,7 @@ void main() { ...@@ -53,7 +62,7 @@ void main() {
// This checks that running the validator doesn't throw an unhandled // This checks that running the validator doesn't throw an unhandled
// exception and that the ProcessException makes it into the error // exception and that the ProcessException makes it into the error
// message list. // message list.
for (final DoctorValidator validator in AndroidStudioValidator.allValidators) { for (final DoctorValidator validator in AndroidStudioValidator.allValidators(globals.config, globals.platform, globals.fs, globals.userMessages)) {
final ValidationResult result = await validator.validate(); final ValidationResult result = await validator.validate();
expect(result.messages.where((ValidationMessage message) { expect(result.messages.where((ValidationMessage message) {
return message.isError && message.message.contains('ProcessException'); return message.isError && message.message.contains('ProcessException');
......
...@@ -10,56 +10,54 @@ import 'package:file/memory.dart'; ...@@ -10,56 +10,54 @@ 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/doctor.dart'; import 'package:flutter_tools/src/doctor.dart';
import 'package:flutter_tools/src/intellij/intellij.dart'; import 'package:flutter_tools/src/intellij/intellij.dart';
import 'package:flutter_tools/src/globals.dart' as globals;
import '../../src/common.dart'; import '../../src/common.dart';
import '../../src/context.dart';
void main() { void main() {
FileSystem fs; FileSystem fileSystem;
void writeFileCreatingDirectories(String path, List<int> bytes) { void writeFileCreatingDirectories(String path, List<int> bytes) {
final File file = globals.fs.file(path); final File file = fileSystem.file(path);
file.parent.createSync(recursive: true); file.parent.createSync(recursive: true);
file.writeAsBytesSync(bytes); file.writeAsBytesSync(bytes);
} }
setUp(() { setUp(() {
fs = MemoryFileSystem(); fileSystem = MemoryFileSystem.test();
}); });
group('IntelliJ', () { testWithoutContext('IntelliJPlugins found', () async {
group('plugins', () { final IntelliJPlugins plugins = IntelliJPlugins(_kPluginsPath, fileSystem: fileSystem);
testUsingContext('found', () async {
final IntelliJPlugins plugins = IntelliJPlugins(_kPluginsPath);
final Archive dartJarArchive = final Archive dartJarArchive =
buildSingleFileArchive('META-INF/plugin.xml', r''' buildSingleFileArchive('META-INF/plugin.xml', r'''
<idea-plugin version="2"> <idea-plugin version="2">
<name>Dart</name> <name>Dart</name>
<version>162.2485</version> <version>162.2485</version>
</idea-plugin> </idea-plugin>
'''); ''');
writeFileCreatingDirectories( writeFileCreatingDirectories(
globals.fs.path.join(_kPluginsPath, 'Dart', 'lib', 'Dart.jar'), fileSystem.path.join(_kPluginsPath, 'Dart', 'lib', 'Dart.jar'),
ZipEncoder().encode(dartJarArchive)); ZipEncoder().encode(dartJarArchive),
);
final Archive flutterJarArchive = final Archive flutterJarArchive = buildSingleFileArchive('META-INF/plugin.xml', r'''
buildSingleFileArchive('META-INF/plugin.xml', r'''
<idea-plugin version="2"> <idea-plugin version="2">
<name>Flutter</name> <name>Flutter</name>
<version>0.1.3</version> <version>0.1.3</version>
</idea-plugin> </idea-plugin>
'''); ''');
writeFileCreatingDirectories( writeFileCreatingDirectories(
globals.fs.path.join(_kPluginsPath, 'flutter-intellij.jar'), fileSystem.path.join(_kPluginsPath, 'flutter-intellij.jar'),
ZipEncoder().encode(flutterJarArchive)); ZipEncoder().encode(flutterJarArchive),
);
final List<ValidationMessage> messages = <ValidationMessage>[]; final List<ValidationMessage> messages = <ValidationMessage>[];
plugins.validatePackage(messages, <String>['Dart'], 'Dart'); plugins.validatePackage(messages, <String>['Dart'], 'Dart', 'download-Dart');
plugins.validatePackage(messages, plugins.validatePackage(messages,
<String>['flutter-intellij', 'flutter-intellij.jar'], 'Flutter', <String>['flutter-intellij', 'flutter-intellij.jar'], 'Flutter', 'download-Flutter',
minVersion: IntelliJPlugins.kMinFlutterPluginVersion); minVersion: IntelliJPlugins.kMinFlutterPluginVersion,
);
ValidationMessage message = messages ValidationMessage message = messages
.firstWhere((ValidationMessage m) => m.message.startsWith('Dart ')); .firstWhere((ValidationMessage m) => m.message.startsWith('Dart '));
...@@ -69,32 +67,27 @@ void main() { ...@@ -69,32 +67,27 @@ void main() {
(ValidationMessage m) => m.message.startsWith('Flutter ')); (ValidationMessage m) => m.message.startsWith('Flutter '));
expect(message.message, contains('Flutter plugin version 0.1.3')); expect(message.message, contains('Flutter plugin version 0.1.3'));
expect(message.message, contains('recommended minimum version')); expect(message.message, contains('recommended minimum version'));
}, overrides: <Type, Generator>{
FileSystem: () => fs,
ProcessManager: () => FakeProcessManager.any(),
}); });
testUsingContext('not found', () async { testWithoutContext('IntelliJPlugins not found displays a link to their download site', () async {
final IntelliJPlugins plugins = IntelliJPlugins(_kPluginsPath); final IntelliJPlugins plugins = IntelliJPlugins(_kPluginsPath, fileSystem: fileSystem);
final List<ValidationMessage> messages = <ValidationMessage>[]; final List<ValidationMessage> messages = <ValidationMessage>[];
plugins.validatePackage(messages, <String>['Dart'], 'Dart'); plugins.validatePackage(messages, <String>['Dart'], 'Dart', 'download-Dart');
plugins.validatePackage(messages, plugins.validatePackage(messages,
<String>['flutter-intellij', 'flutter-intellij.jar'], 'Flutter', <String>['flutter-intellij', 'flutter-intellij.jar'], 'Flutter', 'download-Flutter',
minVersion: IntelliJPlugins.kMinFlutterPluginVersion); minVersion: IntelliJPlugins.kMinFlutterPluginVersion,
);
ValidationMessage message = messages ValidationMessage message = messages
.firstWhere((ValidationMessage m) => m.message.startsWith('Dart ')); .firstWhere((ValidationMessage m) => m.message.startsWith('Dart '));
expect(message.message, contains('Dart plugin not installed')); expect(message.message, contains('Dart plugin can be installed from'));
expect(message.contextUrl, isNotNull);
message = messages.firstWhere( message = messages.firstWhere(
(ValidationMessage m) => m.message.startsWith('Flutter ')); (ValidationMessage m) => m.message.startsWith('Flutter '));
expect(message.message, contains('Flutter plugin not installed')); expect(message.message, contains('Flutter plugin can be installed from'));
}, overrides: <Type, Generator>{ expect(message.contextUrl, isNotNull);
FileSystem: () => fs,
ProcessManager: () => FakeProcessManager.any(),
});
});
}); });
} }
......
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