Unverified Commit 13db2e4a authored by Andrew Kolos's avatar Andrew Kolos Committed by GitHub

[tool] In `flutter doctor -v`, warn when Android Studio version could not be detected. (#126395)

Fixes #122081.

When validating an Android Studio installation, add a warning validation message when we are unable to detect the version. This is because we have logic throughout the tool (JDK/JRE-searching) that is at higher risk of failing when we don't know the version.
parent a3ac142f
...@@ -16,12 +16,17 @@ const String _androidStudioPreviewTitle = 'Android Studio Preview'; ...@@ -16,12 +16,17 @@ const String _androidStudioPreviewTitle = 'Android Studio Preview';
const String _androidStudioPreviewId = 'AndroidStudioPreview'; const String _androidStudioPreviewId = 'AndroidStudioPreview';
class AndroidStudioValidator extends DoctorValidator { class AndroidStudioValidator extends DoctorValidator {
AndroidStudioValidator(this._studio, { required FileSystem fileSystem }) AndroidStudioValidator(this._studio, {
: _fileSystem = fileSystem, required FileSystem fileSystem,
required UserMessages userMessages,
})
: _userMessages = userMessages,
_fileSystem = fileSystem,
super('Android Studio'); super('Android Studio');
final AndroidStudio _studio; final AndroidStudio _studio;
final FileSystem _fileSystem; final FileSystem _fileSystem;
final UserMessages _userMessages;
static const Map<String, String> idToTitle = <String, String>{ static const Map<String, String> idToTitle = <String, String>{
_androidStudioId: _androidStudioTitle, _androidStudioId: _androidStudioTitle,
...@@ -35,7 +40,7 @@ class AndroidStudioValidator extends DoctorValidator { ...@@ -35,7 +40,7 @@ class AndroidStudioValidator extends DoctorValidator {
NoAndroidStudioValidator(config: config, platform: platform, userMessages: userMessages) NoAndroidStudioValidator(config: config, platform: platform, userMessages: userMessages)
else else
...studios.map<DoctorValidator>( ...studios.map<DoctorValidator>(
(AndroidStudio studio) => AndroidStudioValidator(studio, fileSystem: fileSystem) (AndroidStudio studio) => AndroidStudioValidator(studio, fileSystem: fileSystem, userMessages: userMessages)
), ),
]; ];
} }
...@@ -45,11 +50,11 @@ class AndroidStudioValidator extends DoctorValidator { ...@@ -45,11 +50,11 @@ class AndroidStudioValidator extends DoctorValidator {
final List<ValidationMessage> messages = <ValidationMessage>[]; final List<ValidationMessage> messages = <ValidationMessage>[];
ValidationType type = ValidationType.missing; ValidationType type = ValidationType.missing;
final String? studioVersionText = _studio.version == null final String studioVersionText = _studio.version == null
? null ? _userMessages.androidStudioVersion('unknown')
: userMessages.androidStudioVersion(_studio.version.toString()); : _userMessages.androidStudioVersion(_studio.version.toString());
messages.add(ValidationMessage( messages.add(ValidationMessage(
userMessages.androidStudioLocation(_studio.directory), _userMessages.androidStudioLocation(_studio.directory),
)); ));
if (_studio.pluginsPath != null) { if (_studio.pluginsPath != null) {
...@@ -69,6 +74,10 @@ class AndroidStudioValidator extends DoctorValidator { ...@@ -69,6 +74,10 @@ class AndroidStudioValidator extends DoctorValidator {
); );
} }
if (_studio.version == null) {
messages.add(const ValidationMessage.error('Unable to determine Android Studio version.'));
}
if (_studio.isValid) { if (_studio.isValid) {
type = _hasIssues(messages) type = _hasIssues(messages)
? ValidationType.partial ? ValidationType.partial
...@@ -81,9 +90,9 @@ class AndroidStudioValidator extends DoctorValidator { ...@@ -81,9 +90,9 @@ class AndroidStudioValidator extends DoctorValidator {
messages.addAll(_studio.validationMessages.map<ValidationMessage>( messages.addAll(_studio.validationMessages.map<ValidationMessage>(
(String m) => ValidationMessage.error(m), (String m) => ValidationMessage.error(m),
)); ));
messages.add(ValidationMessage(userMessages.androidStudioNeedsUpdate)); messages.add(ValidationMessage(_userMessages.androidStudioNeedsUpdate));
if (_studio.configuredPath != null) { if (_studio.configuredPath != null) {
messages.add(ValidationMessage(userMessages.androidStudioResetDir)); messages.add(ValidationMessage(_userMessages.androidStudioResetDir));
} }
} }
......
...@@ -23,13 +23,20 @@ class VsCodeValidator extends DoctorValidator { ...@@ -23,13 +23,20 @@ class VsCodeValidator extends DoctorValidator {
@override @override
Future<ValidationResult> validate() async { Future<ValidationResult> validate() async {
final String? vsCodeVersionText = _vsCode.version == null final List<ValidationMessage> validationMessages =
? null List<ValidationMessage>.from(_vsCode.validationMessages);
final String vsCodeVersionText = _vsCode.version == null
? userMessages.vsCodeVersion('unknown')
: userMessages.vsCodeVersion(_vsCode.version.toString()); : userMessages.vsCodeVersion(_vsCode.version.toString());
if (_vsCode.version == null) {
validationMessages.add(const ValidationMessage.error('Unable to determine VS Code version.'));
}
return ValidationResult( return ValidationResult(
ValidationType.success, ValidationType.success,
_vsCode.validationMessages.toList(), validationMessages,
statusInfo: vsCodeVersionText, statusInfo: vsCodeVersionText,
); );
} }
......
...@@ -3,14 +3,17 @@ ...@@ -3,14 +3,17 @@
// found in the LICENSE file. // found in the LICENSE file.
import 'package:file/memory.dart'; import 'package:file/memory.dart';
import 'package:flutter_tools/src/android/android_studio.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/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/platform.dart'; import 'package:flutter_tools/src/base/platform.dart';
import 'package:flutter_tools/src/base/user_messages.dart'; import 'package:flutter_tools/src/base/user_messages.dart';
import 'package:flutter_tools/src/base/version.dart';
import 'package:flutter_tools/src/doctor_validator.dart'; import 'package:flutter_tools/src/doctor_validator.dart';
import 'package:flutter_tools/src/globals.dart' as globals; import 'package:flutter_tools/src/globals.dart' as globals;
import 'package:test/fake.dart';
import '../../src/common.dart'; import '../../src/common.dart';
import '../../src/context.dart'; import '../../src/context.dart';
...@@ -23,6 +26,7 @@ final Platform linuxPlatform = FakePlatform( ...@@ -23,6 +26,7 @@ final Platform linuxPlatform = FakePlatform(
); );
void main() { void main() {
late FileSystem fileSystem; late FileSystem fileSystem;
late FakeProcessManager fakeProcessManager; late FakeProcessManager fakeProcessManager;
...@@ -31,49 +35,76 @@ void main() { ...@@ -31,49 +35,76 @@ void main() {
fakeProcessManager = FakeProcessManager.empty(); fakeProcessManager = FakeProcessManager.empty();
}); });
testWithoutContext('NoAndroidStudioValidator shows Android Studio as "not available" when not available.', () async { group(NoAndroidStudioValidator, () {
final Config config = Config.test(); testWithoutContext('shows Android Studio as "not available" when not available.', () async {
final NoAndroidStudioValidator validator = NoAndroidStudioValidator( final Config config = Config.test();
config: config, final NoAndroidStudioValidator validator = NoAndroidStudioValidator(
platform: linuxPlatform, config: config,
userMessages: UserMessages(), platform: linuxPlatform,
); userMessages: UserMessages(),
);
expect((await validator.validate()).type, equals(ValidationType.notAvailable)); expect((await validator.validate()).type, equals(ValidationType.notAvailable));
});
}); });
testUsingContext('AndroidStudioValidator gives doctor error on java crash', () async { group(AndroidStudioValidator, () {
fakeProcessManager.addCommand(const FakeCommand( testUsingContext('gives doctor error on java crash', () async {
command: <String>[ fakeProcessManager.addCommand(const FakeCommand(
'/opt/android-studio-with-cheese-5.0/jre/bin/java', command: <String>[
'-version', '/opt/android-studio-with-cheese-5.0/jre/bin/java',
], '-version',
exception: ProcessException('java', <String>['-version']), ],
)); exception: ProcessException('java', <String>['-version']),
const String installPath = '/opt/android-studio-with-cheese-5.0'; ));
const String studioHome = '$home/.AndroidStudioWithCheese5.0'; const String installPath = '/opt/android-studio-with-cheese-5.0';
const String homeFile = '$studioHome/system/.home'; const String studioHome = '$home/.AndroidStudioWithCheese5.0';
globals.fs.directory(installPath).createSync(recursive: true); const String homeFile = '$studioHome/system/.home';
globals.fs.file(homeFile).createSync(recursive: true); globals.fs.directory(installPath).createSync(recursive: true);
globals.fs.file(homeFile).writeAsStringSync(installPath); globals.fs.file(homeFile).createSync(recursive: true);
globals.fs.file(homeFile).writeAsStringSync(installPath);
// This checks that running the validator doesn't throw an unhandled
// exception and that the ProcessException makes it into the error
// message list.
for (final DoctorValidator validator in AndroidStudioValidator.allValidators(globals.config, globals.platform, globals.fs, globals.userMessages)) {
final ValidationResult result = await validator.validate();
expect(result.messages.where((ValidationMessage message) {
return message.isError && message.message.contains('ProcessException');
}).isNotEmpty, true);
}
expect(fakeProcessManager, hasNoRemainingExpectations);
}, overrides: <Type, Generator>{
FileSystem: () => fileSystem,
ProcessManager: () => fakeProcessManager,
Platform: () => linuxPlatform,
FileSystemUtils: () => FileSystemUtils(
fileSystem: fileSystem,
platform: linuxPlatform,
),
});
// This checks that running the validator doesn't throw an unhandled testWithoutContext('warns if version of Android Studio could not be determined', () async {
// exception and that the ProcessException makes it into the error final AndroidStudio studio = _FakeAndroidStudio();
// message list. final AndroidStudioValidator validator = AndroidStudioValidator(studio, fileSystem: fileSystem, userMessages: UserMessages());
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, contains(const ValidationMessage.error('Unable to determine Android Studio version.')));
return message.isError && message.message.contains('ProcessException'); expect(result.statusInfo, 'version unknown');
}).isNotEmpty, true); });
}
expect(fakeProcessManager, hasNoRemainingExpectations);
}, overrides: <Type, Generator>{
FileSystem: () => fileSystem,
ProcessManager: () => fakeProcessManager,
Platform: () => linuxPlatform,
FileSystemUtils: () => FileSystemUtils(
fileSystem: fileSystem,
platform: linuxPlatform,
),
}); });
} }
class _FakeAndroidStudio extends Fake implements AndroidStudio {
@override
List<String> get validationMessages => <String>[];
@override
bool get isValid => true;
@override
String? get pluginsPath => null;
@override
String get directory => 'android-studio';
@override
Version? get version => null;
@override
String get javaPath => 'android-studio/jbr/bin/java';
}
...@@ -5,10 +5,15 @@ ...@@ -5,10 +5,15 @@
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/platform.dart'; import 'package:flutter_tools/src/base/platform.dart';
import 'package:flutter_tools/src/base/user_messages.dart';
import 'package:flutter_tools/src/base/version.dart';
import 'package:flutter_tools/src/doctor_validator.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:test/fake.dart';
import '../../src/common.dart'; import '../../src/common.dart';
import '../../src/fake_process_manager.dart'; import '../../src/context.dart';
void main() { void main() {
testWithoutContext('VsCode search locations on windows supports an empty environment', () { testWithoutContext('VsCode search locations on windows supports an empty environment', () {
...@@ -20,4 +25,26 @@ void main() { ...@@ -20,4 +25,26 @@ void main() {
expect(VsCode.allInstalled(fileSystem, platform, FakeProcessManager.any()), isEmpty); expect(VsCode.allInstalled(fileSystem, platform, FakeProcessManager.any()), isEmpty);
}); });
group(VsCodeValidator, () {
testUsingContext('Warns if VS Code version could not be found', () async {
final VsCodeValidator validator = VsCodeValidator(_FakeVsCode());
final ValidationResult result = await validator.validate();
expect(result.messages, contains(const ValidationMessage.error('Unable to determine VS Code version.')));
expect(result.statusInfo, 'version unknown');
}, overrides: <Type, Generator>{
UserMessages: () => UserMessages(),
});
});
}
class _FakeVsCode extends Fake implements VsCode {
@override
Iterable<ValidationMessage> get validationMessages => <ValidationMessage>[];
@override
String get productName => 'VS Code';
@override
Version? get version => null;
} }
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