| // 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/memory.dart'; |
| import 'package:flutter_tools/src/android/android_sdk.dart'; |
| import 'package:flutter_tools/src/android/android_workflow.dart'; |
| import 'package:flutter_tools/src/android/java.dart'; |
| import 'package:flutter_tools/src/base/file_system.dart'; |
| import 'package:flutter_tools/src/base/io.dart'; |
| import 'package:flutter_tools/src/base/logger.dart'; |
| import 'package:flutter_tools/src/base/platform.dart'; |
| import 'package:flutter_tools/src/base/user_messages.dart'; |
| import 'package:flutter_tools/src/base/version.dart'; |
| import 'package:flutter_tools/src/doctor_validator.dart'; |
| import 'package:test/fake.dart'; |
| |
| import '../../src/common.dart'; |
| import '../../src/context.dart'; |
| import '../../src/fake_process_manager.dart'; |
| import '../../src/fakes.dart'; |
| |
| void main() { |
| late FakeAndroidSdk sdk; |
| late Logger logger; |
| late MemoryFileSystem fileSystem; |
| late FakeProcessManager processManager; |
| late FakeStdio stdio; |
| |
| setUp(() { |
| sdk = FakeAndroidSdk(); |
| fileSystem = MemoryFileSystem.test(); |
| fileSystem.directory('/home/me').createSync(recursive: true); |
| logger = BufferLogger.test(); |
| processManager = FakeProcessManager.empty(); |
| stdio = FakeStdio(); |
| }); |
| |
| testWithoutContext('AndroidWorkflow handles a null AndroidSDK', () { |
| final AndroidWorkflow androidWorkflow = AndroidWorkflow( |
| featureFlags: TestFeatureFlags(), |
| androidSdk: null, |
| ); |
| |
| expect(androidWorkflow.canLaunchDevices, false); |
| expect(androidWorkflow.canListDevices, false); |
| expect(androidWorkflow.canListEmulators, false); |
| }); |
| |
| testWithoutContext('AndroidWorkflow handles a null adb', () { |
| final FakeAndroidSdk androidSdk = FakeAndroidSdk(); |
| androidSdk.adbPath = null; |
| final AndroidWorkflow androidWorkflow = AndroidWorkflow( |
| featureFlags: TestFeatureFlags(), |
| androidSdk: androidSdk, |
| ); |
| |
| expect(androidWorkflow.canLaunchDevices, false); |
| expect(androidWorkflow.canListDevices, false); |
| expect(androidWorkflow.canListEmulators, false); |
| }); |
| |
| // Android SDK is actually supported on Linux Arm64 hosts. |
| testWithoutContext('Support for Android SDK on Linux Arm Hosts', () { |
| final FakeAndroidSdk androidSdk = FakeAndroidSdk(); |
| androidSdk.adbPath = null; |
| final AndroidWorkflow androidWorkflow = AndroidWorkflow( |
| featureFlags: TestFeatureFlags(), |
| androidSdk: androidSdk, |
| ); |
| |
| expect(androidWorkflow.appliesToHostPlatform, isTrue); |
| expect(androidWorkflow.canLaunchDevices, isFalse); |
| expect(androidWorkflow.canListDevices, isFalse); |
| expect(androidWorkflow.canListEmulators, isFalse); |
| }); |
| |
| testWithoutContext('AndroidWorkflow is disabled if feature is disabled', () { |
| final FakeAndroidSdk androidSdk = FakeAndroidSdk(); |
| androidSdk.adbPath = 'path/to/adb'; |
| final AndroidWorkflow androidWorkflow = AndroidWorkflow( |
| featureFlags: TestFeatureFlags(isAndroidEnabled: false), |
| androidSdk: androidSdk, |
| ); |
| |
| expect(androidWorkflow.appliesToHostPlatform, false); |
| expect(androidWorkflow.canLaunchDevices, false); |
| expect(androidWorkflow.canListDevices, false); |
| expect(androidWorkflow.canListEmulators, false); |
| }); |
| |
| testWithoutContext('AndroidWorkflow cannot list emulators if emulatorPath is null', () { |
| final FakeAndroidSdk androidSdk = FakeAndroidSdk(); |
| androidSdk.adbPath = 'path/to/adb'; |
| final AndroidWorkflow androidWorkflow = AndroidWorkflow( |
| featureFlags: TestFeatureFlags(), |
| androidSdk: androidSdk, |
| ); |
| |
| expect(androidWorkflow.appliesToHostPlatform, true); |
| expect(androidWorkflow.canLaunchDevices, true); |
| expect(androidWorkflow.canListDevices, true); |
| expect(androidWorkflow.canListEmulators, false); |
| }); |
| |
| testWithoutContext('AndroidWorkflow can list emulators', () { |
| final FakeAndroidSdk androidSdk = FakeAndroidSdk(); |
| androidSdk.adbPath = 'path/to/adb'; |
| androidSdk.emulatorPath = 'path/to/emulator'; |
| final AndroidWorkflow androidWorkflow = AndroidWorkflow( |
| featureFlags: TestFeatureFlags(), |
| androidSdk: androidSdk, |
| ); |
| |
| expect(androidWorkflow.appliesToHostPlatform, true); |
| expect(androidWorkflow.canLaunchDevices, true); |
| expect(androidWorkflow.canListDevices, true); |
| expect(androidWorkflow.canListEmulators, true); |
| }); |
| |
| testWithoutContext('licensesAccepted returns LicensesAccepted.unknown if cannot find sdkmanager', () async { |
| sdk.sdkManagerPath = '/foo/bar/sdkmanager'; |
| processManager.excludedExecutables.add('/foo/bar/sdkmanager'); |
| final AndroidLicenseValidator licenseValidator = AndroidLicenseValidator( |
| java: FakeJava(), |
| androidSdk: sdk, |
| processManager: processManager, |
| platform: FakePlatform(environment: <String, String>{'HOME': '/home/me'}), |
| stdio: stdio, |
| logger: BufferLogger.test(), |
| userMessages: UserMessages(), |
| ); |
| final LicensesAccepted licenseStatus = await licenseValidator.licensesAccepted; |
| |
| expect(licenseStatus, LicensesAccepted.unknown); |
| }); |
| |
| testWithoutContext('licensesAccepted returns LicensesAccepted.unknown if cannot run sdkmanager', () async { |
| sdk.sdkManagerPath = '/foo/bar/sdkmanager'; |
| processManager.excludedExecutables.add('/foo/bar/sdkmanager'); |
| final AndroidLicenseValidator licenseValidator = AndroidLicenseValidator( |
| java: FakeJava(), |
| androidSdk: sdk, |
| processManager: processManager, |
| platform: FakePlatform(environment: <String, String>{'HOME': '/home/me'}), |
| stdio: stdio, |
| logger: BufferLogger.test(), |
| userMessages: UserMessages(), |
| ); |
| final LicensesAccepted licenseStatus = await licenseValidator.licensesAccepted; |
| |
| expect(licenseStatus, LicensesAccepted.unknown); |
| }); |
| |
| testWithoutContext('licensesAccepted handles garbage/no output', () async { |
| sdk.sdkManagerPath = '/foo/bar/sdkmanager'; |
| processManager.addCommand(const FakeCommand( |
| command: <String>[ |
| '/foo/bar/sdkmanager', |
| '--licenses', |
| ], stdout: 'asdasassad', |
| )); |
| final AndroidLicenseValidator licenseValidator = AndroidLicenseValidator( |
| java: FakeJava(), |
| androidSdk: sdk, |
| processManager: processManager, |
| platform: FakePlatform(environment: <String, String>{'HOME': '/home/me'}), |
| stdio: stdio, |
| logger: BufferLogger.test(), |
| userMessages: UserMessages(), |
| ); |
| final LicensesAccepted result = await licenseValidator.licensesAccepted; |
| |
| expect(result, LicensesAccepted.unknown); |
| }); |
| |
| testWithoutContext('licensesAccepted works for all licenses accepted', () async { |
| sdk.sdkManagerPath = '/foo/bar/sdkmanager'; |
| const String output = ''' |
| [=======================================] 100% Computing updates... |
| All SDK package licenses accepted. |
| '''; |
| processManager.addCommand(const FakeCommand( |
| command: <String>[ |
| '/foo/bar/sdkmanager', |
| '--licenses', |
| ], stdout: output, |
| )); |
| |
| final AndroidLicenseValidator licenseValidator = AndroidLicenseValidator( |
| java: FakeJava(), |
| androidSdk: sdk, |
| processManager: processManager, |
| platform: FakePlatform(environment: <String, String>{'HOME': '/home/me'}), |
| stdio: stdio, |
| logger: BufferLogger.test(), |
| userMessages: UserMessages(), |
| ); |
| final LicensesAccepted result = await licenseValidator.licensesAccepted; |
| |
| expect(result, LicensesAccepted.all); |
| }); |
| |
| testWithoutContext('licensesAccepted sets environment for finding java', () async { |
| final Java java = FakeJava(); |
| sdk.sdkManagerPath = '/foo/bar/sdkmanager'; |
| processManager.addCommand( |
| FakeCommand( |
| command: <String>[sdk.sdkManagerPath!, '--licenses'], |
| stdout: 'All SDK package licenses accepted.', |
| environment: <String, String>{ |
| 'JAVA_HOME': java.javaHome!, |
| 'PATH': fileSystem.path.join(java.javaHome!, 'bin'), |
| } |
| ) |
| ); |
| final AndroidLicenseValidator licenseValidator = AndroidLicenseValidator( |
| java: java, |
| androidSdk: sdk, |
| processManager: processManager, |
| platform: FakePlatform(environment: <String, String>{'HOME': '/home/me'}), |
| stdio: stdio, |
| logger: BufferLogger.test(), |
| userMessages: UserMessages(), |
| ); |
| final LicensesAccepted licenseStatus = await licenseValidator.licensesAccepted; |
| |
| expect(licenseStatus, LicensesAccepted.all); |
| }); |
| |
| testWithoutContext('licensesAccepted works for some licenses accepted', () async { |
| sdk.sdkManagerPath = '/foo/bar/sdkmanager'; |
| const String output = ''' |
| [=======================================] 100% Computing updates... |
| 2 of 5 SDK package licenses not accepted. |
| Review licenses that have not been accepted (y/N)? |
| '''; |
| processManager.addCommand(const FakeCommand( |
| command: <String>[ |
| '/foo/bar/sdkmanager', |
| '--licenses', |
| ], stdout: output, |
| )); |
| |
| final AndroidLicenseValidator licenseValidator = AndroidLicenseValidator( |
| java: FakeJava(), |
| androidSdk: sdk, |
| processManager: processManager, |
| platform: FakePlatform(environment: <String, String>{'HOME': '/home/me'}), |
| stdio: stdio, |
| logger: BufferLogger.test(), |
| userMessages: UserMessages(), |
| ); |
| final LicensesAccepted result = await licenseValidator.licensesAccepted; |
| |
| expect(result, LicensesAccepted.some); |
| }); |
| |
| testWithoutContext('licensesAccepted works for no licenses accepted', () async { |
| sdk.sdkManagerPath = '/foo/bar/sdkmanager'; |
| const String output = ''' |
| [=======================================] 100% Computing updates... |
| 5 of 5 SDK package licenses not accepted. |
| Review licenses that have not been accepted (y/N)? |
| '''; |
| processManager.addCommand(const FakeCommand( |
| command: <String>[ |
| '/foo/bar/sdkmanager', |
| '--licenses', |
| ], stdout: output, |
| )); |
| |
| final AndroidLicenseValidator licenseValidator = AndroidLicenseValidator( |
| java: FakeJava(), |
| androidSdk: sdk, |
| processManager: processManager, |
| platform: FakePlatform(environment: <String, String>{'HOME': '/home/me'}), |
| stdio: stdio, |
| logger: BufferLogger.test(), |
| userMessages: UserMessages(), |
| ); |
| final LicensesAccepted result = await licenseValidator.licensesAccepted; |
| |
| expect(result, LicensesAccepted.none); |
| }); |
| |
| testWithoutContext('runLicenseManager succeeds for version >= 26', () async { |
| sdk.sdkManagerPath = '/foo/bar/sdkmanager'; |
| sdk.sdkManagerVersion = '26.0.0'; |
| processManager.addCommand(const FakeCommand( |
| command: <String>[ |
| '/foo/bar/sdkmanager', |
| '--licenses', |
| ], |
| )); |
| |
| final AndroidLicenseValidator licenseValidator = AndroidLicenseValidator( |
| java: FakeJava(), |
| androidSdk: sdk, |
| processManager: processManager, |
| platform: FakePlatform(environment: <String, String>{'HOME': '/home/me'}), |
| stdio: stdio, |
| logger: BufferLogger.test(), |
| userMessages: UserMessages(), |
| ); |
| |
| expect(await licenseValidator.runLicenseManager(), isTrue); |
| }); |
| |
| testWithoutContext('runLicenseManager errors when sdkmanager is not found', () async { |
| sdk.sdkManagerPath = '/foo/bar/sdkmanager'; |
| processManager.excludedExecutables.add('/foo/bar/sdkmanager'); |
| |
| final AndroidLicenseValidator licenseValidator = AndroidLicenseValidator( |
| java: FakeJava(), |
| androidSdk: sdk, |
| processManager: processManager, |
| platform: FakePlatform(environment: <String, String>{'HOME': '/home/me'}), |
| stdio: stdio, |
| logger: BufferLogger.test(), |
| userMessages: UserMessages(), |
| ); |
| |
| expect(licenseValidator.runLicenseManager(), throwsToolExit()); |
| }); |
| |
| testWithoutContext('runLicenseManager handles broken pipe without ArgumentError', () async { |
| sdk.sdkManagerPath = '/foo/bar/sdkmanager'; |
| const String exceptionMessage = 'Write failed (OS Error: Broken pipe, errno = 32), port = 0'; |
| const SocketException exception = SocketException(exceptionMessage); |
| // By using a `Socket` generic parameter, the stdin.addStream will return a `Future<Socket>` |
| // We are testing that our error handling properly handles futures of this type |
| final ThrowingStdin<Socket> fakeStdin = ThrowingStdin<Socket>(exception); |
| final FakeCommand licenseCommand = FakeCommand( |
| command: <String>[sdk.sdkManagerPath!, '--licenses'], |
| stdin: fakeStdin, |
| ); |
| processManager.addCommand(licenseCommand); |
| final BufferLogger logger = BufferLogger.test(); |
| |
| final AndroidLicenseValidator licenseValidator = AndroidLicenseValidator( |
| java: FakeJava(), |
| androidSdk: sdk, |
| processManager: processManager, |
| platform: FakePlatform(environment: <String, String>{'HOME': '/home/me'}), |
| stdio: stdio, |
| logger: logger, |
| userMessages: UserMessages(), |
| ); |
| |
| await licenseValidator.runLicenseManager(); |
| expect(logger.traceText, contains(exceptionMessage)); |
| expect(processManager, hasNoRemainingExpectations); |
| }); |
| |
| testWithoutContext('runLicenseManager errors when sdkmanager fails to run', () async { |
| sdk.sdkManagerPath = '/foo/bar/sdkmanager'; |
| processManager.excludedExecutables.add('/foo/bar/sdkmanager'); |
| |
| final AndroidLicenseValidator licenseValidator = AndroidLicenseValidator( |
| java: FakeJava(), |
| androidSdk: sdk, |
| processManager: processManager, |
| platform: FakePlatform(environment: <String, String>{'HOME': '/home/me'}), |
| stdio: stdio, |
| logger: BufferLogger.test(), |
| userMessages: UserMessages(), |
| ); |
| |
| expect(licenseValidator.runLicenseManager(), throwsToolExit()); |
| }); |
| |
| testWithoutContext('runLicenseManager errors when sdkmanager exits non-zero', () async { |
| const String sdkManagerPath = '/foo/bar/sdkmanager'; |
| sdk.sdkManagerPath = sdkManagerPath; |
| final BufferLogger logger = BufferLogger.test(); |
| processManager.addCommand( |
| const FakeCommand( |
| command: <String>[sdkManagerPath, '--licenses'], |
| exitCode: 1, |
| stderr: 'sdkmanager crash', |
| ), |
| ); |
| |
| final AndroidLicenseValidator licenseValidator = AndroidLicenseValidator( |
| java: FakeJava(), |
| androidSdk: sdk, |
| processManager: processManager, |
| platform: FakePlatform(environment: <String, String>{'HOME': '/home/me'}), |
| stdio: stdio, |
| logger: logger, |
| userMessages: UserMessages(), |
| ); |
| |
| await expectLater( |
| licenseValidator.runLicenseManager(), |
| throwsToolExit( |
| message: 'Android sdkmanager tool was found, but failed to run ($sdkManagerPath): "exited code 1"', |
| ), |
| ); |
| expect(processManager, hasNoRemainingExpectations); |
| expect(logger.traceText, isEmpty); |
| expect(stdio.writtenToStdout, isEmpty); |
| expect(stdio.writtenToStderr, contains('sdkmanager crash')); |
| }); |
| |
| testWithoutContext('detects license-only SDK installation with cmdline-tools', () async { |
| sdk |
| ..licensesAvailable = true |
| ..platformToolsAvailable = false |
| ..cmdlineToolsAvailable = true |
| ..directory = fileSystem.directory('/foo/bar'); |
| final ValidationResult validationResult = await AndroidValidator( |
| java: FakeJava(), |
| androidSdk: sdk, |
| logger: logger, |
| platform: FakePlatform()..environment = <String, String>{'HOME': '/home/me'}, |
| userMessages: UserMessages(), |
| ).validate(); |
| |
| expect(validationResult.type, ValidationType.partial); |
| |
| final ValidationMessage sdkMessage = validationResult.messages.first; |
| expect(sdkMessage.type, ValidationMessageType.information); |
| expect(sdkMessage.message, 'Android SDK at /foo/bar'); |
| |
| final ValidationMessage licenseMessage = validationResult.messages.last; |
| expect(licenseMessage.type, ValidationMessageType.hint); |
| expect(licenseMessage.message, UserMessages().androidSdkLicenseOnly(kAndroidHome)); |
| }); |
| |
| testUsingContext('detects minimum required SDK and buildtools', () async { |
| final FakeAndroidSdkVersion sdkVersion = FakeAndroidSdkVersion() |
| ..sdkLevel = 28 |
| ..buildToolsVersion = Version(26, 0, 3); |
| |
| sdk |
| ..licensesAvailable = true |
| ..platformToolsAvailable = true |
| ..cmdlineToolsAvailable = true |
| // Test with invalid SDK and build tools |
| ..directory = fileSystem.directory('/foo/bar') |
| ..sdkManagerPath = '/foo/bar/sdkmanager' |
| ..latestVersion = sdkVersion; |
| |
| final String errorMessage = UserMessages().androidSdkBuildToolsOutdated( |
| kAndroidSdkMinVersion, |
| kAndroidSdkBuildToolsMinVersion.toString(), |
| FakePlatform(), |
| ); |
| |
| final AndroidValidator androidValidator = AndroidValidator( |
| java: null, |
| androidSdk: sdk, |
| logger: logger, |
| platform: FakePlatform()..environment = <String, String>{'HOME': '/home/me'}, |
| userMessages: UserMessages(), |
| ); |
| |
| |
| ValidationResult validationResult = await androidValidator.validate(); |
| expect(validationResult.type, ValidationType.missing); |
| expect( |
| validationResult.messages.last.message, |
| errorMessage, |
| ); |
| |
| // Test with valid SDK but invalid build tools |
| sdkVersion.sdkLevel = 29; |
| sdkVersion.buildToolsVersion = Version(28, 0, 2); |
| |
| validationResult = await androidValidator.validate(); |
| expect(validationResult.type, ValidationType.missing); |
| expect( |
| validationResult.messages.last.message, |
| errorMessage, |
| ); |
| |
| // Test with valid SDK and valid build tools |
| // Will still be partial because AndroidSdk.findJavaBinary is static :( |
| sdkVersion.sdkLevel = kAndroidSdkMinVersion; |
| sdkVersion.buildToolsVersion = kAndroidSdkBuildToolsMinVersion; |
| |
| validationResult = await androidValidator.validate(); |
| expect(validationResult.type, ValidationType.partial); // No Java binary |
| expect( |
| validationResult.messages.any((ValidationMessage message) => message.message == errorMessage), |
| isFalse, |
| ); |
| }); |
| |
| testWithoutContext('detects missing cmdline tools', () async { |
| sdk |
| ..licensesAvailable = true |
| ..platformToolsAvailable = true |
| ..cmdlineToolsAvailable = false |
| ..directory = fileSystem.directory('/foo/bar'); |
| |
| final AndroidValidator androidValidator = AndroidValidator( |
| java: FakeJava(), |
| androidSdk: sdk, |
| logger: logger, |
| platform: FakePlatform()..environment = <String, String>{'HOME': '/home/me'}, |
| userMessages: UserMessages(), |
| ); |
| |
| |
| final String errorMessage = UserMessages().androidMissingCmdTools; |
| |
| final ValidationResult validationResult = await androidValidator.validate(); |
| expect(validationResult.type, ValidationType.missing); |
| |
| final ValidationMessage sdkMessage = validationResult.messages.first; |
| expect(sdkMessage.type, ValidationMessageType.information); |
| expect(sdkMessage.message, 'Android SDK at /foo/bar'); |
| |
| final ValidationMessage cmdlineMessage = validationResult.messages.last; |
| expect(cmdlineMessage.type, ValidationMessageType.error); |
| expect(cmdlineMessage.message, errorMessage); |
| }); |
| |
| testUsingContext('detects minimum required java version', () async { |
| // Test with older version of JDK |
| final Platform platform = FakePlatform()..environment = <String, String>{ |
| 'HOME': '/home/me', |
| Java.javaHomeEnvironmentVariable: 'home/java', |
| 'PATH': '', |
| }; |
| final FakeAndroidSdkVersion sdkVersion = FakeAndroidSdkVersion() |
| ..sdkLevel = 29 |
| ..buildToolsVersion = Version(28, 0, 3); |
| |
| // Mock a pass through scenario to reach _checkJavaVersion() |
| sdk |
| ..licensesAvailable = true |
| ..platformToolsAvailable = true |
| ..cmdlineToolsAvailable = true |
| ..directory = fileSystem.directory('/foo/bar') |
| ..sdkManagerPath = '/foo/bar/sdkmanager'; |
| sdk.latestVersion = sdkVersion; |
| |
| const String javaVersionText = 'openjdk version "1.7.0_212"'; |
| final String errorMessage = UserMessages().androidJavaMinimumVersion(javaVersionText); |
| |
| final ValidationResult validationResult = await AndroidValidator( |
| java: FakeJava(version: const Version.withText(1, 7, 0, javaVersionText)), |
| androidSdk: sdk, |
| logger: logger, |
| platform: platform, |
| userMessages: UserMessages(), |
| ).validate(); |
| expect(validationResult.type, ValidationType.partial); |
| expect( |
| validationResult.messages.last.message, |
| errorMessage, |
| ); |
| expect( |
| validationResult.messages.any( |
| (ValidationMessage message) => message.message.contains('Unable to locate Android SDK') |
| ), |
| false, |
| ); |
| }); |
| |
| testWithoutContext('Mentions `flutter config --android-sdk if user has no AndroidSdk`', () async { |
| final ValidationResult validationResult = await AndroidValidator( |
| java: FakeJava(), |
| androidSdk: null, |
| logger: logger, |
| platform: FakePlatform()..environment = <String, String>{'HOME': '/home/me', Java.javaHomeEnvironmentVariable: 'home/java'}, |
| userMessages: UserMessages(), |
| ).validate(); |
| |
| expect( |
| validationResult.messages.any( |
| (ValidationMessage message) => message.message.contains('flutter config --android-sdk') |
| ), |
| true, |
| ); |
| }); |
| } |
| |
| class FakeAndroidSdk extends Fake implements AndroidSdk { |
| @override |
| String? sdkManagerPath; |
| |
| @override |
| String? sdkManagerVersion; |
| |
| @override |
| String? adbPath; |
| |
| @override |
| bool licensesAvailable = false; |
| |
| @override |
| bool platformToolsAvailable = false; |
| |
| @override |
| bool cmdlineToolsAvailable = false; |
| |
| @override |
| Directory directory = MemoryFileSystem.test().directory('/foo/bar'); |
| |
| @override |
| AndroidSdkVersion? latestVersion; |
| |
| @override |
| String? emulatorPath; |
| |
| @override |
| List<String> validateSdkWellFormed() => <String>[]; |
| } |
| |
| class FakeAndroidSdkVersion extends Fake implements AndroidSdkVersion { |
| @override |
| int sdkLevel = 0; |
| |
| @override |
| Version buildToolsVersion = Version(0, 0, 0); |
| |
| @override |
| String get buildToolsVersionName => ''; |
| |
| @override |
| String get platformName => ''; |
| } |
| |
| class ThrowingStdin<T> extends Fake implements IOSink { |
| ThrowingStdin(this.exception); |
| |
| final Exception exception; |
| |
| @override |
| Future<dynamic> addStream(Stream<List<int>> stream) { |
| return Future<T>.error(exception); |
| } |
| } |