| // Copyright 2014 The Flutter Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| import 'package:file/memory.dart'; |
| import 'package:flutter_tools/src/android/android_sdk.dart'; |
| import 'package:flutter_tools/src/android/android_workflow.dart'; |
| import 'package:flutter_tools/src/base/file_system.dart'; |
| import 'package:flutter_tools/src/base/io.dart'; |
| import 'package:flutter_tools/src/base/logger.dart'; |
| import 'package:flutter_tools/src/base/os.dart'; |
| import 'package:flutter_tools/src/base/platform.dart'; |
| import 'package:flutter_tools/src/base/terminal.dart' show AnsiTerminal, OutputPreferences; |
| import 'package:flutter_tools/src/base/user_messages.dart'; |
| import 'package:flutter_tools/src/base/version.dart'; |
| import 'package:flutter_tools/src/doctor.dart'; |
| import 'package:mockito/mockito.dart'; |
| import 'package:process/process.dart'; |
| |
| import '../../src/common.dart'; |
| import '../../src/context.dart'; |
| import '../../src/mocks.dart' show MockAndroidSdk, MockProcess, MockProcessManager, MockStdio; |
| import '../../src/testbed.dart'; |
| |
| class MockAndroidSdkVersion extends Mock implements AndroidSdkVersion {} |
| class MockOperatingSystemUtils extends Mock implements OperatingSystemUtils {} |
| |
| void main() { |
| AndroidSdk sdk; |
| Logger logger; |
| MemoryFileSystem fs; |
| MockProcessManager processManager; |
| MockStdio stdio; |
| UserMessages userMessages; |
| |
| setUp(() { |
| sdk = MockAndroidSdk(); |
| fs = MemoryFileSystem.test(); |
| fs.directory('/home/me').createSync(recursive: true); |
| logger = BufferLogger( |
| terminal: AnsiTerminal( |
| stdio: null, |
| platform: const LocalPlatform(), |
| ), |
| outputPreferences: OutputPreferences.test(), |
| ); |
| processManager = MockProcessManager(); |
| stdio = MockStdio(); |
| userMessages = UserMessages(); |
| }); |
| |
| MockProcess Function(List<String>) processMetaFactory(List<String> stdout) { |
| final Stream<List<int>> stdoutStream = Stream<List<int>>.fromIterable( |
| stdout.map<List<int>>((String s) => s.codeUnits)); |
| return (List<String> command) => MockProcess(stdout: stdoutStream); |
| } |
| |
| testWithoutContext('AndroidWorkflow handles a null AndroidSDK', () { |
| final AndroidWorkflow androidWorkflow = AndroidWorkflow( |
| featureFlags: TestFeatureFlags(), |
| androidSdk: null, |
| ); |
| |
| expect(androidWorkflow.canLaunchDevices, false); |
| expect(androidWorkflow.canListDevices, false); |
| expect(androidWorkflow.canListEmulators, false); |
| }); |
| |
| testWithoutContext('AndroidWorkflow handles a null adb', () { |
| final MockAndroidSdk androidSdk = MockAndroidSdk(); |
| when(androidSdk.adbPath).thenReturn(null); |
| final AndroidWorkflow androidWorkflow = AndroidWorkflow( |
| featureFlags: TestFeatureFlags(), |
| androidSdk: androidSdk, |
| ); |
| |
| expect(androidWorkflow.canLaunchDevices, false); |
| expect(androidWorkflow.canListDevices, false); |
| expect(androidWorkflow.canListEmulators, false); |
| }); |
| |
| |
| testUsingContext('licensesAccepted returns LicensesAccepted.unknown if cannot find sdkmanager', () async { |
| processManager.canRunSucceeds = false; |
| when(sdk.sdkManagerPath).thenReturn('/foo/bar/sdkmanager'); |
| final AndroidLicenseValidator licenseValidator = AndroidLicenseValidator(); |
| final LicensesAccepted licenseStatus = await licenseValidator.licensesAccepted; |
| expect(licenseStatus, LicensesAccepted.unknown); |
| }, overrides: Map<Type, Generator>.unmodifiable(<Type, Generator>{ |
| AndroidSdk: () => sdk, |
| FileSystem: () => fs, |
| ProcessManager: () => processManager, |
| Platform: () => FakePlatform()..environment = <String, String>{'HOME': '/home/me'}, |
| Stdio: () => stdio, |
| })); |
| |
| testUsingContext('licensesAccepted returns LicensesAccepted.unknown if cannot run sdkmanager', () async { |
| processManager.runSucceeds = false; |
| when(sdk.sdkManagerPath).thenReturn('/foo/bar/sdkmanager'); |
| final AndroidLicenseValidator licenseValidator = AndroidLicenseValidator(); |
| final LicensesAccepted licenseStatus = await licenseValidator.licensesAccepted; |
| expect(licenseStatus, LicensesAccepted.unknown); |
| }, overrides: Map<Type, Generator>.unmodifiable(<Type, Generator>{ |
| AndroidSdk: () => sdk, |
| FileSystem: () => fs, |
| ProcessManager: () => processManager, |
| Platform: () => FakePlatform()..environment = <String, String>{'HOME': '/home/me'}, |
| Stdio: () => stdio, |
| })); |
| |
| testUsingContext('licensesAccepted handles garbage/no output', () async { |
| when(sdk.sdkManagerPath).thenReturn('/foo/bar/sdkmanager'); |
| final AndroidLicenseValidator licenseValidator = AndroidLicenseValidator(); |
| final LicensesAccepted result = await licenseValidator.licensesAccepted; |
| expect(result, equals(LicensesAccepted.unknown)); |
| expect(processManager.commands.first, equals('/foo/bar/sdkmanager')); |
| expect(processManager.commands.last, equals('--licenses')); |
| }, overrides: Map<Type, Generator>.unmodifiable(<Type, Generator>{ |
| AndroidSdk: () => sdk, |
| FileSystem: () => fs, |
| ProcessManager: () => processManager, |
| Platform: () => FakePlatform()..environment = <String, String>{'HOME': '/home/me'}, |
| Stdio: () => stdio, |
| })); |
| |
| testUsingContext('licensesAccepted works for all licenses accepted', () async { |
| when(sdk.sdkManagerPath).thenReturn('/foo/bar/sdkmanager'); |
| processManager.processFactory = processMetaFactory(<String>[ |
| '[=======================================] 100% Computing updates... ', |
| 'All SDK package licenses accepted.', |
| ]); |
| |
| final AndroidLicenseValidator licenseValidator = AndroidLicenseValidator(); |
| final LicensesAccepted result = await licenseValidator.licensesAccepted; |
| expect(result, equals(LicensesAccepted.all)); |
| }, overrides: Map<Type, Generator>.unmodifiable(<Type, Generator>{ |
| AndroidSdk: () => sdk, |
| FileSystem: () => fs, |
| ProcessManager: () => processManager, |
| Platform: () => FakePlatform()..environment = <String, String>{'HOME': '/home/me'}, |
| Stdio: () => stdio, |
| })); |
| |
| testUsingContext('licensesAccepted works for some licenses accepted', () async { |
| 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 AndroidLicenseValidator licenseValidator = AndroidLicenseValidator(); |
| final LicensesAccepted result = await licenseValidator.licensesAccepted; |
| expect(result, equals(LicensesAccepted.some)); |
| }, overrides: Map<Type, Generator>.unmodifiable(<Type, Generator>{ |
| AndroidSdk: () => sdk, |
| FileSystem: () => fs, |
| ProcessManager: () => processManager, |
| Platform: () => FakePlatform()..environment = <String, String>{'HOME': '/home/me'}, |
| Stdio: () => stdio, |
| })); |
| |
| testUsingContext('licensesAccepted works for no licenses accepted', () async { |
| 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 AndroidLicenseValidator licenseValidator = AndroidLicenseValidator(); |
| final LicensesAccepted result = await licenseValidator.licensesAccepted; |
| expect(result, equals(LicensesAccepted.none)); |
| }, overrides: Map<Type, Generator>.unmodifiable(<Type, Generator>{ |
| AndroidSdk: () => sdk, |
| FileSystem: () => fs, |
| ProcessManager: () => processManager, |
| Platform: () => FakePlatform()..environment = <String, String>{'HOME': '/home/me'}, |
| Stdio: () => stdio, |
| })); |
| |
| testUsingContext('runLicenseManager succeeds for version >= 26', () async { |
| when(sdk.sdkManagerPath).thenReturn('/foo/bar/sdkmanager'); |
| when(sdk.sdkManagerVersion).thenReturn('26.0.0'); |
| |
| expect(await AndroidLicenseValidator.runLicenseManager(), isTrue); |
| }, overrides: Map<Type, Generator>.unmodifiable(<Type, Generator>{ |
| AndroidSdk: () => sdk, |
| FileSystem: () => fs, |
| ProcessManager: () => processManager, |
| Platform: () => FakePlatform()..environment = <String, String>{'HOME': '/home/me'}, |
| Stdio: () => stdio, |
| })); |
| |
| testUsingContext('runLicenseManager errors when sdkmanager is not found', () async { |
| when(sdk.sdkManagerPath).thenReturn('/foo/bar/sdkmanager'); |
| processManager.canRunSucceeds = false; |
| |
| expect(AndroidLicenseValidator.runLicenseManager(), throwsToolExit()); |
| }, overrides: Map<Type, Generator>.unmodifiable(<Type, Generator>{ |
| AndroidSdk: () => sdk, |
| FileSystem: () => fs, |
| ProcessManager: () => processManager, |
| Platform: () => FakePlatform()..environment = <String, String>{'HOME': '/home/me'}, |
| Stdio: () => stdio, |
| })); |
| |
| testUsingContext('runLicenseManager errors when sdkmanager fails to run', () async { |
| when(sdk.sdkManagerPath).thenReturn('/foo/bar/sdkmanager'); |
| processManager.runSucceeds = false; |
| |
| expect(AndroidLicenseValidator.runLicenseManager(), throwsToolExit()); |
| }, overrides: Map<Type, Generator>.unmodifiable(<Type, Generator>{ |
| AndroidSdk: () => sdk, |
| FileSystem: () => fs, |
| ProcessManager: () => processManager, |
| Platform: () => FakePlatform()..environment = <String, String>{'HOME': '/home/me'}, |
| Stdio: () => stdio, |
| })); |
| |
| testWithoutContext('detects license-only SDK installation', () async { |
| when(sdk.licensesAvailable).thenReturn(true); |
| when(sdk.platformToolsAvailable).thenReturn(false); |
| final ValidationResult validationResult = await AndroidValidator( |
| androidStudio: null, |
| androidSdk: sdk, |
| fileSystem: fs, |
| logger: logger, |
| processManager: processManager, |
| platform: FakePlatform()..environment = <String, String>{'HOME': '/home/me'}, |
| userMessages: userMessages, |
| ).validate(); |
| expect(validationResult.type, ValidationType.partial); |
| expect( |
| validationResult.messages.last.message, |
| userMessages.androidSdkLicenseOnly(kAndroidHome), |
| ); |
| }); |
| |
| testWithoutContext('detects minimum required SDK and buildtools', () async { |
| final AndroidSdkVersion mockSdkVersion = MockAndroidSdkVersion(); |
| when(sdk.licensesAvailable).thenReturn(true); |
| when(sdk.platformToolsAvailable).thenReturn(true); |
| |
| // Test with invalid SDK and build tools |
| when(mockSdkVersion.sdkLevel).thenReturn(28); |
| when(mockSdkVersion.buildToolsVersion).thenReturn(Version(26, 0, 3)); |
| when(sdk.sdkManagerPath).thenReturn('/foo/bar/sdkmanager'); |
| when(sdk.latestVersion).thenReturn(mockSdkVersion); |
| when(sdk.validateSdkWellFormed()).thenReturn(<String>[]); |
| when(processManager.runSync(<String>['which', 'java'])).thenReturn(ProcessResult(123, 1, '', '')); |
| final String errorMessage = userMessages.androidSdkBuildToolsOutdated( |
| sdk.sdkManagerPath, |
| kAndroidSdkMinVersion, |
| kAndroidSdkBuildToolsMinVersion.toString(), |
| FakePlatform(), |
| ); |
| |
| final AndroidValidator androidValidator = AndroidValidator( |
| androidStudio: null, |
| androidSdk: sdk, |
| fileSystem: fs, |
| logger: logger, |
| processManager: processManager, |
| platform: FakePlatform()..environment = <String, String>{'HOME': '/home/me'}, |
| userMessages: userMessages, |
| ); |
| |
| ValidationResult validationResult = await androidValidator.validate(); |
| expect(validationResult.type, ValidationType.missing); |
| expect( |
| validationResult.messages.last.message, |
| errorMessage, |
| ); |
| |
| // Test with valid SDK but invalid build tools |
| when(mockSdkVersion.sdkLevel).thenReturn(29); |
| when(mockSdkVersion.buildToolsVersion).thenReturn(Version(28, 0, 2)); |
| |
| validationResult = await androidValidator.validate(); |
| expect(validationResult.type, ValidationType.missing); |
| expect( |
| validationResult.messages.last.message, |
| errorMessage, |
| ); |
| |
| // Test with valid SDK and valid build tools |
| // Will still be partial because AnroidSdk.findJavaBinary is static :( |
| when(mockSdkVersion.sdkLevel).thenReturn(kAndroidSdkMinVersion); |
| when(mockSdkVersion.buildToolsVersion).thenReturn(kAndroidSdkBuildToolsMinVersion); |
| |
| validationResult = await androidValidator.validate(); |
| expect(validationResult.type, ValidationType.partial); // No Java binary |
| expect( |
| validationResult.messages.any((ValidationMessage message) => message.message == errorMessage), |
| isFalse, |
| ); |
| }); |
| |
| testWithoutContext('detects minimum required java version', () async { |
| final AndroidSdkVersion mockSdkVersion = MockAndroidSdkVersion(); |
| |
| // Mock a pass through scenario to reach _checkJavaVersion() |
| when(sdk.licensesAvailable).thenReturn(true); |
| when(sdk.platformToolsAvailable).thenReturn(true); |
| when(mockSdkVersion.sdkLevel).thenReturn(29); |
| when(mockSdkVersion.buildToolsVersion).thenReturn(Version(28, 0, 3)); |
| when(sdk.sdkManagerPath).thenReturn('/foo/bar/sdkmanager'); |
| when(sdk.latestVersion).thenReturn(mockSdkVersion); |
| when(sdk.validateSdkWellFormed()).thenReturn(<String>[]); |
| |
| //Test with older version of JDK |
| const String javaVersionText = 'openjdk version "1.7.0_212"'; |
| when(processManager.run(argThat(contains('-version')))).thenAnswer((_) => |
| Future<ProcessResult>.value(ProcessResult(0, 0, null, javaVersionText))); |
| final String errorMessage = userMessages.androidJavaMinimumVersion(javaVersionText); |
| |
| final ValidationResult validationResult = await AndroidValidator( |
| androidSdk: sdk, |
| androidStudio: null, |
| fileSystem: fs, |
| logger: logger, |
| platform: FakePlatform()..environment = <String, String>{'HOME': '/home/me', 'JAVA_HOME': 'home/java'}, |
| processManager: processManager, |
| userMessages: userMessages, |
| ).validate(); |
| expect(validationResult.type, ValidationType.partial); |
| expect( |
| validationResult.messages.last.message, |
| errorMessage, |
| ); |
| expect( |
| validationResult.messages.any( |
| (ValidationMessage message) => message.message.contains('Unable to locate Android SDK') |
| ), |
| false, |
| ); |
| }); |
| |
| testWithoutContext('Mentions `flutter config --android-sdk if user has no AndroidSdk`', () async { |
| final ValidationResult validationResult = await AndroidValidator( |
| androidSdk: null, |
| androidStudio: null, |
| fileSystem: fs, |
| logger: logger, |
| platform: FakePlatform()..environment = <String, String>{'HOME': '/home/me', 'JAVA_HOME': 'home/java'}, |
| processManager: processManager, |
| userMessages: userMessages, |
| ).validate(); |
| expect( |
| validationResult.messages.any( |
| (ValidationMessage message) => message.message.contains('flutter config --android-sdk') |
| ), |
| true, |
| ); |
| }); |
| } |