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,7 +26,58 @@ import '../../src/pubspec_schema.dart'; ...@@ -26,7 +26,58 @@ import '../../src/pubspec_schema.dart';
const String xcodebuild = '/usr/bin/xcodebuild'; const String xcodebuild = '/usr/bin/xcodebuild';
void main() { void main() {
mocks.MockProcessManager processManager; group('MockProcessManager', () {
mocks.MockProcessManager processManager;
XcodeProjectInterpreter xcodeProjectInterpreter;
FakePlatform platform;
BufferLogger logger;
setUp(() {
processManager = mocks.MockProcessManager();
platform = FakePlatform(operatingSystem: 'macos');
final FileSystem fileSystem = MemoryFileSystem();
fileSystem.file(xcodebuild).createSync(recursive: true);
final AnsiTerminal terminal = MockAnsiTerminal();
logger = BufferLogger.test(
terminal: terminal
);
xcodeProjectInterpreter = XcodeProjectInterpreter(
logger: logger,
fileSystem: fileSystem,
platform: platform,
processManager: processManager,
terminal: terminal,
usage: null,
);
});
// Work around https://github.com/flutter/flutter/issues/56415.
testWithoutContext('xcodebuild versionText returns null when xcodebuild is not installed', () {
when(processManager.runSync(<String>[xcodebuild, '-version']))
.thenThrow(const ProcessException(xcodebuild, <String>['-version']));
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; XcodeProjectInterpreter xcodeProjectInterpreter;
FakePlatform platform; FakePlatform platform;
FileSystem fileSystem; FileSystem fileSystem;
...@@ -34,7 +85,7 @@ void main() { ...@@ -34,7 +85,7 @@ void main() {
AnsiTerminal terminal; AnsiTerminal terminal;
setUp(() { setUp(() {
processManager = mocks.MockProcessManager(); fakeProcessManager = FakeProcessManager.list(<FakeCommand>[]);
platform = FakePlatform(operatingSystem: 'macos'); platform = FakePlatform(operatingSystem: 'macos');
fileSystem = MemoryFileSystem(); fileSystem = MemoryFileSystem();
fileSystem.file(xcodebuild).createSync(recursive: true); fileSystem.file(xcodebuild).createSync(recursive: true);
...@@ -46,81 +97,89 @@ void main() { ...@@ -46,81 +97,89 @@ void main() {
logger: logger, logger: logger,
fileSystem: fileSystem, fileSystem: fileSystem,
platform: platform, platform: platform,
processManager: processManager, processManager: fakeProcessManager,
terminal: terminal, terminal: terminal,
usage: null, usage: null,
); );
}); });
testWithoutContext('xcodebuild versionText returns null when xcodebuild is not installed', () {
when(processManager.runSync(<String>[xcodebuild, '-version']))
.thenThrow(const ProcessException(xcodebuild, <String>['-version']));
expect(xcodeProjectInterpreter.versionText, isNull);
});
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,163 +188,154 @@ void main() { ...@@ -129,163 +188,154 @@ 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>{}); fakeProcessManager.addCommand(const FakeCommand(
}); command: <String>[
'/usr/bin/xcodebuild',
testWithoutContext('xcodebuild build settings flakes', () async { '-project',
const Duration delay = Duration(seconds: 1); '/',
processManager.processFactory = mocks.flakyProcessFactory( '-target',
flakes: 1, 'Runner',
delay: delay + const Duration(seconds: 1), '-showBuildSettings'
); ],
platform.environment = Map<String, String>.unmodifiable(<String, String>{}); 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(
xcodebuild, command: <String>[
'-project', xcodebuild,
platform.pathSeparator, '-project',
'-target', fileSystem.path.separator,
'', '-target',
'-showBuildSettings', 'Runner',
'CODE_SIGN_STYLE=Manual', '-showBuildSettings',
'ARCHS=arm64' 'CODE_SIGN_STYLE=Manual',
], 'ARCHS=arm64'
workingDirectory: anyNamed('workingDirectory'), ],
environment: anyNamed('environment'))) ));
.thenReturn(ProcessResult(1, 0, '', '')); expect(await xcodeProjectInterpreter.getBuildSettings('', 'Runner'), const <String, String>{});
expect(await xcodeProjectInterpreter.getBuildSettings('', ''), const <String, String>{}); expect(fakeProcessManager.hasRemainingExpectations, isFalse);
}); });
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, fakeProcessManager.addCommand(const FakeCommand(
workingDirectory: anyNamed('workingDirectory'))) command: <String>[
.thenAnswer((_) => Future<ProcessResult>.value(ProcessResult(1, 0, '', ''))); xcodebuild,
'-workspace',
'workspace_path',
'-scheme',
'Runner',
'-quiet',
'clean',
'CODE_SIGN_STYLE=Manual',
'ARCHS=arm64'
],
));
await xcodeProjectInterpreter.cleanWorkspace('workspace_path', 'Runner'); await xcodeProjectInterpreter.cleanWorkspace('workspace_path', 'Runner');
final List<dynamic> captured = verify(processManager.run( expect(fakeProcessManager.hasRemainingExpectations, isFalse);
captureAny,
workingDirectory: anyNamed('workingDirectory'),
environment: anyNamed('environment'))).captured;
expect(captured.first, <String>[
xcodebuild,
'-workspace',
'workspace_path',
'-scheme',
'Runner',
'-quiet',
'clean',
'CODE_SIGN_STYLE=Manual',
'ARCHS=arm64'
]);
}); });
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