cache_test.dart 47.3 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
// @dart = 2.8

7 8
import 'package:file/file.dart';
import 'package:file/memory.dart';
9
import 'package:file_testing/file_testing.dart';
10
import 'package:flutter_tools/src/android/android_sdk.dart';
11
import 'package:flutter_tools/src/base/file_system.dart';
12
import 'package:flutter_tools/src/base/io.dart';
13
import 'package:flutter_tools/src/base/logger.dart';
14
import 'package:flutter_tools/src/base/os.dart';
15 16
import 'package:flutter_tools/src/base/platform.dart';
import 'package:flutter_tools/src/cache.dart';
17
import 'package:flutter_tools/src/dart/pub.dart';
18
import 'package:flutter_tools/src/flutter_cache.dart';
19
import 'package:flutter_tools/src/globals.dart' as globals;
20
import 'package:meta/meta.dart';
21
import 'package:test/fake.dart';
22

23 24
import '../src/common.dart';
import '../src/context.dart';
25
import '../src/fakes.dart';
26

27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
const FakeCommand unameCommandForX64 = FakeCommand(
  command: <String>[
    'uname',
    '-m',
  ],
  stdout: 'x86_64',
);

const FakeCommand unameCommandForArm64 = FakeCommand(
  command: <String>[
    'uname',
    '-m',
  ],
  stdout: 'aarch64',
);

43
void main() {
44 45 46
  FakeProcessManager fakeProcessManager;

  setUp(() {
47
    fakeProcessManager = FakeProcessManager.empty();
48 49 50 51 52 53 54 55 56
  });

  Cache createCache(Platform platform) {
    return Cache.test(
      platform: platform,
      processManager: fakeProcessManager
    );
  }

57
  group('Cache.checkLockAcquired', () {
58 59 60 61 62 63 64 65 66 67
    setUp(() {
      Cache.enableLocking();
    });

    tearDown(() {
      // Restore locking to prevent potential side-effects in
      // tests outside this group (this option is globally shared).
      Cache.enableLocking();
    });

68
    testWithoutContext('should throw when locking is not acquired', () {
69
      final Cache cache = Cache.test(processManager: FakeProcessManager.any());
70 71

      expect(cache.checkLockAcquired, throwsStateError);
72 73
    });

74
    testWithoutContext('should not throw when locking is disabled', () {
75
      final Cache cache = Cache.test(processManager: FakeProcessManager.any());
76 77
      Cache.disableLocking();

78
      expect(cache.checkLockAcquired, returnsNormally);
79 80
    });

81
    testWithoutContext('should not throw when lock is acquired', () async {
82
      final String oldRoot = Cache.flutterRoot;
83
      Cache.flutterRoot = '';
84 85 86 87 88 89 90 91 92 93 94 95 96 97
      try {
        final FileSystem fileSystem = MemoryFileSystem.test();
        final Cache cache = Cache.test(
            fileSystem: fileSystem, processManager: FakeProcessManager.any());
        fileSystem.file(fileSystem.path.join('bin', 'cache', 'lockfile'))
            .createSync(recursive: true);

        await cache.lock();

        expect(cache.checkLockAcquired, returnsNormally);
        expect(cache.releaseLock, returnsNormally);
      } finally {
        Cache.flutterRoot = oldRoot;
      }
98
      // TODO(zanderso): implement support for lock so this can be tested with the memory file system.
99
    }, skip: true); // https://github.com/flutter/flutter/issues/87923
100 101 102

    testWithoutContext('throws tool exit when lockfile open fails', () async {
      final FileSystem fileSystem = MemoryFileSystem.test();
103
      final Cache cache = Cache.test(fileSystem: fileSystem, processManager: FakeProcessManager.any());
104 105 106
      fileSystem.file(fileSystem.path.join('bin', 'cache', 'lockfile'))
        .createSync(recursive: true);

107
      expect(() async => cache.lock(), throwsToolExit());
108
      // TODO(zanderso): implement support for lock so this can be tested with the memory file system.
109
    }, skip: true); // https://github.com/flutter/flutter/issues/87923
110 111

    testWithoutContext('should not throw when FLUTTER_ALREADY_LOCKED is set', () {
112 113 114 115 116 117
     final Cache cache = Cache.test(
       platform: FakePlatform(environment: <String, String>{
        'FLUTTER_ALREADY_LOCKED': 'true',
       }),
       processManager: FakeProcessManager.any(),
     );
118 119

      expect(cache.checkLockAcquired, returnsNormally);
120 121
    });
  });
122

123
  group('Cache', () {
124 125 126 127 128
    testWithoutContext('Continues on failed stamp file update', () async {
      final FileSystem fileSystem = MemoryFileSystem.test();
      final BufferLogger logger = BufferLogger.test();
      final Directory artifactDir = fileSystem.systemTempDirectory.createTempSync('flutter_cache_test_artifact.');
      final Directory downloadDir = fileSystem.systemTempDirectory.createTempSync('flutter_cache_test_download.');
129 130 131 132 133 134 135 136 137
      final Cache cache = FakeSecondaryCache()
        ..version = 'asdasd'
        ..artifactDirectory = artifactDir
        ..downloadDir = downloadDir
        ..onSetStamp = (String name, String version) {
          throw const FileSystemException('stamp write failed');
        };

      final FakeSimpleArtifact artifact = FakeSimpleArtifact(cache);
138
      await artifact.update(FakeArtifactUpdater(), logger, fileSystem, FakeOperatingSystemUtils());
139

140
      expect(logger.warningText, contains('stamp write failed'));
141 142
    });

143 144 145 146 147
    testWithoutContext('Continues on missing version file', () async {
      final FileSystem fileSystem = MemoryFileSystem.test();
      final BufferLogger logger = BufferLogger.test();
      final Directory artifactDir = fileSystem.systemTempDirectory.createTempSync('flutter_cache_test_artifact.');
      final Directory downloadDir = fileSystem.systemTempDirectory.createTempSync('flutter_cache_test_download.');
148 149 150 151
      final Cache cache = FakeSecondaryCache()
        ..version = null // version is missing.
        ..artifactDirectory = artifactDir
        ..downloadDir = downloadDir;
152

153
      final FakeSimpleArtifact artifact = FakeSimpleArtifact(cache);
154
      await artifact.update(FakeArtifactUpdater(), logger, fileSystem, FakeOperatingSystemUtils());
155

156
      expect(logger.warningText, contains('No known version for the artifact name "fake"'));
157 158
    });

159 160 161
    testWithoutContext('Gradle wrapper should not be up to date, if some cached artifact is not available', () {
      final FileSystem fileSystem = MemoryFileSystem.test();
      final Cache cache = Cache.test(fileSystem: fileSystem, processManager: FakeProcessManager.any());
162
      final GradleWrapper gradleWrapper = GradleWrapper(cache);
163 164
      final Directory directory = cache.getCacheDir(fileSystem.path.join('artifacts', 'gradle_wrapper'));
      fileSystem.file(fileSystem.path.join(directory.path, 'gradle', 'wrapper', 'gradle-wrapper.jar')).createSync(recursive: true);
165

166
      expect(gradleWrapper.isUpToDateInner(fileSystem), false);
167 168
    });

169 170
    testWithoutContext('Gradle wrapper will delete .properties/NOTICES if they exist', () async {
      final FileSystem fileSystem = MemoryFileSystem.test();
171 172 173 174 175
      final Directory artifactDir = fileSystem.systemTempDirectory.createTempSync('flutter_cache_test_artifact.');
      final FakeSecondaryCache cache = FakeSecondaryCache()
        ..artifactDirectory = artifactDir
        ..version = '123456';

176 177 178 179 180 181 182
      final OperatingSystemUtils operatingSystemUtils = OperatingSystemUtils(
        processManager: FakeProcessManager.any(),
        platform: FakePlatform(),
        logger: BufferLogger.test(),
        fileSystem: fileSystem,
      );
      final GradleWrapper gradleWrapper = GradleWrapper(cache);
183
      final File propertiesFile = fileSystem.file(fileSystem.path.join(artifactDir.path, 'gradle', 'wrapper', 'gradle-wrapper.properties'))
184
        ..createSync(recursive: true);
185
      final File noticeFile = fileSystem.file(fileSystem.path.join(artifactDir.path, 'NOTICE'))
186 187 188 189 190 191 192 193
        ..createSync(recursive: true);

      await gradleWrapper.updateInner(FakeArtifactUpdater(), fileSystem, operatingSystemUtils);

      expect(propertiesFile, isNot(exists));
      expect(noticeFile, isNot(exists));
    });

194 195 196
    testWithoutContext('Gradle wrapper should be up to date, only if all cached artifact are available', () {
      final FileSystem fileSystem = MemoryFileSystem.test();
      final Cache cache = Cache.test(fileSystem: fileSystem, processManager: FakeProcessManager.any());
197
      final GradleWrapper gradleWrapper = GradleWrapper(cache);
198 199 200 201
      final Directory directory = cache.getCacheDir(fileSystem.path.join('artifacts', 'gradle_wrapper'));
      fileSystem.file(fileSystem.path.join(directory.path, 'gradle', 'wrapper', 'gradle-wrapper.jar')).createSync(recursive: true);
      fileSystem.file(fileSystem.path.join(directory.path, 'gradlew')).createSync(recursive: true);
      fileSystem.file(fileSystem.path.join(directory.path, 'gradlew.bat')).createSync(recursive: true);
202

203
      expect(gradleWrapper.isUpToDateInner(fileSystem), true);
204 205
    });

206
    testWithoutContext('should not be up to date, if some cached artifact is not', () async {
207
      final CachedArtifact artifact1 = FakeSecondaryCachedArtifact()
208
        ..upToDate = true;
209
      final CachedArtifact artifact2 = FakeSecondaryCachedArtifact()
210
        ..upToDate = false;
211 212 213 214 215 216 217 218
      final FileSystem fileSystem = MemoryFileSystem.test();

      final Cache cache = Cache.test(
        fileSystem: fileSystem,
        artifacts: <CachedArtifact>[artifact1, artifact2],
        processManager: FakeProcessManager.any(),
      );

219
      expect(await cache.isUpToDate(), isFalse);
220
    });
221

222
    testWithoutContext('should be up to date, if all cached artifacts are', () async {
223
      final FakeSecondaryCachedArtifact artifact1 = FakeSecondaryCachedArtifact()
224
        ..upToDate = true;
225
      final FakeSecondaryCachedArtifact artifact2 = FakeSecondaryCachedArtifact()
226
        ..upToDate = true;
227 228 229 230 231 232 233
      final FileSystem fileSystem = MemoryFileSystem.test();
      final Cache cache = Cache.test(
        fileSystem: fileSystem,
        artifacts: <CachedArtifact>[artifact1, artifact2],
        processManager: FakeProcessManager.any(),
      );

234
      expect(await cache.isUpToDate(), isTrue);
235
    });
236

237
    testWithoutContext('should update cached artifacts which are not up to date', () async {
238
      final FakeSecondaryCachedArtifact artifact1 = FakeSecondaryCachedArtifact()
239
        ..upToDate = true;
240
      final FakeSecondaryCachedArtifact artifact2 = FakeSecondaryCachedArtifact()
241
        ..upToDate = false;
242 243 244 245 246 247 248 249
      final FileSystem fileSystem = MemoryFileSystem.test();

      final Cache cache = Cache.test(
        fileSystem: fileSystem,
        artifacts: <CachedArtifact>[artifact1, artifact2],
        processManager: FakeProcessManager.any(),
      );

250
      await cache.updateAll(<DevelopmentArtifact>{
251
        DevelopmentArtifact.universal,
252
      });
253 254
      expect(artifact1.didUpdate, false);
      expect(artifact2.didUpdate, true);
255
    });
256

257
    testWithoutContext("getter dyLdLibEntry concatenates the output of each artifact's dyLdLibEntry getter", () async {
258 259 260 261 262 263 264 265 266 267 268 269
      final FakeIosUsbArtifacts artifact1 = FakeIosUsbArtifacts();
      final FakeIosUsbArtifacts artifact2 = FakeIosUsbArtifacts();
      final FakeIosUsbArtifacts artifact3 = FakeIosUsbArtifacts();
      artifact1.environment = <String, String>{
        'DYLD_LIBRARY_PATH': '/path/to/alpha:/path/to/beta',
      };
      artifact2.environment = <String, String>{
        'DYLD_LIBRARY_PATH': '/path/to/gamma:/path/to/delta:/path/to/epsilon',
      };
      artifact3.environment = <String, String>{
        'DYLD_LIBRARY_PATH': '',
      };
270 271 272 273
      final Cache cache = Cache.test(
        artifacts: <CachedArtifact>[artifact1, artifact2, artifact3],
        processManager: FakeProcessManager.any(),
      );
274

275 276 277 278 279 280
      expect(cache.dyLdLibEntry.key, 'DYLD_LIBRARY_PATH');
      expect(
        cache.dyLdLibEntry.value,
        '/path/to/alpha:/path/to/beta:/path/to/gamma:/path/to/delta:/path/to/epsilon',
      );
    });
281

282
    testWithoutContext('failed storage.googleapis.com download shows China warning', () async {
283
      final InternetAddress address = (await InternetAddress.lookup('storage.googleapis.com')).first;
284
      final FakeSecondaryCachedArtifact artifact1 = FakeSecondaryCachedArtifact()
285
        ..upToDate = false;
286
      final FakeSecondaryCachedArtifact artifact2 = FakeSecondaryCachedArtifact()
287 288
        ..upToDate = false
        ..updateException = SocketException(
289 290
        'Connection reset by peer',
        address: address,
291 292
      );

293 294 295 296 297 298
      final BufferLogger logger = BufferLogger.test();
      final Cache cache = Cache.test(
        artifacts: <CachedArtifact>[artifact1, artifact2],
        processManager: FakeProcessManager.any(),
        logger: logger,
      );
299
      await expectLater(
300
        () => cache.updateAll(<DevelopmentArtifact>{DevelopmentArtifact.universal}),
301
        throwsException,
302
      );
303
      expect(artifact1.didUpdate, true);
304
      // Don't continue when retrieval fails.
305
      expect(artifact2.didUpdate, false);
306 307 308 309
      expect(
        logger.errorText,
        contains('https://flutter.dev/community/china'),
      );
310
    });
311

312 313 314
    testWithoutContext('Invalid URI for FLUTTER_STORAGE_BASE_URL throws ToolExit', () async {
      final Cache cache = Cache.test(
        platform: FakePlatform(environment: <String, String>{
315 316 317 318
          'FLUTTER_STORAGE_BASE_URL': ' http://foo',
        }),
        processManager: FakeProcessManager.any(),
      );
319

Dan Field's avatar
Dan Field committed
320
      expect(() => cache.storageBaseUrl, throwsToolExit());
321
    });
322 323 324 325 326 327 328 329 330 331 332

    testWithoutContext('PubDependencies should be registered as web based', () async {
      final BufferLogger logger = BufferLogger.test();
      final PubDependencies pubDependencies = PubDependencies(
        flutterRoot: () => '',
        logger: logger,
        pub: () => FakePub(),
      );

      expect(pubDependencies.developmentArtifact, DevelopmentArtifact.web);
    });
333
  });
334

335
  testWithoutContext('flattenNameSubdirs', () {
336
    expect(flattenNameSubdirs(Uri.parse('http://flutter.dev/foo/bar'), MemoryFileSystem.test()), 'flutter.dev/foo/bar');
337
    expect(flattenNameSubdirs(Uri.parse('http://api.flutter.dev/foo/bar'), MemoryFileSystem.test()), 'api.flutter.dev/foo/bar');
338
    expect(flattenNameSubdirs(Uri.parse('https://www.flutter.dev'), MemoryFileSystem.test()), 'www.flutter.dev');
339
  });
340

341
  testWithoutContext('EngineCachedArtifact makes binary dirs readable and executable by all', () async {
342
    final FakeOperatingSystemUtils operatingSystemUtils = FakeOperatingSystemUtils();
343 344 345
    final FileSystem fileSystem = MemoryFileSystem.test();
    final Directory artifactDir = fileSystem.systemTempDirectory.createTempSync('flutter_cache_test_artifact.');
    final Directory downloadDir = fileSystem.systemTempDirectory.createTempSync('flutter_cache_test_download.');
346 347 348
    final FakeSecondaryCache cache = FakeSecondaryCache()
      ..artifactDirectory = artifactDir
      ..downloadDir = downloadDir;
349 350 351 352 353 354 355 356 357 358
    artifactDir.childDirectory('bin_dir').createSync();
    artifactDir.childFile('unused_url_path').createSync();

    final FakeCachedArtifact artifact = FakeCachedArtifact(
      cache: cache,
      binaryDirs: <List<String>>[
        <String>['bin_dir', 'unused_url_path'],
      ],
      requiredArtifacts: DevelopmentArtifact.universal,
    );
359
    await artifact.updateInner(FakeArtifactUpdater(), fileSystem, operatingSystemUtils);
360 361 362 363 364 365 366
    final Directory dir = fileSystem.systemTempDirectory
        .listSync(recursive: true)
        .whereType<Directory>()
        .singleWhere((Directory directory) => directory.basename == 'bin_dir', orElse: () => null);

    expect(dir, isNotNull);
    expect(dir.path, artifactDir.childDirectory('bin_dir').path);
367
    expect(operatingSystemUtils.chmods, <List<String>>[<String>['/.tmp_rand0/flutter_cache_test_artifact.rand0/bin_dir', 'a+r,a+x']]);
368
  });
369

370
  testWithoutContext('EngineCachedArtifact removes unzipped FlutterMacOS.framework before replacing', () async {
371
    final OperatingSystemUtils operatingSystemUtils = FakeOperatingSystemUtils();
372 373 374
    final FileSystem fileSystem = MemoryFileSystem.test();
    final Directory artifactDir = fileSystem.systemTempDirectory.createTempSync('flutter_cache_test_artifact.');
    final Directory downloadDir = fileSystem.systemTempDirectory.createTempSync('flutter_cache_test_download.');
375 376 377
    final FakeSecondaryCache cache = FakeSecondaryCache()
      ..artifactDirectory = artifactDir
      ..downloadDir = downloadDir;
378 379 380 381 382 383 384 385 386 387 388 389 390 391

    final Directory binDir = artifactDir.childDirectory('bin_dir')..createSync();
    binDir.childFile('FlutterMacOS.framework.zip').createSync();
    final Directory unzippedFramework = binDir.childDirectory('FlutterMacOS.framework');
    final File staleFile = unzippedFramework.childFile('stale_file')..createSync(recursive: true);
    artifactDir.childFile('unused_url_path').createSync();

    final FakeCachedArtifact artifact = FakeCachedArtifact(
      cache: cache,
      binaryDirs: <List<String>>[
        <String>['bin_dir', 'unused_url_path'],
      ],
      requiredArtifacts: DevelopmentArtifact.universal,
    );
392
    await artifact.updateInner(FakeArtifactUpdater(), fileSystem, operatingSystemUtils);
393 394 395 396
    expect(unzippedFramework, exists);
    expect(staleFile, isNot(exists));
  });

397 398 399 400 401 402 403 404 405
  testWithoutContext('IosUsbArtifacts verifies executables for libimobiledevice in isUpToDateInner', () async {
    final FileSystem fileSystem = MemoryFileSystem.test();
    final Cache cache = Cache.test(fileSystem: fileSystem, processManager: FakeProcessManager.any());
    final IosUsbArtifacts iosUsbArtifacts = IosUsbArtifacts('libimobiledevice', cache, platform: FakePlatform(operatingSystem: 'macos'));
    iosUsbArtifacts.location.createSync();
    final File ideviceScreenshotFile = iosUsbArtifacts.location.childFile('idevicescreenshot')
      ..createSync();
    iosUsbArtifacts.location.childFile('idevicesyslog')
      .createSync();
406

407
    expect(iosUsbArtifacts.isUpToDateInner(fileSystem), true);
408

409
    ideviceScreenshotFile.deleteSync();
410

411
    expect(iosUsbArtifacts.isUpToDateInner(fileSystem), false);
412
  });
413

414 415 416 417 418 419 420
  testWithoutContext('IosUsbArtifacts verifies iproxy for usbmuxd in isUpToDateInner', () async {
    final FileSystem fileSystem = MemoryFileSystem.test();
    final Cache cache = Cache.test(fileSystem: fileSystem, processManager: FakeProcessManager.any());
    final IosUsbArtifacts iosUsbArtifacts = IosUsbArtifacts('usbmuxd', cache, platform: FakePlatform(operatingSystem: 'macos'));
    iosUsbArtifacts.location.createSync();
    final File iproxy = iosUsbArtifacts.location.childFile('iproxy')
      ..createSync();
421

422
    expect(iosUsbArtifacts.isUpToDateInner(fileSystem), true);
423

424
    iproxy.deleteSync();
425

426 427
    expect(iosUsbArtifacts.isUpToDateInner(fileSystem), false);
  });
428

429 430 431 432 433
  testWithoutContext('IosUsbArtifacts does not verify executables for openssl in isUpToDateInner', () async {
    final FileSystem fileSystem = MemoryFileSystem.test();
    final Cache cache = Cache.test(fileSystem: fileSystem, processManager: FakeProcessManager.any());
    final IosUsbArtifacts iosUsbArtifacts = IosUsbArtifacts('openssl', cache, platform: FakePlatform(operatingSystem: 'macos'));
    iosUsbArtifacts.location.createSync();
434

435 436
    expect(iosUsbArtifacts.isUpToDateInner(fileSystem), true);
  });
437

438
  testWithoutContext('IosUsbArtifacts uses unsigned when specified', () async {
439
    final Cache cache = Cache.test(processManager: FakeProcessManager.any());
440
    cache.useUnsignedMacBinaries = true;
441

442 443 444
    final IosUsbArtifacts iosUsbArtifacts = IosUsbArtifacts('name', cache, platform: FakePlatform(operatingSystem: 'macos'));
    expect(iosUsbArtifacts.archiveUri.toString(), contains('/unsigned/'));
  });
445

446
  testWithoutContext('IosUsbArtifacts does not use unsigned when not specified', () async {
447
    final Cache cache = Cache.test(processManager: FakeProcessManager.any());
448
    final IosUsbArtifacts iosUsbArtifacts = IosUsbArtifacts('name', cache, platform: FakePlatform(operatingSystem: 'macos'));
449

450
    expect(iosUsbArtifacts.archiveUri.toString(), isNot(contains('/unsigned/')));
451
  });
452

453 454
  testWithoutContext('FlutterRunnerDebugSymbols downloads Flutter runner debug symbols', () async {
    final FileSystem fileSystem = MemoryFileSystem.test();
455 456 457
    final Cache cache = FakeSecondaryCache()
      ..version = '123456';

458
    final FakeVersionedPackageResolver packageResolver = FakeVersionedPackageResolver();
459
    final FlutterRunnerDebugSymbols flutterRunnerDebugSymbols = FlutterRunnerDebugSymbols(
460
      cache,
461
      packageResolver: packageResolver,
462
      platform: FakePlatform(),
463
    );
464

465
    await flutterRunnerDebugSymbols.updateInner(FakeArtifactUpdater(), fileSystem, FakeOperatingSystemUtils());
466

467
    expect(packageResolver.resolved, <List<String>>[
468 469
      <String>['fuchsia-debug-symbols-x64', '123456'],
      <String>['fuchsia-debug-symbols-arm64', '123456'],
470 471
    ]);
  });
472

473
  testWithoutContext('FontSubset in universal artifacts', () {
474
    final Cache cache = Cache.test(processManager: FakeProcessManager.any());
475
    final FontSubsetArtifacts artifacts = FontSubsetArtifacts(cache, platform: FakePlatform());
476

477 478 479
    expect(artifacts.developmentArtifact, DevelopmentArtifact.universal);
  });

480 481 482
  testWithoutContext('FontSubset artifacts on x64 linux', () {
    fakeProcessManager.addCommand(unameCommandForX64);

483 484
    final Cache cache = createCache(FakePlatform());
    final FontSubsetArtifacts artifacts = FontSubsetArtifacts(cache, platform: FakePlatform());
485
    cache.includeAllPlatforms = false;
486

487 488 489
    expect(artifacts.getBinaryDirs(), <List<String>>[<String>['linux-x64', 'linux-x64/font-subset.zip']]);
  });

490 491 492
  testWithoutContext('FontSubset artifacts on arm64 linux', () {
    fakeProcessManager.addCommand(unameCommandForArm64);

493 494
    final Cache cache = createCache(FakePlatform());
    final FontSubsetArtifacts artifacts = FontSubsetArtifacts(cache, platform: FakePlatform());
495 496 497 498 499
    cache.includeAllPlatforms = false;

    expect(artifacts.getBinaryDirs(), <List<String>>[<String>['linux-arm64', 'linux-arm64/font-subset.zip']]);
  });

500
  testWithoutContext('FontSubset artifacts on windows', () {
501
    final Cache cache = createCache(FakePlatform(operatingSystem: 'windows'));
502
    final FontSubsetArtifacts artifacts = FontSubsetArtifacts(cache, platform: FakePlatform(operatingSystem: 'windows'));
503
    cache.includeAllPlatforms = false;
504

505 506 507
    expect(artifacts.getBinaryDirs(), <List<String>>[<String>['windows-x64', 'windows-x64/font-subset.zip']]);
  });

508
  testWithoutContext('FontSubset artifacts on macos', () {
509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526
    fakeProcessManager.addCommands(<FakeCommand>[
      const FakeCommand(
        command: <String>[
          'which',
          'sysctl'
        ],
        stdout: '/sbin/sysctl',
      ),
      const FakeCommand(
        command: <String>[
          'sysctl',
          'hw.optional.arm64',
        ],
        stdout: 'hw.optional.arm64: 0',
      ),
    ]);

    final Cache cache = createCache(FakePlatform(operatingSystem: 'macos'));
527
    final FontSubsetArtifacts artifacts = FontSubsetArtifacts(cache, platform: FakePlatform(operatingSystem: 'macos'));
528
    cache.includeAllPlatforms = false;
529

530 531 532
    expect(artifacts.getBinaryDirs(), <List<String>>[<String>['darwin-x64', 'darwin-x64/font-subset.zip']]);
  });

533
  testWithoutContext('FontSubset artifacts on fuchsia', () {
534 535 536
    fakeProcessManager.addCommand(unameCommandForX64);

    final Cache cache = createCache(FakePlatform(operatingSystem: 'fuchsia'));
537
    final FontSubsetArtifacts artifacts = FontSubsetArtifacts(cache, platform: FakePlatform(operatingSystem: 'fuchsia'));
538
    cache.includeAllPlatforms = false;
539 540

    expect(artifacts.getBinaryDirs, throwsToolExit(message: 'Unsupported operating system: fuchsia'));
541
  });
542

543 544
  testWithoutContext('FontSubset artifacts for all platforms on x64 hosts', () {
      fakeProcessManager.addCommand(unameCommandForX64);
545

546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568
      final Cache cache = createCache(FakePlatform(operatingSystem: 'fuchsia'));
      final FontSubsetArtifacts artifacts = FontSubsetArtifacts(cache, platform: FakePlatform(operatingSystem: 'fuchsia'));
      cache.includeAllPlatforms = true;

      expect(artifacts.getBinaryDirs(), <List<String>>[
        <String>['darwin-x64', 'darwin-x64/font-subset.zip'],
        <String>['linux-x64', 'linux-x64/font-subset.zip'],
        <String>['windows-x64', 'windows-x64/font-subset.zip'],
      ]);
  });

  testWithoutContext('FontSubset artifacts for all platforms on arm64 hosts', () {
      fakeProcessManager.addCommand(unameCommandForArm64);

      final Cache cache = createCache(FakePlatform(operatingSystem: 'fuchsia'));
      final FontSubsetArtifacts artifacts = FontSubsetArtifacts(cache, platform: FakePlatform(operatingSystem: 'fuchsia'));
      cache.includeAllPlatforms = true;

      expect(artifacts.getBinaryDirs(), <List<String>>[
        <String>['darwin-x64', 'darwin-x64/font-subset.zip'], // arm64 macOS hosts are not supported now
        <String>['linux-arm64', 'linux-arm64/font-subset.zip'],
        <String>['windows-x64', 'windows-x64/font-subset.zip'], // arm64 macOS hosts are not supported now
      ]);
569 570
  });

571 572 573 574 575 576 577 578 579 580 581 582 583
  testWithoutContext('macOS desktop artifacts include all gen_snapshot binaries', () {
    final Cache cache = Cache.test(processManager: FakeProcessManager.any());
    final MacOSEngineArtifacts artifacts = MacOSEngineArtifacts(cache, platform: FakePlatform());
    cache.includeAllPlatforms = false;
    cache.platformOverrideArtifacts = <String>{'macos'};

    expect(artifacts.getBinaryDirs(), containsAll(<List<String>>[
      <String>['darwin-x64', 'darwin-x64/gen_snapshot.zip'],
      <String>['darwin-x64-profile', 'darwin-x64-profile/gen_snapshot.zip'],
      <String>['darwin-x64-release', 'darwin-x64-release/gen_snapshot.zip'],
    ]));
  });

584
  testWithoutContext('macOS desktop artifacts ignore filtering when requested', () {
585
    final Cache cache = Cache.test(processManager: FakeProcessManager.any());
586
    final MacOSEngineArtifacts artifacts = MacOSEngineArtifacts(cache, platform: FakePlatform());
587 588
    cache.includeAllPlatforms = false;
    cache.platformOverrideArtifacts = <String>{'macos'};
589 590 591 592

    expect(artifacts.getBinaryDirs(), isNotEmpty);
  });

593
  testWithoutContext('Windows desktop artifacts ignore filtering when requested', () {
594
    final Cache cache = Cache.test(processManager: FakeProcessManager.any());
595
    final WindowsEngineArtifacts artifacts = WindowsEngineArtifacts(
596
      cache,
597
      platform: FakePlatform(),
598
    );
599 600
    cache.includeAllPlatforms = false;
    cache.platformOverrideArtifacts = <String>{'windows'};
601 602 603 604

    expect(artifacts.getBinaryDirs(), isNotEmpty);
  });

605
  testWithoutContext('Windows desktop artifacts include profile and release artifacts', () {
606
    final Cache cache = Cache.test(processManager: FakeProcessManager.any());
607
    final WindowsEngineArtifacts artifacts = WindowsEngineArtifacts(
608
      cache,
609 610 611 612 613 614 615 616 617
      platform: FakePlatform(operatingSystem: 'windows'),
    );

    expect(artifacts.getBinaryDirs(), containsAll(<Matcher>[
      contains(contains('profile')),
      contains(contains('release')),
    ]));
  });

618 619 620 621 622 623 624 625 626 627 628 629 630 631
  testWithoutContext('Windows UWP desktop artifacts include profile, debug, and release artifacts', () {
    final Cache cache = Cache.test(processManager: FakeProcessManager.any());
    final WindowsUwpEngineArtifacts artifacts = WindowsUwpEngineArtifacts(
      cache,
      platform: FakePlatform(operatingSystem: 'windows'),
    );

    expect(artifacts.getBinaryDirs(), containsAll(<Matcher>[
      contains(contains('profile')),
      contains(contains('release')),
      contains(contains('debug')),
    ]));
  });

632
  testWithoutContext('Linux desktop artifacts ignore filtering when requested', () {
633 634
    fakeProcessManager.addCommand(unameCommandForX64);

635
    final Cache cache = createCache(FakePlatform());
636
    final LinuxEngineArtifacts artifacts = LinuxEngineArtifacts(
637
      cache,
638 639
      platform: FakePlatform(operatingSystem: 'macos'),
    );
640 641
    cache.includeAllPlatforms = false;
    cache.platformOverrideArtifacts = <String>{'linux'};
642 643

    expect(artifacts.getBinaryDirs(), isNotEmpty);
644
  });
645

646 647
  testWithoutContext('Linux desktop artifacts for x64 include profile and release artifacts', () {
      fakeProcessManager.addCommand(unameCommandForX64);
648

649
      final Cache cache = createCache(FakePlatform());
650 651
      final LinuxEngineArtifacts artifacts = LinuxEngineArtifacts(
        cache,
652
        platform: FakePlatform(),
653 654 655 656 657 658 659 660 661 662 663 664
      );

      expect(artifacts.getBinaryDirs(), <List<String>>[
        <String>['linux-x64', 'linux-x64/linux-x64-flutter-gtk.zip'],
        <String>['linux-x64-profile', 'linux-x64-profile/linux-x64-flutter-gtk.zip'],
        <String>['linux-x64-release', 'linux-x64-release/linux-x64-flutter-gtk.zip'],
      ]);
  });

  testWithoutContext('Linux desktop artifacts for arm64 include profile and release artifacts', () {
      fakeProcessManager.addCommand(unameCommandForArm64);

665
      final Cache cache = createCache(FakePlatform());
666 667
      final LinuxEngineArtifacts artifacts = LinuxEngineArtifacts(
        cache,
668
        platform: FakePlatform(),
669 670 671 672 673 674 675
      );

      expect(artifacts.getBinaryDirs(), <List<String>>[
        <String>['linux-arm64', 'linux-arm64/linux-arm64-flutter-gtk.zip'],
        <String>['linux-arm64-profile', 'linux-arm64-profile/linux-arm64-flutter-gtk.zip'],
        <String>['linux-arm64-release', 'linux-arm64-release/linux-arm64-flutter-gtk.zip'],
      ]);
676
  });
677 678 679

  testWithoutContext('Cache can delete stampfiles of artifacts', () {
    final FileSystem fileSystem = MemoryFileSystem.test();
680
    final FakeIosUsbArtifacts artifactSet = FakeIosUsbArtifacts();
681 682
    final BufferLogger logger = BufferLogger.test();

683
    artifactSet.stampName = 'STAMP';
684 685 686 687 688 689 690
    final Cache cache = Cache(
      artifacts: <ArtifactSet>[
        artifactSet,
      ],
      logger: logger,
      fileSystem: fileSystem,
      platform: FakePlatform(),
691
      osUtils: FakeOperatingSystemUtils(),
692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707
      rootOverride: fileSystem.currentDirectory,
    );
    final File toolStampFile = fileSystem.file('bin/cache/flutter_tools.stamp');
    final File stampFile = cache.getStampFileFor(artifactSet.stampName);
    stampFile.createSync(recursive: true);
    toolStampFile.createSync(recursive: true);

    cache.clearStampFiles();

    expect(logger.errorText, isEmpty);
    expect(stampFile, isNot(exists));
    expect(toolStampFile, isNot(exists));
  });

   testWithoutContext('Cache does not attempt to delete already missing stamp files', () {
    final FileSystem fileSystem = MemoryFileSystem.test();
708
    final FakeIosUsbArtifacts artifactSet = FakeIosUsbArtifacts();
709 710
    final BufferLogger logger = BufferLogger.test();

711
    artifactSet.stampName = 'STAMP';
712 713 714 715 716 717 718
    final Cache cache = Cache(
      artifacts: <ArtifactSet>[
        artifactSet,
      ],
      logger: logger,
      fileSystem: fileSystem,
      platform: FakePlatform(),
719
      osUtils: FakeOperatingSystemUtils(),
720 721 722 723 724 725 726 727 728 729 730 731 732 733 734
      rootOverride: fileSystem.currentDirectory,
    );
    final File toolStampFile = fileSystem.file('bin/cache/flutter_tools.stamp');
    final File stampFile = cache.getStampFileFor(artifactSet.stampName);
    toolStampFile.createSync(recursive: true);

    cache.clearStampFiles();

    expect(logger.errorText, isEmpty);
    expect(stampFile, isNot(exists));
    expect(toolStampFile, isNot(exists));
  });

  testWithoutContext('Cache catches file system exception from missing tool stamp file', () {
    final FileSystem fileSystem = MemoryFileSystem.test();
735
    final FakeIosUsbArtifacts artifactSet = FakeIosUsbArtifacts();
736 737
    final BufferLogger logger = BufferLogger.test();

738
    artifactSet.stampName = 'STAMP';
739 740 741 742 743 744 745
    final Cache cache = Cache(
      artifacts: <ArtifactSet>[
        artifactSet,
      ],
      logger: logger,
      fileSystem: fileSystem,
      platform: FakePlatform(),
746
      osUtils: FakeOperatingSystemUtils(),
747 748 749 750 751
      rootOverride: fileSystem.currentDirectory,
    );

    cache.clearStampFiles();

752
    expect(logger.warningText, contains('Failed to delete some stamp files'));
753
  });
754

755
  testWithoutContext('FlutterWebSdk fetches web artifacts and deletes previous directory contents', () async {
756
    final MemoryFileSystem fileSystem = MemoryFileSystem.test();
757
    final Directory internalDir = fileSystem.currentDirectory
758 759
      .childDirectory('cache')
      .childDirectory('bin')
760 761
      .childDirectory('internal');
    final File canvasKitVersionFile = internalDir.childFile('canvaskit.version');
762 763 764
    canvasKitVersionFile.createSync(recursive: true);
    canvasKitVersionFile.writeAsStringSync('abcdefg');

765 766 767 768
    final File engineVersionFile = internalDir.childFile('engine.version');
    engineVersionFile.createSync(recursive: true);
    engineVersionFile.writeAsStringSync('hijklmnop');

769 770 771
    final Cache cache = Cache.test(processManager: FakeProcessManager.any(), fileSystem: fileSystem);
    final Directory webCacheDirectory = cache.getWebSdkDirectory();
    final FakeArtifactUpdater artifactUpdater = FakeArtifactUpdater();
772
    final FlutterWebSdk webSdk = FlutterWebSdk(cache, platform: FakePlatform());
773

774 775 776
    final List<String> messages = <String>[];
    final List<String> downloads = <String>[];
    final List<String> locations = <String>[];
777
    artifactUpdater.onDownloadZipArchive = (String message, Uri uri, Directory location) {
778 779 780
      messages.add(message);
      downloads.add(uri.toString());
      locations.add(location.path);
781 782 783 784
      location.createSync(recursive: true);
      location.childFile('foo').createSync();
    };
    webCacheDirectory.childFile('bar').createSync(recursive: true);
785

786 787 788 789 790 791 792 793
    await webSdk.updateInner(artifactUpdater, fileSystem, FakeOperatingSystemUtils());

    expect(messages, <String>[
      'Downloading Web SDK...',
      'Downloading CanvasKit...',
    ]);

    expect(downloads, <String>[
794
      'https://storage.googleapis.com/flutter_infra_release/flutter/hijklmnop/flutter-web-sdk-linux-x64.zip',
795 796 797 798 799 800 801
      'https://chrome-infra-packages.appspot.com/dl/flutter/web/canvaskit_bundle/+/abcdefg',
    ]);

    expect(locations, <String>[
      'cache/bin/cache/flutter_web_sdk',
      'cache/bin/cache/flutter_web_sdk',
    ]);
802

803 804
    expect(webCacheDirectory.childFile('foo'), exists);
    expect(webCacheDirectory.childFile('bar'), isNot(exists));
805 806
  });

807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851
  testWithoutContext('FlutterWebSdk CanvasKit URL can be overridden via FLUTTER_STORAGE_BASE_URL', () async {
    final MemoryFileSystem fileSystem = MemoryFileSystem.test();
    final Directory internalDir = fileSystem.currentDirectory
      .childDirectory('cache')
      .childDirectory('bin')
      .childDirectory('internal');
    final File canvasKitVersionFile = internalDir.childFile('canvaskit.version');
    canvasKitVersionFile.createSync(recursive: true);
    canvasKitVersionFile.writeAsStringSync('abcdefg');

    final File engineVersionFile = internalDir.childFile('engine.version');
    engineVersionFile.createSync(recursive: true);
    engineVersionFile.writeAsStringSync('hijklmnop');

    final Cache cache = Cache.test(
      processManager: FakeProcessManager.any(),
      fileSystem: fileSystem,
      platform: FakePlatform(
        environment: <String, String>{
          'FLUTTER_STORAGE_BASE_URL': 'https://flutter.storage.com/override'
        },
      ),
    );
    final Directory webCacheDirectory = cache.getWebSdkDirectory();
    final FakeArtifactUpdater artifactUpdater = FakeArtifactUpdater();
    final FlutterWebSdk webSdk = FlutterWebSdk(cache, platform: FakePlatform());

    final List<String> downloads = <String>[];
    final List<String> locations = <String>[];
    artifactUpdater.onDownloadZipArchive = (String message, Uri uri, Directory location) {
      downloads.add(uri.toString());
      locations.add(location.path);
      location.createSync(recursive: true);
      location.childFile('foo').createSync();
    };
    webCacheDirectory.childFile('bar').createSync(recursive: true);

    await webSdk.updateInner(artifactUpdater, fileSystem, FakeOperatingSystemUtils());

    expect(downloads, <String>[
      'https://flutter.storage.com/override/flutter_infra_release/flutter/hijklmnop/flutter-web-sdk-linux-x64.zip',
      'https://flutter.storage.com/override/flutter_infra_release/cipd/flutter/web/canvaskit_bundle/+/abcdefg'
    ]);
  });

852 853 854
  testWithoutContext('FlutterWebSdk uses tryToDelete to handle directory edge cases', () async {
    final FileExceptionHandler handler = FileExceptionHandler();
    final MemoryFileSystem fileSystem = MemoryFileSystem.test(opHandle: handler.opHandle);
855 856 857
    final Cache cache = Cache.test(processManager: FakeProcessManager.any(), fileSystem: fileSystem);
    final Directory webCacheDirectory = cache.getWebSdkDirectory();
    final FakeArtifactUpdater artifactUpdater = FakeArtifactUpdater();
858
    final FlutterWebSdk webSdk = FlutterWebSdk(cache, platform: FakePlatform());
859

860 861 862 863 864 865
    artifactUpdater.onDownloadZipArchive = (String message, Uri uri, Directory location) {
      location.createSync(recursive: true);
      location.childFile('foo').createSync();
    };
    webCacheDirectory.childFile('bar').createSync(recursive: true);
    handler.addError(webCacheDirectory, FileSystemOp.delete, const FileSystemException('', '', OSError('', 2)));
866

867 868
    await expectLater(() => webSdk.updateInner(artifactUpdater, fileSystem, FakeOperatingSystemUtils()), throwsToolExit(
      message: RegExp('The Flutter tool tried to delete the file or directory cache/bin/cache/flutter_web_sdk but was unable to'),
869 870 871
    ));
  });

872
  testWithoutContext('Cache handles exception thrown if stamp file cannot be parsed', () {
873 874
    final FileExceptionHandler exceptionHandler = FileExceptionHandler();
    final FileSystem fileSystem = MemoryFileSystem.test(opHandle: exceptionHandler.opHandle);
875 876 877 878 879
    final Logger logger = BufferLogger.test();
    final FakeCache cache = FakeCache(
      fileSystem: fileSystem,
      logger: logger,
      platform: FakePlatform(),
880
      osUtils: FakeOperatingSystemUtils()
881
    );
882
    final File file = fileSystem.file('stamp');
883 884 885 886
    cache.stampFile = file;

    expect(cache.getStampFor('foo'), null);

887 888 889 890 891 892
    file.createSync();
    exceptionHandler.addError(
      file,
      FileSystemOp.read,
      const FileSystemException(),
    );
893 894

    expect(cache.getStampFor('foo'), null);
895 896 897 898 899 900
  });

  testWithoutContext('Cache parses stamp file', () {
    final FileSystem fileSystem = MemoryFileSystem.test();
    final Logger logger = BufferLogger.test();
    final FakeCache cache = FakeCache(
901 902 903 904
      fileSystem: fileSystem,
      logger: logger,
      platform: FakePlatform(),
      osUtils: FakeOperatingSystemUtils()
905
    );
906

907 908
    final File file = fileSystem.file('stamp')..writeAsStringSync('ABC ');
    cache.stampFile = file;
909 910 911

    expect(cache.getStampFor('foo'), 'ABC');
  });
912 913 914 915 916 917 918 919

  testWithoutContext('PubDependencies needs to be updated if the package config'
    ' file or the source directories are missing', () async {
    final BufferLogger logger = BufferLogger.test();
    final MemoryFileSystem fileSystem = MemoryFileSystem.test();
    final PubDependencies pubDependencies = PubDependencies(
      flutterRoot: () => '',
      logger: logger,
920
      pub: () => FakePub(),
921 922
    );

923
    expect(await pubDependencies.isUpToDate(fileSystem), false); // no package config
924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946

    fileSystem.file('packages/flutter_tools/.packages')
      ..createSync(recursive: true)
      ..writeAsStringSync('\n');
    fileSystem.file('packages/flutter_tools/.dart_tool/package_config.json')
      ..createSync(recursive: true)
      ..writeAsStringSync('''
{
  "configVersion": 2,
  "packages": [
    {
      "name": "example",
      "rootUri": "file:///.pub-cache/hosted/pub.dartlang.org/example-7.0.0",
      "packageUri": "lib/",
      "languageVersion": "2.7"
    }
  ],
  "generated": "2020-09-15T20:29:20.691147Z",
  "generator": "pub",
  "generatorVersion": "2.10.0-121.0.dev"
}
''');

947
    expect(await pubDependencies.isUpToDate(fileSystem), false); // dependencies are missing.
948 949 950 951

    fileSystem.file('.pub-cache/hosted/pub.dartlang.org/example-7.0.0/lib/foo.dart')
      .createSync(recursive: true);

952
    expect(await pubDependencies.isUpToDate(fileSystem), true);
953 954 955 956 957
  });

  testWithoutContext('PubDependencies updates via pub get', () async {
    final BufferLogger logger = BufferLogger.test();
    final MemoryFileSystem fileSystem = MemoryFileSystem.test();
958
    final FakePub pub = FakePub();
959 960 961 962 963 964
    final PubDependencies pubDependencies = PubDependencies(
      flutterRoot: () => '',
      logger: logger,
      pub: () => pub,
    );

965
    await pubDependencies.update(FakeArtifactUpdater(), logger, fileSystem, FakeOperatingSystemUtils());
966

967
    expect(pub.calledGet, 1);
968
  });
969

970 971 972 973 974 975
  testUsingContext('Check current DevTools version', () async {
    final String currentDevToolsVersion = globals.cache.devToolsVersion;
    final RegExp devToolsVersionFormat = RegExp(r'\d+\.\d+\.\d+(?:-\S+)?');
    expect(devToolsVersionFormat.allMatches(currentDevToolsVersion).length, 1,);
  });

976 977 978 979 980 981 982 983 984
  // Check that the build number matches the format documented here:
  // https://dart.dev/get-dart#release-channels
  testUsingContext('Check current Dart SDK build number', () async {
    final String currentDartSdkVersion = globals.cache.dartSdkBuild;
    final RegExp dartSdkVersionFormat = RegExp(r'\d+\.\d+\.\d+(?:-\S+)?');

    expect(dartSdkVersionFormat.allMatches(currentDartSdkVersion).length, 1,);
  });

985 986 987
  group('AndroidMavenArtifacts', () {
    MemoryFileSystem memoryFileSystem;
    Cache cache;
988
    FakeAndroidSdk fakeAndroidSdk;
989 990 991 992 993 994 995

    setUp(() {
      memoryFileSystem = MemoryFileSystem.test();
      cache = Cache.test(
        fileSystem: memoryFileSystem,
        processManager: FakeProcessManager.any(),
      );
996
      fakeAndroidSdk = FakeAndroidSdk();
997 998
    });

999
    testWithoutContext('AndroidMavenArtifacts has a specified development artifact', () async {
1000
      final AndroidMavenArtifacts mavenArtifacts = AndroidMavenArtifacts(cache, platform: FakePlatform());
1001 1002 1003
      expect(mavenArtifacts.developmentArtifact, DevelopmentArtifact.androidMaven);
    });

1004
    testUsingContext('AndroidMavenArtifacts can invoke Gradle resolve dependencies if Android SDK is present', () async {
1005
      final String oldRoot = Cache.flutterRoot;
1006
      Cache.flutterRoot = '';
1007 1008 1009
      try {
        final AndroidMavenArtifacts mavenArtifacts = AndroidMavenArtifacts(cache, platform: FakePlatform());
        expect(await mavenArtifacts.isUpToDate(memoryFileSystem), isFalse);
1010

1011 1012 1013
        final Directory gradleWrapperDir = cache.getArtifactDirectory('gradle_wrapper')..createSync(recursive: true);
        gradleWrapperDir.childFile('gradlew').writeAsStringSync('irrelevant');
        gradleWrapperDir.childFile('gradlew.bat').writeAsStringSync('irrelevant');
1014

1015
        await mavenArtifacts.update(FakeArtifactUpdater(), BufferLogger.test(), memoryFileSystem, FakeOperatingSystemUtils());
1016

1017 1018 1019 1020 1021
        expect(await mavenArtifacts.isUpToDate(memoryFileSystem), isFalse);
        expect(fakeAndroidSdk.reinitialized, true);
      } finally {
        Cache.flutterRoot = oldRoot;
      }
1022 1023 1024
    }, overrides: <Type, Generator>{
      Cache: () => cache,
      FileSystem: () => memoryFileSystem,
1025
      Platform: () => FakePlatform(),
1026 1027 1028 1029 1030 1031 1032 1033 1034 1035
      ProcessManager: () => FakeProcessManager.list(<FakeCommand>[
        const FakeCommand(command: <String>[
          '/cache/bin/cache/flutter_gradle_wrapper.rand0/gradlew',
          '-b',
          'packages/flutter_tools/gradle/resolve_dependencies.gradle',
          '--project-cache-dir',
          'cache/bin/cache/flutter_gradle_wrapper.rand0',
          'resolveDependencies',
        ])
      ]),
1036
      AndroidSdk: () => fakeAndroidSdk
1037 1038 1039
    });

    testUsingContext('AndroidMavenArtifacts is a no-op if the Android SDK is absent', () async {
1040
      final AndroidMavenArtifacts mavenArtifacts = AndroidMavenArtifacts(cache, platform: FakePlatform());
1041
      expect(await mavenArtifacts.isUpToDate(memoryFileSystem), isFalse);
1042

1043
      await mavenArtifacts.update(FakeArtifactUpdater(), BufferLogger.test(), memoryFileSystem, FakeOperatingSystemUtils());
1044 1045 1046 1047 1048

      expect(await mavenArtifacts.isUpToDate(memoryFileSystem), isFalse);
    }, overrides: <Type, Generator>{
      Cache: () => cache,
      FileSystem: () => memoryFileSystem,
1049
      ProcessManager: () => FakeProcessManager.empty(),
1050
      AndroidSdk: () => null // Android SDK was not located.
1051 1052
    });
  });
1053 1054 1055 1056 1057 1058
}

class FakeCachedArtifact extends EngineCachedArtifact {
  FakeCachedArtifact({
    String stampName = 'STAMP',
    @required Cache cache,
1059
    DevelopmentArtifact requiredArtifacts,
1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076
    this.binaryDirs = const <List<String>>[],
    this.licenseDirs = const <String>[],
    this.packageDirs = const <String>[],
  }) : super(stampName, cache, requiredArtifacts);

  final List<List<String>> binaryDirs;
  final List<String> licenseDirs;
  final List<String> packageDirs;

  @override
  List<List<String>> getBinaryDirs() => binaryDirs;

  @override
  List<String> getLicenseDirs() => licenseDirs;

  @override
  List<String> getPackageDirs() => packageDirs;
1077 1078
}

1079 1080 1081 1082 1083 1084 1085 1086
class FakeSimpleArtifact extends CachedArtifact {
  FakeSimpleArtifact(Cache cache) : super(
    'fake',
    cache,
    DevelopmentArtifact.universal,
  );

  @override
1087
  Future<void> updateInner(ArtifactUpdater artifactUpdater, FileSystem fileSystem, OperatingSystemUtils operatingSystemUtils) async { }
1088 1089
}

1090
class FakeSecondaryCachedArtifact extends Fake implements CachedArtifact {
1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116
  bool upToDate = false;
  bool didUpdate = false;
  Exception updateException;

  @override
  Future<bool> isUpToDate(FileSystem fileSystem) async => upToDate;

  @override
  Future<void> update(ArtifactUpdater artifactUpdater, Logger logger, FileSystem fileSystem, OperatingSystemUtils operatingSystemUtils) async {
    if (updateException != null) {
      throw updateException;
    }
    didUpdate = true;
  }

  @override
  DevelopmentArtifact get developmentArtifact => DevelopmentArtifact.universal;
}

class FakeIosUsbArtifacts extends Fake implements IosUsbArtifacts {
  @override
  Map<String, String> environment =  <String, String>{};

  @override
  String stampName = 'ios-usb';
}
1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133

class FakeSecondaryCache extends Fake implements Cache {
  Directory downloadDir;
  Directory artifactDirectory;
  String version;
  void Function(String artifactName, String version) onSetStamp;

  @override
  String get storageBaseUrl => 'https://storage.googleapis.com';

  @override
  Directory getDownloadDir() => artifactDirectory;

  @override
  Directory getArtifactDirectory(String name) => artifactDirectory;

  @override
1134
  Directory getCacheDir(String name, { bool shouldCreate = true }) {
1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150
    return artifactDirectory.childDirectory(name);
  }

  @override
  File getLicenseFile() {
    return artifactDirectory.childFile('LICENSE');
  }

  @override
  String getVersionFor(String artifactName) => version;

  @override
  void setStampFor(String artifactName, String version) {
    onSetStamp(artifactName, version);
  }
}
1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165

class FakeVersionedPackageResolver extends Fake implements VersionedPackageResolver {
  final List<List<String>> resolved = <List<String>>[];

  @override
  String resolveUrl(String packageName, String version) {
    resolved.add(<String>[packageName, version]);
    return '';
  }
}

class FakePub extends Fake implements Pub {
  int calledGet = 0;

  @override
1166 1167 1168 1169 1170 1171 1172 1173 1174 1175
  Future<void> get({
    PubContext context,
    String directory,
    bool skipIfAbsent = false,
    bool upgrade = false,
    bool offline = false,
    bool generateSyntheticPackage = false,
    String flutterRootOverride,
    bool checkUpToDate = false,
    bool shouldSkipThirdPartyGenerator = true,
1176
    bool printProgress = true,
1177
  }) async {
1178 1179 1180
    calledGet += 1;
  }
}
1181

1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202
class FakeCache extends Cache {
  FakeCache({
    @required Logger logger,
    @required FileSystem fileSystem,
    @required Platform platform,
    @required OperatingSystemUtils osUtils,
  }) : super(
    logger: logger,
    fileSystem: fileSystem,
    platform: platform,
    osUtils: osUtils,
    artifacts: <ArtifactSet>[],
  );

  File stampFile;

  @override
  File getStampFileFor(String artifactName) {
    return stampFile;
  }
}
1203

1204 1205 1206 1207 1208 1209 1210 1211
class FakeAndroidSdk extends Fake implements AndroidSdk {
  bool reinitialized = false;

  @override
  void reinitialize() {
    reinitialized = true;
  }
}
1212 1213

class FakeArtifactUpdater extends Fake implements ArtifactUpdater {
1214 1215 1216
  void Function(String, Uri, Directory) onDownloadZipArchive;
  void Function(String, Uri, Directory) onDownloadZipTarball;

1217 1218
  @override
  Future<void> downloadZippedTarball(String message, Uri url, Directory location) async {
1219 1220 1221 1222 1223 1224
    onDownloadZipTarball?.call(message, url, location);
  }

  @override
  Future<void> downloadZipArchive(String message, Uri url, Directory location) async {
    onDownloadZipArchive?.call(message, url, location);
1225
  }
1226 1227 1228

  @override
  void removeDownloadedFiles() { }
1229
}