Unverified Commit 4be79199 authored by Jenn Magder's avatar Jenn Magder Committed by GitHub

Swap xcodeproj_tests from MockProcessManager to FakeProcessManager (#56505)

parent 13501af6
...@@ -26,19 +26,18 @@ import '../../src/pubspec_schema.dart'; ...@@ -26,19 +26,18 @@ import '../../src/pubspec_schema.dart';
const String xcodebuild = '/usr/bin/xcodebuild'; const String xcodebuild = '/usr/bin/xcodebuild';
void main() { void main() {
group('MockProcessManager', () {
mocks.MockProcessManager processManager; mocks.MockProcessManager processManager;
XcodeProjectInterpreter xcodeProjectInterpreter; XcodeProjectInterpreter xcodeProjectInterpreter;
FakePlatform platform; FakePlatform platform;
FileSystem fileSystem;
BufferLogger logger; BufferLogger logger;
AnsiTerminal terminal;
setUp(() { setUp(() {
processManager = mocks.MockProcessManager(); processManager = mocks.MockProcessManager();
platform = FakePlatform(operatingSystem: 'macos'); platform = FakePlatform(operatingSystem: 'macos');
fileSystem = MemoryFileSystem(); final FileSystem fileSystem = MemoryFileSystem();
fileSystem.file(xcodebuild).createSync(recursive: true); fileSystem.file(xcodebuild).createSync(recursive: true);
terminal = MockAnsiTerminal(); final AnsiTerminal terminal = MockAnsiTerminal();
logger = BufferLogger.test( logger = BufferLogger.test(
terminal: terminal terminal: terminal
); );
...@@ -52,6 +51,7 @@ void main() { ...@@ -52,6 +51,7 @@ void main() {
); );
}); });
// Work around https://github.com/flutter/flutter/issues/56415.
testWithoutContext('xcodebuild versionText returns null when xcodebuild is not installed', () { testWithoutContext('xcodebuild versionText returns null when xcodebuild is not installed', () {
when(processManager.runSync(<String>[xcodebuild, '-version'])) when(processManager.runSync(<String>[xcodebuild, '-version']))
.thenThrow(const ProcessException(xcodebuild, <String>['-version'])); .thenThrow(const ProcessException(xcodebuild, <String>['-version']));
...@@ -59,68 +59,127 @@ void main() { ...@@ -59,68 +59,127 @@ void main() {
expect(xcodeProjectInterpreter.versionText, isNull); expect(xcodeProjectInterpreter.versionText, isNull);
}); });
testWithoutContext('xcodebuild build settings flakes', () async {
const Duration delay = Duration(seconds: 1);
processManager.processFactory = mocks.flakyProcessFactory(
flakes: 1,
delay: delay + const Duration(seconds: 1),
);
platform.environment = const <String, String>{};
expect(await xcodeProjectInterpreter.getBuildSettings(
'', '', timeout: delay),
const <String, String>{});
// build settings times out and is killed once, then succeeds.
verify(processManager.killPid(any)).called(1);
// The verbose logs should tell us something timed out.
expect(logger.traceText, contains('timed out'));
});
});
FakeProcessManager fakeProcessManager;
XcodeProjectInterpreter xcodeProjectInterpreter;
FakePlatform platform;
FileSystem fileSystem;
BufferLogger logger;
AnsiTerminal terminal;
setUp(() {
fakeProcessManager = FakeProcessManager.list(<FakeCommand>[]);
platform = FakePlatform(operatingSystem: 'macos');
fileSystem = MemoryFileSystem();
fileSystem.file(xcodebuild).createSync(recursive: true);
terminal = MockAnsiTerminal();
logger = BufferLogger.test(
terminal: terminal
);
xcodeProjectInterpreter = XcodeProjectInterpreter(
logger: logger,
fileSystem: fileSystem,
platform: platform,
processManager: fakeProcessManager,
terminal: terminal,
usage: null,
);
});
testWithoutContext('xcodebuild versionText returns null when xcodebuild is not fully installed', () { testWithoutContext('xcodebuild versionText returns null when xcodebuild is not fully installed', () {
when(processManager.runSync(<String>[xcodebuild, '-version'])).thenReturn( fakeProcessManager.addCommand(const FakeCommand(
ProcessResult( command: <String>[xcodebuild, '-version'],
0, stdout: "xcode-select: error: tool 'xcodebuild' requires Xcode, "
1,
"xcode-select: error: tool 'xcodebuild' requires Xcode, "
"but active developer directory '/Library/Developer/CommandLineTools' " "but active developer directory '/Library/Developer/CommandLineTools' "
'is a command line tools instance', 'is a command line tools instance',
'', exitCode: 1,
), ));
);
expect(xcodeProjectInterpreter.versionText, isNull); expect(xcodeProjectInterpreter.versionText, isNull);
expect(fakeProcessManager.hasRemainingExpectations, isFalse);
}); });
testWithoutContext('xcodebuild versionText returns formatted version text', () { testWithoutContext('xcodebuild versionText returns formatted version text', () {
when(processManager.runSync(<String>[xcodebuild, '-version'])) fakeProcessManager.addCommand(const FakeCommand(
.thenReturn(ProcessResult(1, 0, 'Xcode 8.3.3\nBuild version 8E3004b', '')); command: <String>[xcodebuild, '-version'],
stdout: 'Xcode 8.3.3\nBuild version 8E3004b',
));
expect(xcodeProjectInterpreter.versionText, 'Xcode 8.3.3, Build version 8E3004b'); expect(xcodeProjectInterpreter.versionText, 'Xcode 8.3.3, Build version 8E3004b');
expect(fakeProcessManager.hasRemainingExpectations, isFalse);
}); });
testWithoutContext('xcodebuild versionText handles Xcode version string with unexpected format', () { testWithoutContext('xcodebuild versionText handles Xcode version string with unexpected format', () {
when(processManager.runSync(<String>[xcodebuild, '-version'])) fakeProcessManager.addCommand(const FakeCommand(
.thenReturn(ProcessResult(1, 0, 'Xcode Ultra5000\nBuild version 8E3004b', '')); command: <String>[xcodebuild, '-version'],
stdout: 'Xcode Ultra5000\nBuild version 8E3004b',
));
expect(xcodeProjectInterpreter.versionText, 'Xcode Ultra5000, Build version 8E3004b'); expect(xcodeProjectInterpreter.versionText, 'Xcode Ultra5000, Build version 8E3004b');
expect(fakeProcessManager.hasRemainingExpectations, isFalse);
}); });
testWithoutContext('xcodebuild majorVersion returns major version', () { testWithoutContext('xcodebuild majorVersion returns major version', () {
when(processManager.runSync(<String>[xcodebuild, '-version'])) fakeProcessManager.addCommand(const FakeCommand(
.thenReturn(ProcessResult(1, 0, 'Xcode 11.4.1\nBuild version 11N111s', '')); command: <String>[xcodebuild, '-version'],
stdout: 'Xcode 11.4.1\nBuild version 11N111s',
));
expect(xcodeProjectInterpreter.majorVersion, 11); expect(xcodeProjectInterpreter.majorVersion, 11);
expect(fakeProcessManager.hasRemainingExpectations, isFalse);
}); });
testWithoutContext('xcodebuild majorVersion is null when version has unexpected format', () { testWithoutContext('xcodebuild majorVersion is null when version has unexpected format', () {
when(processManager.runSync(<String>[xcodebuild, '-version'])) fakeProcessManager.addCommand(const FakeCommand(
.thenReturn(ProcessResult(1, 0, 'Xcode Ultra5000\nBuild version 8E3004b', '')); command: <String>[xcodebuild, '-version'],
stdout: 'Xcode Ultra5000\nBuild version 8E3004b',
));
expect(xcodeProjectInterpreter.majorVersion, isNull); expect(xcodeProjectInterpreter.majorVersion, isNull);
expect(fakeProcessManager.hasRemainingExpectations, isFalse);
}); });
testWithoutContext('xcodebuild minorVersion returns minor version', () { testWithoutContext('xcodebuild minorVersion returns minor version', () {
when(processManager.runSync(<String>[xcodebuild, '-version'])) fakeProcessManager.addCommand(const FakeCommand(
.thenReturn(ProcessResult(1, 0, 'Xcode 8.3.3\nBuild version 8E3004b', '')); command: <String>[xcodebuild, '-version'],
stdout: 'Xcode 8.3.3\nBuild version 8E3004b',
));
expect(xcodeProjectInterpreter.minorVersion, 3); expect(xcodeProjectInterpreter.minorVersion, 3);
expect(fakeProcessManager.hasRemainingExpectations, isFalse);
}); });
testWithoutContext('xcodebuild minorVersion returns 0 when minor version is unspecified', () { testWithoutContext('xcodebuild minorVersion returns 0 when minor version is unspecified', () {
when(processManager.runSync(<String>[xcodebuild, '-version'])) fakeProcessManager.addCommand(const FakeCommand(
.thenReturn(ProcessResult(1, 0, 'Xcode 8\nBuild version 8E3004b', '')); command: <String>[xcodebuild, '-version'],
stdout: 'Xcode 8\nBuild version 8E3004b',
));
expect(xcodeProjectInterpreter.minorVersion, 0); expect(xcodeProjectInterpreter.minorVersion, 0);
expect(fakeProcessManager.hasRemainingExpectations, isFalse);
}); });
testWithoutContext('xcodebuild minorVersion is null when version has unexpected format', () { testWithoutContext('xcodebuild minorVersion is null when version has unexpected format', () {
when(processManager.runSync(<String>[xcodebuild, '-version'])) fakeProcessManager.addCommand(const FakeCommand(
.thenReturn(ProcessResult(1, 0, 'Xcode Ultra5000\nBuild version 8E3004b', '')); command: <String>[xcodebuild, '-version'],
stdout: 'Xcode Ultra5000\nBuild version 8E3004b',
));
expect(xcodeProjectInterpreter.minorVersion, isNull); expect(xcodeProjectInterpreter.minorVersion, isNull);
expect(fakeProcessManager.hasRemainingExpectations, isFalse);
}); });
testWithoutContext('xcodebuild isInstalled is false when not on MacOS', () { testWithoutContext('xcodebuild isInstalled is false when not on MacOS', () {
...@@ -129,115 +188,104 @@ void main() { ...@@ -129,115 +188,104 @@ void main() {
logger: logger, logger: logger,
fileSystem: fileSystem, fileSystem: fileSystem,
platform: platform, platform: platform,
processManager: processManager, processManager: fakeProcessManager,
terminal: terminal, terminal: terminal,
usage: Usage.test(), usage: Usage.test(),
); );
fileSystem.file(xcodebuild).deleteSync(); fileSystem.file(xcodebuild).deleteSync();
expect(xcodeProjectInterpreter.isInstalled, isFalse); expect(xcodeProjectInterpreter.isInstalled, isFalse);
expect(fakeProcessManager.hasRemainingExpectations, isFalse);
}); });
testWithoutContext('xcodebuild isInstalled is false when xcodebuild does not exist', () { testWithoutContext('xcodebuild isInstalled is false when xcodebuild does not exist', () {
fileSystem.file(xcodebuild).deleteSync(); fileSystem.file(xcodebuild).deleteSync();
expect(xcodeProjectInterpreter.isInstalled, isFalse); expect(xcodeProjectInterpreter.isInstalled, isFalse);
expect(fakeProcessManager.hasRemainingExpectations, isFalse);
}); });
testWithoutContext('xcodebuild isInstalled is false when Xcode is not fully installed', () { testWithoutContext('xcodebuild isInstalled is false when Xcode is not fully installed', () {
when(processManager.runSync(<String>[xcodebuild, '-version'])).thenReturn( fakeProcessManager.addCommand(const FakeCommand(
ProcessResult( command: <String>[xcodebuild, '-version'],
0, stdout: "xcode-select: error: tool 'xcodebuild' requires Xcode, "
1,
"xcode-select: error: tool 'xcodebuild' requires Xcode, "
"but active developer directory '/Library/Developer/CommandLineTools' " "but active developer directory '/Library/Developer/CommandLineTools' "
'is a command line tools instance', 'is a command line tools instance',
'', exitCode: 1,
), ));
);
expect(xcodeProjectInterpreter.isInstalled, isFalse); expect(xcodeProjectInterpreter.isInstalled, isFalse);
expect(fakeProcessManager.hasRemainingExpectations, isFalse);
}); });
testWithoutContext('xcodebuild isInstalled is false when version has unexpected format', () { testWithoutContext('xcodebuild isInstalled is false when version has unexpected format', () {
when(processManager.runSync(<String>[xcodebuild, '-version'])) fakeProcessManager.addCommand(const FakeCommand(
.thenReturn(ProcessResult(1, 0, 'Xcode Ultra5000\nBuild version 8E3004b', '')); command: <String>[xcodebuild, '-version'],
stdout: 'Xcode Ultra5000\nBuild version 8E3004b',
));
expect(xcodeProjectInterpreter.isInstalled, isFalse); expect(xcodeProjectInterpreter.isInstalled, isFalse);
expect(fakeProcessManager.hasRemainingExpectations, isFalse);
}); });
testWithoutContext('xcodebuild isInstalled is true when version has expected format', () { testWithoutContext('xcodebuild isInstalled is true when version has expected format', () {
when(processManager.runSync(<String>[xcodebuild, '-version'])) fakeProcessManager.addCommand(const FakeCommand(
.thenReturn(ProcessResult(1, 0, 'Xcode 8.3.3\nBuild version 8E3004b', '')); command: <String>[xcodebuild, '-version'],
stdout: 'Xcode 8.3.3\nBuild version 8E3004b',
));
expect(xcodeProjectInterpreter.isInstalled, isTrue); expect(xcodeProjectInterpreter.isInstalled, isTrue);
expect(fakeProcessManager.hasRemainingExpectations, isFalse);
}); });
testWithoutContext('xcodebuild build settings is empty when xcodebuild failed to get the build settings', () async { testWithoutContext('xcodebuild build settings is empty when xcodebuild failed to get the build settings', () async {
platform.environment = Map<String, String>.unmodifiable(<String, String>{}); platform.environment = const <String, String>{};
when(processManager.runSync(
argThat(contains(xcodebuild)),
workingDirectory: anyNamed('workingDirectory'),
environment: anyNamed('environment')))
.thenReturn(ProcessResult(0, 1, '', ''));
expect(await xcodeProjectInterpreter.getBuildSettings('', ''), const <String, String>{});
});
testWithoutContext('xcodebuild build settings flakes', () async { fakeProcessManager.addCommand(const FakeCommand(
const Duration delay = Duration(seconds: 1); command: <String>[
processManager.processFactory = mocks.flakyProcessFactory( '/usr/bin/xcodebuild',
flakes: 1, '-project',
delay: delay + const Duration(seconds: 1), '/',
); '-target',
platform.environment = Map<String, String>.unmodifiable(<String, String>{}); 'Runner',
'-showBuildSettings'
],
exitCode: 1,
));
expect(await xcodeProjectInterpreter.getBuildSettings( expect(await xcodeProjectInterpreter.getBuildSettings('', 'Runner'), const <String, String>{});
'', '', timeout: delay), expect(fakeProcessManager.hasRemainingExpectations, isFalse);
const <String, String>{});
// build settings times out and is killed once, then succeeds.
verify(processManager.killPid(any)).called(1);
// The verbose logs should tell us something timed out.
expect(logger.traceText, contains('timed out'));
}); });
testWithoutContext('xcodebuild build settings contains Flutter Xcode environment variables', () async { testWithoutContext('xcodebuild build settings contains Flutter Xcode environment variables', () async {
platform.environment = Map<String, String>.unmodifiable(<String, String>{ platform.environment = const <String, String>{
'FLUTTER_XCODE_CODE_SIGN_STYLE': 'Manual', 'FLUTTER_XCODE_CODE_SIGN_STYLE': 'Manual',
'FLUTTER_XCODE_ARCHS': 'arm64' 'FLUTTER_XCODE_ARCHS': 'arm64'
}); };
when(processManager.runSync(<String>[ fakeProcessManager.addCommand(FakeCommand(
command: <String>[
xcodebuild, xcodebuild,
'-project', '-project',
platform.pathSeparator, fileSystem.path.separator,
'-target', '-target',
'', 'Runner',
'-showBuildSettings', '-showBuildSettings',
'CODE_SIGN_STYLE=Manual', 'CODE_SIGN_STYLE=Manual',
'ARCHS=arm64' 'ARCHS=arm64'
], ],
workingDirectory: anyNamed('workingDirectory'), ));
environment: anyNamed('environment'))) expect(await xcodeProjectInterpreter.getBuildSettings('', 'Runner'), const <String, String>{});
.thenReturn(ProcessResult(1, 0, '', '')); expect(fakeProcessManager.hasRemainingExpectations, isFalse);
expect(await xcodeProjectInterpreter.getBuildSettings('', ''), const <String, String>{});
}); });
testWithoutContext('xcodebuild clean contains Flutter Xcode environment variables', () async { testWithoutContext('xcodebuild clean contains Flutter Xcode environment variables', () async {
platform.environment = Map<String, String>.unmodifiable(<String, String>{ platform.environment = const <String, String>{
'FLUTTER_XCODE_CODE_SIGN_STYLE': 'Manual', 'FLUTTER_XCODE_CODE_SIGN_STYLE': 'Manual',
'FLUTTER_XCODE_ARCHS': 'arm64' 'FLUTTER_XCODE_ARCHS': 'arm64'
}); };
when(processManager.run(
any,
workingDirectory: anyNamed('workingDirectory')))
.thenAnswer((_) => Future<ProcessResult>.value(ProcessResult(1, 0, '', '')));
await xcodeProjectInterpreter.cleanWorkspace('workspace_path', 'Runner');
final List<dynamic> captured = verify(processManager.run(
captureAny,
workingDirectory: anyNamed('workingDirectory'),
environment: anyNamed('environment'))).captured;
expect(captured.first, <String>[ fakeProcessManager.addCommand(const FakeCommand(
command: <String>[
xcodebuild, xcodebuild,
'-workspace', '-workspace',
'workspace_path', 'workspace_path',
...@@ -247,45 +295,47 @@ void main() { ...@@ -247,45 +295,47 @@ void main() {
'clean', 'clean',
'CODE_SIGN_STYLE=Manual', 'CODE_SIGN_STYLE=Manual',
'ARCHS=arm64' 'ARCHS=arm64'
]); ],
));
await xcodeProjectInterpreter.cleanWorkspace('workspace_path', 'Runner');
expect(fakeProcessManager.hasRemainingExpectations, isFalse);
}); });
testWithoutContext('xcodebuild -list getInfo returns something when xcodebuild -list succeeds', () async { testWithoutContext('xcodebuild -list getInfo returns something when xcodebuild -list succeeds', () async {
const String workingDirectory = '/'; const String workingDirectory = '/';
when(processManager.run( fakeProcessManager.addCommand(const FakeCommand(
<String>[xcodebuild, '-list'], command: <String>[xcodebuild, '-list'],
environment: anyNamed('environment'), ));
workingDirectory: workingDirectory),
).thenAnswer((_) {
return Future<ProcessResult>.value(ProcessResult(1, 0, '', ''));
});
final XcodeProjectInterpreter xcodeProjectInterpreter = XcodeProjectInterpreter( final XcodeProjectInterpreter xcodeProjectInterpreter = XcodeProjectInterpreter(
logger: logger, logger: logger,
fileSystem: fileSystem, fileSystem: fileSystem,
platform: platform, platform: platform,
processManager: processManager, processManager: fakeProcessManager,
terminal: terminal, terminal: terminal,
usage: Usage.test(), usage: Usage.test(),
); );
expect(await xcodeProjectInterpreter.getInfo(workingDirectory), isNotNull); expect(await xcodeProjectInterpreter.getInfo(workingDirectory), isNotNull);
expect(fakeProcessManager.hasRemainingExpectations, isFalse);
}); });
testWithoutContext('xcodebuild -list getInfo throws a tool exit when it is unable to find a project', () async { testWithoutContext('xcodebuild -list getInfo throws a tool exit when it is unable to find a project', () async {
const String workingDirectory = '/'; const String workingDirectory = '/';
const String stderr = 'Useful Xcode failure message about missing project.'; const String stderr = 'Useful Xcode failure message about missing project.';
when(processManager.run(
<String>[xcodebuild, '-list'], fakeProcessManager.addCommand(const FakeCommand(
environment: anyNamed('environment'), command: <String>[xcodebuild, '-list'],
workingDirectory: workingDirectory), exitCode: 66,
).thenAnswer((_) { stderr: stderr,
return Future<ProcessResult>.value(ProcessResult(1, 66, '', stderr)); ));
});
final XcodeProjectInterpreter xcodeProjectInterpreter = XcodeProjectInterpreter( final XcodeProjectInterpreter xcodeProjectInterpreter = XcodeProjectInterpreter(
logger: logger, logger: logger,
fileSystem: fileSystem, fileSystem: fileSystem,
platform: platform, platform: platform,
processManager: processManager, processManager: fakeProcessManager,
terminal: terminal, terminal: terminal,
usage: Usage.test(), usage: Usage.test(),
); );
...@@ -293,6 +343,7 @@ void main() { ...@@ -293,6 +343,7 @@ void main() {
expect( expect(
() async => await xcodeProjectInterpreter.getInfo(workingDirectory), () async => await xcodeProjectInterpreter.getInfo(workingDirectory),
throwsToolExit(message: stderr)); throwsToolExit(message: stderr));
expect(fakeProcessManager.hasRemainingExpectations, isFalse);
}); });
testWithoutContext('Xcode project properties from default project can be parsed', () { testWithoutContext('Xcode project properties from default project can be parsed', () {
...@@ -427,12 +478,12 @@ Information about project "Runner": ...@@ -427,12 +478,12 @@ Information about project "Runner":
}); });
testWithoutContext('environment variables as Xcode build settings', () { testWithoutContext('environment variables as Xcode build settings', () {
platform.environment = Map<String, String>.unmodifiable(<String, String>{ platform.environment = const <String, String>{
'Ignored': 'Bogus', 'Ignored': 'Bogus',
'FLUTTER_NOT_XCODE': 'Bogus', 'FLUTTER_NOT_XCODE': 'Bogus',
'FLUTTER_XCODE_CODE_SIGN_STYLE': 'Manual', 'FLUTTER_XCODE_CODE_SIGN_STYLE': 'Manual',
'FLUTTER_XCODE_ARCHS': 'arm64' 'FLUTTER_XCODE_ARCHS': 'arm64'
}); };
final List<String> environmentVariablesAsBuildSettings = environmentVariablesAsXcodeBuildSettings(platform); final List<String> environmentVariablesAsBuildSettings = environmentVariablesAsXcodeBuildSettings(platform);
expect(environmentVariablesAsBuildSettings, <String>['CODE_SIGN_STYLE=Manual', 'ARCHS=arm64']); expect(environmentVariablesAsBuildSettings, <String>['CODE_SIGN_STYLE=Manual', 'ARCHS=arm64']);
}); });
...@@ -440,14 +491,12 @@ Information about project "Runner": ...@@ -440,14 +491,12 @@ Information about project "Runner":
group('updateGeneratedXcodeProperties', () { group('updateGeneratedXcodeProperties', () {
MockLocalEngineArtifacts mockArtifacts; MockLocalEngineArtifacts mockArtifacts;
MockProcessManager mockProcessManager;
FakePlatform macOS; FakePlatform macOS;
FileSystem fs; FileSystem fs;
setUp(() { setUp(() {
fs = MemoryFileSystem(); fs = MemoryFileSystem();
mockArtifacts = MockLocalEngineArtifacts(); mockArtifacts = MockLocalEngineArtifacts();
mockProcessManager = MockProcessManager();
macOS = FakePlatform(operatingSystem: 'macos'); macOS = FakePlatform(operatingSystem: 'macos');
fs.file(xcodebuild).createSync(recursive: true); fs.file(xcodebuild).createSync(recursive: true);
}); });
...@@ -457,7 +506,7 @@ Information about project "Runner": ...@@ -457,7 +506,7 @@ Information about project "Runner":
Artifacts: () => mockArtifacts, Artifacts: () => mockArtifacts,
Platform: () => macOS, Platform: () => macOS,
FileSystem: () => fs, FileSystem: () => fs,
ProcessManager: () => mockProcessManager, ProcessManager: () => FakeProcessManager.any(),
}); });
} }
...@@ -805,7 +854,6 @@ flutter: ...@@ -805,7 +854,6 @@ flutter:
} }
class MockLocalEngineArtifacts extends Mock implements LocalEngineArtifacts {} class MockLocalEngineArtifacts extends Mock implements LocalEngineArtifacts {}
class MockProcessManager extends Mock implements ProcessManager {}
class MockXcodeProjectInterpreter extends Mock implements XcodeProjectInterpreter {} class MockXcodeProjectInterpreter extends Mock implements XcodeProjectInterpreter {}
class MockAnsiTerminal extends Mock implements AnsiTerminal { class MockAnsiTerminal extends Mock implements AnsiTerminal {
@override @override
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment