Cleanup, and move `FakeProcessManager` to main package (#37)
diff --git a/.github/workflows/process_runner.yml b/.github/workflows/process_runner.yml index d803c61..32faff2 100644 --- a/.github/workflows/process_runner.yml +++ b/.github/workflows/process_runner.yml
@@ -2,9 +2,9 @@ on: push: - branches: [ master ] + branches: [master] pull_request: - branches: [ master ] + branches: [master] workflow_dispatch: jobs: @@ -18,7 +18,7 @@ - name: Install dependencies run: dart pub upgrade - name: Verify formatting - run: dart format --output=none --line-length=100 --set-exit-if-changed . + run: dart format --output=none --set-exit-if-changed . - name: Analyze project source run: dart analyze --fatal-infos test:
diff --git a/analysis_options.yaml b/analysis_options.yaml index c9dd2bb..6a26036 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml
@@ -2,213 +2,8 @@ # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. -# Specify analysis options. -# -# Until there are meta linter rules, each desired lint must be explicitly enabled. -# See: https://github.com/dart-lang/linter/issues/288 -# -# For a list of lints, see: http://dart-lang.github.io/linter/lints/ -# See the configuration guide for more -# https://github.com/dart-lang/sdk/tree/master/pkg/analyzer#configuring-the-analyzer - -analyzer: - language: - strict-casts: false - strict-raw-types: false - errors: - # treat missing required parameters as a warning (not a hint) - missing_required_param: warning - # treat missing returns as a warning (not a hint) - missing_return: warning - # allow having TODOs in the code - todo: ignore - # allow self-reference to deprecated members (we do this because otherwise we have - # to annotate every member in every test, assert, etc, when we deprecate something) - deprecated_member_use_from_same_package: ignore +include: package:dart_flutter_team_lints/analysis_options.yaml linter: rules: - # these rules are documented on and in the same order as - # the Dart Lint rules page to make maintenance easier - # https://github.com/dart-lang/linter/blob/master/example/all.yaml - - always_declare_return_types - - always_put_control_body_on_new_line - # - always_put_required_named_parameters_first # we prefer having parameters in the same order as fields https://github.com/flutter/flutter/issues/10219 - - always_specify_types - # - always_use_package_imports # we do this commonly - - annotate_overrides - # - avoid_annotating_with_dynamic # conflicts with always_specify_types - - avoid_bool_literals_in_conditional_expressions - # - avoid_catches_without_on_clauses # we do this commonly - # - avoid_catching_errors # we do this commonly - - avoid_classes_with_only_static_members - # - avoid_double_and_int_checks # only useful when targeting JS runtime - - avoid_dynamic_calls - - avoid_empty_else - - avoid_equals_and_hash_code_on_mutable_classes - - avoid_escaping_inner_quotes - - avoid_field_initializers_in_const_classes - - avoid_function_literals_in_foreach_calls - # - avoid_implementing_value_types # not yet tested - - avoid_init_to_null - # - avoid_js_rounded_ints # only useful when targeting JS runtime - - avoid_null_checks_in_equality_operators - # - avoid_positional_boolean_parameters # not yet tested - # - avoid_print # not yet tested - # - avoid_private_typedef_functions # we prefer having typedef (discussion in https://github.com/flutter/flutter/pull/16356) - # - avoid_redundant_argument_values # not yet tested - - avoid_relative_lib_imports - - avoid_renaming_method_parameters - - avoid_return_types_on_setters - # - avoid_returning_null # there are plenty of valid reasons to return null - # - avoid_returning_null_for_future # not yet tested - - avoid_returning_null_for_void - # - avoid_returning_this # there are plenty of valid reasons to return this - # - avoid_setters_without_getters # not yet tested - - avoid_shadowing_type_parameters - - avoid_single_cascade_in_expression_statements - - avoid_slow_async_io - - avoid_type_to_string - - avoid_types_as_parameter_names - # - avoid_types_on_closure_parameters # conflicts with always_specify_types - - avoid_unnecessary_containers - - avoid_unused_constructor_parameters - - avoid_void_async - # - avoid_web_libraries_in_flutter # not yet tested - - await_only_futures - - camel_case_extensions - - camel_case_types - - cancel_subscriptions - # - cascade_invocations # not yet tested - - cast_nullable_to_non_nullable - - collection_methods_unrelated_type - # - close_sinks # not reliable enough - # - comment_references # blocked on https://github.com/dart-lang/linter/issues/1142 - # - constant_identifier_names # needs an opt-out https://github.com/dart-lang/linter/issues/204 - - control_flow_in_finally - # - curly_braces_in_flow_control_structures # not required by flutter style - - deprecated_consistency - # - diagnostic_describe_all_properties # not yet tested - - directives_ordering - # - do_not_use_environment # we do this commonly - - empty_catches - - empty_constructor_bodies - - empty_statements - - exhaustive_cases - - file_names - - flutter_style_todos - - hash_and_equals - - implementation_imports - # - invariant_booleans # too many false positives: https://github.com/dart-lang/linter/issues/811 - # - join_return_with_assignment # not required by flutter style - - leading_newlines_in_multiline_strings - - library_names - - library_prefixes - - library_private_types_in_public_api - # - lines_longer_than_80_chars # not required by flutter style - # - literal_only_boolean_expressions # too many false positives: https://github.com/dart-lang/sdk/issues/34181 - - missing_whitespace_between_adjacent_strings - - no_adjacent_strings_in_list - # - no_default_cases # too many false positives - - no_duplicate_case_values - - no_logic_in_create_state - # - no_runtimeType_toString # ok in tests; we enable this only in packages/ - - noop_primitive_operations - - non_constant_identifier_names - - null_check_on_nullable_type_parameter - - null_closures - # - omit_local_variable_types # opposite of always_specify_types - # - one_member_abstracts # too many false positives - # - only_throw_errors # https://github.com/flutter/flutter/issues/5792 - - overridden_fields - - package_api_docs - - package_names - - package_prefixed_library_names - # - parameter_assignments # we do this commonly - - prefer_adjacent_string_concatenation - - prefer_asserts_in_initializer_lists - # - prefer_asserts_with_message # not required by flutter style - - prefer_collection_literals - - prefer_conditional_assignment - - prefer_const_constructors - - prefer_const_constructors_in_immutables - - prefer_const_declarations - - prefer_const_literals_to_create_immutables - # - prefer_constructors_over_static_methods # far too many false positives - - prefer_contains - # - prefer_double_quotes # opposite of prefer_single_quotes - # - prefer_expression_function_bodies # conflicts with https://github.com/flutter/flutter/wiki/Style-guide-for-Flutter-repo#consider-using--for-short-functions-and-methods - - prefer_final_fields - - prefer_final_in_for_each - prefer_final_locals - - prefer_for_elements_to_map_fromIterable - - prefer_foreach - - prefer_function_declarations_over_variables - - prefer_generic_function_type_aliases - - prefer_if_elements_to_conditional_expressions - - prefer_if_null_operators - - prefer_initializing_formals - - prefer_inlined_adds - # - prefer_int_literals # conflicts with https://github.com/flutter/flutter/wiki/Style-guide-for-Flutter-repo#use-double-literals-for-double-constants - - prefer_interpolation_to_compose_strings - - prefer_is_empty - - prefer_is_not_empty - - prefer_is_not_operator - - prefer_iterable_whereType - # - prefer_mixin # https://github.com/dart-lang/language/issues/32 - - prefer_null_aware_operators - # - prefer_relative_imports # incompatible with sub-package imports - - prefer_single_quotes - - prefer_spread_collections - - prefer_typing_uninitialized_variables - - prefer_void_to_null - - provide_deprecation_message - # - public_member_api_docs # enabled on a case-by-case basis; see e.g. packages/analysis_options.yaml - - recursive_getters - - sized_box_for_whitespace - - slash_for_doc_comments - # - sort_child_properties_last # not yet tested - - sort_constructors_first - # - sort_pub_dependencies # prevents separating pinned transitive dependencies - - sort_unnamed_constructors_first - - test_types_in_equals - - throw_in_finally - - tighten_type_of_initializing_formals - # - type_annotate_public_apis # subset of always_specify_types - - type_init_formals - # - unawaited_futures # too many false positives - - unnecessary_await_in_return - - unnecessary_brace_in_string_interps - - unnecessary_const - # - unnecessary_final # conflicts with prefer_final_locals - - unnecessary_getters_setters - # - unnecessary_lambdas # has false positives: https://github.com/dart-lang/linter/issues/498 - - unnecessary_new - - unnecessary_null_aware_assignments - - unnecessary_null_checks - - unnecessary_null_in_if_null_operators - - unnecessary_nullable_for_final_variable_declarations - - unnecessary_overrides - - unnecessary_parenthesis - # - unnecessary_raw_strings # not yet tested - - unnecessary_statements - - unnecessary_string_escapes - - unnecessary_string_interpolations - - unnecessary_this - - unrelated_type_equality_checks - # - unsafe_html # not yet tested - - use_full_hex_values_for_flutter_colors - - use_function_type_syntax_for_parameters - # - use_if_null_to_convert_nulls_to_bools # not yet tested - - use_is_even_rather_than_modulo - - use_key_in_widget_constructors - - use_late_for_private_fields_and_variables - - use_named_constants - - use_raw_strings - - use_rethrow_when_possible - # - use_setters_to_change_properties # not yet tested - # - use_string_buffers # has false positives: https://github.com/dart-lang/sdk/issues/34182 - - use_test_throws_matchers - # - use_to_and_as_if_applicable # has false positives, so we prefer to catch this by code-review - - valid_regexps - - void_checks
diff --git a/bin/process_runner.dart b/bin/process_runner.dart index fc8c6bc..0491cee 100644 --- a/bin/process_runner.dart +++ b/bin/process_runner.dart
@@ -5,8 +5,8 @@ // This utility sends a bunch of jobs to a ProcessPool for processing. // // It can speed up processing of a bunch of single-threaded CPU-intensive -// commands by a multiple of the number of processor cores you have (modulo being -// disk/network bound, of course). +// commands by a multiple of the number of processor cores you have (modulo +// being disk/network bound, of course). // // You can install this command with "dart pub global activate process_runner", // and you can run it with "dart pub global run process_runner".
diff --git a/ci/fix_format.sh b/ci/fix_format.sh index af5d087..c4c5e54 100755 --- a/ci/fix_format.sh +++ b/ci/fix_format.sh
@@ -17,7 +17,7 @@ REPO_DIR="$(dirname "$SCRIPT_DIR")" function format() { - (cd "$REPO_DIR" && dart format --set-exit-if-changed --line-length=100 "$@" lib test ci example) + (cd "$REPO_DIR" && dart format --set-exit-if-changed "$@" lib test ci example) } # Make sure dartfmt is run on everything
diff --git a/example/main.dart b/example/main.dart index 766d4a5..c34fab7 100644 --- a/example/main.dart +++ b/example/main.dart
@@ -39,13 +39,13 @@ // This only works for escaped spaces and things in double or single quotes. // This is just an example, modify to meet your own requirements. List<String> splitIntoArgs(String args) { - bool inQuote = false; - bool inEscape = false; - String quoteMatch = ''; - final List<String> result = <String>[]; - final List<String> currentArg = <String>[]; - for (int i = 0; i < args.length; ++i) { - final String char = args[i]; + var inQuote = false; + var inEscape = false; + var quoteMatch = ''; + final result = <String>[]; + final currentArg = <String>[]; + for (var i = 0; i < args.length; ++i) { + final char = args[i]; if (inEscape) { switch (char) { case 'n': @@ -98,23 +98,6 @@ return result; } -String? findOption(String option, List<String> args) { - for (int i = 0; i < args.length - 1; ++i) { - if (args[i] == option) { - return args[i + 1]; - } - } - return null; -} - -Iterable<String> findAllOptions(String option, List<String> args) sync* { - for (int i = 0; i < args.length - 1; ++i) { - if (args[i] == option) { - yield args[i + 1]; - } - } -} - // Print reports to stderr, to avoid polluting any stdout from the jobs. void stderrPrintReport( int total, @@ -123,11 +106,12 @@ int pending, int failed, ) { - stderr.write(ProcessPool.defaultReportToString(total, completed, inProgress, pending, failed)); + stderr.write(ProcessPool.defaultReportToString( + total, completed, inProgress, pending, failed)); } Future<void> main(List<String> args) async { - final ArgParser parser = ArgParser(usageLineLength: 80); + final parser = ArgParser(usageLineLength: 80); parser.addFlag( _kHelpFlag, abbr: 'h', @@ -154,15 +138,15 @@ _kPrintStdoutFlag, defaultsTo: true, help: 'Prints the stdout output of the commands to stdout in the order ' - 'they complete. Will not interleave lines from separate processes. Has no ' - 'effect if --$_kQuietFlag is specified.', + 'they complete. Will not interleave lines from separate processes. Has ' + 'no effect if --$_kQuietFlag is specified.', ); parser.addFlag( _kPrintStderrFlag, defaultsTo: true, help: 'Prints the stderr output of the commands to stderr in the order ' - 'they complete. Will not interleave lines from separate processes. Has no ' - 'effect if --$_kQuietFlag is specified', + 'they complete. Will not interleave lines from separate processes. Has ' + 'no effect if --$_kQuietFlag is specified', ); parser.addFlag( _kRunInShellFlag, @@ -173,10 +157,11 @@ parser.addFlag( _kAllowFailureFlag, defaultsTo: false, - help: 'If set, allows continuing execution of the remaining commands even if ' + help: + 'If set, allows continuing execution of the remaining commands even if ' 'one fails to execute. If not set, ("--no-$_kAllowFailureFlag") then ' - 'process will just exit with a non-zero code at completion if there were ' - 'any jobs that failed.', + 'process will just exit with a non-zero code at completion if there ' + 'were any jobs that failed.', ); parser.addOption( _kJobsOption, @@ -246,52 +231,54 @@ return; } - final bool quiet = options[_kQuietFlag]! as bool; - final bool printStderr = !quiet && options[_kPrintStderrFlag]! as bool; - final bool printStdout = !quiet && options[_kPrintStdoutFlag]! as bool; - final bool printReport = options[_kReportFlag]! as bool; - final bool runInShell = options[_kRunInShellFlag]! as bool; - final bool failOk = options[_kAllowFailureFlag]! as bool; + final quiet = options[_kQuietFlag]! as bool; + final printStderr = !quiet && options[_kPrintStderrFlag]! as bool; + final printStdout = !quiet && options[_kPrintStdoutFlag]! as bool; + final printReport = options[_kReportFlag]! as bool; + final runInShell = options[_kRunInShellFlag]! as bool; + final failOk = options[_kAllowFailureFlag]! as bool; // Collect the commands to be run from the command file(s). - final List<String>? commandFiles = options[_kSourceOption] as List<String>?; + final commandFiles = options[_kSourceOption] as List<String>?; // Collect all the commands, both from input files, and from the command // line. The command line commands are run first (although they could all // potentially be executed simultaneously, depending on the number of workers, // and number of commands). - final List<List<String>> fileCommands = getCommandsFromFiles(commandFiles); + final fileCommands = getCommandsFromFiles(commandFiles); - final List<String> collectedCommands = <String>[ - if (options[_kCommandOption] != null) ...options[_kCommandOption]! as List<String>, + final collectedCommands = <String>[ + if (options[_kCommandOption] != null) + ...options[_kCommandOption]! as List<String>, ]; - fileCommands - .forEach(collectedCommands.addAll); // Flatten the groups so they can be run in parallel. + fileCommands.forEach(collectedCommands + .addAll); // Flatten the groups so they can be run in parallel. - final List<List<String>> splitCommands = - collectedCommands.map<List<String>>((String command) => splitIntoArgs(command)).toList(); + final splitCommands = + collectedCommands.map<List<String>>(splitIntoArgs).toList(); // Collect the commands to be run from the group file(s). - final List<List<String>> groupCommands = - getCommandsFromFiles(options[_kGroupOption] as List<String>, allowStdin: false); + final groupCommands = getCommandsFromFiles( + options[_kGroupOption] as List<String>, + allowStdin: false); // Split each command entry into a list of strings, taking into account some // simple quoting and escaping. - final List<List<List<String>>> splitGroupCommands = groupCommands - .map<List<List<String>>>( - (List<String> group) => group.map<List<String>>(splitIntoArgs).toList()) + final splitGroupCommands = groupCommands + .map<List<List<String>>>((List<String> group) => + group.map<List<String>>(splitIntoArgs).toList()) .toList(); // If the numWorkers is set to null, then the ProcessPool will automatically // select the number of processes based on how many CPU cores the machine has. - final int? numWorkers = int.tryParse(options[_kJobsOption] as String? ?? ''); - final Directory workingDirectory = + final numWorkers = int.tryParse(options[_kJobsOption] as String? ?? ''); + final workingDirectory = Directory((options[_kWorkingDirectoryOption] as String?) ?? '.'); - final ProcessPool pool = ProcessPool( + final pool = ProcessPool( numWorkers: numWorkers, printReport: printReport ? stderrPrintReport : null, ); - final Iterable<WorkerJobGroup> groupedJobs = + final groupedJobs = splitGroupCommands.map<WorkerJobGroup>((List<List<String>> group) { return WorkerJobGroup(group .map<WorkerJob>((List<String> command) => WorkerJob( @@ -302,14 +289,17 @@ )) .toList()); }); - final Iterable<WorkerJob> parallelJobs = + final parallelJobs = splitCommands.map<WorkerJob>((List<String> command) => WorkerJob( command, workingDirectory: workingDirectory, runInShell: runInShell, failOk: failOk, )); - final Iterable<DependentJob> jobs = <DependentJob>[...parallelJobs, ...groupedJobs]; + final Iterable<DependentJob> jobs = <DependentJob>[ + ...parallelJobs, + ...groupedJobs + ]; try { await for (final WorkerJob done in pool.startWorkers(jobs)) { if (printStdout) { @@ -331,21 +321,23 @@ exitCode = pool.failedJobs != 0 ? 1 : 0; } -List<List<String>> getCommandsFromFiles(List<String>? commandFiles, {bool allowStdin = false}) { - final List<List<String>> fileCommands = <List<String>>[]; +List<List<String>> getCommandsFromFiles(List<String>? commandFiles, + {bool allowStdin = false}) { + final fileCommands = <List<String>>[]; if (commandFiles != null) { - bool sawStdinAlready = false; - for (final String commandFile in commandFiles) { + var sawStdinAlready = false; + for (final commandFile in commandFiles) { // Read from stdin if the --file option is set to '-'. if (allowStdin && commandFile == '-') { if (sawStdinAlready) { - stderr.writeln('ERROR: The stdin can only be specified once with "--$_kSourceOption -"'); + stderr.writeln('ERROR: The stdin can only be specified once with ' + '"--$_kSourceOption -"'); exitCode = 1; return <List<String>>[]; } sawStdinAlready = true; - String? line = stdin.readLineSync(); - final List<String> commands = <String>[]; + var line = stdin.readLineSync(); + final commands = <String>[]; while (line != null) { commands.add(line); line = stdin.readLineSync(); @@ -353,7 +345,7 @@ fileCommands.add(commands); } else { // Read the commands from a file. - final File cmdFile = File(commandFile); + final cmdFile = File(commandFile); if (!cmdFile.existsSync()) { print('''Command file "$commandFile" doesn't exist.'''); exit(1);
diff --git a/lib/process_runner.dart b/lib/process_runner.dart index 309e76d..c9b26dc 100644 --- a/lib/process_runner.dart +++ b/lib/process_runner.dart
@@ -2,7 +2,5 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -library process_runner; - export 'src/process_pool.dart'; export 'src/process_runner.dart';
diff --git a/lib/src/process_pool.dart b/lib/src/process_pool.dart index 34960a8..93481c7 100644 --- a/lib/src/process_pool.dart +++ b/lib/src/process_pool.dart
@@ -4,10 +4,11 @@ import 'dart:async'; import 'dart:convert' show Encoding; -import 'dart:io' show Directory, Platform, stdout, SystemEncoding, stderr, ProcessStartMode; +import 'dart:io' + show Directory, Platform, ProcessStartMode, SystemEncoding, stderr, stdout; import 'package:async/async.dart' show StreamGroup; -import 'package:process_runner/process_runner.dart'; +import '../process_runner.dart'; import 'process_runner.dart'; @@ -61,7 +62,8 @@ throw ProcessRunnerException('$job is already a dependency of $this'); } if (job._dependsOn.contains(this)) { - throw ProcessRunnerException('$this is already a dependency of $job, no cycle allowed'); + throw ProcessRunnerException( + '$this is already a dependency of $job, no cycle allowed'); } _dependsOn.add(job); } @@ -190,7 +192,7 @@ /// Once the job is complete, this contains the result of the job. /// - /// The [stderr], [stdout], and [output] accessors will decode their raw + /// The [stderr] and [stdout] accessors will decode their raw /// equivalents using the [ProcessRunner.decoder] that is set on the process /// runner for the pool that ran this job. /// @@ -224,10 +226,13 @@ {Iterable<DependentJob>? dependsOn, this.name = 'Group'}) : assert(jobs.isNotEmpty), jobs = <DependentJob>[...jobs], - super(dependsOn: <DependentJob>{...jobs.toSet(), if (dependsOn != null) ...dependsOn}) { + super(dependsOn: <DependentJob>{ + ...jobs.toSet(), + if (dependsOn != null) ...dependsOn + }) { // Make sure they run in series, and they depend on anything that the group // depends on. - for (int i = 1; i < this.jobs.length; i++) { + for (var i = 1; i < this.jobs.length; i++) { this.jobs[i].addDependency(this.jobs[i - 1]); } } @@ -240,7 +245,7 @@ @override void addDependency(DependentJob job) { - for (final DependentJob worker in jobs) { + for (final worker in jobs) { worker.addDependency(job); } super.addDependency(job); @@ -248,7 +253,7 @@ @override void removeDependency(DependentJob job) { - for (final DependentJob worker in jobs) { + for (final worker in jobs) { worker.removeDependency(job); } super.removeDependency(job); @@ -261,7 +266,8 @@ } @override - String toString() => '${name.isNotEmpty ? name : 'Group'} with ${jobs.length} members'; + String toString() => + '${name.isNotEmpty ? name : 'Group'} with ${jobs.length} members'; } /// The type of the reporting function for [ProcessPool.printReport]. @@ -288,7 +294,7 @@ /// /// May be set to null if no progress report is desired. /// - /// Defaults to [defaultProgressReport], which prints the progress report to + /// Defaults to [defaultPrintReport], which prints the progress report to /// stdout. final ProcessPoolProgressReporter? printReport; @@ -325,7 +331,10 @@ /// Returns the total number of jobs that have been given to this pool. int get totalJobs => - _completedJobs.length + _inProgressJobs + _pendingJobs.length + _failedJobs.length; + _completedJobs.length + + _inProgressJobs + + _pendingJobs.length + + _failedJobs.length; final List<DependentJob> _pendingJobs = <DependentJob>[]; final List<DependentJob> _failedJobs = <DependentJob>[]; @@ -351,13 +360,14 @@ int pending, int failed, ) { - final String percent = - total == 0 ? '100' : ((100 * (completed + failed)) ~/ total).toString().padLeft(3); - final String completedStr = completed.toString().padLeft(3); - final String totalStr = total.toString().padRight(3); - final String inProgressStr = inProgress.toString().padLeft(2); - final String pendingStr = pending.toString().padLeft(3); - final String failedStr = failed.toString().padLeft(3); + final percent = total == 0 + ? '100' + : ((100 * (completed + failed)) ~/ total).toString().padLeft(3); + final completedStr = completed.toString().padLeft(3); + final totalStr = total.toString().padRight(3); + final inProgressStr = inProgress.toString().padLeft(2); + final pendingStr = pending.toString().padLeft(3); + final failedStr = failed.toString().padLeft(3); return 'Jobs: $percent% done, $completedStr/$totalStr completed, $inProgressStr in progress, $pendingStr pending, $failedStr failed. \r'; } @@ -369,7 +379,8 @@ int pending, int failed, ) { - stdout.write(defaultReportToString(total, completed, inProgress, pending, failed)); + stdout.write( + defaultReportToString(total, completed, inProgress, pending, failed)); } Future<WorkerJob> _performJob(WorkerJob job) async { @@ -388,15 +399,18 @@ } job.result = await processRunner.runProcess( job.command, - workingDirectory: job.workingDirectory ?? processRunner.defaultWorkingDirectory, + workingDirectory: + job.workingDirectory ?? processRunner.defaultWorkingDirectory, printOutput: job.printOutput, - stdin: job.stdinRaw ?? encoding.encoder.bind(job.stdin ?? const Stream<String>.empty()), + stdin: job.stdinRaw ?? + encoding.encoder.bind(job.stdin ?? const Stream<String>.empty()), // Starting process pool jobs in any other mode makes no sense: they // would all just be immediately started and bring the machine to its // knees. startMode: ProcessStartMode.normal, runInShell: job.runInShell, - failOk: false, // Must be false so that we can catch the exception below. + failOk: + false, // Must be false so that we can catch the exception below. ); _completedJobs.add(job); } on ProcessRunnerException catch (e) { @@ -422,22 +436,24 @@ return null; } if (inProgressJobs == 0 && _completedJobs.isEmpty && _failedJobs.isEmpty) { - final int firstIndependent = - _pendingJobs.indexWhere((DependentJob element) => element.dependsOn.isEmpty); + final firstIndependent = _pendingJobs + .indexWhere((DependentJob element) => element.dependsOn.isEmpty); if (firstIndependent == -1) { throw ProcessRunnerException( - 'Nothing is in progress, and no pending jobs are without dependencies. ' - 'At least one must have no dependencies so that something can start.', + 'Nothing is in progress, and no pending jobs are without ' + 'dependencies. At least one must have no dependencies so that ' + 'something can start.', ); } return _pendingJobs.removeAt(firstIndependent); } // Go through the list of jobs, looking for the first one where all of its // dependencies have been satisfied by appearing in the _completedJobs list. - final Set<DependentJob> allFinishedJobs = _completedJobs.toSet().union(_failedJobs.toSet()); - for (int i = 0; i < _pendingJobs.length; i += 1) { - final DependentJob job = _pendingJobs[i]; - if (job.dependsOn.isEmpty || job.dependsOn.difference(allFinishedJobs.toSet()).isEmpty) { + final allFinishedJobs = _completedJobs.toSet().union(_failedJobs.toSet()); + for (var i = 0; i < _pendingJobs.length; i += 1) { + final job = _pendingJobs[i]; + if (job.dependsOn.isEmpty || + job.dependsOn.difference(allFinishedJobs.toSet()).isEmpty) { return _pendingJobs.removeAt(i); } } @@ -447,7 +463,7 @@ Stream<WorkerJob> _startWorker() async* { while (_pendingJobs.isNotEmpty) { - final DependentJob? newJob = _getNextIndependentJob(); + final newJob = _getNextIndependentJob(); if (newJob == null && _inProgressJobs > 0) { // All the dependent jobs are still pending. // Small pause to let pending jobs complete, so we don't just spin. @@ -472,7 +488,7 @@ /// /// To listen to jobs as they are completed, use [startWorkers] instead. Future<List<WorkerJob>> runToCompletion(Iterable<DependentJob> jobs) async { - final List<WorkerJob> results = <WorkerJob>[]; + final results = <WorkerJob>[]; await startWorkers(jobs).forEach(results.add); return results; } @@ -492,12 +508,12 @@ if (jobs.isEmpty) { return; } - for (final DependentJob job in jobs) { + for (final job in jobs) { job.addToQueue(_pendingJobs); } _verifyDependencies(); - final List<Stream<WorkerJob>> streams = <Stream<WorkerJob>>[]; - for (int i = 0; i < numWorkers; ++i) { + final streams = <Stream<WorkerJob>>[]; + for (var i = 0; i < numWorkers; ++i) { if (_pendingJobs.isEmpty) { break; } @@ -512,12 +528,13 @@ return; } - bool _hasDependencyLoop(DependentJob job, {required Set<DependentJob> visited}) { + bool _hasDependencyLoop(DependentJob job, + {required Set<DependentJob> visited}) { if (visited.contains(job)) { return true; } visited.add(job); - for (final DependentJob dependentJob in job.dependsOn) { + for (final dependentJob in job.dependsOn) { if (_hasDependencyLoop(dependentJob, visited: visited)) { return true; } @@ -528,20 +545,22 @@ void _verifyDependencies() { // Dependencies for all jobs must also appear in the pending jobs. - assert( - _completedJobs.isEmpty && _inProgressJobs == 0, "Can't verify dependencies once started."); - final Set<DependentJob> pending = _pendingJobs.toSet(); - for (final DependentJob job in pending) { - final Set<DependentJob> diff = job.dependsOn.difference(pending); + assert(_completedJobs.isEmpty && _inProgressJobs == 0, + "Can't verify dependencies once started."); + final pending = _pendingJobs.toSet(); + for (final job in pending) { + final diff = job.dependsOn.difference(pending); if (diff.isNotEmpty) { + final diffs = + diff.map<String>((DependentJob item) => item.name).join('\n '); throw ProcessRunnerException( "${job.name} has dependent jobs that aren't scheduled to be run:\n" - " ${diff.map<String>((DependentJob item) => item.name).join('\n ')}"); + ' $diffs'); } } // Check for dependency loops. - for (final DependentJob job in pending) { - final Set<DependentJob> visited = <DependentJob>{}; + for (final job in pending) { + final visited = <DependentJob>{}; if (_hasDependencyLoop(job, visited: visited)) { throw ProcessRunnerException('Illegal dependency loop detected:\n' ' ${<DependentJob>[
diff --git a/lib/src/process_runner.dart b/lib/src/process_runner.dart index 277eac0..aef2637 100644 --- a/lib/src/process_runner.dart +++ b/lib/src/process_runner.dart
@@ -5,11 +5,21 @@ import 'dart:async' show Completer; import 'dart:convert' show Encoding; import 'dart:io' - show Process, ProcessStartMode, ProcessException, Directory, stderr, stdout, SystemEncoding - hide Platform; + show + Directory, + Process, + ProcessException, + ProcessStartMode, + SystemEncoding, + stderr, + stdout; -import 'package:platform/platform.dart' show Platform, LocalPlatform; -import 'package:process/process.dart' show ProcessManager, LocalProcessManager; +import 'package:platform/platform.dart' show LocalPlatform, Platform; +import 'package:process/process.dart' show LocalProcessManager, ProcessManager; + +import '../process_runner.dart' show ProcessPool; + +import 'process_pool.dart' show ProcessPool; const Platform defaultPlatform = LocalPlatform(); @@ -25,9 +35,9 @@ @override String toString() { - String output = runtimeType.toString(); + var output = runtimeType.toString(); output += ': $message'; - final String stderr = result?.stderr ?? ''; + final stderr = result?.stderr ?? ''; if (stderr.isNotEmpty) { output += ':\n$stderr'; } @@ -73,8 +83,8 @@ /// Information appears in the order supplied by the process. final List<int> outputRaw; - /// The optional encoder to use in [stdout], [stderr], and [output] accessors to decode - /// the raw data. + /// The optional encoder to use in [stdout], [stderr], and [output] accessors + /// to decode the raw data. /// /// Defaults to using [SystemEncoding]. final Encoding decoder; @@ -115,11 +125,15 @@ String? _output; - /// A constant to use if there is no result data available, but the process failed. - static final ProcessRunnerResult failed = ProcessRunnerResult(-1, <int>[], <int>[], <int>[]); + /// A constant to use if there is no result data available, but the process + /// failed. + static final ProcessRunnerResult failed = + ProcessRunnerResult(-1, <int>[], <int>[], <int>[]); - /// A constant to use if there is no result data available, but the process succeeded. - static final ProcessRunnerResult emptySuccess = ProcessRunnerResult(0, <int>[], <int>[], <int>[]); + /// A constant to use if there is no result data available, but the process + /// succeeded. + static final ProcessRunnerResult emptySuccess = + ProcessRunnerResult(0, <int>[], <int>[], <int>[]); } /// A helper class for classes that want to run a process, optionally have the @@ -134,7 +148,8 @@ this.printOutputDefault = false, this.decoder = const SystemEncoding(), }) : defaultWorkingDirectory = defaultWorkingDirectory ?? Directory.current, - environment = environment ?? Map<String, String>.from(defaultPlatform.environment); + environment = environment ?? + Map<String, String>.from(defaultPlatform.environment); /// Set the [processManager] in order to allow injecting a test instance to /// perform testing. @@ -202,14 +217,15 @@ workingDirectory ??= defaultWorkingDirectory; printOutput ??= printOutputDefault; if (printOutput) { - stderr.write('Running "${commandLine.join(' ')}" in ${workingDirectory.path}.\n'); + stderr.write( + 'Running "${commandLine.join(' ')}" in ${workingDirectory.path}.\n'); } - final List<int> stdoutOutput = <int>[]; - final List<int> stderrOutput = <int>[]; - final List<int> combinedOutput = <int>[]; - final Completer<void> stdoutComplete = Completer<void>(); - final Completer<void> stderrComplete = Completer<void>(); - final Completer<void> stdinComplete = Completer<void>(); + final stdoutOutput = <int>[]; + final stderrOutput = <int>[]; + final combinedOutput = <int>[]; + final stdoutComplete = Completer<void>(); + final stderrComplete = Completer<void>(); + final stdinComplete = Completer<void>(); late Process process; Future<int> allComplete() async { @@ -219,7 +235,9 @@ } await stderrComplete.future; await stdoutComplete.future; - return startMode == ProcessStartMode.normal ? process.exitCode : Future<int>.value(0); + return startMode == ProcessStartMode.normal + ? process.exitCode + : Future<int>.value(0); } try { @@ -230,7 +248,8 @@ runInShell: runInShell, mode: startMode, ); - if (startMode == ProcessStartMode.normal || startMode == ProcessStartMode.detachedWithStdio) { + if (startMode == ProcessStartMode.normal || + startMode == ProcessStartMode.detachedWithStdio) { if (stdin != null) { stdin.listen((List<int> data) { process.stdin.add(data); @@ -263,19 +282,23 @@ stderrComplete.complete(); } } on ProcessException catch (e) { - final String message = 'Running "${commandLine.join(' ')}" in ${workingDirectory.path} ' + final message = + 'Running "${commandLine.join(' ')}" in ${workingDirectory.path} ' 'failed with:\n$e'; throw ProcessRunnerException(message); + // ignore: avoid_catching_errors } on ArgumentError catch (e) { - final String message = 'Running "${commandLine.join(' ')}" in ${workingDirectory.path} ' + final message = + 'Running "${commandLine.join(' ')}" in ${workingDirectory.path} ' 'failed with:\n$e'; throw ProcessRunnerException(message); } - final int exitCode = await allComplete(); + final exitCode = await allComplete(); if (exitCode != 0 && !failOk) { - final String message = - 'Running "${commandLine.join(' ')}" in ${workingDirectory.path} exited with code $exitCode\n${decoder.decode(combinedOutput)}'; + final message = + 'Running "${commandLine.join(' ')}" in ${workingDirectory.path} ' + 'exited with code $exitCode\n${decoder.decode(combinedOutput)}'; throw ProcessRunnerException( message, result: ProcessRunnerResult(
diff --git a/test/src/fake_process_manager.dart b/lib/test/fake_process_manager.dart similarity index 79% rename from test/src/fake_process_manager.dart rename to lib/test/fake_process_manager.dart index 74f0c72..e78fc12 100644 --- a/test/src/fake_process_manager.dart +++ b/lib/test/fake_process_manager.dart
@@ -18,11 +18,8 @@ final String? workingDirectory; } -/// A mock that can be used to fake a process manager that runs commands -/// and returns results. -/// -/// Call [setResults] to provide a list of results that will return from -/// each command line (with arguments). +/// A mock that can be used to fake a process manager that runs commands and +/// returns results. /// /// Call [verifyCalls] to verify that each desired call occurred. class FakeProcessManager implements ProcessManager { @@ -41,11 +38,13 @@ /// output that will be returned on each successive call. Map<FakeInvocationRecord, List<ProcessResult>> _fakeResults = <FakeInvocationRecord, List<ProcessResult>>{}; - Map<FakeInvocationRecord, List<ProcessResult>> get fakeResults => _fakeResults; + Map<FakeInvocationRecord, List<ProcessResult>> get fakeResults => + _fakeResults; set fakeResults(Map<FakeInvocationRecord, List<ProcessResult>> value) { _fakeResults = <FakeInvocationRecord, List<ProcessResult>>{}; - for (final FakeInvocationRecord key in value.keys) { - _fakeResults[key] = (value[key] ?? <ProcessResult>[ProcessResult(0, 0, '', '')]).toList(); + for (final key in value.keys) { + _fakeResults[key] = + (value[key] ?? <ProcessResult>[ProcessResult(0, 0, '', '')]).toList(); } } @@ -55,11 +54,12 @@ /// Verify that the given command lines were called, in the given order, and /// that the parameters were in the same order. void verifyCalls(Iterable<FakeInvocationRecord> calls) { - int index = 0; + var index = 0; expect(invocations.length, equals(calls.length)); - for (final FakeInvocationRecord call in calls) { + for (final call in calls) { expect(call.invocation, orderedEquals(invocations[index].invocation)); - expect(call.workingDirectory, equals(invocations[index].workingDirectory)); + expect( + call.workingDirectory, equals(invocations[index].workingDirectory)); index++; } } @@ -68,12 +68,12 @@ expect(fakeResults, isNotEmpty); late List<ProcessResult> foundResult; late FakeInvocationRecord foundCommand; - for (final FakeInvocationRecord fakeCommand in fakeResults.keys) { + for (final fakeCommand in fakeResults.keys) { if (fakeCommand.invocation.length != command.invocation.length) { continue; } - bool listsIdentical = true; - for (int i = 0; i < fakeCommand.invocation.length; ++i) { + var listsIdentical = true; + for (var i = 0; i < fakeCommand.invocation.length; ++i) { if (fakeCommand.invocation[i] != command.invocation[i]) { listsIdentical = false; break; @@ -85,28 +85,33 @@ break; } } - expect(foundResult, isNotNull, reason: '$command not found in expected results.'); + expect(foundResult, isNotNull, + reason: '$command not found in expected results.'); expect(foundResult, isNotEmpty); - return fakeResults[foundCommand]?.removeAt(0) ?? ProcessResult(0, 0, '', ''); + return fakeResults[foundCommand]?.removeAt(0) ?? + ProcessResult(0, 0, '', ''); } FakeProcess _popProcess(FakeInvocationRecord command) => FakeProcess(_popResult(command), stdinResults); - Future<Process> _nextProcess(List<String> invocation, String? workingDirectory) async { - final FakeInvocationRecord record = FakeInvocationRecord(invocation, workingDirectory); + Future<Process> _nextProcess( + List<String> invocation, String? workingDirectory) async { + final record = FakeInvocationRecord(invocation, workingDirectory); invocations.add(record); return Future<Process>.value(_popProcess(record)); } - ProcessResult _nextResultSync(List<String> invocation, String? workingDirectory) { - final FakeInvocationRecord record = FakeInvocationRecord(invocation, workingDirectory); + ProcessResult _nextResultSync( + List<String> invocation, String? workingDirectory) { + final record = FakeInvocationRecord(invocation, workingDirectory); invocations.add(record); return _popResult(record); } - Future<ProcessResult> _nextResult(List<String> invocation, String? workingDirectory) async { - final FakeInvocationRecord record = FakeInvocationRecord(invocation, workingDirectory); + Future<ProcessResult> _nextResult( + List<String> invocation, String? workingDirectory) async { + final record = FakeInvocationRecord(invocation, workingDirectory); invocations.add(record); return Future<ProcessResult>.value(_popResult(record)); } @@ -175,8 +180,10 @@ /// FakeProcessManager. class FakeProcess implements Process { FakeProcess(ProcessResult result, StdinResults stdinResults) - : stdoutStream = Stream<List<int>>.value((result.stdout as String).codeUnits), - stderrStream = Stream<List<int>>.value((result.stderr as String).codeUnits), + : stdoutStream = + Stream<List<int>>.value((result.stdout as String).codeUnits), + stderrStream = + Stream<List<int>>.value((result.stderr as String).codeUnits), desiredExitCode = result.exitCode, stdinSink = IOSink(StringStreamConsumer(stdinResults)); @@ -214,7 +221,8 @@ StringStreamConsumer(this.sendString); List<Stream<List<int>>> streams = <Stream<List<int>>>[]; - List<StreamSubscription<List<int>>> subscriptions = <StreamSubscription<List<int>>>[]; + List<StreamSubscription<List<int>>> subscriptions = + <StreamSubscription<List<int>>>[]; List<Completer<dynamic>> completers = <Completer<dynamic>>[]; /// The callback called when this consumer receives input. @@ -235,7 +243,7 @@ @override Future<dynamic> close() async { - for (final Completer<dynamic> completer in completers) { + for (final completer in completers) { await completer.future; } completers.clear();
diff --git a/pubspec.yaml b/pubspec.yaml index be11ae8..d1b596b 100644 --- a/pubspec.yaml +++ b/pubspec.yaml
@@ -13,17 +13,18 @@ dependencies: args: ^2.3.0 async: ^2.5.0 - file: '>=6.1.0 <8.0.0' + file: ">=6.1.0 <8.0.0" meta: ^1.3.0 path: ^1.8.0 platform: ^3.1.0 process: ^5.0.1 + test: ^1.26.3 dev_dependencies: - test: ^1.16.8 + dart_flutter_team_lints: ^3.5.2 environment: - sdk: '>=2.12.0 <4.0.0' + sdk: ">=2.12.0 <4.0.0" binaries: process_runner:
diff --git a/test/src/fake_process_manager_test.dart b/test/src/fake_process_manager_test.dart index b9440a2..5060361 100644 --- a/test/src/fake_process_manager_test.dart +++ b/test/src/fake_process_manager_test.dart
@@ -5,42 +5,39 @@ import 'dart:convert'; import 'dart:io'; -import 'package:test/test.dart' as test_package show TypeMatcher; +import 'package:process_runner/test/fake_process_manager.dart'; import 'package:test/test.dart' hide TypeMatcher, isInstanceOf; -import 'fake_process_manager.dart'; - -test_package.TypeMatcher<T> isInstanceOf<T>() => isA<T>(); - void main() { group('ArchivePublisher', () { - final List<String> stdinCaptured = <String>[]; - void _captureStdin(String item) { + final stdinCaptured = <String>[]; + void captureStdin(String item) { stdinCaptured.add(item); } - FakeProcessManager processManager = FakeProcessManager(_captureStdin); + var processManager = FakeProcessManager(captureStdin); setUp(() async { - processManager = FakeProcessManager(_captureStdin); + processManager = FakeProcessManager(captureStdin); }); tearDown(() async {}); test('start works', () async { - final Map<FakeInvocationRecord, List<ProcessResult>> calls = - <FakeInvocationRecord, List<ProcessResult>>{ - FakeInvocationRecord(<String>['command', 'arg1', 'arg2']): <ProcessResult>[ + final calls = <FakeInvocationRecord, List<ProcessResult>>{ + FakeInvocationRecord(<String>['command', 'arg1', 'arg2']): + <ProcessResult>[ ProcessResult(0, 0, 'output1', ''), ], - FakeInvocationRecord(<String>['command2', 'arg1', 'arg2']): <ProcessResult>[ + FakeInvocationRecord(<String>['command2', 'arg1', 'arg2']): + <ProcessResult>[ ProcessResult(0, 0, 'output2', ''), ], }; processManager.fakeResults = calls; - for (final FakeInvocationRecord key in calls.keys) { - final Process process = await processManager.start(key.invocation); - String output = ''; + for (final key in calls.keys) { + final process = await processManager.start(key.invocation); + var output = ''; process.stdout.listen((List<int> item) { output += utf8.decode(item); }); @@ -51,59 +48,65 @@ }); test('run works', () async { - final Map<FakeInvocationRecord, List<ProcessResult>> calls = - <FakeInvocationRecord, List<ProcessResult>>{ - FakeInvocationRecord(<String>['command', 'arg1', 'arg2']): <ProcessResult>[ + final calls = <FakeInvocationRecord, List<ProcessResult>>{ + FakeInvocationRecord(<String>['command', 'arg1', 'arg2']): + <ProcessResult>[ ProcessResult(0, 0, 'output1', ''), ], - FakeInvocationRecord(<String>['command2', 'arg1', 'arg2']): <ProcessResult>[ + FakeInvocationRecord(<String>['command2', 'arg1', 'arg2']): + <ProcessResult>[ ProcessResult(0, 0, 'output2', ''), ], }; processManager.fakeResults = calls; - for (final FakeInvocationRecord key in calls.keys) { - final ProcessResult result = await processManager.run(key.invocation); - expect(result.stdout, equals((calls[key] ?? <ProcessResult>[])[0].stdout)); + for (final key in calls.keys) { + final result = await processManager.run(key.invocation); + expect( + result.stdout, equals((calls[key] ?? <ProcessResult>[])[0].stdout)); } processManager.verifyCalls(calls.keys.toList()); }); test('runSync works', () async { - final Map<FakeInvocationRecord, List<ProcessResult>> calls = - <FakeInvocationRecord, List<ProcessResult>>{ - FakeInvocationRecord(<String>['command', 'arg1', 'arg2']): <ProcessResult>[ + final calls = <FakeInvocationRecord, List<ProcessResult>>{ + FakeInvocationRecord(<String>['command', 'arg1', 'arg2']): + <ProcessResult>[ ProcessResult(0, 0, 'output1', ''), ], - FakeInvocationRecord(<String>['command2', 'arg1', 'arg2']): <ProcessResult>[ + FakeInvocationRecord(<String>['command2', 'arg1', 'arg2']): + <ProcessResult>[ ProcessResult(0, 0, 'output2', ''), ], }; processManager.fakeResults = calls; - for (final FakeInvocationRecord key in calls.keys) { - final ProcessResult result = processManager.runSync(key.invocation); - expect(result.stdout, equals((calls[key] ?? <ProcessResult>[])[0].stdout)); + for (final key in calls.keys) { + final result = processManager.runSync(key.invocation); + expect( + result.stdout, equals((calls[key] ?? <ProcessResult>[])[0].stdout)); } processManager.verifyCalls(calls.keys.toList()); }); test('captures stdin', () async { - final Map<FakeInvocationRecord, List<ProcessResult>> calls = - <FakeInvocationRecord, List<ProcessResult>>{ - FakeInvocationRecord(<String>['command', 'arg1', 'arg2']): <ProcessResult>[ + final calls = <FakeInvocationRecord, List<ProcessResult>>{ + FakeInvocationRecord(<String>['command', 'arg1', 'arg2']): + <ProcessResult>[ ProcessResult(0, 0, 'output1', ''), ], - FakeInvocationRecord(<String>['command2', 'arg1', 'arg2']): <ProcessResult>[ + FakeInvocationRecord(<String>['command2', 'arg1', 'arg2']): + <ProcessResult>[ ProcessResult(0, 0, 'output2', ''), ], }; processManager.fakeResults = calls; - for (final FakeInvocationRecord key in calls.keys) { - final Process process = await processManager.start(key.invocation); - String output = ''; + for (final key in calls.keys) { + final process = await processManager.start(key.invocation); + var output = ''; process.stdout.listen((List<int> item) { output += utf8.decode(item); }); - final String testInput = '${(calls[key] ?? <ProcessResult>[])[0].stdout} input'; + final testInput = + '${(calls[key] ?? <ProcessResult>[])[0].stdout} input'; process.stdin.add(testInput.codeUnits); await process.exitCode; expect(output, equals((calls[key] ?? <ProcessResult>[])[0].stdout));
diff --git a/test/src/live_process_test.dart b/test/src/live_process_test.dart index 540b5f5..223df79 100644 --- a/test/src/live_process_test.dart +++ b/test/src/live_process_test.dart
@@ -19,12 +19,14 @@ } late Directory tmpdir; - ProcessRunner processRunner = ProcessRunner(processManager: const LocalProcessManager()); + var processRunner = + ProcessRunner(processManager: const LocalProcessManager()); setUp(() { tmpdir = Directory.systemTemp.createTempSync('live_process_test.'); - processRunner = - ProcessRunner(processManager: const LocalProcessManager(), defaultWorkingDirectory: tmpdir); + processRunner = ProcessRunner( + processManager: const LocalProcessManager(), + defaultWorkingDirectory: tmpdir); }); tearDown(() { @@ -33,41 +35,43 @@ group('Output Capture', () { test('runProcess returns correct return value', () async { - final ProcessRunnerResult result = await processRunner.runProcess(<String>['true']); + final result = await processRunner.runProcess(<String>['true']); expect(result.exitCode, equals(0)); - final ProcessRunnerResult result1 = + final result1 = await processRunner.runProcess(<String>['false'], failOk: true); expect(result1.exitCode, isNot(equals(0))); }); test('runProcess captures stdout', () async { - final ProcessRunnerResult result = + final result = await processRunner.runProcess(<String>['echo', 'process output']); expect(result.exitCode, equals(0)); expect(result.stdout, equals('process output\n')); }); test('runProcess captures stderr', () async { - final ProcessRunnerResult result = - await processRunner.runProcess(<String>['cat', '--flutter'], failOk: true); + final result = await processRunner + .runProcess(<String>['cat', '--flutter'], failOk: true); expect(result.exitCode, isNot(equals(0))); expect(result.stderr, contains(RegExp(r'(unrecognized|illegal) option'))); }); test('runProcess captures detachedWithStdio stdout', () async { - final ProcessRunnerResult result = await processRunner.runProcess( + final result = await processRunner.runProcess( <String>['echo', 'process output'], startMode: ProcessStartMode.detachedWithStdio); expect(result.exitCode, equals(0)); expect(result.stdout, equals('process output\n')); }); test('runProcess captures detachedWithStdio stderr', () async { - final ProcessRunnerResult result = await processRunner.runProcess( + final result = await processRunner.runProcess( <String>['cat', '--flutter'], failOk: true, startMode: ProcessStartMode.detachedWithStdio); - expect(result.exitCode, equals(0)); // failed detached processes don't report an exit code. + expect(result.exitCode, + equals(0)); // failed detached processes don't report an exit code. expect(result.stderr, contains(RegExp(r'(unrecognized|illegal) option'))); }); test('runProcess captures nothing with detached process', () async { - final ProcessRunnerResult result = await processRunner - .runProcess(<String>['echo', 'process output'], startMode: ProcessStartMode.detached); + final result = await processRunner.runProcess( + <String>['echo', 'process output'], + startMode: ProcessStartMode.detached); expect(result.exitCode, equals(0)); expect(result.stdout, isEmpty); expect(result.stderr, isEmpty);
diff --git a/test/src/process_pool_test.dart b/test/src/process_pool_test.dart index 549b193..3bb11da 100644 --- a/test/src/process_pool_test.dart +++ b/test/src/process_pool_test.dart
@@ -5,15 +5,14 @@ import 'dart:io'; import 'package:process_runner/process_runner.dart'; +import 'package:process_runner/test/fake_process_manager.dart'; import 'package:test/test.dart'; -import 'fake_process_manager.dart'; - void main() { late FakeProcessManager fakeProcessManager; late ProcessRunner processRunner; late ProcessPool processPool; - final String testPath = Platform.isWindows ? r'C:\tmp\foo' : '/tmp/foo'; + final testPath = Platform.isWindows ? r'C:\tmp\foo' : '/tmp/foo'; setUp(() { fakeProcessManager = FakeProcessManager((String value) {}); @@ -25,111 +24,119 @@ }); test('startWorkers works', () async { - final Map<FakeInvocationRecord, List<ProcessResult>> calls = - <FakeInvocationRecord, List<ProcessResult>>{ - FakeInvocationRecord(<String>['command', 'arg1', 'arg2'], testPath): <ProcessResult>[ + final calls = <FakeInvocationRecord, List<ProcessResult>>{ + FakeInvocationRecord(<String>['command', 'arg1', 'arg2'], testPath): + <ProcessResult>[ ProcessResult(0, 0, 'output1', ''), ], }; fakeProcessManager.fakeResults = calls; - final List<WorkerJob> jobs = <WorkerJob>[ + final jobs = <WorkerJob>[ WorkerJob(<String>['command', 'arg1', 'arg2'], name: 'job 1'), ]; await for (final WorkerJob _ in processPool.startWorkers(jobs)) {} fakeProcessManager.verifyCalls(calls.keys); }); test('runToCompletion works', () async { - final Map<FakeInvocationRecord, List<ProcessResult>> calls = - <FakeInvocationRecord, List<ProcessResult>>{ - FakeInvocationRecord(<String>['command', 'arg1', 'arg2'], testPath): <ProcessResult>[ + final calls = <FakeInvocationRecord, List<ProcessResult>>{ + FakeInvocationRecord(<String>['command', 'arg1', 'arg2'], testPath): + <ProcessResult>[ ProcessResult(0, 0, 'output1', ''), ], }; fakeProcessManager.fakeResults = calls; - final List<WorkerJob> jobs = <WorkerJob>[ + final jobs = <WorkerJob>[ WorkerJob(<String>['command', 'arg1', 'arg2'], name: 'job 1'), ]; await processPool.runToCompletion(jobs); fakeProcessManager.verifyCalls(calls.keys); }); test('failed tests report results', () async { - final Map<FakeInvocationRecord, List<ProcessResult>> calls = - <FakeInvocationRecord, List<ProcessResult>>{ - FakeInvocationRecord(<String>['command', 'arg1', 'arg2'], testPath): <ProcessResult>[ + final calls = <FakeInvocationRecord, List<ProcessResult>>{ + FakeInvocationRecord(<String>['command', 'arg1', 'arg2'], testPath): + <ProcessResult>[ ProcessResult(0, -1, 'output1', 'stderr1'), ], }; fakeProcessManager.fakeResults = calls; - final List<WorkerJob> jobs = <WorkerJob>[ + final jobs = <WorkerJob>[ WorkerJob(<String>['command', 'arg1', 'arg2'], name: 'job 1'), ]; - final List<WorkerJob> completed = await processPool.runToCompletion(jobs); + final completed = await processPool.runToCompletion(jobs); expect(completed.first.result.exitCode, equals(-1)); expect(completed.first.result.stdout, equals('output1')); expect(completed.first.result.stderr, equals('stderr1')); expect(completed.first.result.output, equals('output1stderr1')); }); test('failed tests throw when failOk is false', () async { - final Map<FakeInvocationRecord, List<ProcessResult>> calls = - <FakeInvocationRecord, List<ProcessResult>>{ - FakeInvocationRecord(<String>['command', 'arg1', 'arg2'], testPath): <ProcessResult>[ + final calls = <FakeInvocationRecord, List<ProcessResult>>{ + FakeInvocationRecord(<String>['command', 'arg1', 'arg2'], testPath): + <ProcessResult>[ ProcessResult(0, -1, 'output1', 'stderr1'), ], }; fakeProcessManager.fakeResults = calls; - final List<WorkerJob> jobs = <WorkerJob>[ - WorkerJob(<String>['command', 'arg1', 'arg2'], name: 'job 1', failOk: false), + final jobs = <WorkerJob>[ + WorkerJob(<String>['command', 'arg1', 'arg2'], + name: 'job 1', failOk: false), ]; expect(() async { await processPool.runToCompletion(jobs); }, throwsException); }); test('Commands that throw exceptions report results', () async { - fakeProcessManager = FakeProcessManager((String value) {}, commandsThrow: true); + fakeProcessManager = + FakeProcessManager((String value) {}, commandsThrow: true); processRunner = ProcessRunner(processManager: fakeProcessManager); processPool = ProcessPool(processRunner: processRunner, printReport: null); - final Map<FakeInvocationRecord, List<ProcessResult>> calls = - <FakeInvocationRecord, List<ProcessResult>>{ - FakeInvocationRecord(<String>['command', 'arg1', 'arg2'], testPath): <ProcessResult>[ + final calls = <FakeInvocationRecord, List<ProcessResult>>{ + FakeInvocationRecord(<String>['command', 'arg1', 'arg2'], testPath): + <ProcessResult>[ ProcessResult(0, -1, 'output1', 'stderr1'), ], }; fakeProcessManager.fakeResults = calls; - final List<WorkerJob> jobs = <WorkerJob>[ + final jobs = <WorkerJob>[ WorkerJob(<String>['command', 'arg1', 'arg2'], name: 'job 1'), ]; - final List<WorkerJob> completed = await processPool.runToCompletion(jobs); + final completed = await processPool.runToCompletion(jobs); expect(completed.first.result, equals(ProcessRunnerResult.failed)); expect(completed.first.exception, isNotNull); }); - test('Commands in task groups run in order, but parallel with other groups', () async { + test('Commands in task groups run in order, but parallel with other groups', + () async { fakeProcessManager = FakeProcessManager((String value) {}); processRunner = ProcessRunner(processManager: fakeProcessManager); processPool = ProcessPool(processRunner: processRunner, printReport: null); - final Map<FakeInvocationRecord, List<ProcessResult>> calls = - <FakeInvocationRecord, List<ProcessResult>>{ - FakeInvocationRecord(<String>['commandA1', 'arg1', 'arg2'], testPath): <ProcessResult>[ + final calls = <FakeInvocationRecord, List<ProcessResult>>{ + FakeInvocationRecord(<String>['commandA1', 'arg1', 'arg2'], testPath): + <ProcessResult>[ ProcessResult(0, 0, 'outputA1', 'stderrA1'), ], - FakeInvocationRecord(<String>['commandB1', 'arg1', 'arg2'], testPath): <ProcessResult>[ + FakeInvocationRecord(<String>['commandB1', 'arg1', 'arg2'], testPath): + <ProcessResult>[ ProcessResult(0, 0, 'outputB1', 'stderrB1'), ], - FakeInvocationRecord(<String>['commandA2', 'arg1', 'arg2'], testPath): <ProcessResult>[ + FakeInvocationRecord(<String>['commandA2', 'arg1', 'arg2'], testPath): + <ProcessResult>[ ProcessResult(0, 0, 'outputA2', 'stderrA2'), ], - FakeInvocationRecord(<String>['commandB2', 'arg1', 'arg2'], testPath): <ProcessResult>[ + FakeInvocationRecord(<String>['commandB2', 'arg1', 'arg2'], testPath): + <ProcessResult>[ ProcessResult(0, -1, 'outputB2', 'stderrB2'), ], - FakeInvocationRecord(<String>['commandA3', 'arg1', 'arg2'], testPath): <ProcessResult>[ + FakeInvocationRecord(<String>['commandA3', 'arg1', 'arg2'], testPath): + <ProcessResult>[ ProcessResult(0, 0, 'outputA3', 'stderrA3'), ], - FakeInvocationRecord(<String>['commandB3', 'arg1', 'arg2'], testPath): <ProcessResult>[ + FakeInvocationRecord(<String>['commandB3', 'arg1', 'arg2'], testPath): + <ProcessResult>[ ProcessResult(0, 0, 'outputB3', 'stderrB3'), ], }; fakeProcessManager.fakeResults = calls; - final List<WorkerJobGroup> jobs = <WorkerJobGroup>[ + final jobs = <WorkerJobGroup>[ WorkerJobGroup( <WorkerJob>[ WorkerJob(<String>['commandA1', 'arg1', 'arg2'], name: 'job A1'), @@ -147,19 +154,25 @@ name: 'Group B', ), ]; - final List<WorkerJob> completed = await processPool.runToCompletion(jobs); + final completed = await processPool.runToCompletion(jobs); expect(completed.length, equals(6)); // Command B2 failed with -1, so B3 should also fail. expect( - completed.where((WorkerJob job) => job.result.exitCode != 0).map((WorkerJob job) => job.name), + completed + .where((WorkerJob job) => job.result.exitCode != 0) + .map((WorkerJob job) => job.name), unorderedEquals(<String>['job B2', 'job B3']), ); expect( - completed.where((WorkerJob job) => job.exception == null).map((WorkerJob job) => job.name), + completed + .where((WorkerJob job) => job.exception == null) + .map((WorkerJob job) => job.name), unorderedEquals(<String>['job A1', 'job B1', 'job A2', 'job A3']), ); expect( - completed.where((WorkerJob job) => job.result.exitCode == 0).map((WorkerJob job) => job.name), + completed + .where((WorkerJob job) => job.result.exitCode == 0) + .map((WorkerJob job) => job.name), unorderedEquals(<String>['job B1', 'job A1', 'job A2', 'job A3']), ); // Either group A or B can come first, but the individual group tasks should @@ -181,29 +194,34 @@ fakeProcessManager = FakeProcessManager((String value) {}); processRunner = ProcessRunner(processManager: fakeProcessManager); processPool = ProcessPool(processRunner: processRunner, printReport: null); - final Map<FakeInvocationRecord, List<ProcessResult>> calls = - <FakeInvocationRecord, List<ProcessResult>>{ - FakeInvocationRecord(<String>['commandA1', 'arg1', 'arg2'], testPath): <ProcessResult>[ + final calls = <FakeInvocationRecord, List<ProcessResult>>{ + FakeInvocationRecord(<String>['commandA1', 'arg1', 'arg2'], testPath): + <ProcessResult>[ ProcessResult(0, 0, 'outputA1', 'stderrA1'), ], - FakeInvocationRecord(<String>['commandB1', 'arg1', 'arg2'], testPath): <ProcessResult>[ + FakeInvocationRecord(<String>['commandB1', 'arg1', 'arg2'], testPath): + <ProcessResult>[ ProcessResult(0, 0, 'outputB1', 'stderrB1'), ], - FakeInvocationRecord(<String>['commandA2', 'arg1', 'arg2'], testPath): <ProcessResult>[ + FakeInvocationRecord(<String>['commandA2', 'arg1', 'arg2'], testPath): + <ProcessResult>[ ProcessResult(0, 0, 'outputA2', 'stderrA2'), ], - FakeInvocationRecord(<String>['commandB2', 'arg1', 'arg2'], testPath): <ProcessResult>[ + FakeInvocationRecord(<String>['commandB2', 'arg1', 'arg2'], testPath): + <ProcessResult>[ ProcessResult(0, -1, 'outputB2', 'stderrB2'), ], - FakeInvocationRecord(<String>['commandA3', 'arg1', 'arg2'], testPath): <ProcessResult>[ + FakeInvocationRecord(<String>['commandA3', 'arg1', 'arg2'], testPath): + <ProcessResult>[ ProcessResult(0, 0, 'outputA3', 'stderrA3'), ], - FakeInvocationRecord(<String>['commandB3', 'arg1', 'arg2'], testPath): <ProcessResult>[ + FakeInvocationRecord(<String>['commandB3', 'arg1', 'arg2'], testPath): + <ProcessResult>[ ProcessResult(0, 0, 'outputB3', 'stderrB3'), ], }; fakeProcessManager.fakeResults = calls; - final WorkerJobGroup groupA = WorkerJobGroup( + final groupA = WorkerJobGroup( <DependentJob>[ WorkerJob(<String>['commandA1', 'arg1', 'arg2'], name: 'job A1'), WorkerJob(<String>['commandA2', 'arg1', 'arg2'], name: 'job A2'), @@ -211,7 +229,7 @@ ], name: 'Group A', ); - final WorkerJobGroup groupB = WorkerJobGroup( + final groupB = WorkerJobGroup( <DependentJob>[ WorkerJob(<String>['commandB1', 'arg1', 'arg2'], name: 'job B1'), WorkerJob(<String>['commandB2', 'arg1', 'arg2'], name: 'job B2'), @@ -220,28 +238,43 @@ name: 'Group B', ); groupB.addDependency(groupA); - final List<DependentJob> jobs = <DependentJob>[groupA, groupB]; - final List<WorkerJob> completed = await processPool.runToCompletion(jobs); + final jobs = <DependentJob>[groupA, groupB]; + final completed = await processPool.runToCompletion(jobs); expect(completed.length, equals(6)); // Make sure they executed in the correct order. - expect(completed.map<String>((WorkerJob job) => job.name), - equals(<String>['job A1', 'job A2', 'job A3', 'job B1', 'job B2', 'job B3'])); + expect( + completed.map<String>((WorkerJob job) => job.name), + equals(<String>[ + 'job A1', + 'job A2', + 'job A3', + 'job B1', + 'job B2', + 'job B3' + ])); // Command B2 failed with -1, so B3 should also fail. expect( - completed.where((WorkerJob job) => job.result.exitCode != 0).map((WorkerJob job) => job.name), + completed + .where((WorkerJob job) => job.result.exitCode != 0) + .map((WorkerJob job) => job.name), unorderedEquals(<String>['job B2', 'job B3']), ); expect( - completed.where((WorkerJob job) => job.exception == null).map((WorkerJob job) => job.name), + completed + .where((WorkerJob job) => job.exception == null) + .map((WorkerJob job) => job.name), unorderedEquals(<String>['job A1', 'job B1', 'job A2', 'job A3']), ); expect( - completed.where((WorkerJob job) => job.result.exitCode == 0).map((WorkerJob job) => job.name), + completed + .where((WorkerJob job) => job.result.exitCode == 0) + .map((WorkerJob job) => job.name), unorderedEquals(<String>['job B1', 'job A1', 'job A2', 'job A3']), ); }); test("Jobs can't depend on themselves", () async { - final WorkerJob job = WorkerJob(<String>['commandA1', 'arg1', 'arg2'], name: 'job A1'); + final job = + WorkerJob(<String>['commandA1', 'arg1', 'arg2'], name: 'job A1'); ProcessRunnerException? exception; try { @@ -253,8 +286,10 @@ expect(exception!.message, equals('A job cannot depend on itself')); }); test("Jobs can't depend on each other directly", () async { - final WorkerJob jobA = WorkerJob(<String>['commandA1', 'arg1', 'arg2'], name: 'job A1'); - final WorkerJob jobB = WorkerJob(<String>['commandB1', 'arg1', 'arg2'], name: 'job B1'); + final jobA = + WorkerJob(<String>['commandA1', 'arg1', 'arg2'], name: 'job A1'); + final jobB = + WorkerJob(<String>['commandB1', 'arg1', 'arg2'], name: 'job B1'); ProcessRunnerException? exception; try { @@ -273,22 +308,27 @@ fakeProcessManager = FakeProcessManager((String value) {}); processRunner = ProcessRunner(processManager: fakeProcessManager); processPool = ProcessPool(processRunner: processRunner, printReport: null); - final Map<FakeInvocationRecord, List<ProcessResult>> calls = - <FakeInvocationRecord, List<ProcessResult>>{ - FakeInvocationRecord(<String>['commandA1', 'arg1', 'arg2'], testPath): <ProcessResult>[ + final calls = <FakeInvocationRecord, List<ProcessResult>>{ + FakeInvocationRecord(<String>['commandA1', 'arg1', 'arg2'], testPath): + <ProcessResult>[ ProcessResult(0, 0, 'outputA1', 'stderrA1'), ], - FakeInvocationRecord(<String>['commandB1', 'arg1', 'arg2'], testPath): <ProcessResult>[ + FakeInvocationRecord(<String>['commandB1', 'arg1', 'arg2'], testPath): + <ProcessResult>[ ProcessResult(0, 0, 'outputB1', 'stderrB1'), ], - FakeInvocationRecord(<String>['commandC1', 'arg1', 'arg2'], testPath): <ProcessResult>[ + FakeInvocationRecord(<String>['commandC1', 'arg1', 'arg2'], testPath): + <ProcessResult>[ ProcessResult(0, 0, 'outputC1', 'stderrC1'), ], }; fakeProcessManager.fakeResults = calls; - final WorkerJob jobA = WorkerJob(<String>['commandA1', 'arg1', 'arg2'], name: 'job A1'); - final WorkerJob jobB = WorkerJob(<String>['commandB1', 'arg1', 'arg2'], name: 'job B1'); - final WorkerJob jobC = WorkerJob(<String>['commandC1', 'arg1', 'arg2'], name: 'job C1'); + final jobA = + WorkerJob(<String>['commandA1', 'arg1', 'arg2'], name: 'job A1'); + final jobB = + WorkerJob(<String>['commandB1', 'arg1', 'arg2'], name: 'job B1'); + final jobC = + WorkerJob(<String>['commandC1', 'arg1', 'arg2'], name: 'job C1'); ProcessRunnerException? exception; try {
diff --git a/test/src/process_runner_test.dart b/test/src/process_runner_test.dart index c3571de..b7b4d60 100644 --- a/test/src/process_runner_test.dart +++ b/test/src/process_runner_test.dart
@@ -5,28 +5,28 @@ import 'dart:io'; import 'package:process_runner/process_runner.dart'; +import 'package:process_runner/test/fake_process_manager.dart'; import 'package:test/test.dart'; -import 'fake_process_manager.dart'; - void main() { - FakeProcessManager fakeProcessManager = FakeProcessManager((String value) {}); - ProcessRunner processRunner = ProcessRunner(processManager: fakeProcessManager); - final String testPath = Platform.isWindows ? r'C:\tmp\foo' : '/tmp/foo'; + var fakeProcessManager = FakeProcessManager((String value) {}); + var processRunner = ProcessRunner(processManager: fakeProcessManager); + final testPath = Platform.isWindows ? r'C:\tmp\foo' : '/tmp/foo'; setUp(() { fakeProcessManager = FakeProcessManager((String value) {}); processRunner = ProcessRunner( - processManager: fakeProcessManager, defaultWorkingDirectory: Directory(testPath)); + processManager: fakeProcessManager, + defaultWorkingDirectory: Directory(testPath)); }); tearDown(() {}); group('Output Capture', () { test('runProcess works', () async { - final Map<FakeInvocationRecord, List<ProcessResult>> calls = - <FakeInvocationRecord, List<ProcessResult>>{ - FakeInvocationRecord(<String>['command', 'arg1', 'arg2'], testPath): <ProcessResult>[ + final calls = <FakeInvocationRecord, List<ProcessResult>>{ + FakeInvocationRecord(<String>['command', 'arg1', 'arg2'], testPath): + <ProcessResult>[ ProcessResult(0, 0, 'output1', ''), ], }; @@ -35,14 +35,14 @@ fakeProcessManager.verifyCalls(calls.keys); }); test('runProcess returns correct output', () async { - final Map<FakeInvocationRecord, List<ProcessResult>> calls = - <FakeInvocationRecord, List<ProcessResult>>{ - FakeInvocationRecord(<String>['command', 'arg1', 'arg2'], testPath): <ProcessResult>[ + final calls = <FakeInvocationRecord, List<ProcessResult>>{ + FakeInvocationRecord(<String>['command', 'arg1', 'arg2'], testPath): + <ProcessResult>[ ProcessResult(0, 0, 'output1', 'stderr1'), ], }; fakeProcessManager.fakeResults = calls; - final ProcessRunnerResult result = + final result = await processRunner.runProcess(calls.keys.first.invocation); fakeProcessManager.verifyCalls(calls.keys); expect(result.stdout, equals('output1')); @@ -50,26 +50,27 @@ expect(result.output, equals('output1stderr1')); }); test('runProcess fails properly', () async { - final Map<FakeInvocationRecord, List<ProcessResult>> calls = - <FakeInvocationRecord, List<ProcessResult>>{ - FakeInvocationRecord(<String>['command', 'arg1', 'arg2'], ''): <ProcessResult>[ + final calls = <FakeInvocationRecord, List<ProcessResult>>{ + FakeInvocationRecord(<String>['command', 'arg1', 'arg2'], ''): + <ProcessResult>[ ProcessResult(0, -1, 'output1', 'stderr1'), ], }; fakeProcessManager.fakeResults = calls; await expectLater( - () => processRunner.runProcess(calls.keys.first.invocation), throwsException); + () => processRunner.runProcess(calls.keys.first.invocation), + throwsException); }); test('runProcess returns the failed results properly', () async { - final Map<FakeInvocationRecord, List<ProcessResult>> calls = - <FakeInvocationRecord, List<ProcessResult>>{ - FakeInvocationRecord(<String>['command', 'arg1', 'arg2'], testPath): <ProcessResult>[ + final calls = <FakeInvocationRecord, List<ProcessResult>>{ + FakeInvocationRecord(<String>['command', 'arg1', 'arg2'], testPath): + <ProcessResult>[ ProcessResult(0, -1, 'output1', 'stderr1'), ], }; fakeProcessManager.fakeResults = calls; - final ProcessRunnerResult result = - await processRunner.runProcess(calls.keys.first.invocation, failOk: true); + final result = await processRunner.runProcess(calls.keys.first.invocation, + failOk: true); expect(result.stdout, equals('output1')); expect(result.stderr, equals('stderr1')); expect(result.output, equals('output1stderr1'));