android_sdk_test.dart 19.8 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
import 'package:file/memory.dart';
6
import 'package:flutter_tools/src/android/android_sdk.dart';
7
import 'package:flutter_tools/src/android/android_studio.dart';
8
import 'package:flutter_tools/src/android/java.dart';
9
import 'package:flutter_tools/src/base/config.dart';
10
import 'package:flutter_tools/src/base/file_system.dart';
11
import 'package:flutter_tools/src/base/logger.dart';
12
import 'package:flutter_tools/src/base/platform.dart';
13
import 'package:flutter_tools/src/globals.dart' as globals;
14
import 'package:test/fake.dart';
15

16 17
import '../../src/common.dart';
import '../../src/context.dart';
18

19
void main() {
20 21 22
  late MemoryFileSystem fileSystem;
  late FakeProcessManager processManager;
  late Config config;
23 24

  setUp(() {
25
    fileSystem = MemoryFileSystem.test();
26
    processManager = FakeProcessManager.empty();
27
    config = Config.test();
28 29
  });

30
  group('AndroidSdk', () {
31 32 33 34 35 36 37 38
    testUsingContext('constructing an AndroidSdk handles no matching lines in build.prop', () {
      final Directory sdkDir = createSdkDirectory(
        fileSystem: fileSystem,
        withAndroidN: true,
        // Does not have valid version string
        buildProp: '\n\n\n',
      );
      config.setValue('android-sdk', sdkDir.path);
39

40 41 42 43
      try {
        AndroidSdk.locateAndroidSdk()!;
      } on StateError catch (err) {
        fail('sdk.reinitialize() threw a StateError:\n$err');
44
      }
45 46 47 48
    }, overrides: <Type, Generator>{
      FileSystem: () => fileSystem,
      ProcessManager: () => FakeProcessManager.any(),
      Config: () => config,
49 50 51
    });

    testUsingContext('parse sdk', () {
52 53
      final Directory sdkDir = createSdkDirectory(fileSystem: fileSystem);
      config.setValue('android-sdk', sdkDir.path);
54

55
      final AndroidSdk sdk = AndroidSdk.locateAndroidSdk()!;
56
      expect(sdk.latestVersion, isNotNull);
57
      expect(sdk.latestVersion!.sdkLevel, 23);
58
    }, overrides: <Type, Generator>{
59
      FileSystem: () => fileSystem,
60
      ProcessManager: () => FakeProcessManager.any(),
61
      Config: () => config,
62 63 64
    });

    testUsingContext('parse sdk N', () {
65 66 67 68 69
      final Directory sdkDir = createSdkDirectory(
        withAndroidN: true,
        fileSystem: fileSystem,
      );
      config.setValue('android-sdk', sdkDir.path);
70

71
      final AndroidSdk sdk = AndroidSdk.locateAndroidSdk()!;
72
      expect(sdk.latestVersion, isNotNull);
73
      expect(sdk.latestVersion!.sdkLevel, 24);
74
    }, overrides: <Type, Generator>{
75
      FileSystem: () => fileSystem,
76
      ProcessManager: () => FakeProcessManager.any(),
77
      Config: () => config,
78
    });
79

80
    testUsingContext('returns sdkmanager path under cmdline tools on Linux/macOS', () {
81 82
      final Directory sdkDir = createSdkDirectory(fileSystem: fileSystem);
      config.setValue('android-sdk', sdkDir.path);
83

84
      final AndroidSdk sdk = AndroidSdk.locateAndroidSdk()!;
85
      fileSystem.file(
86
        fileSystem.path.join(sdk.directory.path, 'cmdline-tools', 'latest', 'bin', 'sdkmanager')
87 88
      ).createSync(recursive: true);

89
      expect(sdk.sdkManagerPath, fileSystem.path.join(sdk.directory.path, 'cmdline-tools', 'latest', 'bin', 'sdkmanager'));
90
    }, overrides: <Type, Generator>{
91
      FileSystem: () => fileSystem,
92
      ProcessManager: () => FakeProcessManager.any(),
93
      Platform: () => FakePlatform(),
94
      Config: () => config,
95 96
    });

97
    testUsingContext('returns sdkmanager path under cmdline tools (highest version) on Linux/macOS', () {
98 99 100 101 102
      final Directory sdkDir = createSdkDirectory(
        fileSystem: fileSystem,
        withSdkManager: false,
      );
      config.setValue('android-sdk', sdkDir.path);
103

104
      final AndroidSdk sdk = AndroidSdk.locateAndroidSdk()!;
105 106 107 108 109 110 111 112 113 114 115
      final List<String> versions = <String>['3.0', '2.1', '1.0'];
      for (final String version in versions) {
        fileSystem.file(
          fileSystem.path.join(sdk.directory.path, 'cmdline-tools', version, 'bin', 'sdkmanager')
        ).createSync(recursive: true);
      }

      expect(sdk.sdkManagerPath, fileSystem.path.join(sdk.directory.path, 'cmdline-tools', '3.0', 'bin', 'sdkmanager'));
    }, overrides: <Type, Generator>{
      FileSystem: () => fileSystem,
      ProcessManager: () => FakeProcessManager.any(),
116
      Platform: () => FakePlatform(),
117 118 119
      Config: () => config,
    });

120
    testUsingContext('Does not return sdkmanager under deprecated tools component', () {
121 122 123 124 125
      final Directory sdkDir = createSdkDirectory(
        fileSystem: fileSystem,
        withSdkManager: false,
      );
      config.setValue('android-sdk', sdkDir.path);
126

127
      final AndroidSdk sdk = AndroidSdk.locateAndroidSdk()!;
128 129 130
      fileSystem.file(
        fileSystem.path.join(sdk.directory.path, 'tools/bin/sdkmanager')
      ).createSync(recursive: true);
131

132
      expect(sdk.sdkManagerPath, null);
133 134 135
    }, overrides: <Type, Generator>{
      FileSystem: () => fileSystem,
      ProcessManager: () => FakeProcessManager.any(),
136
      Platform: () => FakePlatform(),
137 138 139
      Config: () => config,
    });

140
    testUsingContext('Can look up cmdline tool from deprecated tools path', () {
141 142 143 144 145
      final Directory sdkDir = createSdkDirectory(
        fileSystem: fileSystem,
        withSdkManager: false,
      );
      config.setValue('android-sdk', sdkDir.path);
146

147
      final AndroidSdk sdk = AndroidSdk.locateAndroidSdk()!;
148
      fileSystem.file(
149
        fileSystem.path.join(sdk.directory.path, 'tools/bin/foo')
150 151
      ).createSync(recursive: true);

152
      expect(sdk.getCmdlineToolsPath('foo'), '/.tmp_rand0/flutter_mock_android_sdk.rand0/tools/bin/foo');
153
    }, overrides: <Type, Generator>{
154
      FileSystem: () => fileSystem,
155
      ProcessManager: () => FakeProcessManager.any(),
156
      Platform: () => FakePlatform(),
157
      Config: () => config,
158 159
    });

160
    testUsingContext('Caches adb location after first access', () {
161 162
      final Directory sdkDir = createSdkDirectory(fileSystem: fileSystem);
      config.setValue('android-sdk', sdkDir.path);
163

164
      final AndroidSdk sdk = AndroidSdk.locateAndroidSdk()!;
165 166 167
      final File adbFile = fileSystem.file(
        fileSystem.path.join(sdk.directory.path, 'cmdline-tools', 'adb.exe')
      )..createSync(recursive: true);
168

169 170 171 172 173
      expect(sdk.adbPath,  fileSystem.path.join(sdk.directory.path, 'cmdline-tools', 'adb.exe'));

      adbFile.deleteSync(recursive: true);

      expect(sdk.adbPath,  fileSystem.path.join(sdk.directory.path, 'cmdline-tools', 'adb.exe'));
174
    }, overrides: <Type, Generator>{
175
      FileSystem: () => fileSystem,
176
      ProcessManager: () => FakeProcessManager.any(),
177
      Platform: () => FakePlatform(operatingSystem: 'windows'),
178
      Config: () => config,
179 180
    });

181
    testUsingContext('returns sdkmanager.bat path under cmdline tools for windows', () {
182 183
      final Directory sdkDir = createSdkDirectory(fileSystem: fileSystem);
      config.setValue('android-sdk', sdkDir.path);
184

185
      final AndroidSdk sdk = AndroidSdk.locateAndroidSdk()!;
186 187 188
      fileSystem.file(
        fileSystem.path.join(sdk.directory.path, 'cmdline-tools', 'latest', 'bin', 'sdkmanager.bat')
      ).createSync(recursive: true);
189

190 191
      expect(sdk.sdkManagerPath,
        fileSystem.path.join(sdk.directory.path, 'cmdline-tools', 'latest', 'bin', 'sdkmanager.bat'));
192 193 194 195
    }, overrides: <Type, Generator>{
      FileSystem: () => fileSystem,
      ProcessManager: () => FakeProcessManager.any(),
      Platform: () => FakePlatform(operatingSystem: 'windows'),
196
      Config: () => config,
197 198 199
    });

    testUsingContext('returns sdkmanager version', () {
200 201
      final Directory sdkDir = createSdkDirectory(fileSystem: fileSystem);
      config.setValue('android-sdk', sdkDir.path);
202 203 204
      processManager.addCommand(
        const FakeCommand(
            command: <String>[
205
            '/.tmp_rand0/flutter_mock_android_sdk.rand0/cmdline-tools/latest/bin/sdkmanager',
206 207 208 209 210
            '--version',
          ],
          stdout: '26.1.1\n',
        ),
      );
211
      final AndroidSdk sdk = AndroidSdk.locateAndroidSdk()!;
212

213 214
      expect(sdk.sdkManagerVersion, '26.1.1');
    }, overrides: <Type, Generator>{
215
      FileSystem: () => fileSystem,
216
      ProcessManager: () => processManager,
217
      Config: () => config,
218
      Platform: () => FakePlatform(environment: <String, String>{}),
219 220
    });

221
    testUsingContext('returns validate sdk is well formed', () {
222
      final Directory sdkDir = createBrokenSdkDirectory(fileSystem: fileSystem);
223
      processManager.addCommand(const FakeCommand(command: <String>[
224
        '/.tmp_rand0/flutter_mock_android_sdk.rand0/cmdline-tools/latest/bin/sdkmanager',
225 226
        '--version',
      ]));
227
      config.setValue('android-sdk', sdkDir.path);
228
      final AndroidSdk sdk = AndroidSdk.locateAndroidSdk()!;
229 230 231 232 233 234 235

      final List<String> validationIssues = sdk.validateSdkWellFormed();
      expect(validationIssues.first, 'No valid Android SDK platforms found in'
        ' /.tmp_rand0/flutter_mock_android_sdk.rand0/platforms. Candidates were:\n'
        '  - android-22\n'
        '  - android-23');
    }, overrides: <Type, Generator>{
236
      FileSystem: () => fileSystem,
237
      ProcessManager: () => processManager,
238
      Config: () => config,
239
      Platform: () => FakePlatform(),
240 241
    });

242
    testUsingContext('does not throw on sdkmanager version check failure', () {
243 244
      final Directory sdkDir = createSdkDirectory(fileSystem: fileSystem);
      config.setValue('android-sdk', sdkDir.path);
245 246 247
      processManager.addCommand(
        const FakeCommand(
          command: <String>[
248
            '/.tmp_rand0/flutter_mock_android_sdk.rand0/cmdline-tools/latest/bin/sdkmanager',
249 250 251 252 253 254 255
            '--version',
          ],
          stdout: '\n',
          stderr: 'Mystery error',
          exitCode: 1,
        ),
      );
256

257
      final AndroidSdk sdk = AndroidSdk.locateAndroidSdk()!;
258

259
      expect(sdk.sdkManagerVersion, isNull);
260
    }, overrides: <Type, Generator>{
261
      FileSystem: () => fileSystem,
262
      ProcessManager: () => processManager,
263
      Config: () => config,
264
      Platform: () => FakePlatform(environment: <String, String>{}),
265 266 267
    });

    testUsingContext('throws on sdkmanager version check if sdkmanager not found', () {
268 269
      final Directory sdkDir = createSdkDirectory(withSdkManager: false, fileSystem: fileSystem);
      config.setValue('android-sdk', sdkDir.path);
270
      processManager.excludedExecutables.add('/.tmp_rand0/flutter_mock_android_sdk.rand0/cmdline-tools/latest/bin/sdkmanager');
271
      final AndroidSdk? sdk = AndroidSdk.locateAndroidSdk();
272

273
      expect(() => sdk!.sdkManagerVersion, throwsToolExit());
274
    }, overrides: <Type, Generator>{
275
      FileSystem: () => fileSystem,
276
      ProcessManager: () => processManager,
277
      Config: () => config,
278
      Platform: () => FakePlatform(),
279
    });
280 281

    testUsingContext('returns avdmanager path under cmdline tools', () {
282 283
      final Directory sdkDir = createSdkDirectory(fileSystem: fileSystem);
      config.setValue('android-sdk', sdkDir.path);
284

285
      final AndroidSdk sdk = AndroidSdk.locateAndroidSdk()!;
286 287 288 289 290 291 292 293
      fileSystem.file(
        fileSystem.path.join(sdk.directory.path, 'cmdline-tools', 'latest', 'bin', 'avdmanager')
      ).createSync(recursive: true);

      expect(sdk.avdManagerPath, fileSystem.path.join(sdk.directory.path, 'cmdline-tools', 'latest', 'bin', 'avdmanager'));
    }, overrides: <Type, Generator>{
      FileSystem: () => fileSystem,
      ProcessManager: () => FakeProcessManager.any(),
294
      Platform: () => FakePlatform(),
295 296 297 298
      Config: () => config,
    });

    testUsingContext('returns avdmanager path under cmdline tools on windows', () {
299 300
      final Directory sdkDir = createSdkDirectory(fileSystem: fileSystem);
      config.setValue('android-sdk', sdkDir.path);
301

302
      final AndroidSdk sdk = AndroidSdk.locateAndroidSdk()!;
303 304 305 306 307 308 309 310 311 312 313 314 315
      fileSystem.file(
        fileSystem.path.join(sdk.directory.path, 'cmdline-tools', 'latest', 'bin', 'avdmanager.bat')
      ).createSync(recursive: true);

      expect(sdk.avdManagerPath, fileSystem.path.join(sdk.directory.path, 'cmdline-tools', 'latest', 'bin', 'avdmanager.bat'));
    }, overrides: <Type, Generator>{
      FileSystem: () => fileSystem,
      ProcessManager: () => FakeProcessManager.any(),
      Platform: () => FakePlatform(operatingSystem: 'windows'),
      Config: () => config,
    });

    testUsingContext("returns avdmanager path under tools if cmdline doesn't exist", () {
316 317
      final Directory sdkDir = createSdkDirectory(fileSystem: fileSystem);
      config.setValue('android-sdk', sdkDir.path);
318

319
      final AndroidSdk sdk = AndroidSdk.locateAndroidSdk()!;
320 321 322 323 324 325 326 327
      fileSystem.file(
        fileSystem.path.join(sdk.directory.path, 'tools', 'bin', 'avdmanager')
      ).createSync(recursive: true);

      expect(sdk.avdManagerPath, fileSystem.path.join(sdk.directory.path, 'tools', 'bin', 'avdmanager'));
    }, overrides: <Type, Generator>{
      FileSystem: () => fileSystem,
      ProcessManager: () => FakeProcessManager.any(),
328
      Platform: () => FakePlatform(),
329 330 331 332
      Config: () => config,
    });

    testUsingContext("returns avdmanager path under tools if cmdline doesn't exist on windows", () {
333 334
      final Directory sdkDir = createSdkDirectory(fileSystem: fileSystem);
      config.setValue('android-sdk', sdkDir.path);
335

336
      final AndroidSdk sdk = AndroidSdk.locateAndroidSdk()!;
337 338 339 340 341 342 343 344 345 346 347
      fileSystem.file(
        fileSystem.path.join(sdk.directory.path, 'tools', 'bin', 'avdmanager.bat')
      ).createSync(recursive: true);

      expect(sdk.avdManagerPath, fileSystem.path.join(sdk.directory.path, 'tools', 'bin', 'avdmanager.bat'));
    }, overrides: <Type, Generator>{
      FileSystem: () => fileSystem,
      ProcessManager: () => FakeProcessManager.any(),
      Platform: () => FakePlatform(operatingSystem: 'windows'),
      Config: () => config,
    });
348 349 350 351 352 353

    group('findJavaBinary', () {
      testUsingContext('returns the path of the JDK bundled with Android Studio, if it exists', () {
        final String androidStudioBundledJdkHome = globals.androidStudio!.javaPath!;
        final String expectedJavaBinaryPath = globals.fs.path.join(androidStudioBundledJdkHome, 'bin', 'java');

354 355
        final String? foundJavaBinaryPath = Java.find(
          logger: globals.logger,
356 357 358
          androidStudio: globals.androidStudio,
          fileSystem: globals.fs,
          platform: globals.platform,
359 360
          processManager: globals.processManager,
        )?.binaryPath;
361 362 363 364 365 366 367 368 369 370 371 372 373

        expect(foundJavaBinaryPath, expectedJavaBinaryPath);
      }, overrides: <Type, Generator>{
        FileSystem: () => MemoryFileSystem.test(),
        ProcessManager: () => FakeProcessManager.any(),
        Platform: () => FakePlatform(),
        Config: () => Config,
        AndroidStudio: () => FakeAndroidStudioWithJdk(),
      });

      testUsingContext('returns the current value of JAVA_HOME if it is set and the JDK bundled with Android Studio could not be found', () {
        final String expectedJavaBinaryPath = globals.fs.path.join('java-home-path', 'bin', 'java');

374 375
        final String? foundJavaBinaryPath = Java.find(
          logger: globals.logger,
376 377 378
          androidStudio: globals.androidStudio,
          fileSystem: globals.fs,
          platform: globals.platform,
379 380
          processManager: globals.processManager,
        )?.binaryPath;
381 382 383 384

        expect(foundJavaBinaryPath, expectedJavaBinaryPath);
      }, overrides: <Type, Generator>{
        FileSystem: () => MemoryFileSystem.test(),
385
        ProcessManager: () => FakeProcessManager.empty(),
386
        Platform: () => FakePlatform(environment: <String, String>{
387
          AndroidSdk.javaHomeEnvironmentVariable: 'java-home-path',
388 389 390 391 392 393
        }),
        Config: () => Config,
        AndroidStudio: () => FakeAndroidStudioWithoutJdk(),
      });

      testUsingContext('returns the java binary found on PATH if no other can be found', () {
394 395
        final String? foundJavaBinaryPath = Java.find(
          logger: globals.logger,
396 397 398
          androidStudio: globals.androidStudio,
          fileSystem: globals.fs,
          platform: globals.platform,
399 400
          processManager: globals.processManager,
        )?.binaryPath;
401

402
        expect(foundJavaBinaryPath, 'java');
403
      }, overrides: <Type, Generator>{
404
        Logger: () => BufferLogger.test(),
405
        FileSystem: () => MemoryFileSystem.test(),
406 407 408 409 410 411
        ProcessManager: () => FakeProcessManager.list(<FakeCommand>[
          const FakeCommand(
            command: <String>['which', 'java'],
            stdout: 'java',
          ),
        ]),
412 413 414 415 416 417
        Platform: () => FakePlatform(),
        Config: () => Config,
        AndroidStudio: () => FakeAndroidStudioWithoutJdk(),
      });

      testUsingContext('returns null if no java binary could be found', () {
418 419
        final String? foundJavaBinaryPath = Java.find(
          logger: globals.logger,
420 421 422
          androidStudio: globals.androidStudio,
          fileSystem: globals.fs,
          platform: globals.platform,
423 424
          processManager: globals.processManager,
        )?.binaryPath;
425 426 427

        expect(foundJavaBinaryPath, null);
      }, overrides: <Type, Generator>{
428
        Logger: () => BufferLogger.test(),
429
        FileSystem: () => MemoryFileSystem.test(),
430 431 432 433 434 435
        ProcessManager: () => FakeProcessManager.list(<FakeCommand>[
          const FakeCommand(
            command: <String>['which', 'java'],
            exitCode: 1,
          ),
        ]),
436 437 438 439 440
        Platform: () => FakePlatform(),
        Config: () => Config,
        AndroidStudio: () => FakeAndroidStudioWithoutJdk(),
      });
    });
441 442
  });
}
443 444

/// A broken SDK installation.
445
Directory createBrokenSdkDirectory({
446 447
  bool withAndroidN = false,
  bool withSdkManager = true,
448
  required FileSystem fileSystem,
449 450 451 452 453 454 455 456 457 458 459 460 461 462
}) {
  final Directory dir = fileSystem.systemTempDirectory.createTempSync('flutter_mock_android_sdk.');
  _createSdkFile(dir, 'licenses/dummy');
  _createSdkFile(dir, 'platform-tools/adb');

  _createSdkFile(dir, 'build-tools/sda/aapt');
  _createSdkFile(dir, 'build-tools/af/aapt');
  _createSdkFile(dir, 'build-tools/ljkasd/aapt');

  _createSdkFile(dir, 'platforms/android-22/android.jar');
  _createSdkFile(dir, 'platforms/android-23/android.jar');

  return dir;
}
463

464
void _createSdkFile(Directory dir, String filePath, { String? contents }) {
465 466 467 468
  final File file = dir.childFile(filePath);
  file.createSync(recursive: true);
  if (contents != null) {
    file.writeAsStringSync(contents, flush: true);
469 470
  }
}
471 472 473 474 475 476

Directory createSdkDirectory({
  bool withAndroidN = false,
  bool withSdkManager = true,
  bool withPlatformTools = true,
  bool withBuildTools = true,
477
  required FileSystem fileSystem,
478
  String buildProp = _buildProp,
479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507
}) {
  final Directory dir = fileSystem.systemTempDirectory.createTempSync('flutter_mock_android_sdk.');
  final String exe = globals.platform.isWindows ? '.exe' : '';
  final String bat = globals.platform.isWindows ? '.bat' : '';

  void createDir(Directory dir, String path) {
    final Directory directory = dir.fileSystem.directory(dir.fileSystem.path.join(dir.path, path));
    directory.createSync(recursive: true);
  }

  createDir(dir, 'licenses');

  if (withPlatformTools) {
    _createSdkFile(dir, 'platform-tools/adb$exe');
  }

  if (withBuildTools) {
    _createSdkFile(dir, 'build-tools/19.1.0/aapt$exe');
    _createSdkFile(dir, 'build-tools/22.0.1/aapt$exe');
    _createSdkFile(dir, 'build-tools/23.0.2/aapt$exe');
    if (withAndroidN) {
      _createSdkFile(dir, 'build-tools/24.0.0-preview/aapt$exe');
    }
  }

  _createSdkFile(dir, 'platforms/android-22/android.jar');
  _createSdkFile(dir, 'platforms/android-23/android.jar');
  if (withAndroidN) {
    _createSdkFile(dir, 'platforms/android-N/android.jar');
508
    _createSdkFile(dir, 'platforms/android-N/build.prop', contents: buildProp);
509 510 511
  }

  if (withSdkManager) {
512
    _createSdkFile(dir, 'cmdline-tools/latest/bin/sdkmanager$bat');
513 514 515 516 517 518 519 520 521
  }
  return dir;
}

const String _buildProp = r'''
ro.build.version.incremental=1624448
ro.build.version.sdk=24
ro.build.version.codename=REL
''';
522 523 524 525 526 527 528 529 530 531

class FakeAndroidStudioWithJdk extends Fake implements AndroidStudio {
  @override
  String? get javaPath => '/fake/android_studio/java/path/';
}

class FakeAndroidStudioWithoutJdk extends Fake implements AndroidStudio {
  @override
  String? get javaPath => null;
}