android_workflow_test.dart 13.5 KB
Newer Older
Ian Hickson's avatar
Ian Hickson committed
1
// Copyright 2014 The Flutter Authors. All rights reserved.
2 3 4
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

5 6
import 'dart:async';

7 8 9
import 'package:file/memory.dart';
import 'package:flutter_tools/src/android/android_sdk.dart';
import 'package:flutter_tools/src/android/android_workflow.dart';
10 11
import 'package:flutter_tools/src/base/file_system.dart';
import 'package:flutter_tools/src/base/io.dart';
12
import 'package:flutter_tools/src/base/logger.dart';
13 14
import 'package:flutter_tools/src/base/os.dart';
import 'package:flutter_tools/src/base/platform.dart';
15
import 'package:flutter_tools/src/base/terminal.dart' show AnsiTerminal, OutputPreferences;
16 17 18
import 'package:flutter_tools/src/base/user_messages.dart';
import 'package:flutter_tools/src/base/version.dart';
import 'package:flutter_tools/src/doctor.dart';
19 20 21
import 'package:mockito/mockito.dart';
import 'package:process/process.dart';

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

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

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

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

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

59 60 61 62 63 64
  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);
65
  }, overrides: Map<Type, Generator>.unmodifiable(<Type, Generator>{
66 67 68
    AndroidSdk: () => sdk,
    FileSystem: () => fs,
    ProcessManager: () => processManager,
69
    Platform: () => FakePlatform()..environment = <String, String>{'HOME': '/home/me'},
70
    Stdio: () => stdio,
71
  }));
72

73
  testUsingContext('licensesAccepted returns LicensesAccepted.unknown if cannot run sdkmanager', () async {
74
    processManager.runSucceeds = false;
75
    when(sdk.sdkManagerPath).thenReturn('/foo/bar/sdkmanager');
76
    final AndroidLicenseValidator licenseValidator = AndroidLicenseValidator();
77 78
    final LicensesAccepted licenseStatus = await licenseValidator.licensesAccepted;
    expect(licenseStatus, LicensesAccepted.unknown);
79
  }, overrides: Map<Type, Generator>.unmodifiable(<Type, Generator>{
80 81 82
    AndroidSdk: () => sdk,
    FileSystem: () => fs,
    ProcessManager: () => processManager,
83
    Platform: () => FakePlatform()..environment = <String, String>{'HOME': '/home/me'},
84
    Stdio: () => stdio,
85
  }));
86

87 88
  testUsingContext('licensesAccepted handles garbage/no output', () async {
    when(sdk.sdkManagerPath).thenReturn('/foo/bar/sdkmanager');
89 90
    final AndroidLicenseValidator licenseValidator = AndroidLicenseValidator();
    final LicensesAccepted result = await licenseValidator.licensesAccepted;
91 92 93
    expect(result, equals(LicensesAccepted.unknown));
    expect(processManager.commands.first, equals('/foo/bar/sdkmanager'));
    expect(processManager.commands.last, equals('--licenses'));
94
  }, overrides: Map<Type, Generator>.unmodifiable(<Type, Generator>{
95 96 97
    AndroidSdk: () => sdk,
    FileSystem: () => fs,
    ProcessManager: () => processManager,
98
    Platform: () => FakePlatform()..environment = <String, String>{'HOME': '/home/me'},
99
    Stdio: () => stdio,
100
  }));
101 102 103 104

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

109 110
    final AndroidLicenseValidator licenseValidator = AndroidLicenseValidator();
    final LicensesAccepted result = await licenseValidator.licensesAccepted;
111
    expect(result, equals(LicensesAccepted.all));
112
  }, overrides: Map<Type, Generator>.unmodifiable(<Type, Generator>{
113 114 115
    AndroidSdk: () => sdk,
    FileSystem: () => fs,
    ProcessManager: () => processManager,
116
    Platform: () => FakePlatform()..environment = <String, String>{'HOME': '/home/me'},
117
    Stdio: () => stdio,
118
  }));
119 120 121 122 123 124 125 126 127

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

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

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

147 148
    final AndroidLicenseValidator licenseValidator = AndroidLicenseValidator();
    final LicensesAccepted result = await licenseValidator.licensesAccepted;
149
    expect(result, equals(LicensesAccepted.none));
150
  }, overrides: Map<Type, Generator>.unmodifiable(<Type, Generator>{
151 152 153
    AndroidSdk: () => sdk,
    FileSystem: () => fs,
    ProcessManager: () => processManager,
154
    Platform: () => FakePlatform()..environment = <String, String>{'HOME': '/home/me'},
155
    Stdio: () => stdio,
156
  }));
157

158 159 160 161
  testUsingContext('runLicenseManager succeeds for version >= 26', () async {
    when(sdk.sdkManagerPath).thenReturn('/foo/bar/sdkmanager');
    when(sdk.sdkManagerVersion).thenReturn('26.0.0');

162
    expect(await AndroidLicenseValidator.runLicenseManager(), isTrue);
163
  }, overrides: Map<Type, Generator>.unmodifiable(<Type, Generator>{
164 165 166
    AndroidSdk: () => sdk,
    FileSystem: () => fs,
    ProcessManager: () => processManager,
167
    Platform: () => FakePlatform()..environment = <String, String>{'HOME': '/home/me'},
168
    Stdio: () => stdio,
169
  }));
170 171 172

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

    expect(AndroidLicenseValidator.runLicenseManager(), throwsToolExit());
176
  }, overrides: Map<Type, Generator>.unmodifiable(<Type, Generator>{
177 178 179
    AndroidSdk: () => sdk,
    FileSystem: () => fs,
    ProcessManager: () => processManager,
180
    Platform: () => FakePlatform()..environment = <String, String>{'HOME': '/home/me'},
181
    Stdio: () => stdio,
182
  }));
183 184 185 186

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

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

197
  testWithoutContext('detects license-only SDK installation', () async {
198 199
    when(sdk.licensesAvailable).thenReturn(true);
    when(sdk.platformToolsAvailable).thenReturn(false);
200 201 202 203 204 205 206 207 208
    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();
209 210 211 212 213
    expect(validationResult.type, ValidationType.partial);
    expect(
      validationResult.messages.last.message,
      userMessages.androidSdkLicenseOnly(kAndroidHome),
    );
214
  });
215

216
  testWithoutContext('detects minimum required SDK and buildtools', () async {
217
    final AndroidSdkVersion mockSdkVersion = MockAndroidSdkVersion();
218 219
    when(sdk.licensesAvailable).thenReturn(true);
    when(sdk.platformToolsAvailable).thenReturn(true);
220 221 222 223 224 225 226

    // Test with invalid SDK and build tools
    when(mockSdkVersion.sdkLevel).thenReturn(26);
    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>[]);
227
    when(processManager.runSync(<String>['which', 'java'])).thenReturn(ProcessResult(123, 1, '', ''));
228 229 230 231
    final String errorMessage = userMessages.androidSdkBuildToolsOutdated(
      sdk.sdkManagerPath,
      kAndroidSdkMinVersion,
      kAndroidSdkBuildToolsMinVersion.toString(),
232
      FakePlatform(),
233 234
    );

235 236 237 238 239 240 241 242 243 244 245
    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();
246 247 248 249 250 251 252 253 254 255
    expect(validationResult.type, ValidationType.missing);
    expect(
      validationResult.messages.last.message,
      errorMessage,
    );

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

256
    validationResult = await androidValidator.validate();
257 258 259 260 261 262 263 264 265 266 267
    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);

268
    validationResult = await androidValidator.validate();
269 270 271 272 273
    expect(validationResult.type, ValidationType.partial); // No Java binary
    expect(
      validationResult.messages.any((ValidationMessage message) => message.message == errorMessage),
      isFalse,
    );
274
  });
275

276
  testWithoutContext('detects minimum required java version', () async {
277 278 279 280 281 282 283 284 285 286 287 288 289
    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(28);
    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"';
290
    when(processManager.run(argThat(contains('-version')))).thenAnswer((_) =>
291 292 293
      Future<ProcessResult>.value(ProcessResult(0, 0, null, javaVersionText)));
    final String errorMessage = userMessages.androidJavaMinimumVersion(javaVersionText);

294 295 296 297 298 299 300 301 302
    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();
303 304 305 306 307
    expect(validationResult.type, ValidationType.partial);
    expect(
      validationResult.messages.last.message,
      errorMessage,
    );
308
  });
309

310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326
  testWithoutContext('Mentions `kAndroidSdkRoot 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(kAndroidSdkRoot)
      ),
      true,
    );
  });
327
}