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

import 'package:file/file.dart';
import 'package:file/memory.dart';
7
import 'package:file_testing/file_testing.dart';
8
import 'package:flutter_tools/src/android/gradle_utils.dart';
9
import 'package:flutter_tools/src/base/file_system.dart';
10
import 'package:flutter_tools/src/base/io.dart' show InternetAddress, SocketException;
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 18 19
import 'package:meta/meta.dart';
import 'package:mockito/mockito.dart';
import 'package:process/process.dart';
20

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

void main() {
25
  group('Cache.checkLockAcquired', () {
26 27 28 29 30 31 32 33 34 35
    setUp(() {
      Cache.enableLocking();
    });

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

36 37 38 39
    testWithoutContext('should throw when locking is not acquired', () {
      final Cache cache = Cache.test();

      expect(cache.checkLockAcquired, throwsStateError);
40 41
    });

42 43
    testWithoutContext('should not throw when locking is disabled', () {
      final Cache cache = Cache.test();
44 45
      Cache.disableLocking();

46
      expect(cache.checkLockAcquired, returnsNormally);
47 48
    });

49 50 51 52 53 54
    testWithoutContext('should not throw when lock is acquired', () async {
      Cache.flutterRoot = '';
      final FileSystem fileSystem = MemoryFileSystem.test();
      final Cache cache = Cache.test(fileSystem: fileSystem);
      fileSystem.file(fileSystem.path.join('bin', 'cache', 'lockfile'))
        .createSync(recursive: true);
55

56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76
      await cache.lock();

      expect(cache.checkLockAcquired, returnsNormally);
      expect(cache.releaseLock, returnsNormally);
    }, skip: true); // TODO(jonahwilliams): implement support for lock so this can be tested with the memory file system.

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

      expect(() async => await cache.lock(), throwsToolExit());
    }, skip: true); // TODO(jonahwilliams): implement support for lock so this can be tested with the memory file system.

    testWithoutContext('should not throw when FLUTTER_ALREADY_LOCKED is set', () {
     final Cache cache = Cache.test(platform: FakePlatform(environment: <String, String>{
       'FLUTTER_ALREADY_LOCKED': 'true',
     }));

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

80
  group('Cache', () {
81 82 83 84 85 86
    testWithoutContext('Continues on failed stamp file update', () async {
      final FileSystem fileSystem = MemoryFileSystem.test();
      final BufferLogger logger = BufferLogger.test();
      final Cache mockCache = MockCache();
      final Directory artifactDir = fileSystem.systemTempDirectory.createTempSync('flutter_cache_test_artifact.');
      final Directory downloadDir = fileSystem.systemTempDirectory.createTempSync('flutter_cache_test_download.');
87

88 89 90 91 92 93
      when(mockCache.getArtifactDirectory(any)).thenReturn(artifactDir);
      when(mockCache.getDownloadDir()).thenReturn(downloadDir);
      when(mockCache.setStampFor(any, any)).thenAnswer((_) {
        throw const FileSystemException('stamp write failed');
      });
      final FakeSimpleArtifact artifact = FakeSimpleArtifact(mockCache);
94 95 96
      await artifact.update(MockArtifactUpdater(), logger, fileSystem, MockOperatingSystemUtils());

      expect(logger.errorText, contains('stamp write failed'));
97 98
    });

99 100 101
    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());
102
      final GradleWrapper gradleWrapper = GradleWrapper(cache);
103 104
      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);
105

106
      expect(gradleWrapper.isUpToDateInner(fileSystem), false);
107 108
    });

109 110 111
    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());
112
      final GradleWrapper gradleWrapper = GradleWrapper(cache);
113 114 115 116
      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);
117

118
      expect(gradleWrapper.isUpToDateInner(fileSystem), true);
119 120
    });

121
    testWithoutContext('should not be up to date, if some cached artifact is not', () async {
122 123
      final CachedArtifact artifact1 = MockCachedArtifact();
      final CachedArtifact artifact2 = MockCachedArtifact();
124 125 126 127 128 129 130 131 132 133
      final FileSystem fileSystem = MemoryFileSystem.test();

      when(artifact1.isUpToDate(fileSystem)).thenAnswer((Invocation _) => Future<bool>.value(true));
      when(artifact2.isUpToDate(fileSystem)).thenAnswer((Invocation _) => Future<bool>.value(false));
      final Cache cache = Cache.test(
        fileSystem: fileSystem,
        artifacts: <CachedArtifact>[artifact1, artifact2],
        processManager: FakeProcessManager.any(),
      );

134
      expect(await cache.isUpToDate(), isFalse);
135
    });
136

137
    testWithoutContext('should be up to date, if all cached artifacts are', () async {
138 139
      final CachedArtifact artifact1 = MockCachedArtifact();
      final CachedArtifact artifact2 = MockCachedArtifact();
140 141 142 143 144 145 146 147 148 149
      final FileSystem fileSystem = MemoryFileSystem.test();

      when(artifact1.isUpToDate(fileSystem)).thenAnswer((Invocation _) => Future<bool>.value(true));
      when(artifact2.isUpToDate(fileSystem)).thenAnswer((Invocation _) => Future<bool>.value(true));
      final Cache cache = Cache.test(
        fileSystem: fileSystem,
        artifacts: <CachedArtifact>[artifact1, artifact2],
        processManager: FakeProcessManager.any(),
      );

150
      expect(await cache.isUpToDate(), isTrue);
151
    });
152

153
    testWithoutContext('should update cached artifacts which are not up to date', () async {
154 155
      final CachedArtifact artifact1 = MockCachedArtifact();
      final CachedArtifact artifact2 = MockCachedArtifact();
156 157 158 159 160 161 162 163 164 165
      final FileSystem fileSystem = MemoryFileSystem.test();

      when(artifact1.isUpToDate(fileSystem)).thenAnswer((Invocation _) => Future<bool>.value(true));
      when(artifact2.isUpToDate(fileSystem)).thenAnswer((Invocation _) => Future<bool>.value(false));
      final Cache cache = Cache.test(
        fileSystem: fileSystem,
        artifacts: <CachedArtifact>[artifact1, artifact2],
        processManager: FakeProcessManager.any(),
      );

166 167 168
      await cache.updateAll(<DevelopmentArtifact>{
        null,
      });
169 170
      verifyNever(artifact1.update(any, any, any, any));
      verify(artifact2.update(any, any, any, any));
171
    });
172

173
    testWithoutContext("getter dyLdLibEntry concatenates the output of each artifact's dyLdLibEntry getter", () async {
174 175 176 177 178 179 180 181 182 183 184 185 186 187 188
      final IosUsbArtifacts artifact1 = MockIosUsbArtifacts();
      final IosUsbArtifacts artifact2 = MockIosUsbArtifacts();
      final IosUsbArtifacts artifact3 = MockIosUsbArtifacts();
      when(artifact1.environment)
          .thenReturn(<String, String>{
            'DYLD_LIBRARY_PATH': '/path/to/alpha:/path/to/beta',
          });
      when(artifact2.environment)
          .thenReturn(<String, String>{
            'DYLD_LIBRARY_PATH': '/path/to/gamma:/path/to/delta:/path/to/epsilon',
          });
      when(artifact3.environment)
          .thenReturn(<String, String>{
            'DYLD_LIBRARY_PATH': '',
          });
189 190 191 192
      final Cache cache = Cache.test(
        artifacts: <CachedArtifact>[artifact1, artifact2, artifact3],
        processManager: FakeProcessManager.any(),
      );
193

194 195 196 197 198 199
      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',
      );
    });
200

201
    testWithoutContext('failed storage.googleapis.com download shows China warning', () async {
202 203
      final CachedArtifact artifact1 = MockCachedArtifact();
      final CachedArtifact artifact2 = MockCachedArtifact();
204 205
      when(artifact1.isUpToDate(any)).thenAnswer((Invocation _) => Future<bool>.value(false));
      when(artifact2.isUpToDate(any)).thenAnswer((Invocation _) => Future<bool>.value(false));
206
      final MockInternetAddress address = MockInternetAddress();
207
      when(address.host).thenReturn('storage.googleapis.com');
208
      when(artifact1.update(any, any, any, any)).thenThrow(SocketException(
209 210 211
        'Connection reset by peer',
        address: address,
      ));
212 213 214 215 216 217
      final BufferLogger logger = BufferLogger.test();
      final Cache cache = Cache.test(
        artifacts: <CachedArtifact>[artifact1, artifact2],
        processManager: FakeProcessManager.any(),
        logger: logger,
      );
218
      try {
219 220 221
        await cache.updateAll(<DevelopmentArtifact>{
          null,
        });
222
        fail('Mock thrown exception expected');
223
      } on Exception {
224
        verify(artifact1.update(any, any, any, any));
225
        // Don't continue when retrieval fails.
226
        verifyNever(artifact2.update(any, any, any, any));
227
        expect(
228
          logger.errorText,
229
          contains('https://flutter.dev/community/china'),
230 231 232
        );
      }
    });
233

234 235 236 237 238 239
    testWithoutContext('Invalid URI for FLUTTER_STORAGE_BASE_URL throws ToolExit', () async {
      final Cache cache = Cache.test(
        platform: FakePlatform(environment: <String, String>{
        'FLUTTER_STORAGE_BASE_URL': ' http://foo',
        },
      ));
240

Dan Field's avatar
Dan Field committed
241
      expect(() => cache.storageBaseUrl, throwsToolExit());
242
    });
243
  });
244

245
  testWithoutContext('flattenNameSubdirs', () {
246 247 248
    expect(flattenNameSubdirs(Uri.parse('http://flutter.dev/foo/bar'), MemoryFileSystem.test()), 'flutter.dev/foo/bar');
    expect(flattenNameSubdirs(Uri.parse('http://docs.flutter.io/foo/bar'), MemoryFileSystem.test()), 'docs.flutter.io/foo/bar');
    expect(flattenNameSubdirs(Uri.parse('https://www.flutter.dev'), MemoryFileSystem.test()), 'www.flutter.dev');
249
  });
250

251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278
  testWithoutContext('EngineCachedArtifact makes binary dirs readable and executable by all', () async {
    final OperatingSystemUtils operatingSystemUtils = MockOperatingSystemUtils();
    final MockCache cache = MockCache();
    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.');

    when(cache.getArtifactDirectory(any)).thenReturn(artifactDir);
    when(cache.getDownloadDir()).thenReturn(downloadDir);
    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,
    );
    await artifact.updateInner(MockArtifactUpdater(), fileSystem, operatingSystemUtils);
    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);
    verify(operatingSystemUtils.chmod(argThat(hasPath(dir.path)), 'a+r,a+x'));
279
  });
280

281 282 283 284 285 286 287 288 289
  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();
290

291
    expect(iosUsbArtifacts.isUpToDateInner(fileSystem), true);
292

293
    ideviceScreenshotFile.deleteSync();
294

295
    expect(iosUsbArtifacts.isUpToDateInner(fileSystem), false);
296
  });
297

298 299 300 301 302 303 304
  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();
305

306
    expect(iosUsbArtifacts.isUpToDateInner(fileSystem), true);
307

308
    iproxy.deleteSync();
309

310 311
    expect(iosUsbArtifacts.isUpToDateInner(fileSystem), false);
  });
312

313 314 315 316 317
  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();
318

319 320
    expect(iosUsbArtifacts.isUpToDateInner(fileSystem), true);
  });
321

322 323 324
  testWithoutContext('IosUsbArtifacts uses unsigned when specified', () async {
    final Cache cache = Cache.test();
    cache.useUnsignedMacBinaries = true;
325

326 327 328
    final IosUsbArtifacts iosUsbArtifacts = IosUsbArtifacts('name', cache, platform: FakePlatform(operatingSystem: 'macos'));
    expect(iosUsbArtifacts.archiveUri.toString(), contains('/unsigned/'));
  });
329

330 331 332
  testWithoutContext('IosUsbArtifacts does not use unsigned when not specified', () async {
    final Cache cache = Cache.test();
    final IosUsbArtifacts iosUsbArtifacts = IosUsbArtifacts('name', cache, platform: FakePlatform(operatingSystem: 'macos'));
333

334
    expect(iosUsbArtifacts.archiveUri.toString(), isNot(contains('/unsigned/')));
335
  });
336

337 338
  testWithoutContext('FlutterRunnerDebugSymbols downloads Flutter runner debug symbols', () async {
    final FileSystem fileSystem = MemoryFileSystem.test();
339
    final Cache cache = Cache.test(
340
      fileSystem: fileSystem,
341 342
      processManager: FakeProcessManager.any(),
    );
343 344
    final MockVersionedPackageResolver mockPackageResolver = MockVersionedPackageResolver();
    final FlutterRunnerDebugSymbols flutterRunnerDebugSymbols = FlutterRunnerDebugSymbols(
345
      cache,
346 347 348 349
      packageResolver: mockPackageResolver,
      platform: FakePlatform(operatingSystem: 'linux'),
    );
    when(mockPackageResolver.resolveUrl(any, any)).thenReturn('');
350

351
    await flutterRunnerDebugSymbols.updateInner(MockArtifactUpdater(), fileSystem, MockOperatingSystemUtils());
352

353 354 355 356 357
    verifyInOrder(<void>[
      mockPackageResolver.resolveUrl('fuchsia-debug-symbols-x64', any),
      mockPackageResolver.resolveUrl('fuchsia-debug-symbols-arm64', any),
    ]);
  });
358

359
  testWithoutContext('FontSubset in universal artifacts', () {
360
    final Cache cache = Cache.test();
361 362
    final FontSubsetArtifacts artifacts = FontSubsetArtifacts(cache, platform: FakePlatform(operatingSystem: 'linux'));

363 364 365
    expect(artifacts.developmentArtifact, DevelopmentArtifact.universal);
  });

366
  testWithoutContext('FontSubset artifacts on linux', () {
367
    final Cache cache = Cache.test();
368
    final FontSubsetArtifacts artifacts = FontSubsetArtifacts(cache, platform: FakePlatform(operatingSystem: 'linux'));
369
    cache.includeAllPlatforms = false;
370

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

374
  testWithoutContext('FontSubset artifacts on windows', () {
375
    final Cache cache = Cache.test();
376
    final FontSubsetArtifacts artifacts = FontSubsetArtifacts(cache, platform: FakePlatform(operatingSystem: 'windows'));
377
    cache.includeAllPlatforms = false;
378

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

382
  testWithoutContext('FontSubset artifacts on macos', () {
383
    final Cache cache = Cache.test();
384
    final FontSubsetArtifacts artifacts = FontSubsetArtifacts(cache, platform: FakePlatform(operatingSystem: 'macos'));
385
    cache.includeAllPlatforms = false;
386

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

390
  testWithoutContext('FontSubset artifacts on fuchsia', () {
391
    final Cache cache = Cache.test();
392
    final FontSubsetArtifacts artifacts = FontSubsetArtifacts(cache, platform: FakePlatform(operatingSystem: 'fuchsia'));
393
    cache.includeAllPlatforms = false;
394 395

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

398
  testWithoutContext('FontSubset artifacts for all platforms', () {
399
    final Cache cache = Cache.test();
400
    final FontSubsetArtifacts artifacts = FontSubsetArtifacts(cache, platform: FakePlatform(operatingSystem: 'fuchsia'));
401
    cache.includeAllPlatforms = true;
402

403
    expect(artifacts.getBinaryDirs(), <List<String>>[
404 405 406
      <String>['darwin-x64', 'darwin-x64/font-subset.zip'],
      <String>['linux-x64', 'linux-x64/font-subset.zip'],
      <String>['windows-x64', 'windows-x64/font-subset.zip'],
407
    ]);
408 409
  });

410
  testWithoutContext('macOS desktop artifacts ignore filtering when requested', () {
411
    final Cache cache = Cache.test();
412
    final MacOSEngineArtifacts artifacts = MacOSEngineArtifacts(cache, platform: FakePlatform(operatingSystem: 'linux'));
413 414
    cache.includeAllPlatforms = false;
    cache.platformOverrideArtifacts = <String>{'macos'};
415 416 417 418

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

419
  testWithoutContext('Windows desktop artifacts ignore filtering when requested', () {
420
    final Cache cache = Cache.test();
421
    final WindowsEngineArtifacts artifacts = WindowsEngineArtifacts(
422
      cache,
423 424
      platform: FakePlatform(operatingSystem: 'linux'),
    );
425 426
    cache.includeAllPlatforms = false;
    cache.platformOverrideArtifacts = <String>{'windows'};
427 428 429 430

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

431
  testWithoutContext('Windows desktop artifacts include profile and release artifacts', () {
432
    final Cache cache = Cache.test();
433
    final WindowsEngineArtifacts artifacts = WindowsEngineArtifacts(
434
      cache,
435 436 437 438 439 440 441 442 443 444
      platform: FakePlatform(operatingSystem: 'windows'),
    );

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

  testWithoutContext('Linux desktop artifacts ignore filtering when requested', () {
445
    final Cache cache = Cache.test();
446
    final LinuxEngineArtifacts artifacts = LinuxEngineArtifacts(
447
      cache,
448 449
      platform: FakePlatform(operatingSystem: 'macos'),
    );
450 451
    cache.includeAllPlatforms = false;
    cache.platformOverrideArtifacts = <String>{'linux'};
452 453

    expect(artifacts.getBinaryDirs(), isNotEmpty);
454
  });
455 456

  testWithoutContext('Linux desktop artifacts include profile and release artifacts', () {
457
    final Cache cache = Cache.test();
458
    final LinuxEngineArtifacts artifacts = LinuxEngineArtifacts(
459
      cache,
460 461 462 463 464 465 466 467
      platform: FakePlatform(operatingSystem: 'linux'),
    );

    expect(artifacts.getBinaryDirs(), containsAll(<Matcher>[
      contains(contains('profile')),
      contains(contains('release')),
    ]));
  });
468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544

  testWithoutContext('Cache can delete stampfiles of artifacts', () {
    final FileSystem fileSystem = MemoryFileSystem.test();
    final ArtifactSet artifactSet = MockIosUsbArtifacts();
    final BufferLogger logger = BufferLogger.test();

    when(artifactSet.stampName).thenReturn('STAMP');
    final Cache cache = Cache(
      artifacts: <ArtifactSet>[
        artifactSet,
      ],
      logger: logger,
      fileSystem: fileSystem,
      platform: FakePlatform(),
      osUtils: MockOperatingSystemUtils(),
      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();
    final ArtifactSet artifactSet = MockIosUsbArtifacts();
    final BufferLogger logger = BufferLogger.test();

    when(artifactSet.stampName).thenReturn('STAMP');
    final Cache cache = Cache(
      artifacts: <ArtifactSet>[
        artifactSet,
      ],
      logger: logger,
      fileSystem: fileSystem,
      platform: FakePlatform(),
      osUtils: MockOperatingSystemUtils(),
      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();
    final ArtifactSet artifactSet = MockIosUsbArtifacts();
    final BufferLogger logger = BufferLogger.test();

    when(artifactSet.stampName).thenReturn('STAMP');
    final Cache cache = Cache(
      artifacts: <ArtifactSet>[
        artifactSet,
      ],
      logger: logger,
      fileSystem: fileSystem,
      platform: FakePlatform(),
      osUtils: MockOperatingSystemUtils(),
      rootOverride: fileSystem.currentDirectory,
    );

    cache.clearStampFiles();

    expect(logger.errorText, contains('Failed to delete some stamp files'));
  });
545

546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561
  testWithoutContext('FlutterWebSdk deletes previous directory contents', () {
    final MemoryFileSystem fileSystem = MemoryFileSystem.test();
    final Directory webStuff = fileSystem.directory('web-stuff');
    final MockCache cache = MockCache();
    final MockArtifactUpdater artifactUpdater = MockArtifactUpdater();
    final FlutterWebSdk webSdk = FlutterWebSdk(cache, platform: FakePlatform(operatingSystem: 'linux'));

    when(cache.getWebSdkDirectory()).thenReturn(webStuff);
    when(artifactUpdater.downloadZipArchive('Downloading Web SDK...', any, any))
      .thenAnswer((Invocation invocation) async {
        final Directory location = invocation.positionalArguments[2] as Directory;
        location.createSync(recursive: true);
        location.childFile('foo').createSync();
      });
    webStuff.childFile('bar').createSync(recursive: true);

562
    webSdk.updateInner(artifactUpdater, fileSystem, MockOperatingSystemUtils());
563 564 565 566 567

    expect(webStuff.childFile('foo'), exists);
    expect(webStuff.childFile('bar'), isNot(exists));
  });

568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592
  testWithoutContext('Cache handles exception thrown if stamp file cannot be parsed', () {
    final FileSystem fileSystem = MemoryFileSystem.test();
    final Logger logger = BufferLogger.test();
    final FakeCache cache = FakeCache(
      fileSystem: fileSystem,
      logger: logger,
      platform: FakePlatform(),
      osUtils: MockOperatingSystemUtils()
    );
    final MockFile file = MockFile();
    cache.stampFile = file;
    when(file.existsSync()).thenReturn(false);

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

    when(file.existsSync()).thenReturn(true);
    when(file.readAsStringSync()).thenThrow(const FileSystemException());

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

    when(file.existsSync()).thenReturn(true);
    when(file.readAsStringSync()).thenReturn('ABC ');

    expect(cache.getStampFor('foo'), 'ABC');
  });
593 594 595 596 597 598 599 600 601 602 603

  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,
      pub: () => MockPub(),
    );

604
    expect(await pubDependencies.isUpToDate(fileSystem), false); // no package config
605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627

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

628
    expect(await pubDependencies.isUpToDate(fileSystem), false); // dependencies are missing.
629 630 631 632

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

633
    expect(await pubDependencies.isUpToDate(fileSystem), true);
634 635 636 637 638 639 640 641 642 643 644 645
  });

  testWithoutContext('PubDependencies updates via pub get', () async {
    final BufferLogger logger = BufferLogger.test();
    final MemoryFileSystem fileSystem = MemoryFileSystem.test();
    final MockPub pub = MockPub();
    final PubDependencies pubDependencies = PubDependencies(
      flutterRoot: () => '',
      logger: logger,
      pub: () => pub,
    );

646
    await pubDependencies.update(MockArtifactUpdater(), logger, fileSystem, MockOperatingSystemUtils());
647 648 649 650 651 652

    verify(pub.get(
      context: PubContext.pubGet,
      directory: 'packages/flutter_tools',
    )).called(1);
  });
653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700

  group('AndroidMavenArtifacts', () {
    MemoryFileSystem memoryFileSystem;
    MockProcessManager processManager;
    Cache cache;

    setUp(() {
      memoryFileSystem = MemoryFileSystem.test();
      processManager = MockProcessManager();
      cache = Cache.test(
        fileSystem: memoryFileSystem,
        processManager: FakeProcessManager.any(),
      );
    });

    testWithoutContext('development artifact', () async {
      final AndroidMavenArtifacts mavenArtifacts = AndroidMavenArtifacts(cache, platform: FakePlatform(operatingSystem: 'linux'));
      expect(mavenArtifacts.developmentArtifact, DevelopmentArtifact.androidMaven);
    });

    testUsingContext('update', () async {
      final AndroidMavenArtifacts mavenArtifacts = AndroidMavenArtifacts(cache, platform: FakePlatform(operatingSystem: 'linux'));
      expect(await mavenArtifacts.isUpToDate(memoryFileSystem), isFalse);

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

      when(processManager.run(any, environment: captureAnyNamed('environment')))
        .thenAnswer((Invocation invocation) {
          final List<String> args = invocation.positionalArguments[0] as List<String>;
          expect(args.length, 6);
          expect(args[1], '-b');
          expect(args[2].endsWith('resolve_dependencies.gradle'), isTrue);
          expect(args[5], 'resolveDependencies');
          expect(invocation.namedArguments[#environment], gradleEnvironment);
          return Future<ProcessResult>.value(ProcessResult(0, 0, '', ''));
        });

      await mavenArtifacts.update(MockArtifactUpdater(), BufferLogger.test(), memoryFileSystem, MockOperatingSystemUtils());

      expect(await mavenArtifacts.isUpToDate(memoryFileSystem), isFalse);
    }, overrides: <Type, Generator>{
      Cache: () => cache,
      FileSystem: () => memoryFileSystem,
      ProcessManager: () => processManager,
    });
  });
701 702 703 704 705 706
}

class FakeCachedArtifact extends EngineCachedArtifact {
  FakeCachedArtifact({
    String stampName = 'STAMP',
    @required Cache cache,
707
    DevelopmentArtifact requiredArtifacts,
708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724
    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;
725 726
}

727 728 729 730 731 732 733 734
class FakeSimpleArtifact extends CachedArtifact {
  FakeSimpleArtifact(Cache cache) : super(
    'fake',
    cache,
    DevelopmentArtifact.universal,
  );

  @override
735
  Future<void> updateInner(ArtifactUpdater artifactUpdater, FileSystem fileSystem, OperatingSystemUtils operatingSystemUtils) async { }
736 737
}

738 739 740 741 742 743 744 745 746 747
class FakeDownloadedArtifact extends CachedArtifact {
  FakeDownloadedArtifact(this.downloadedFile, Cache cache) : super(
    'fake',
    cache,
    DevelopmentArtifact.universal,
  );

  final File downloadedFile;

  @override
748
  Future<void> updateInner(ArtifactUpdater artifactUpdater, FileSystem fileSystem, OperatingSystemUtils operatingSystemUtils) async { }
749 750
}

751
class MockArtifactUpdater extends Mock implements ArtifactUpdater {}
752
class MockProcessManager extends Mock implements ProcessManager {}
753 754
class MockFileSystem extends Mock implements FileSystem {}
class MockFile extends Mock implements File {}
755
class MockDirectory extends Mock implements Directory {}
756
class MockRandomAccessFile extends Mock implements RandomAccessFile {}
757
class MockCachedArtifact extends Mock implements CachedArtifact {}
758
class MockIosUsbArtifacts extends Mock implements IosUsbArtifacts {}
759
class MockInternetAddress extends Mock implements InternetAddress {}
760
class MockCache extends Mock implements Cache {}
761
class MockOperatingSystemUtils extends Mock implements OperatingSystemUtils {}
762
class MockVersionedPackageResolver extends Mock implements VersionedPackageResolver {}
763
class MockPub extends Mock implements Pub {}
764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784
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;
  }
}