android_workflow_test.dart 14.6 KB
Newer Older
Ian Hickson's avatar
Ian Hickson committed
1
// Copyright 2014 The Flutter Authors. All rights reserved.
2 3 4 5 6 7
// 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';
8 9
import 'package:flutter_tools/src/base/file_system.dart';
import 'package:flutter_tools/src/base/io.dart';
10
import 'package:flutter_tools/src/base/logger.dart';
11 12
import 'package:flutter_tools/src/base/os.dart';
import 'package:flutter_tools/src/base/platform.dart';
13
import 'package:flutter_tools/src/base/terminal.dart' show AnsiTerminal, OutputPreferences;
14 15 16
import 'package:flutter_tools/src/base/user_messages.dart';
import 'package:flutter_tools/src/base/version.dart';
import 'package:flutter_tools/src/doctor.dart';
17 18 19
import 'package:mockito/mockito.dart';
import 'package:process/process.dart';

20 21 22
import '../../src/common.dart';
import '../../src/context.dart';
import '../../src/mocks.dart' show MockAndroidSdk, MockProcess, MockProcessManager, MockStdio;
23
import '../../src/testbed.dart';
24

25
class MockAndroidSdkVersion extends Mock implements AndroidSdkVersion {}
26
class MockOperatingSystemUtils extends Mock implements OperatingSystemUtils {}
27

28 29
void main() {
  AndroidSdk sdk;
30
  Logger logger;
31 32 33
  MemoryFileSystem fs;
  MockProcessManager processManager;
  MockStdio stdio;
34
  UserMessages userMessages;
35 36

  setUp(() {
37
    sdk = MockAndroidSdk();
38
    fs = MemoryFileSystem.test();
39
    fs.directory('/home/me').createSync(recursive: true);
40 41 42 43 44 45 46
    logger = BufferLogger(
      terminal: AnsiTerminal(
        stdio: null,
        platform: const LocalPlatform(),
      ),
      outputPreferences: OutputPreferences.test(),
    );
47 48
    processManager = MockProcessManager();
    stdio = MockStdio();
49
    userMessages = UserMessages();
50 51
  });

52
  MockProcess Function(List<String>) processMetaFactory(List<String> stdout) {
53
    final Stream<List<int>> stdoutStream = Stream<List<int>>.fromIterable(
54
        stdout.map<List<int>>((String s) => s.codeUnits));
55
    return (List<String> command) => MockProcess(stdout: stdoutStream);
56 57
  }

58 59 60 61 62 63 64 65 66 67 68
  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);
  });

69 70 71 72 73 74 75 76 77 78 79 80 81 82
  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);
  });


83 84 85 86 87 88
  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);
89
  }, overrides: Map<Type, Generator>.unmodifiable(<Type, Generator>{
90 91 92
    AndroidSdk: () => sdk,
    FileSystem: () => fs,
    ProcessManager: () => processManager,
93
    Platform: () => FakePlatform()..environment = <String, String>{'HOME': '/home/me'},
94
    Stdio: () => stdio,
95
  }));
96

97
  testUsingContext('licensesAccepted returns LicensesAccepted.unknown if cannot run sdkmanager', () async {
98
    processManager.runSucceeds = false;
99
    when(sdk.sdkManagerPath).thenReturn('/foo/bar/sdkmanager');
100
    final AndroidLicenseValidator licenseValidator = AndroidLicenseValidator();
101 102
    final LicensesAccepted licenseStatus = await licenseValidator.licensesAccepted;
    expect(licenseStatus, LicensesAccepted.unknown);
103
  }, overrides: Map<Type, Generator>.unmodifiable(<Type, Generator>{
104 105 106
    AndroidSdk: () => sdk,
    FileSystem: () => fs,
    ProcessManager: () => processManager,
107
    Platform: () => FakePlatform()..environment = <String, String>{'HOME': '/home/me'},
108
    Stdio: () => stdio,
109
  }));
110

111 112
  testUsingContext('licensesAccepted handles garbage/no output', () async {
    when(sdk.sdkManagerPath).thenReturn('/foo/bar/sdkmanager');
113 114
    final AndroidLicenseValidator licenseValidator = AndroidLicenseValidator();
    final LicensesAccepted result = await licenseValidator.licensesAccepted;
115 116 117
    expect(result, equals(LicensesAccepted.unknown));
    expect(processManager.commands.first, equals('/foo/bar/sdkmanager'));
    expect(processManager.commands.last, equals('--licenses'));
118
  }, overrides: Map<Type, Generator>.unmodifiable(<Type, Generator>{
119 120 121
    AndroidSdk: () => sdk,
    FileSystem: () => fs,
    ProcessManager: () => processManager,
122
    Platform: () => FakePlatform()..environment = <String, String>{'HOME': '/home/me'},
123
    Stdio: () => stdio,
124
  }));
125 126 127 128

  testUsingContext('licensesAccepted works for all licenses accepted', () async {
    when(sdk.sdkManagerPath).thenReturn('/foo/bar/sdkmanager');
    processManager.processFactory = processMetaFactory(<String>[
129 130
      '[=======================================] 100% Computing updates...             ',
      'All SDK package licenses accepted.',
131 132
    ]);

133 134
    final AndroidLicenseValidator licenseValidator = AndroidLicenseValidator();
    final LicensesAccepted result = await licenseValidator.licensesAccepted;
135
    expect(result, equals(LicensesAccepted.all));
136
  }, overrides: Map<Type, Generator>.unmodifiable(<Type, Generator>{
137 138 139
    AndroidSdk: () => sdk,
    FileSystem: () => fs,
    ProcessManager: () => processManager,
140
    Platform: () => FakePlatform()..environment = <String, String>{'HOME': '/home/me'},
141
    Stdio: () => stdio,
142
  }));
143 144 145 146 147 148 149 150 151

  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)?',
    ]);

152 153
    final AndroidLicenseValidator licenseValidator = AndroidLicenseValidator();
    final LicensesAccepted result = await licenseValidator.licensesAccepted;
154
    expect(result, equals(LicensesAccepted.some));
155
  }, overrides: Map<Type, Generator>.unmodifiable(<Type, Generator>{
156 157 158
    AndroidSdk: () => sdk,
    FileSystem: () => fs,
    ProcessManager: () => processManager,
159
    Platform: () => FakePlatform()..environment = <String, String>{'HOME': '/home/me'},
160
    Stdio: () => stdio,
161
  }));
162 163 164 165 166 167 168 169 170

  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)?',
    ]);

171 172
    final AndroidLicenseValidator licenseValidator = AndroidLicenseValidator();
    final LicensesAccepted result = await licenseValidator.licensesAccepted;
173
    expect(result, equals(LicensesAccepted.none));
174
  }, overrides: Map<Type, Generator>.unmodifiable(<Type, Generator>{
175 176 177
    AndroidSdk: () => sdk,
    FileSystem: () => fs,
    ProcessManager: () => processManager,
178
    Platform: () => FakePlatform()..environment = <String, String>{'HOME': '/home/me'},
179
    Stdio: () => stdio,
180
  }));
181

182 183 184 185
  testUsingContext('runLicenseManager succeeds for version >= 26', () async {
    when(sdk.sdkManagerPath).thenReturn('/foo/bar/sdkmanager');
    when(sdk.sdkManagerVersion).thenReturn('26.0.0');

186
    expect(await AndroidLicenseValidator.runLicenseManager(), isTrue);
187
  }, overrides: Map<Type, Generator>.unmodifiable(<Type, Generator>{
188 189 190
    AndroidSdk: () => sdk,
    FileSystem: () => fs,
    ProcessManager: () => processManager,
191
    Platform: () => FakePlatform()..environment = <String, String>{'HOME': '/home/me'},
192
    Stdio: () => stdio,
193
  }));
194 195 196

  testUsingContext('runLicenseManager errors when sdkmanager is not found', () async {
    when(sdk.sdkManagerPath).thenReturn('/foo/bar/sdkmanager');
197 198 199
    processManager.canRunSucceeds = false;

    expect(AndroidLicenseValidator.runLicenseManager(), throwsToolExit());
200
  }, overrides: Map<Type, Generator>.unmodifiable(<Type, Generator>{
201 202 203
    AndroidSdk: () => sdk,
    FileSystem: () => fs,
    ProcessManager: () => processManager,
204
    Platform: () => FakePlatform()..environment = <String, String>{'HOME': '/home/me'},
205
    Stdio: () => stdio,
206
  }));
207 208 209 210

  testUsingContext('runLicenseManager errors when sdkmanager fails to run', () async {
    when(sdk.sdkManagerPath).thenReturn('/foo/bar/sdkmanager');
    processManager.runSucceeds = false;
211

212
    expect(AndroidLicenseValidator.runLicenseManager(), throwsToolExit());
213
  }, overrides: Map<Type, Generator>.unmodifiable(<Type, Generator>{
214 215 216
    AndroidSdk: () => sdk,
    FileSystem: () => fs,
    ProcessManager: () => processManager,
217
    Platform: () => FakePlatform()..environment = <String, String>{'HOME': '/home/me'},
218
    Stdio: () => stdio,
219
  }));
220

221
  testWithoutContext('detects license-only SDK installation', () async {
222 223
    when(sdk.licensesAvailable).thenReturn(true);
    when(sdk.platformToolsAvailable).thenReturn(false);
224 225 226 227 228 229 230 231 232
    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();
233 234 235 236 237
    expect(validationResult.type, ValidationType.partial);
    expect(
      validationResult.messages.last.message,
      userMessages.androidSdkLicenseOnly(kAndroidHome),
    );
238
  });
239

240
  testWithoutContext('detects minimum required SDK and buildtools', () async {
241
    final AndroidSdkVersion mockSdkVersion = MockAndroidSdkVersion();
242 243
    when(sdk.licensesAvailable).thenReturn(true);
    when(sdk.platformToolsAvailable).thenReturn(true);
244 245

    // Test with invalid SDK and build tools
246
    when(mockSdkVersion.sdkLevel).thenReturn(28);
247 248 249 250
    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>[]);
251
    when(processManager.runSync(<String>['which', 'java'])).thenReturn(ProcessResult(123, 1, '', ''));
252 253 254 255
    final String errorMessage = userMessages.androidSdkBuildToolsOutdated(
      sdk.sdkManagerPath,
      kAndroidSdkMinVersion,
      kAndroidSdkBuildToolsMinVersion.toString(),
256
      FakePlatform(),
257 258
    );

259 260 261 262 263 264 265 266 267 268 269
    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();
270 271 272 273 274 275 276
    expect(validationResult.type, ValidationType.missing);
    expect(
      validationResult.messages.last.message,
      errorMessage,
    );

    // Test with valid SDK but invalid build tools
277
    when(mockSdkVersion.sdkLevel).thenReturn(29);
278 279
    when(mockSdkVersion.buildToolsVersion).thenReturn(Version(28, 0, 2));

280
    validationResult = await androidValidator.validate();
281 282 283 284 285 286 287 288 289 290 291
    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);

292
    validationResult = await androidValidator.validate();
293 294 295 296 297
    expect(validationResult.type, ValidationType.partial); // No Java binary
    expect(
      validationResult.messages.any((ValidationMessage message) => message.message == errorMessage),
      isFalse,
    );
298
  });
299

300
  testWithoutContext('detects minimum required java version', () async {
301 302 303 304 305
    final AndroidSdkVersion mockSdkVersion = MockAndroidSdkVersion();

    // Mock a pass through scenario to reach _checkJavaVersion()
    when(sdk.licensesAvailable).thenReturn(true);
    when(sdk.platformToolsAvailable).thenReturn(true);
306
    when(mockSdkVersion.sdkLevel).thenReturn(29);
307 308 309 310 311 312 313
    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"';
314
    when(processManager.run(argThat(contains('-version')))).thenAnswer((_) =>
315 316 317
      Future<ProcessResult>.value(ProcessResult(0, 0, null, javaVersionText)));
    final String errorMessage = userMessages.androidJavaMinimumVersion(javaVersionText);

318 319 320 321 322 323 324 325 326
    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();
327 328 329 330 331
    expect(validationResult.type, ValidationType.partial);
    expect(
      validationResult.messages.last.message,
      errorMessage,
    );
332 333 334 335 336 337
    expect(
      validationResult.messages.any(
        (ValidationMessage message) => message.message.contains('Unable to locate Android SDK')
      ),
      false,
    );
338
  });
339

340
  testWithoutContext('Mentions `flutter config --android-sdk if user has no AndroidSdk`', () async {
341 342 343 344 345 346 347 348 349 350 351
    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(
352
        (ValidationMessage message) => message.message.contains('flutter config --android-sdk')
353 354 355 356
      ),
      true,
    );
  });
357
}