// Copyright 2014 The Flutter Authors. All rights reserved. // 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'; import 'package:file_testing/file_testing.dart'; import 'package:meta/meta.dart'; import 'package:mockito/mockito.dart'; import 'package:platform/platform.dart'; import 'package:process/process.dart'; import 'package:flutter_tools/src/android/gradle_utils.dart'; import 'package:flutter_tools/src/base/io.dart'; import 'package:flutter_tools/src/cache.dart'; import 'package:flutter_tools/src/base/file_system.dart'; import 'package:flutter_tools/src/base/io.dart' show InternetAddress, SocketException; import 'package:flutter_tools/src/base/net.dart'; import 'package:flutter_tools/src/base/os.dart'; import 'package:flutter_tools/src/globals.dart' as globals; import '../src/common.dart'; import '../src/context.dart'; import '../src/testbed.dart'; void main() { group('$Cache.checkLockAcquired', () { MockFileSystem mockFileSystem; MemoryFileSystem memoryFileSystem; MockFile mockFile; MockRandomAccessFile mockRandomAccessFile; setUp(() { mockFileSystem = MockFileSystem(); memoryFileSystem = MemoryFileSystem(); mockFile = MockFile(); mockRandomAccessFile = MockRandomAccessFile(); when(mockFileSystem.path).thenReturn(memoryFileSystem.path); Cache.enableLocking(); }); tearDown(() { // Restore locking to prevent potential side-effects in // tests outside this group (this option is globally shared). Cache.enableLocking(); Cache.releaseLockEarly(); }); test('should throw when locking is not acquired', () { expect(() => Cache.checkLockAcquired(), throwsStateError); }); test('should not throw when locking is disabled', () { Cache.disableLocking(); Cache.checkLockAcquired(); }); testUsingContext('should not throw when lock is acquired', () async { when(mockFileSystem.file(argThat(endsWith('lockfile')))).thenReturn(mockFile); when(mockFile.openSync(mode: anyNamed('mode'))).thenReturn(mockRandomAccessFile); await Cache.lock(); Cache.checkLockAcquired(); Cache.releaseLockEarly(); }, overrides: { FileSystem: () => mockFileSystem, ProcessManager: () => FakeProcessManager.any(), }); testUsingContext('throws tool exit when lockfile open fails', () async { when(mockFileSystem.file(argThat(endsWith('lockfile')))).thenReturn(mockFile); when(mockFile.openSync(mode: anyNamed('mode'))).thenThrow(const FileSystemException()); expect(() async => await Cache.lock(), throwsToolExit()); }, overrides: { FileSystem: () => mockFileSystem, ProcessManager: () => FakeProcessManager.any(), }); testUsingContext('should not throw when FLUTTER_ALREADY_LOCKED is set', () async { Cache.checkLockAcquired(); }, overrides: { Platform: () => FakePlatform()..environment = {'FLUTTER_ALREADY_LOCKED': 'true'}, }); }); group('Cache', () { MockCache mockCache; MemoryFileSystem memoryFileSystem; setUp(() { mockCache = MockCache(); memoryFileSystem = MemoryFileSystem(); }); testUsingContext('Continues on failed delete', () async { final Directory artifactDir = globals.fs.systemTempDirectory.createTempSync('flutter_cache_test_artifact.'); final Directory downloadDir = globals.fs.systemTempDirectory.createTempSync('flutter_cache_test_download.'); when(mockCache.getArtifactDirectory(any)).thenReturn(artifactDir); when(mockCache.getDownloadDir()).thenReturn(downloadDir); final File mockFile = MockFile(); when(mockFile.deleteSync()).thenAnswer((_) { throw const FileSystemException('delete failed'); }); final FakeDownloadedArtifact artifact = FakeDownloadedArtifact( mockFile, mockCache, ); await artifact.update(); expect(testLogger.errorText, contains('delete failed')); }, overrides: { Cache: () => mockCache, FileSystem: () => memoryFileSystem, ProcessManager: () => FakeProcessManager.any(), }); testUsingContext('Continues on failed stamp file update', () async { final Directory artifactDir = globals.fs.systemTempDirectory.createTempSync('flutter_cache_test_artifact.'); final Directory downloadDir = globals.fs.systemTempDirectory.createTempSync('flutter_cache_test_download.'); 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); await artifact.update(); expect(testLogger.errorText, contains('stamp write failed')); }, overrides: { Cache: () => mockCache, FileSystem: () => memoryFileSystem, ProcessManager: () => FakeProcessManager.any(), }); testUsingContext('Gradle wrapper should not be up to date, if some cached artifact is not available', () { final GradleWrapper gradleWrapper = GradleWrapper(mockCache); final Directory directory = globals.fs.directory('/Applications/flutter/bin/cache'); directory.createSync(recursive: true); globals.fs.file(globals.fs.path.join(directory.path, 'artifacts', 'gradle_wrapper', 'gradle', 'wrapper', 'gradle-wrapper.jar')).createSync(recursive: true); when(mockCache.getCacheDir(globals.fs.path.join('artifacts', 'gradle_wrapper'))).thenReturn(globals.fs.directory(globals.fs.path.join(directory.path, 'artifacts', 'gradle_wrapper'))); expect(gradleWrapper.isUpToDateInner(), false); }, overrides: { Cache: () => mockCache, FileSystem: () => memoryFileSystem, ProcessManager: () => FakeProcessManager.any(), }); testUsingContext('Gradle wrapper should be up to date, only if all cached artifact are available', () { final GradleWrapper gradleWrapper = GradleWrapper(mockCache); final Directory directory = globals.fs.directory('/Applications/flutter/bin/cache'); directory.createSync(recursive: true); globals.fs.file(globals.fs.path.join(directory.path, 'artifacts', 'gradle_wrapper', 'gradle', 'wrapper', 'gradle-wrapper.jar')).createSync(recursive: true); globals.fs.file(globals.fs.path.join(directory.path, 'artifacts', 'gradle_wrapper', 'gradlew')).createSync(recursive: true); globals.fs.file(globals.fs.path.join(directory.path, 'artifacts', 'gradle_wrapper', 'gradlew.bat')).createSync(recursive: true); when(mockCache.getCacheDir(globals.fs.path.join('artifacts', 'gradle_wrapper'))).thenReturn(globals.fs.directory(globals.fs.path.join(directory.path, 'artifacts', 'gradle_wrapper'))); expect(gradleWrapper.isUpToDateInner(), true); }, overrides: { Cache: () => mockCache, FileSystem: () => memoryFileSystem, ProcessManager: () => FakeProcessManager.any(), }); test('should not be up to date, if some cached artifact is not', () { final CachedArtifact artifact1 = MockCachedArtifact(); final CachedArtifact artifact2 = MockCachedArtifact(); when(artifact1.isUpToDate()).thenReturn(true); when(artifact2.isUpToDate()).thenReturn(false); final Cache cache = Cache(artifacts: [artifact1, artifact2]); expect(cache.isUpToDate(), isFalse); }); test('should be up to date, if all cached artifacts are', () { final CachedArtifact artifact1 = MockCachedArtifact(); final CachedArtifact artifact2 = MockCachedArtifact(); when(artifact1.isUpToDate()).thenReturn(true); when(artifact2.isUpToDate()).thenReturn(true); final Cache cache = Cache(artifacts: [artifact1, artifact2]); expect(cache.isUpToDate(), isTrue); }); test('should update cached artifacts which are not up to date', () async { final CachedArtifact artifact1 = MockCachedArtifact(); final CachedArtifact artifact2 = MockCachedArtifact(); when(artifact1.isUpToDate()).thenReturn(true); when(artifact2.isUpToDate()).thenReturn(false); final Cache cache = Cache(artifacts: [artifact1, artifact2]); await cache.updateAll({ null, }); verifyNever(artifact1.update()); verify(artifact2.update()); }); testUsingContext("getter dyLdLibEntry concatenates the output of each artifact's dyLdLibEntry getter", () async { final IosUsbArtifacts artifact1 = MockIosUsbArtifacts(); final IosUsbArtifacts artifact2 = MockIosUsbArtifacts(); final IosUsbArtifacts artifact3 = MockIosUsbArtifacts(); when(artifact1.environment) .thenReturn({ 'DYLD_LIBRARY_PATH': '/path/to/alpha:/path/to/beta', }); when(artifact2.environment) .thenReturn({ 'DYLD_LIBRARY_PATH': '/path/to/gamma:/path/to/delta:/path/to/epsilon', }); when(artifact3.environment) .thenReturn({ 'DYLD_LIBRARY_PATH': '', }); final Cache cache = Cache(artifacts: [artifact1, artifact2, artifact3]); 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', ); }, overrides: { Cache: () => mockCache, }); testUsingContext('failed storage.googleapis.com download shows China warning', () async { final CachedArtifact artifact1 = MockCachedArtifact(); final CachedArtifact artifact2 = MockCachedArtifact(); when(artifact1.isUpToDate()).thenReturn(false); when(artifact2.isUpToDate()).thenReturn(false); final MockInternetAddress address = MockInternetAddress(); when(address.host).thenReturn('storage.googleapis.com'); when(artifact1.update()).thenThrow(SocketException( 'Connection reset by peer', address: address, )); final Cache cache = Cache(artifacts: [artifact1, artifact2]); try { await cache.updateAll({ null, }); fail('Mock thrown exception expected'); } on Exception { verify(artifact1.update()); // Don't continue when retrieval fails. verifyNever(artifact2.update()); expect( testLogger.errorText, contains('https://flutter.dev/community/china'), ); } }); testUsingContext('Invalid URI for FLUTTER_STORAGE_BASE_URL throws ToolExit', () async { when(globals.platform.environment).thenReturn(const { 'FLUTTER_STORAGE_BASE_URL': ' http://foo', }); final Cache cache = Cache(); expect(() => cache.storageBaseUrl, throwsToolExit()); }, overrides: { Platform: () => MockPlatform(), }); }); testUsingContext('flattenNameSubdirs', () { expect(flattenNameSubdirs(Uri.parse('http://flutter.dev/foo/bar')), 'flutter.dev/foo/bar'); expect(flattenNameSubdirs(Uri.parse('http://docs.flutter.io/foo/bar')), 'docs.flutter.io/foo/bar'); expect(flattenNameSubdirs(Uri.parse('https://www.flutter.dev')), 'www.flutter.dev'); }, overrides: { FileSystem: () => MemoryFileSystem(), ProcessManager: () => FakeProcessManager.any(), }); test('Unstable artifacts', () { expect(DevelopmentArtifact.web.unstable, false); expect(DevelopmentArtifact.linux.unstable, false); expect(DevelopmentArtifact.macOS.unstable, false); expect(DevelopmentArtifact.windows.unstable, false); expect(DevelopmentArtifact.fuchsia.unstable, true); expect(DevelopmentArtifact.flutterRunner.unstable, true); }); group('EngineCachedArtifact', () { FakeHttpClient fakeHttpClient; FakePlatform fakePlatform; MemoryFileSystem memoryFileSystem; MockCache mockCache; MockOperatingSystemUtils mockOperatingSystemUtils; MockHttpClient mockHttpClient; setUp(() { fakeHttpClient = FakeHttpClient(); mockHttpClient = MockHttpClient(); fakePlatform = FakePlatform()..environment = const {}; memoryFileSystem = MemoryFileSystem(); mockCache = MockCache(); mockOperatingSystemUtils = MockOperatingSystemUtils(); }); testUsingContext('makes binary dirs readable and executable by all', () async { when(mockOperatingSystemUtils.verifyZip(any)).thenReturn(true); final Directory artifactDir = globals.fs.systemTempDirectory.createTempSync('flutter_cache_test_artifact.'); final Directory downloadDir = globals.fs.systemTempDirectory.createTempSync('flutter_cache_test_download.'); when(mockCache.getArtifactDirectory(any)).thenReturn(artifactDir); when(mockCache.getDownloadDir()).thenReturn(downloadDir); final FakeCachedArtifact artifact = FakeCachedArtifact( cache: mockCache, binaryDirs: >[ ['bin_dir', 'unused_url_path'], ], requiredArtifacts: DevelopmentArtifact.universal, ); await artifact.updateInner(); final Directory dir = memoryFileSystem.systemTempDirectory .listSync(recursive: true) .whereType() .singleWhere((Directory directory) => directory.basename == 'bin_dir', orElse: () => null); expect(dir, isNotNull); expect(dir.path, artifactDir.childDirectory('bin_dir').path); verify(mockOperatingSystemUtils.chmod(argThat(hasPath(dir.path)), 'a+r,a+x')); }, overrides: { Cache: () => mockCache, FileSystem: () => memoryFileSystem, ProcessManager: () => FakeProcessManager.any(), HttpClientFactory: () => () => fakeHttpClient, OperatingSystemUtils: () => mockOperatingSystemUtils, Platform: () => fakePlatform, }); testUsingContext('prints a friendly name when downloading', () async { when(mockOperatingSystemUtils.verifyZip(any)).thenReturn(false); final MockHttpClientRequest httpClientRequest = MockHttpClientRequest(); final MockHttpClientResponse httpClientResponse = MockHttpClientResponse(); when(httpClientResponse.statusCode).thenReturn(200); when(httpClientRequest.close()).thenAnswer((_) async => httpClientResponse); when(mockHttpClient.getUrl(any)).thenAnswer((_) async => httpClientRequest); final Directory artifactDir = globals.fs.systemTempDirectory.createTempSync('flutter_cache_test_artifact.'); final Directory downloadDir = globals.fs.systemTempDirectory.createTempSync('flutter_cache_test_download.'); when(mockCache.getArtifactDirectory(any)).thenReturn(artifactDir); when(mockCache.getDownloadDir()).thenReturn(downloadDir); final FakeCachedArtifact artifact = FakeCachedArtifact( cache: mockCache, binaryDirs: >[ ['bin_dir', 'darwin-x64/artifacts.zip'], ['font-subset', 'darwin-x64/font-subset.zip'], ], requiredArtifacts: DevelopmentArtifact.universal, ); await artifact.updateInner(); expect(testLogger.statusText, isNotNull); expect(testLogger.statusText, isNotEmpty); expect( testLogger.statusText.split('\n'), [ 'Downloading darwin-x64 tools...', 'Downloading darwin-x64/font-subset tools...', '', ], ); }, overrides: { Cache: () => mockCache, FileSystem: () => memoryFileSystem, ProcessManager: () => FakeProcessManager.any(), HttpClientFactory: () => () => mockHttpClient, OperatingSystemUtils: () => mockOperatingSystemUtils, Platform: () => fakePlatform, }); }); group('AndroidMavenArtifacts', () { MemoryFileSystem memoryFileSystem; MockProcessManager processManager; MockCache mockCache; setUp(() { memoryFileSystem = MemoryFileSystem(); processManager = MockProcessManager(); mockCache = MockCache(); }); test('development artifact', () async { final AndroidMavenArtifacts mavenArtifacts = AndroidMavenArtifacts(); expect(mavenArtifacts.developmentArtifact, DevelopmentArtifact.androidMaven); }); testUsingContext('update', () async { final AndroidMavenArtifacts mavenArtifacts = AndroidMavenArtifacts(); expect(mavenArtifacts.isUpToDate(), isFalse); final Directory gradleWrapperDir = globals.fs.systemTempDirectory.createTempSync('flutter_cache_test_gradle_wrapper.'); when(mockCache.getArtifactDirectory('gradle_wrapper')).thenReturn(gradleWrapperDir); globals.fs.directory(gradleWrapperDir.childDirectory('gradle').childDirectory('wrapper')) .createSync(recursive: true); globals.fs.file(globals.fs.path.join(gradleWrapperDir.path, 'gradlew')).writeAsStringSync('irrelevant'); globals.fs.file(globals.fs.path.join(gradleWrapperDir.path, 'gradlew.bat')).writeAsStringSync('irrelevant'); when(globals.processManager.run(any, environment: captureAnyNamed('environment'))) .thenAnswer((Invocation invocation) { final List args = invocation.positionalArguments[0] as List; 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.value(ProcessResult(0, 0, '', '')); }); await mavenArtifacts.update(); expect(mavenArtifacts.isUpToDate(), isFalse); }, overrides: { Cache: () => mockCache, FileSystem: () => memoryFileSystem, ProcessManager: () => processManager, }); }); group('macOS artifacts', () { MockCache mockCache; setUp(() { mockCache = MockCache(); }); testUsingContext('verifies executables for libimobiledevice in isUpToDateInner', () async { final IosUsbArtifacts iosUsbArtifacts = IosUsbArtifacts('libimobiledevice', mockCache); when(mockCache.getArtifactDirectory(any)).thenReturn(globals.fs.currentDirectory); iosUsbArtifacts.location.createSync(); final File ideviceScreenshotFile = iosUsbArtifacts.location.childFile('idevicescreenshot') ..createSync(); iosUsbArtifacts.location.childFile('idevicesyslog') .createSync(); expect(iosUsbArtifacts.isUpToDateInner(), true); ideviceScreenshotFile.deleteSync(); expect(iosUsbArtifacts.isUpToDateInner(), false); }, overrides: { Cache: () => mockCache, FileSystem: () => MemoryFileSystem(), ProcessManager: () => FakeProcessManager.any(), }); testUsingContext('verifies iproxy for usbmuxd in isUpToDateInner', () async { final IosUsbArtifacts iosUsbArtifacts = IosUsbArtifacts('usbmuxd', mockCache); when(mockCache.getArtifactDirectory(any)).thenReturn(globals.fs.currentDirectory); iosUsbArtifacts.location.createSync(); final File iproxy = iosUsbArtifacts.location.childFile('iproxy') ..createSync(); expect(iosUsbArtifacts.isUpToDateInner(), true); iproxy.deleteSync(); expect(iosUsbArtifacts.isUpToDateInner(), false); }, overrides: { Cache: () => mockCache, FileSystem: () => MemoryFileSystem(), ProcessManager: () => FakeProcessManager.any(), }); testUsingContext('Does not verify executables for openssl in isUpToDateInner', () async { final IosUsbArtifacts iosUsbArtifacts = IosUsbArtifacts('openssl', mockCache); when(mockCache.getArtifactDirectory(any)).thenReturn(globals.fs.currentDirectory); iosUsbArtifacts.location.createSync(); expect(iosUsbArtifacts.isUpToDateInner(), true); }, overrides: { Cache: () => mockCache, FileSystem: () => MemoryFileSystem(), ProcessManager: () => FakeProcessManager.any(), }); testUsingContext('use unsigned when specified', () async { when(mockCache.useUnsignedMacBinaries).thenReturn(true); final IosUsbArtifacts iosUsbArtifacts = IosUsbArtifacts('name', mockCache); expect(iosUsbArtifacts.archiveUri.toString(), contains('/unsigned/')); }, overrides: { Cache: () => mockCache, }); testUsingContext('not use unsigned when not specified', () async { when(mockCache.useUnsignedMacBinaries).thenReturn(false); final IosUsbArtifacts iosUsbArtifacts = IosUsbArtifacts('name', mockCache); expect(iosUsbArtifacts.archiveUri.toString(), isNot(contains('/unsigned/'))); }, overrides: { Cache: () => mockCache, }); }); group('Flutter runner debug symbols', () { MockCache mockCache; MockVersionedPackageResolver mockPackageResolver; setUp(() { mockCache = MockCache(); mockPackageResolver = MockVersionedPackageResolver(); }); testUsingContext('Downloads Flutter runner debug symbols', () async { final FlutterRunnerDebugSymbols flutterRunnerDebugSymbols = FlutterRunnerDebugSymbols(mockCache, packageResolver: mockPackageResolver, dryRun: true); await flutterRunnerDebugSymbols.updateInner(); verifyInOrder([ mockPackageResolver.resolveUrl('fuchsia-debug-symbols-x64', any), mockPackageResolver.resolveUrl('fuchsia-debug-symbols-arm64', any), ]); }); }, skip: !globals.platform.isLinux); testUsingContext('FontSubset in univeral artifacts', () { final MockCache mockCache = MockCache(); final FontSubsetArtifacts artifacts = FontSubsetArtifacts(mockCache); expect(artifacts.developmentArtifact, DevelopmentArtifact.universal); }); testUsingContext('FontSubset artifacts on linux', () { final MockCache mockCache = MockCache(); final FontSubsetArtifacts artifacts = FontSubsetArtifacts(mockCache); when(mockCache.includeAllPlatforms).thenReturn(false); expect(artifacts.getBinaryDirs(), >[['linux-x64', 'linux-x64/font-subset.zip']]); }, overrides: { Platform: () => FakePlatform(operatingSystem: 'linux'), }); testUsingContext('FontSubset artifacts on windows', () { final MockCache mockCache = MockCache(); final FontSubsetArtifacts artifacts = FontSubsetArtifacts(mockCache); when(mockCache.includeAllPlatforms).thenReturn(false); expect(artifacts.getBinaryDirs(), >[['windows-x64', 'windows-x64/font-subset.zip']]); }, overrides: { Platform: () => FakePlatform(operatingSystem: 'windows'), }); testUsingContext('FontSubset artifacts on macos', () { final MockCache mockCache = MockCache(); final FontSubsetArtifacts artifacts = FontSubsetArtifacts(mockCache); when(mockCache.includeAllPlatforms).thenReturn(false); expect(artifacts.getBinaryDirs(), >[['darwin-x64', 'darwin-x64/font-subset.zip']]); }, overrides: { Platform: () => FakePlatform(operatingSystem: 'macos'), }); testUsingContext('FontSubset artifacts on fuchsia', () { final MockCache mockCache = MockCache(); final FontSubsetArtifacts artifacts = FontSubsetArtifacts(mockCache); when(mockCache.includeAllPlatforms).thenReturn(false); expect(() => artifacts.getBinaryDirs(), throwsToolExit(message: 'Unsupported operating system: ${globals.platform.operatingSystem}')); }, overrides: { Platform: () => FakePlatform(operatingSystem: 'fuchsia'), }); testUsingContext('FontSubset artifacts for all platforms', () { final MockCache mockCache = MockCache(); final FontSubsetArtifacts artifacts = FontSubsetArtifacts(mockCache); when(mockCache.includeAllPlatforms).thenReturn(true); expect(artifacts.getBinaryDirs(), >[ ['darwin-x64', 'darwin-x64/font-subset.zip'], ['linux-x64', 'linux-x64/font-subset.zip'], ['windows-x64', 'windows-x64/font-subset.zip'], ]); }, overrides: { Platform: () => FakePlatform(operatingSystem: 'fuchsia'), }); testUsingContext('macOS desktop artifacts ignore filtering when requested', () { final MockCache mockCache = MockCache(); final MacOSEngineArtifacts artifacts = MacOSEngineArtifacts(mockCache); when(mockCache.includeAllPlatforms).thenReturn(false); when(mockCache.platformOverrideArtifacts).thenReturn({'macos'}); expect(artifacts.getBinaryDirs(), isNotEmpty); }, overrides: { Platform: () => FakePlatform(operatingSystem: 'linux'), }); testUsingContext('Windows desktop artifacts ignore filtering when requested', () { final MockCache mockCache = MockCache(); final WindowsEngineArtifacts artifacts = WindowsEngineArtifacts(mockCache); when(mockCache.includeAllPlatforms).thenReturn(false); when(mockCache.platformOverrideArtifacts).thenReturn({'windows'}); expect(artifacts.getBinaryDirs(), isNotEmpty); }, overrides: { Platform: () => FakePlatform(operatingSystem: 'linux'), }); testUsingContext('Linux desktop artifacts ignore filtering when requested', () { final MockCache mockCache = MockCache(); final LinuxEngineArtifacts artifacts = LinuxEngineArtifacts(mockCache); when(mockCache.includeAllPlatforms).thenReturn(false); when(mockCache.platformOverrideArtifacts).thenReturn({'linux'}); expect(artifacts.getBinaryDirs(), isNotEmpty); }, overrides: { Platform: () => FakePlatform(operatingSystem: 'macos'), }); } class FakeCachedArtifact extends EngineCachedArtifact { FakeCachedArtifact({ String stampName = 'STAMP', @required Cache cache, DevelopmentArtifact requiredArtifacts, this.binaryDirs = const >[], this.licenseDirs = const [], this.packageDirs = const [], }) : super(stampName, cache, requiredArtifacts); final List> binaryDirs; final List licenseDirs; final List packageDirs; @override List> getBinaryDirs() => binaryDirs; @override List getLicenseDirs() => licenseDirs; @override List getPackageDirs() => packageDirs; } class FakeSimpleArtifact extends CachedArtifact { FakeSimpleArtifact(Cache cache) : super( 'fake', cache, DevelopmentArtifact.universal, ); @override Future updateInner() async { // nop. } } class FakeDownloadedArtifact extends CachedArtifact { FakeDownloadedArtifact(this.downloadedFile, Cache cache) : super( 'fake', cache, DevelopmentArtifact.universal, ); final File downloadedFile; @override Future updateInner() async { downloadedFiles.add(downloadedFile); } } class MockProcessManager extends Mock implements ProcessManager {} class MockFileSystem extends Mock implements FileSystem {} class MockFile extends Mock implements File {} class MockDirectory extends Mock implements Directory {} class MockRandomAccessFile extends Mock implements RandomAccessFile {} class MockCachedArtifact extends Mock implements CachedArtifact {} class MockIosUsbArtifacts extends Mock implements IosUsbArtifacts {} class MockInternetAddress extends Mock implements InternetAddress {} class MockCache extends Mock implements Cache {} class MockOperatingSystemUtils extends Mock implements OperatingSystemUtils {} class MockPlatform extends Mock implements Platform {} class MockVersionedPackageResolver extends Mock implements VersionedPackageResolver {} class MockHttpClientRequest extends Mock implements HttpClientRequest {} class MockHttpClientResponse extends Mock implements HttpClientResponse {}