// Copyright 2013 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 'dart:async'; import 'dart:io' as io; import 'package:args/command_runner.dart'; import 'package:file/file.dart'; import 'package:file/memory.dart'; import 'package:flutter_plugin_tools/src/common/core.dart'; import 'package:flutter_plugin_tools/src/common/package_looping_command.dart'; import 'package:flutter_plugin_tools/src/common/process_runner.dart'; import 'package:git/git.dart'; import 'package:mockito/mockito.dart'; import 'package:platform/platform.dart'; import 'package:test/test.dart'; import '../mocks.dart'; import '../util.dart'; import 'plugin_command_test.mocks.dart'; // Constants for colorized output start and end. const String _startElapsedTimeColor = '\x1B[90m'; const String _startErrorColor = '\x1B[31m'; const String _startHeadingColor = '\x1B[36m'; const String _startSkipColor = '\x1B[90m'; const String _startSkipWithWarningColor = '\x1B[93m'; const String _startSuccessColor = '\x1B[32m'; const String _startWarningColor = '\x1B[33m'; const String _endColor = '\x1B[0m'; // The filename within a package containing warnings to log during runForPackage. enum _ResultFileType { /// A file containing errors to return. errors, /// A file containing warnings that should be logged. warns, /// A file indicating that the package should be skipped, and why. skips, /// A file indicating that the package should throw. throws, } // The filename within a package containing errors to return from runForPackage. const String _errorFile = 'errors'; // The filename within a package indicating that it should be skipped. const String _skipFile = 'skip'; // The filename within a package containing warnings to log during runForPackage. const String _warningFile = 'warnings'; // The filename within a package indicating that it should throw. const String _throwFile = 'throw'; /// Writes a file to [package] to control the behavior of /// [TestPackageLoopingCommand] for that package. void _addResultFile(RepositoryPackage package, _ResultFileType type, {String? contents}) { final File file = package.directory.childFile(_filenameForType(type)); file.createSync(); if (contents != null) { file.writeAsStringSync(contents); } } String _filenameForType(_ResultFileType type) { switch (type) { case _ResultFileType.errors: return _errorFile; case _ResultFileType.warns: return _warningFile; case _ResultFileType.skips: return _skipFile; case _ResultFileType.throws: return _throwFile; } } void main() { late FileSystem fileSystem; late MockPlatform mockPlatform; late Directory packagesDir; late Directory thirdPartyPackagesDir; setUp(() { fileSystem = MemoryFileSystem(); mockPlatform = MockPlatform(); packagesDir = createPackagesDirectory(fileSystem: fileSystem); thirdPartyPackagesDir = packagesDir.parent .childDirectory('third_party') .childDirectory('packages'); }); /// Creates a TestPackageLoopingCommand instance that uses [gitDiffResponse] /// for git diffs, and logs output to [printOutput]. TestPackageLoopingCommand createTestCommand({ String gitDiffResponse = '', bool hasLongOutput = true, PackageLoopingType packageLoopingType = PackageLoopingType.topLevelOnly, bool failsDuringInit = false, bool warnsDuringInit = false, bool warnsDuringCleanup = false, bool captureOutput = false, String? customFailureListHeader, String? customFailureListFooter, }) { // Set up the git diff response. final MockGitDir gitDir = MockGitDir(); when(gitDir.runCommand(any, throwOnError: anyNamed('throwOnError'))) .thenAnswer((Invocation invocation) { final MockProcessResult mockProcessResult = MockProcessResult(); if (invocation.positionalArguments[0][0] == 'diff') { when(mockProcessResult.stdout as String?) .thenReturn(gitDiffResponse); } return Future.value(mockProcessResult); }); return TestPackageLoopingCommand( packagesDir, platform: mockPlatform, hasLongOutput: hasLongOutput, packageLoopingType: packageLoopingType, failsDuringInit: failsDuringInit, warnsDuringInit: warnsDuringInit, warnsDuringCleanup: warnsDuringCleanup, customFailureListHeader: customFailureListHeader, customFailureListFooter: customFailureListFooter, captureOutput: captureOutput, gitDir: gitDir, ); } /// Runs [command] with the given [arguments], and returns its output. Future> runCommand( TestPackageLoopingCommand command, { List arguments = const [], void Function(Error error)? errorHandler, }) async { late CommandRunner runner; runner = CommandRunner('test_package_looping_command', 'Test for base package looping functionality'); runner.addCommand(command); return await runCapturingPrint( runner, [command.name, ...arguments], errorHandler: errorHandler, ); } group('tool exit', () { test('is handled during initializeRun', () async { final TestPackageLoopingCommand command = createTestCommand(failsDuringInit: true); expect(() => runCommand(command), throwsA(isA())); }); test('does not stop looping on error', () async { createFakePackage('package_a', packagesDir); final RepositoryPackage failingPackage = createFakePlugin('package_b', packagesDir); createFakePackage('package_c', packagesDir); _addResultFile(failingPackage, _ResultFileType.errors); final TestPackageLoopingCommand command = createTestCommand(hasLongOutput: false); Error? commandError; final List output = await runCommand(command, errorHandler: (Error e) { commandError = e; }); expect(commandError, isA()); expect( output, containsAllInOrder([ '${_startHeadingColor}Running for package_a...$_endColor', '${_startHeadingColor}Running for package_b...$_endColor', '${_startHeadingColor}Running for package_c...$_endColor', ])); }); test('does not stop looping on exceptions', () async { createFakePackage('package_a', packagesDir); final RepositoryPackage failingPackage = createFakePlugin('package_b', packagesDir); createFakePackage('package_c', packagesDir); _addResultFile(failingPackage, _ResultFileType.throws); final TestPackageLoopingCommand command = createTestCommand(hasLongOutput: false); Error? commandError; final List output = await runCommand(command, errorHandler: (Error e) { commandError = e; }); expect(commandError, isA()); expect( output, containsAllInOrder([ '${_startHeadingColor}Running for package_a...$_endColor', '${_startHeadingColor}Running for package_b...$_endColor', '${_startHeadingColor}Running for package_c...$_endColor', ])); }); }); group('package iteration', () { test('includes plugins and packages', () async { final RepositoryPackage plugin = createFakePlugin('a_plugin', packagesDir); final RepositoryPackage package = createFakePackage('a_package', packagesDir); final TestPackageLoopingCommand command = createTestCommand(); await runCommand(command); expect(command.checkedPackages, unorderedEquals([plugin.path, package.path])); }); test('includes third_party/packages', () async { final RepositoryPackage package1 = createFakePackage('a_package', packagesDir); final RepositoryPackage package2 = createFakePackage('another_package', thirdPartyPackagesDir); final TestPackageLoopingCommand command = createTestCommand(); await runCommand(command); expect(command.checkedPackages, unorderedEquals([package1.path, package2.path])); }); test('includes all subpackages when requested', () async { final RepositoryPackage plugin = createFakePlugin('a_plugin', packagesDir, examples: ['example1', 'example2']); final RepositoryPackage package = createFakePackage('a_package', packagesDir); final RepositoryPackage subPackage = createFakePackage( 'sub_package', package.directory, examples: []); final TestPackageLoopingCommand command = createTestCommand( packageLoopingType: PackageLoopingType.includeAllSubpackages); await runCommand(command); expect( command.checkedPackages, unorderedEquals([ plugin.path, getExampleDir(plugin).childDirectory('example1').path, getExampleDir(plugin).childDirectory('example2').path, package.path, getExampleDir(package).path, subPackage.path, ])); }); test('includes examples when requested', () async { final RepositoryPackage plugin = createFakePlugin('a_plugin', packagesDir, examples: ['example1', 'example2']); final RepositoryPackage package = createFakePackage('a_package', packagesDir); final RepositoryPackage subPackage = createFakePackage('sub_package', package.directory); final TestPackageLoopingCommand command = createTestCommand( packageLoopingType: PackageLoopingType.includeExamples); await runCommand(command); expect( command.checkedPackages, unorderedEquals([ plugin.path, getExampleDir(plugin).childDirectory('example1').path, getExampleDir(plugin).childDirectory('example2').path, package.path, getExampleDir(package).path, ])); expect(command.checkedPackages, isNot(contains(subPackage.path))); }); test('excludes subpackages when main package is excluded', () async { final RepositoryPackage excluded = createFakePlugin( 'a_plugin', packagesDir, examples: ['example1', 'example2']); final RepositoryPackage included = createFakePackage('a_package', packagesDir); final RepositoryPackage subpackage = createFakePackage('sub_package', excluded.directory); final TestPackageLoopingCommand command = createTestCommand( packageLoopingType: PackageLoopingType.includeAllSubpackages); await runCommand(command, arguments: ['--exclude=a_plugin']); final Iterable examples = excluded.getExamples(); expect( command.checkedPackages, unorderedEquals([ included.path, getExampleDir(included).path, ])); expect(command.checkedPackages, isNot(contains(excluded.path))); expect(examples.length, 2); for (final RepositoryPackage example in examples) { expect(command.checkedPackages, isNot(contains(example.path))); } expect(command.checkedPackages, isNot(contains(subpackage.path))); }); test('excludes examples when main package is excluded', () async { final RepositoryPackage excluded = createFakePlugin( 'a_plugin', packagesDir, examples: ['example1', 'example2']); final RepositoryPackage included = createFakePackage('a_package', packagesDir); final TestPackageLoopingCommand command = createTestCommand( packageLoopingType: PackageLoopingType.includeExamples); await runCommand(command, arguments: ['--exclude=a_plugin']); final Iterable examples = excluded.getExamples(); expect( command.checkedPackages, unorderedEquals([ included.path, getExampleDir(included).path, ])); expect(command.checkedPackages, isNot(contains(excluded.path))); expect(examples.length, 2); for (final RepositoryPackage example in examples) { expect(command.checkedPackages, isNot(contains(example.path))); } }); test('skips unsupported versions when requested', () async { final RepositoryPackage excluded = createFakePlugin( 'a_plugin', packagesDir, flutterConstraint: '>=2.10.0'); final RepositoryPackage included = createFakePackage('a_package', packagesDir); final TestPackageLoopingCommand command = createTestCommand( packageLoopingType: PackageLoopingType.includeAllSubpackages, hasLongOutput: false); final List output = await runCommand(command, arguments: [ '--skip-if-not-supporting-flutter-version=2.5.0' ]); expect( command.checkedPackages, unorderedEquals([ included.path, getExampleDir(included).path, ])); expect(command.checkedPackages, isNot(contains(excluded.path))); expect( output, containsAllInOrder([ '${_startHeadingColor}Running for a_package...$_endColor', '${_startHeadingColor}Running for a_plugin...$_endColor', '$_startSkipColor SKIPPING: Does not support Flutter 2.5.0$_endColor', ])); }); }); group('output', () { test('has the expected package headers for long-form output', () async { createFakePlugin('package_a', packagesDir); createFakePackage('package_b', packagesDir); final TestPackageLoopingCommand command = createTestCommand(hasLongOutput: true); final List output = await runCommand(command); const String separator = '============================================================'; expect( output, containsAllInOrder([ '$_startHeadingColor\n$separator\n|| Running for package_a\n$separator\n$_endColor', '$_startHeadingColor\n$separator\n|| Running for package_b\n$separator\n$_endColor', ])); }); test('has the expected package headers for short-form output', () async { createFakePlugin('package_a', packagesDir); createFakePackage('package_b', packagesDir); final TestPackageLoopingCommand command = createTestCommand(hasLongOutput: false); final List output = await runCommand(command); expect( output, containsAllInOrder([ '${_startHeadingColor}Running for package_a...$_endColor', '${_startHeadingColor}Running for package_b...$_endColor', ])); }); test('prints timing info in long-form output when requested', () async { createFakePlugin('package_a', packagesDir); createFakePackage('package_b', packagesDir); final TestPackageLoopingCommand command = createTestCommand(hasLongOutput: true); final List output = await runCommand(command, arguments: ['--log-timing']); const String separator = '============================================================'; expect( output, containsAllInOrder([ '$_startHeadingColor\n$separator\n|| Running for package_a [@0:00]\n$separator\n$_endColor', '$_startElapsedTimeColor\n[package_a completed in 0m 0s]$_endColor', '$_startHeadingColor\n$separator\n|| Running for package_b [@0:00]\n$separator\n$_endColor', '$_startElapsedTimeColor\n[package_b completed in 0m 0s]$_endColor', ])); }); test('prints timing info in short-form output when requested', () async { createFakePlugin('package_a', packagesDir); createFakePackage('package_b', packagesDir); final TestPackageLoopingCommand command = createTestCommand(hasLongOutput: false); final List output = await runCommand(command, arguments: ['--log-timing']); expect( output, containsAllInOrder([ '$_startHeadingColor[0:00] Running for package_a...$_endColor', '$_startHeadingColor[0:00] Running for package_b...$_endColor', ])); // Short-form output should not include elapsed time. expect(output, isNot(contains('[package_a completed in 0m 0s]'))); }); test('shows the success message when nothing fails', () async { createFakePackage('package_a', packagesDir); createFakePackage('package_b', packagesDir); final TestPackageLoopingCommand command = createTestCommand(hasLongOutput: false); final List output = await runCommand(command); expect( output, containsAllInOrder([ '\n', '${_startSuccessColor}No issues found!$_endColor', ])); }); test('shows failure summaries when something fails without extra details', () async { createFakePackage('package_a', packagesDir); final RepositoryPackage failingPackage1 = createFakePlugin('package_b', packagesDir); createFakePackage('package_c', packagesDir); final RepositoryPackage failingPackage2 = createFakePlugin('package_d', packagesDir); _addResultFile(failingPackage1, _ResultFileType.errors); _addResultFile(failingPackage2, _ResultFileType.errors); final TestPackageLoopingCommand command = createTestCommand(hasLongOutput: false); Error? commandError; final List output = await runCommand(command, errorHandler: (Error e) { commandError = e; }); expect(commandError, isA()); expect( output, containsAllInOrder([ '\n', '${_startErrorColor}The following packages had errors:$_endColor', '$_startErrorColor package_b$_endColor', '$_startErrorColor package_d$_endColor', '${_startErrorColor}See above for full details.$_endColor', ])); }); test('uses custom summary header and footer if provided', () async { createFakePackage('package_a', packagesDir); final RepositoryPackage failingPackage1 = createFakePlugin('package_b', packagesDir); createFakePackage('package_c', packagesDir); final RepositoryPackage failingPackage2 = createFakePlugin('package_d', packagesDir); _addResultFile(failingPackage1, _ResultFileType.errors); _addResultFile(failingPackage2, _ResultFileType.errors); final TestPackageLoopingCommand command = createTestCommand( hasLongOutput: false, customFailureListHeader: 'This is a custom header', customFailureListFooter: 'And a custom footer!'); Error? commandError; final List output = await runCommand(command, errorHandler: (Error e) { commandError = e; }); expect(commandError, isA()); expect( output, containsAllInOrder([ '\n', '${_startErrorColor}This is a custom header$_endColor', '$_startErrorColor package_b$_endColor', '$_startErrorColor package_d$_endColor', '${_startErrorColor}And a custom footer!$_endColor', ])); }); test('shows failure summaries when something fails with extra details', () async { createFakePackage('package_a', packagesDir); final RepositoryPackage failingPackage1 = createFakePlugin('package_b', packagesDir); createFakePackage('package_c', packagesDir); final RepositoryPackage failingPackage2 = createFakePlugin('package_d', packagesDir); _addResultFile(failingPackage1, _ResultFileType.errors, contents: 'just one detail'); _addResultFile(failingPackage2, _ResultFileType.errors, contents: 'first detail\nsecond detail'); final TestPackageLoopingCommand command = createTestCommand(hasLongOutput: false); Error? commandError; final List output = await runCommand(command, errorHandler: (Error e) { commandError = e; }); expect(commandError, isA()); expect( output, containsAllInOrder([ '\n', '${_startErrorColor}The following packages had errors:$_endColor', '$_startErrorColor package_b:\n just one detail$_endColor', '$_startErrorColor package_d:\n first detail\n second detail$_endColor', '${_startErrorColor}See above for full details.$_endColor', ])); }); test('is captured, not printed, when requested', () async { createFakePlugin('package_a', packagesDir); createFakePackage('package_b', packagesDir); final TestPackageLoopingCommand command = createTestCommand(hasLongOutput: true, captureOutput: true); final List output = await runCommand(command); expect(output, isEmpty); // None of the output should be colorized when captured. const String separator = '============================================================'; expect( command.capturedOutput, containsAllInOrder([ '\n$separator\n|| Running for package_a\n$separator\n', '\n$separator\n|| Running for package_b\n$separator\n', 'No issues found!', ])); }); test('logs skips', () async { createFakePackage('package_a', packagesDir); final RepositoryPackage skipPackage = createFakePackage('package_b', packagesDir); _addResultFile(skipPackage, _ResultFileType.skips, contents: 'For a reason'); final TestPackageLoopingCommand command = createTestCommand(hasLongOutput: false); final List output = await runCommand(command); expect( output, containsAllInOrder([ '${_startHeadingColor}Running for package_a...$_endColor', '${_startHeadingColor}Running for package_b...$_endColor', '$_startSkipColor SKIPPING: For a reason$_endColor', ])); }); test('logs exclusions', () async { createFakePackage('package_a', packagesDir); createFakePackage('package_b', packagesDir); final TestPackageLoopingCommand command = createTestCommand(hasLongOutput: false); final List output = await runCommand(command, arguments: ['--exclude=package_b']); expect( output, containsAllInOrder([ '${_startHeadingColor}Running for package_a...$_endColor', '${_startSkipColor}Not running for package_b; excluded$_endColor', ])); }); test('logs warnings', () async { final RepositoryPackage warnPackage = createFakePackage('package_a', packagesDir); _addResultFile(warnPackage, _ResultFileType.warns, contents: 'Warning 1\nWarning 2'); createFakePackage('package_b', packagesDir); final TestPackageLoopingCommand command = createTestCommand(hasLongOutput: false); final List output = await runCommand(command); expect( output, containsAllInOrder([ '${_startHeadingColor}Running for package_a...$_endColor', '${_startWarningColor}Warning 1$_endColor', '${_startWarningColor}Warning 2$_endColor', '${_startHeadingColor}Running for package_b...$_endColor', ])); }); test('logs unhandled exceptions as errors', () async { createFakePackage('package_a', packagesDir); final RepositoryPackage failingPackage = createFakePlugin('package_b', packagesDir); createFakePackage('package_c', packagesDir); _addResultFile(failingPackage, _ResultFileType.throws); final TestPackageLoopingCommand command = createTestCommand(hasLongOutput: false); Error? commandError; final List output = await runCommand(command, errorHandler: (Error e) { commandError = e; }); expect(commandError, isA()); expect( output, containsAllInOrder([ '${_startErrorColor}Exception: Uh-oh$_endColor', '${_startErrorColor}The following packages had errors:$_endColor', '$_startErrorColor package_b:\n Unhandled exception$_endColor', ])); }); test('prints run summary on success', () async { final RepositoryPackage warnPackage1 = createFakePackage('package_a', packagesDir); _addResultFile(warnPackage1, _ResultFileType.warns, contents: 'Warning 1\nWarning 2'); createFakePackage('package_b', packagesDir); final RepositoryPackage skipPackage = createFakePackage('package_c', packagesDir); _addResultFile(skipPackage, _ResultFileType.skips, contents: 'For a reason'); final RepositoryPackage skipAndWarnPackage = createFakePackage('package_d', packagesDir); _addResultFile(skipAndWarnPackage, _ResultFileType.warns, contents: 'Warning'); _addResultFile(skipAndWarnPackage, _ResultFileType.skips, contents: 'See warning'); final RepositoryPackage warnPackage2 = createFakePackage('package_e', packagesDir); _addResultFile(warnPackage2, _ResultFileType.warns, contents: 'Warning 1\nWarning 2'); createFakePackage('package_f', packagesDir); final TestPackageLoopingCommand command = createTestCommand(hasLongOutput: false); final List output = await runCommand(command); expect( output, containsAllInOrder([ '------------------------------------------------------------', 'Ran for 4 package(s) (2 with warnings)', 'Skipped 2 package(s) (1 with warnings)', '\n', '${_startSuccessColor}No issues found!$_endColor', ])); // The long-form summary should not be printed for short-form commands. expect(output, isNot(contains('Run summary:'))); expect(output, isNot(contains(contains('package a - ran')))); }); test('counts exclusions as skips in run summary', () async { createFakePackage('package_a', packagesDir); final TestPackageLoopingCommand command = createTestCommand(hasLongOutput: false); final List output = await runCommand(command, arguments: ['--exclude=package_a']); expect( output, containsAllInOrder([ '------------------------------------------------------------', 'Skipped 1 package(s)', '\n', '${_startSuccessColor}No issues found!$_endColor', ])); }); test('prints long-form run summary for long-output commands', () async { final RepositoryPackage warnPackage1 = createFakePackage('package_a', packagesDir); _addResultFile(warnPackage1, _ResultFileType.warns, contents: 'Warning 1\nWarning 2'); createFakePackage('package_b', packagesDir); final RepositoryPackage skipPackage = createFakePackage('package_c', packagesDir); _addResultFile(skipPackage, _ResultFileType.skips, contents: 'For a reason'); final RepositoryPackage skipAndWarnPackage = createFakePackage('package_d', packagesDir); _addResultFile(skipAndWarnPackage, _ResultFileType.warns, contents: 'Warning'); _addResultFile(skipAndWarnPackage, _ResultFileType.skips, contents: 'See warning'); final RepositoryPackage warnPackage2 = createFakePackage('package_e', packagesDir); _addResultFile(warnPackage2, _ResultFileType.warns, contents: 'Warning 1\nWarning 2'); createFakePackage('package_f', packagesDir); final TestPackageLoopingCommand command = createTestCommand(hasLongOutput: true); final List output = await runCommand(command); expect( output, containsAllInOrder([ '------------------------------------------------------------', 'Run overview:', ' package_a - ${_startWarningColor}ran (with warning)$_endColor', ' package_b - ${_startSuccessColor}ran$_endColor', ' package_c - ${_startSkipColor}skipped$_endColor', ' package_d - ${_startSkipWithWarningColor}skipped (with warning)$_endColor', ' package_e - ${_startWarningColor}ran (with warning)$_endColor', ' package_f - ${_startSuccessColor}ran$_endColor', '', 'Ran for 4 package(s) (2 with warnings)', 'Skipped 2 package(s) (1 with warnings)', '\n', '${_startSuccessColor}No issues found!$_endColor', ])); }); test('prints exclusions as skips in long-form run summary', () async { createFakePackage('package_a', packagesDir); final TestPackageLoopingCommand command = createTestCommand(hasLongOutput: true); final List output = await runCommand(command, arguments: ['--exclude=package_a']); expect( output, containsAllInOrder([ ' package_a - ${_startSkipColor}excluded$_endColor', '', 'Skipped 1 package(s)', '\n', '${_startSuccessColor}No issues found!$_endColor', ])); }); test('handles warnings outside of runForPackage', () async { createFakePackage('package_a', packagesDir); final TestPackageLoopingCommand command = createTestCommand( hasLongOutput: false, warnsDuringCleanup: true, warnsDuringInit: true, ); final List output = await runCommand(command); expect( output, containsAllInOrder([ '${_startWarningColor}Warning during initializeRun$_endColor', '${_startHeadingColor}Running for package_a...$_endColor', '${_startWarningColor}Warning during completeRun$_endColor', '------------------------------------------------------------', 'Ran for 1 package(s)', '2 warnings not associated with a package', '\n', '${_startSuccessColor}No issues found!$_endColor', ])); }); }); } class TestPackageLoopingCommand extends PackageLoopingCommand { TestPackageLoopingCommand( Directory packagesDir, { required Platform platform, this.hasLongOutput = true, this.packageLoopingType = PackageLoopingType.topLevelOnly, this.customFailureListHeader, this.customFailureListFooter, this.failsDuringInit = false, this.warnsDuringInit = false, this.warnsDuringCleanup = false, this.captureOutput = false, ProcessRunner processRunner = const ProcessRunner(), GitDir? gitDir, }) : super(packagesDir, processRunner: processRunner, platform: platform, gitDir: gitDir); final List checkedPackages = []; final List capturedOutput = []; final String? customFailureListHeader; final String? customFailureListFooter; final bool failsDuringInit; final bool warnsDuringInit; final bool warnsDuringCleanup; @override bool hasLongOutput; @override PackageLoopingType packageLoopingType; @override String get failureListHeader => customFailureListHeader ?? super.failureListHeader; @override String get failureListFooter => customFailureListFooter ?? super.failureListFooter; @override bool captureOutput; @override final String name = 'loop-test'; @override final String description = 'sample package looping command'; @override Future initializeRun() async { if (warnsDuringInit) { logWarning('Warning during initializeRun'); } if (failsDuringInit) { throw ToolExit(2); } } @override Future runForPackage(RepositoryPackage package) async { checkedPackages.add(package.path); final File warningFile = package.directory.childFile(_warningFile); if (warningFile.existsSync()) { final List warnings = warningFile.readAsLinesSync(); warnings.forEach(logWarning); } final File skipFile = package.directory.childFile(_skipFile); if (skipFile.existsSync()) { return PackageResult.skip(skipFile.readAsStringSync()); } final File errorFile = package.directory.childFile(_errorFile); if (errorFile.existsSync()) { return PackageResult.fail(errorFile.readAsLinesSync()); } final File throwFile = package.directory.childFile(_throwFile); if (throwFile.existsSync()) { throw Exception('Uh-oh'); } return PackageResult.success(); } @override Future completeRun() async { if (warnsDuringInit) { logWarning('Warning during completeRun'); } } @override Future handleCapturedOutput(List output) async { capturedOutput.addAll(output); } } class MockProcessResult extends Mock implements io.ProcessResult {}