cache_test.dart 47 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:collection/collection.dart' show IterableExtension;
6 7
import 'package:file/file.dart';
import 'package:file/memory.dart';
8
import 'package:file_testing/file_testing.dart';
9
import 'package:flutter_tools/src/android/android_sdk.dart';
10
import 'package:flutter_tools/src/base/file_system.dart';
11
import 'package:flutter_tools/src/base/io.dart';
12
import 'package:flutter_tools/src/base/logger.dart';
13
import 'package:flutter_tools/src/base/os.dart';
14 15
import 'package:flutter_tools/src/base/platform.dart';
import 'package:flutter_tools/src/cache.dart';
16
import 'package:flutter_tools/src/dart/pub.dart';
17
import 'package:flutter_tools/src/flutter_cache.dart';
18
import 'package:flutter_tools/src/globals.dart' as globals;
19
import 'package:test/fake.dart';
20

21 22
import '../src/common.dart';
import '../src/context.dart';
23
import '../src/fakes.dart';
24

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

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

41
void main() {
42
  late FakeProcessManager fakeProcessManager;
43 44

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

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

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

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

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

      expect(cache.checkLockAcquired, throwsStateError);
70 71
    });

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

76
      expect(cache.checkLockAcquired, returnsNormally);
77 78
    });

79
    testWithoutContext('should not throw when lock is acquired', () async {
80
      final String? oldRoot = Cache.flutterRoot;
81
      Cache.flutterRoot = '';
82 83 84 85 86 87 88 89 90 91 92 93 94 95
      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;
      }
96
      // TODO(zanderso): implement support for lock so this can be tested with the memory file system.
97
    }, skip: true); // https://github.com/flutter/flutter/issues/87923
98 99 100

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

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

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

      expect(cache.checkLockAcquired, returnsNormally);
118 119
    });
  });
120

121
  group('Cache', () {
122 123 124 125 126
    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.');
127 128 129 130 131 132 133 134 135
      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);
136
      await artifact.update(FakeArtifactUpdater(), logger, fileSystem, FakeOperatingSystemUtils());
137

138
      expect(logger.warningText, contains('stamp write failed'));
139 140
    });

141 142 143 144 145
    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.');
146 147 148 149
      final Cache cache = FakeSecondaryCache()
        ..version = null // version is missing.
        ..artifactDirectory = artifactDir
        ..downloadDir = downloadDir;
150

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

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

157 158 159
    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());
160
      final GradleWrapper gradleWrapper = GradleWrapper(cache);
161 162
      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);
163

164
      expect(gradleWrapper.isUpToDateInner(fileSystem), false);
165 166
    });

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

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

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

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

192 193 194
    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());
195
      final GradleWrapper gradleWrapper = GradleWrapper(cache);
196 197 198 199
      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);
200

201
      expect(gradleWrapper.isUpToDateInner(fileSystem), true);
202 203
    });

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

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

217
      expect(await cache.isUpToDate(), isFalse);
218
    });
219

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

232
      expect(await cache.isUpToDate(), isTrue);
233
    });
234

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

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

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

255
    testWithoutContext("getter dyLdLibEntry concatenates the output of each artifact's dyLdLibEntry getter", () async {
256 257 258 259 260 261 262 263 264 265 266 267
      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': '',
      };
268 269 270 271
      final Cache cache = Cache.test(
        artifacts: <CachedArtifact>[artifact1, artifact2, artifact3],
        processManager: FakeProcessManager.any(),
      );
272

273 274 275 276 277 278
      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',
      );
    });
279

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

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

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

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

    testWithoutContext('overridden storage base url prints warning to STDERR', () async {
      final BufferLogger logger = BufferLogger.test();
      const String baseUrl = 'https://storage.com';
      final Cache cache = Cache.test(
        platform: FakePlatform(environment: <String, String>{
          'FLUTTER_STORAGE_BASE_URL': baseUrl,
        }),
        processManager: FakeProcessManager.any(),
        logger: logger,
      );

      expect(cache.storageBaseUrl, baseUrl);
      expect(logger.errorText, contains('Flutter assets will be downloaded from $baseUrl'));
      expect(logger.statusText, isEmpty);
    });
336
  });
337

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

344
  testWithoutContext('EngineCachedArtifact makes binary dirs readable and executable by all', () async {
345
    final FakeOperatingSystemUtils operatingSystemUtils = FakeOperatingSystemUtils();
346 347 348
    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.');
349 350 351
    final FakeSecondaryCache cache = FakeSecondaryCache()
      ..artifactDirectory = artifactDir
      ..downloadDir = downloadDir;
352 353 354 355 356 357 358 359 360 361
    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,
    );
362
    await artifact.updateInner(FakeArtifactUpdater(), fileSystem, operatingSystemUtils);
363 364 365
    final Directory dir = fileSystem.systemTempDirectory
        .listSync(recursive: true)
        .whereType<Directory>()
366
        .singleWhereOrNull((Directory directory) => directory.basename == 'bin_dir')!;
367 368 369

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

373
  testWithoutContext('EngineCachedArtifact removes unzipped FlutterMacOS.framework before replacing', () async {
374
    final OperatingSystemUtils operatingSystemUtils = FakeOperatingSystemUtils();
375 376 377
    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.');
378 379 380
    final FakeSecondaryCache cache = FakeSecondaryCache()
      ..artifactDirectory = artifactDir
      ..downloadDir = downloadDir;
381 382 383 384 385 386 387 388 389 390 391 392 393 394

    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,
    );
395
    await artifact.updateInner(FakeArtifactUpdater(), fileSystem, operatingSystemUtils);
396 397 398 399
    expect(unzippedFramework, exists);
    expect(staleFile, isNot(exists));
  });

400 401 402 403 404 405 406 407 408
  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();
409

410
    expect(iosUsbArtifacts.isUpToDateInner(fileSystem), true);
411

412
    ideviceScreenshotFile.deleteSync();
413

414
    expect(iosUsbArtifacts.isUpToDateInner(fileSystem), false);
415
  });
416

417 418 419 420 421 422 423
  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();
424

425
    expect(iosUsbArtifacts.isUpToDateInner(fileSystem), true);
426

427
    iproxy.deleteSync();
428

429 430
    expect(iosUsbArtifacts.isUpToDateInner(fileSystem), false);
  });
431

432 433 434 435 436
  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();
437

438 439
    expect(iosUsbArtifacts.isUpToDateInner(fileSystem), true);
  });
440

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

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

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

453
    expect(iosUsbArtifacts.archiveUri.toString(), isNot(contains('/unsigned/')));
454
  });
455

456 457
  testWithoutContext('FlutterRunnerDebugSymbols downloads Flutter runner debug symbols', () async {
    final FileSystem fileSystem = MemoryFileSystem.test();
458
    final Cache cache = FakeSecondaryCache()
459
      ..artifactDirectory = fileSystem.currentDirectory
460 461
      ..version = '123456';

462
    final FakeVersionedPackageResolver packageResolver = FakeVersionedPackageResolver();
463
    final FlutterRunnerDebugSymbols flutterRunnerDebugSymbols = FlutterRunnerDebugSymbols(
464
      cache,
465
      packageResolver: packageResolver,
466
      platform: FakePlatform(),
467
    );
468

469
    await flutterRunnerDebugSymbols.updateInner(FakeArtifactUpdater(), fileSystem, FakeOperatingSystemUtils());
470

471
    expect(packageResolver.resolved, <List<String>>[
472 473
      <String>['fuchsia-debug-symbols-x64', '123456'],
      <String>['fuchsia-debug-symbols-arm64', '123456'],
474 475
    ]);
  });
476

477
  testWithoutContext('FontSubset in universal artifacts', () {
478
    final Cache cache = Cache.test(processManager: FakeProcessManager.any());
479
    final FontSubsetArtifacts artifacts = FontSubsetArtifacts(cache, platform: FakePlatform());
480

481 482 483
    expect(artifacts.developmentArtifact, DevelopmentArtifact.universal);
  });

484 485 486
  testWithoutContext('FontSubset artifacts on x64 linux', () {
    fakeProcessManager.addCommand(unameCommandForX64);

487 488
    final Cache cache = createCache(FakePlatform());
    final FontSubsetArtifacts artifacts = FontSubsetArtifacts(cache, platform: FakePlatform());
489
    cache.includeAllPlatforms = false;
490

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

494 495 496
  testWithoutContext('FontSubset artifacts on arm64 linux', () {
    fakeProcessManager.addCommand(unameCommandForArm64);

497 498
    final Cache cache = createCache(FakePlatform());
    final FontSubsetArtifacts artifacts = FontSubsetArtifacts(cache, platform: FakePlatform());
499 500 501 502 503
    cache.includeAllPlatforms = false;

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

504
  testWithoutContext('FontSubset artifacts on windows', () {
505
    final Cache cache = createCache(FakePlatform(operatingSystem: 'windows'));
506
    final FontSubsetArtifacts artifacts = FontSubsetArtifacts(cache, platform: FakePlatform(operatingSystem: 'windows'));
507
    cache.includeAllPlatforms = false;
508

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

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

    final Cache cache = createCache(FakePlatform(operatingSystem: 'macos'));
531
    final FontSubsetArtifacts artifacts = FontSubsetArtifacts(cache, platform: FakePlatform(operatingSystem: 'macos'));
532
    cache.includeAllPlatforms = false;
533

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

537
  testWithoutContext('FontSubset artifacts on fuchsia', () {
538 539 540
    fakeProcessManager.addCommand(unameCommandForX64);

    final Cache cache = createCache(FakePlatform(operatingSystem: 'fuchsia'));
541
    final FontSubsetArtifacts artifacts = FontSubsetArtifacts(cache, platform: FakePlatform(operatingSystem: 'fuchsia'));
542
    cache.includeAllPlatforms = false;
543 544

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

547 548
  testWithoutContext('FontSubset artifacts for all platforms on x64 hosts', () {
      fakeProcessManager.addCommand(unameCommandForX64);
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>>[
569
        <String>['darwin-x64', 'darwin-arm64/font-subset.zip'],
570
        <String>['linux-arm64', 'linux-arm64/font-subset.zip'],
571
        <String>['windows-x64', 'windows-x64/font-subset.zip'], // arm64 windows hosts are not supported now
572
      ]);
573 574
  });

575 576 577 578 579 580 581 582 583 584 585 586 587
  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'],
    ]));
  });

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

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

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

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

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

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

  testWithoutContext('Linux desktop artifacts ignore filtering when requested', () {
623 624
    fakeProcessManager.addCommand(unameCommandForX64);

625
    final Cache cache = createCache(FakePlatform());
626
    final LinuxEngineArtifacts artifacts = LinuxEngineArtifacts(
627
      cache,
628 629
      platform: FakePlatform(operatingSystem: 'macos'),
    );
630 631
    cache.includeAllPlatforms = false;
    cache.platformOverrideArtifacts = <String>{'linux'};
632 633

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

636 637
  testWithoutContext('Linux desktop artifacts for x64 include profile and release artifacts', () {
      fakeProcessManager.addCommand(unameCommandForX64);
638

639
      final Cache cache = createCache(FakePlatform());
640 641
      final LinuxEngineArtifacts artifacts = LinuxEngineArtifacts(
        cache,
642
        platform: FakePlatform(),
643 644 645 646 647 648 649 650 651 652 653 654
      );

      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);

655
      final Cache cache = createCache(FakePlatform());
656 657
      final LinuxEngineArtifacts artifacts = LinuxEngineArtifacts(
        cache,
658
        platform: FakePlatform(),
659 660 661 662 663 664 665
      );

      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'],
      ]);
666
  });
667 668 669

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

673
    artifactSet.stampName = 'STAMP';
674 675 676 677 678 679 680
    final Cache cache = Cache(
      artifacts: <ArtifactSet>[
        artifactSet,
      ],
      logger: logger,
      fileSystem: fileSystem,
      platform: FakePlatform(),
681
      osUtils: FakeOperatingSystemUtils(),
682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697
      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();
698
    final FakeIosUsbArtifacts artifactSet = FakeIosUsbArtifacts();
699 700
    final BufferLogger logger = BufferLogger.test();

701
    artifactSet.stampName = 'STAMP';
702 703 704 705 706 707 708
    final Cache cache = Cache(
      artifacts: <ArtifactSet>[
        artifactSet,
      ],
      logger: logger,
      fileSystem: fileSystem,
      platform: FakePlatform(),
709
      osUtils: FakeOperatingSystemUtils(),
710 711 712 713 714 715 716 717 718 719 720 721 722 723 724
      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();
725
    final FakeIosUsbArtifacts artifactSet = FakeIosUsbArtifacts();
726 727
    final BufferLogger logger = BufferLogger.test();

728
    artifactSet.stampName = 'STAMP';
729 730 731 732 733 734 735
    final Cache cache = Cache(
      artifacts: <ArtifactSet>[
        artifactSet,
      ],
      logger: logger,
      fileSystem: fileSystem,
      platform: FakePlatform(),
736
      osUtils: FakeOperatingSystemUtils(),
737 738 739 740 741
      rootOverride: fileSystem.currentDirectory,
    );

    cache.clearStampFiles();

742
    expect(logger.warningText, contains('Failed to delete some stamp files'));
743
  });
744

745
  testWithoutContext('FlutterWebSdk fetches web artifacts and deletes previous directory contents', () async {
746
    final MemoryFileSystem fileSystem = MemoryFileSystem.test();
747
    final Directory internalDir = fileSystem.currentDirectory
748 749
      .childDirectory('cache')
      .childDirectory('bin')
750 751
      .childDirectory('internal');
    final File canvasKitVersionFile = internalDir.childFile('canvaskit.version');
752 753 754
    canvasKitVersionFile.createSync(recursive: true);
    canvasKitVersionFile.writeAsStringSync('abcdefg');

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

759 760 761
    final Cache cache = Cache.test(processManager: FakeProcessManager.any(), fileSystem: fileSystem);
    final Directory webCacheDirectory = cache.getWebSdkDirectory();
    final FakeArtifactUpdater artifactUpdater = FakeArtifactUpdater();
762
    final FlutterWebSdk webSdk = FlutterWebSdk(cache, platform: FakePlatform());
763

764 765 766
    final List<String> messages = <String>[];
    final List<String> downloads = <String>[];
    final List<String> locations = <String>[];
767
    artifactUpdater.onDownloadZipArchive = (String message, Uri uri, Directory location) {
768 769 770
      messages.add(message);
      downloads.add(uri.toString());
      locations.add(location.path);
771 772 773 774
      location.createSync(recursive: true);
      location.childFile('foo').createSync();
    };
    webCacheDirectory.childFile('bar').createSync(recursive: true);
775

776 777 778 779 780 781 782 783
    await webSdk.updateInner(artifactUpdater, fileSystem, FakeOperatingSystemUtils());

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

    expect(downloads, <String>[
784
      'https://storage.googleapis.com/flutter_infra_release/flutter/hijklmnop/flutter-web-sdk-linux-x64.zip',
785 786 787 788 789 790 791
      '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',
    ]);
792

793 794
    expect(webCacheDirectory.childFile('foo'), exists);
    expect(webCacheDirectory.childFile('bar'), isNot(exists));
795 796
  });

797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815
  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>{
816
          'FLUTTER_STORAGE_BASE_URL': 'https://flutter.storage.com/override',
817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837
        },
      ),
    );
    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',
838
      'https://flutter.storage.com/override/flutter_infra_release/cipd/flutter/web/canvaskit_bundle/+/abcdefg',
839 840 841
    ]);
  });

842 843 844
  testWithoutContext('FlutterWebSdk uses tryToDelete to handle directory edge cases', () async {
    final FileExceptionHandler handler = FileExceptionHandler();
    final MemoryFileSystem fileSystem = MemoryFileSystem.test(opHandle: handler.opHandle);
845 846 847
    final Cache cache = Cache.test(processManager: FakeProcessManager.any(), fileSystem: fileSystem);
    final Directory webCacheDirectory = cache.getWebSdkDirectory();
    final FakeArtifactUpdater artifactUpdater = FakeArtifactUpdater();
848
    final FlutterWebSdk webSdk = FlutterWebSdk(cache, platform: FakePlatform());
849

850 851 852 853 854 855
    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)));
856

857 858
    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'),
859 860 861
    ));
  });

862
  testWithoutContext('Cache handles exception thrown if stamp file cannot be parsed', () {
863 864
    final FileExceptionHandler exceptionHandler = FileExceptionHandler();
    final FileSystem fileSystem = MemoryFileSystem.test(opHandle: exceptionHandler.opHandle);
865 866 867 868 869
    final Logger logger = BufferLogger.test();
    final FakeCache cache = FakeCache(
      fileSystem: fileSystem,
      logger: logger,
      platform: FakePlatform(),
870
      osUtils: FakeOperatingSystemUtils()
871
    );
872
    final File file = fileSystem.file('stamp');
873 874 875 876
    cache.stampFile = file;

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

877 878 879 880 881 882
    file.createSync();
    exceptionHandler.addError(
      file,
      FileSystemOp.read,
      const FileSystemException(),
    );
883 884

    expect(cache.getStampFor('foo'), null);
885 886 887 888 889 890
  });

  testWithoutContext('Cache parses stamp file', () {
    final FileSystem fileSystem = MemoryFileSystem.test();
    final Logger logger = BufferLogger.test();
    final FakeCache cache = FakeCache(
891 892 893 894
      fileSystem: fileSystem,
      logger: logger,
      platform: FakePlatform(),
      osUtils: FakeOperatingSystemUtils()
895
    );
896

897 898
    final File file = fileSystem.file('stamp')..writeAsStringSync('ABC ');
    cache.stampFile = file;
899 900 901

    expect(cache.getStampFor('foo'), 'ABC');
  });
902 903 904 905 906 907 908 909

  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,
910
      pub: () => FakePub(),
911 912
    );

913
    expect(await pubDependencies.isUpToDate(fileSystem), false); // no package config
914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936

    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"
}
''');

937
    expect(await pubDependencies.isUpToDate(fileSystem), false); // dependencies are missing.
938

939
    fileSystem.file('.pub-cache/hosted/pub.dartlang.org/example-7.0.0/pubspec.yaml')
940 941
      .createSync(recursive: true);

942
    expect(await pubDependencies.isUpToDate(fileSystem), true);
943 944 945 946 947
  });

  testWithoutContext('PubDependencies updates via pub get', () async {
    final BufferLogger logger = BufferLogger.test();
    final MemoryFileSystem fileSystem = MemoryFileSystem.test();
948
    final FakePub pub = FakePub();
949 950 951 952 953 954
    final PubDependencies pubDependencies = PubDependencies(
      flutterRoot: () => '',
      logger: logger,
      pub: () => pub,
    );

955
    await pubDependencies.update(FakeArtifactUpdater(), logger, fileSystem, FakeOperatingSystemUtils());
956

957
    expect(pub.calledGet, 1);
958
  });
959

960 961 962 963 964 965
  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,);
  });

966 967 968 969 970 971 972 973 974
  // 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,);
  });

975
  group('AndroidMavenArtifacts', () {
976 977 978
    MemoryFileSystem? memoryFileSystem;
    Cache? cache;
    FakeAndroidSdk? fakeAndroidSdk;
979 980 981 982 983 984 985

    setUp(() {
      memoryFileSystem = MemoryFileSystem.test();
      cache = Cache.test(
        fileSystem: memoryFileSystem,
        processManager: FakeProcessManager.any(),
      );
986
      fakeAndroidSdk = FakeAndroidSdk();
987 988
    });

989
    testWithoutContext('AndroidMavenArtifacts has a specified development artifact', () async {
990
      final AndroidMavenArtifacts mavenArtifacts = AndroidMavenArtifacts(cache!, platform: FakePlatform());
991 992 993
      expect(mavenArtifacts.developmentArtifact, DevelopmentArtifact.androidMaven);
    });

994
    testUsingContext('AndroidMavenArtifacts can invoke Gradle resolve dependencies if Android SDK is present', () async {
995
      final String? oldRoot = Cache.flutterRoot;
996
      Cache.flutterRoot = '';
997
      try {
998 999
        final AndroidMavenArtifacts mavenArtifacts = AndroidMavenArtifacts(cache!, platform: FakePlatform());
        expect(await mavenArtifacts.isUpToDate(memoryFileSystem!), isFalse);
1000

1001
        final Directory gradleWrapperDir = cache!.getArtifactDirectory('gradle_wrapper')..createSync(recursive: true);
1002 1003
        gradleWrapperDir.childFile('gradlew').writeAsStringSync('irrelevant');
        gradleWrapperDir.childFile('gradlew.bat').writeAsStringSync('irrelevant');
1004

1005
        await mavenArtifacts.update(FakeArtifactUpdater(), BufferLogger.test(), memoryFileSystem!, FakeOperatingSystemUtils());
1006

1007 1008
        expect(await mavenArtifacts.isUpToDate(memoryFileSystem!), isFalse);
        expect(fakeAndroidSdk!.reinitialized, true);
1009 1010 1011
      } finally {
        Cache.flutterRoot = oldRoot;
      }
1012 1013 1014
    }, overrides: <Type, Generator>{
      Cache: () => cache,
      FileSystem: () => memoryFileSystem,
1015
      Platform: () => FakePlatform(),
1016 1017 1018 1019 1020 1021 1022 1023
      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',
1024
        ]),
1025
      ]),
1026
      AndroidSdk: () => fakeAndroidSdk,
1027 1028 1029
    });

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

1033
      await mavenArtifacts.update(FakeArtifactUpdater(), BufferLogger.test(), memoryFileSystem!, FakeOperatingSystemUtils());
1034

1035
      expect(await mavenArtifacts.isUpToDate(memoryFileSystem!), isFalse);
1036 1037 1038
    }, overrides: <Type, Generator>{
      Cache: () => cache,
      FileSystem: () => memoryFileSystem,
1039
      ProcessManager: () => FakeProcessManager.empty(),
1040
      AndroidSdk: () => null, // Android SDK was not located.
1041 1042
    });
  });
1043 1044 1045 1046 1047
}

class FakeCachedArtifact extends EngineCachedArtifact {
  FakeCachedArtifact({
    String stampName = 'STAMP',
1048 1049
    required Cache cache,
    required DevelopmentArtifact requiredArtifacts,
1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066
    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;
1067 1068
}

1069 1070 1071 1072 1073 1074 1075 1076
class FakeSimpleArtifact extends CachedArtifact {
  FakeSimpleArtifact(Cache cache) : super(
    'fake',
    cache,
    DevelopmentArtifact.universal,
  );

  @override
1077
  Future<void> updateInner(ArtifactUpdater artifactUpdater, FileSystem fileSystem, OperatingSystemUtils operatingSystemUtils) async { }
1078 1079
}

1080
class FakeSecondaryCachedArtifact extends Fake implements CachedArtifact {
1081 1082
  bool upToDate = false;
  bool didUpdate = false;
1083
  Exception? updateException;
1084 1085 1086 1087 1088

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

  @override
1089
  Future<void> update(ArtifactUpdater artifactUpdater, Logger logger, FileSystem fileSystem, OperatingSystemUtils operatingSystemUtils, {bool offline = false}) async {
1090
    if (updateException != null) {
1091
      throw updateException!;
1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106
    }
    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';
}
1107 1108

class FakeSecondaryCache extends Fake implements Cache {
1109 1110 1111 1112
  Directory? downloadDir;
  late Directory artifactDirectory;
  String? version;
  late void Function(String artifactName, String version) onSetStamp;
1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123

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

  @override
  Directory getDownloadDir() => artifactDirectory;

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

  @override
1124
  Directory getCacheDir(String name, { bool shouldCreate = true }) {
1125 1126 1127 1128 1129 1130 1131 1132 1133
    return artifactDirectory.childDirectory(name);
  }

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

  @override
1134
  String? getVersionFor(String artifactName) => version;
1135 1136 1137 1138 1139 1140

  @override
  void setStampFor(String artifactName, String version) {
    onSetStamp(artifactName, version);
  }
}
1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155

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
1156
  Future<void> get({
1157
    PubContext? context,
1158
    String? directory,
1159 1160 1161 1162
    bool skipIfAbsent = false,
    bool upgrade = false,
    bool offline = false,
    bool generateSyntheticPackage = false,
1163
    String? flutterRootOverride,
1164 1165
    bool checkUpToDate = false,
    bool shouldSkipThirdPartyGenerator = true,
1166
    bool printProgress = true,
1167
  }) async {
1168 1169 1170
    calledGet += 1;
  }
}
1171

1172 1173
class FakeCache extends Cache {
  FakeCache({
1174 1175 1176 1177
    required super.logger,
    required super.fileSystem,
    required super.platform,
    required super.osUtils,
1178 1179 1180 1181
  }) : super(
    artifacts: <ArtifactSet>[],
  );

1182
  late File stampFile;
1183 1184 1185 1186 1187 1188

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

1190 1191 1192 1193 1194 1195 1196 1197
class FakeAndroidSdk extends Fake implements AndroidSdk {
  bool reinitialized = false;

  @override
  void reinitialize() {
    reinitialized = true;
  }
}
1198 1199

class FakeArtifactUpdater extends Fake implements ArtifactUpdater {
1200 1201
  void Function(String, Uri, Directory)? onDownloadZipArchive;
  void Function(String, Uri, Directory)? onDownloadZipTarball;
1202

1203 1204
  @override
  Future<void> downloadZippedTarball(String message, Uri url, Directory location) async {
1205 1206 1207 1208 1209 1210
    onDownloadZipTarball?.call(message, url, location);
  }

  @override
  Future<void> downloadZipArchive(String message, Uri url, Directory location) async {
    onDownloadZipArchive?.call(message, url, location);
1211
  }
1212 1213 1214

  @override
  void removeDownloadedFiles() { }
1215
}