Convert to null safety
diff --git a/CHANGELOG.md b/CHANGELOG.md index 66f3b55..b06a179 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md
@@ -1,5 +1,9 @@ # Change Log for `process_runner` +## 4.0.0-nullsafety + +* Convert to non-nullable by default, enable null-safety experiment for Dart. + ## 3.0.0 * Breaking change to change the `result` given in the `ProcessRunnerException`
diff --git a/analysis_options.yaml b/analysis_options.yaml index db2cc78..452eeb3 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml
@@ -16,6 +16,8 @@ # include: package:pedantic/analysis_options.yaml analyzer: + enable-experiment: + - non-nullable strong-mode: implicit-casts: false implicit-dynamic: false
diff --git a/ci/analyze.sh b/ci/analyze.sh index 283098a..bdb49f6 100755 --- a/ci/analyze.sh +++ b/ci/analyze.sh
@@ -17,7 +17,7 @@ function analyze() { # Make sure we pass the analyzer echo "Checking dartanalyzer..." - fails_analyzer="$(find lib test ci -name "*.dart" | xargs dartanalyzer --options analysis_options.yaml)" + fails_analyzer="$(find lib test ci -name "*.dart" | xargs dartanalyzer --enable-experiment=non-nullable --options analysis_options.yaml)" if [[ "$fails_analyzer" == *"[error]"* ]]; then echo "FAILED" echo "$fails_analyzer"
diff --git a/ci/test.sh b/ci/test.sh index 6939bc8..35d25f3 100755 --- a/ci/test.sh +++ b/ci/test.sh
@@ -17,4 +17,4 @@ cd "$REPO_DIR" # Run the tests. -pub run test +pub run --enable-experiment=non-nullable test
diff --git a/example/main.dart b/example/main.dart index 0172923..f2e3e52 100644 --- a/example/main.dart +++ b/example/main.dart
@@ -9,9 +9,10 @@ // of single-threaded CPU-intensive commands by a multple of the number of // processor cores you have (modulo being disk/network bound, of course). +// @dart = 2.10 + import 'dart:io'; -import 'package:args/args.dart'; import 'package:process_runner/process_runner.dart'; // This only works for escaped spaces and things in double or single quotes. @@ -76,55 +77,70 @@ return result; } -Future<void> main(List<String> args) async { - final ArgParser parser = ArgParser(); - parser.addFlag('help', help: 'Print help.'); - parser.addFlag('report', help: 'Print progress on the jobs while running.', defaultsTo: false); - parser.addOption('workers', - abbr: 'w', - help: 'Specify the number of workers jobs to run simultanously. Defaults ' - 'to the number of processors on the machine.'); - parser.addOption('workingDirectory', - abbr: 'd', help: 'Specify the working directory to run on', defaultsTo: '.'); - parser.addMultiOption('cmd', - abbr: 'c', - help: 'Specify a command to add to the commands to be run. Entire ' - 'command must be quoted by the shell. Commands specified with this ' - 'option run before those specified with --cmdFile'); - parser.addOption('file', - abbr: 'f', - help: 'Specify the name of a file to read commands from, one per line, as ' - 'they would appear on the command line, with spaces escaped or ' - 'quoted. Specify "-" to read from stdin.', - defaultsTo: '-'); - final ArgResults options = parser.parse(args); +String usage() { + return ''' +main.dart [flags] + --[no-]help Print help. + --[no-]report Print progress on the jobs while running. +-w, --workers Specify the number of workers jobs to run simultanously. Defaults to the number of processors on the machine. +-d, --workingDirectory Specify the working directory to run on + (defaults to ".") +-c, --cmd Specify a command to add to the commands to be run. Entire command must be quoted by the shell. Commands specified with this option run before those specified with --cmdFile +-f, --file Specify the name of a file to read commands from, one per line, as they would appear on the command line, with spaces escaped or quoted. Specify "-" to read from stdin. + (defaults to "-") +'''; +} - if (options['help'] as bool) { +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]; + } + } +} + +Future<void> main(List<String> args) async { + // Parse args without ArgParser until ArgParser is null-safe. + if (args.contains('--help')) { print('main.dart [flags]'); - print(parser.usage); + print(usage()); exit(0); } + final bool printReport = args.contains('--report'); + // 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(findOption('workers', args) ?? ''); + final Directory workingDirectory = Directory(findOption('workingDirectory', args) ?? '.'); + final List<String> cmds = findAllOptions('cmd', args).toList(); + // Collect the commands to be run from the command file. - final String commandFile = options['file'] as String; + final String commandFile = findOption('file', args) ?? '-'; List<String> fileCommands = <String>[]; - if (commandFile != null) { - // Read from stdin if the --file option is set to '-'. - if (commandFile == '-') { - String line = stdin.readLineSync(); - while (line != null) { - fileCommands.add(line); - line = stdin.readLineSync(); - } - } else { - // Read the commands from a file. - final File cmdFile = File(commandFile); - if (!cmdFile.existsSync()) { - print('Command file "$commandFile" doesn\'t exist.'); - exit(1); - } - fileCommands = cmdFile.readAsLinesSync(); + // Read from stdin if the --file option is set to '-'. + if (commandFile == '-') { + String? line = stdin.readLineSync(); + while (line != null) { + fileCommands.add(line); + line = stdin.readLineSync(); } + } else { + // Read the commands from a file. + final File cmdFile = File(commandFile); + if (!cmdFile.existsSync()) { + print('Command file "$commandFile" doesn\'t exist.'); + exit(1); + } + fileCommands = cmdFile.readAsLinesSync(); } // Collect all the commands, both from the input file, and from the command @@ -132,7 +148,7 @@ // executed simultaneously, depending on the number of workers, and number of // commands). final List<String> commands = <String>[ - ...options['cmd'] as List<String>, + ...cmds, ...fileCommands, ]; @@ -140,14 +156,6 @@ // simple quoting and escaping. final List<List<String>> splitCommands = commands.map<List<String>>(splitIntoArgs).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['workers'] as String ?? ''); - - final bool printReport = (options['report'] as bool) ?? false; - - final Directory workingDirectory = Directory((options['workingDirectory'] as String) ?? '.'); - final ProcessPool pool = ProcessPool( numWorkers: numWorkers, printReport: printReport ? ProcessPool.defaultPrintReport : null, @@ -159,5 +167,6 @@ if (printReport) { print('\nFinished job ${done.name}'); } + stdout.write(done.result!.stdout); } }
diff --git a/example/pubspec.yaml b/example/pubspec.yaml index fcb215a..9b96455 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml
@@ -3,14 +3,13 @@ # found in the LICENSE file. name: process_runner_example -version: 1.0.0 +version: 2.0.0-nullsafety description: A an example for process_runner. homepage: https://github.com/google/process_runner/example dependencies: - args: ^1.6.0 process_runner: path: .. environment: - sdk: '>=2.3.0 <3.0.0' + sdk: '>=2.10.0-4.0.dev <2.10.0' \ No newline at end of file
diff --git a/lib/src/process_pool.dart b/lib/src/process_pool.dart index 5873c9c..1a57bb3 100644 --- a/lib/src/process_pool.dart +++ b/lib/src/process_pool.dart
@@ -17,14 +17,13 @@ class WorkerJob { WorkerJob( this.command, { - String name, - this.workingDirectory, + String? name, + Directory? workingDirectory, this.printOutput = false, this.stdin, this.stdinRaw, this.failOk = true, - }) : assert(failOk != null), - assert(printOutput != null), + }) : workingDirectory = workingDirectory ?? Directory.current, name = name ?? command.join(' '); /// The name of the job. @@ -45,14 +44,14 @@ /// the process. /// /// If both [stdin] and [stdinRaw] are set, only [stdinRaw] will be used. - final Stream<String> stdin; + final Stream<String>? stdin; /// If set, the stream to read the raw stdin for this process from. /// /// It will be used directly, and not encoded (as [stdin] would be). /// /// If both [stdin] and [stdinRaw] are set, only [stdinRaw] will be used. - final Stream<List<int>> stdinRaw; + final Stream<List<int>>? stdinRaw; /// Whether or not this command should print it's stdout when it runs. final bool printOutput; @@ -71,7 +70,7 @@ /// /// If no process runner is supplied to the pool, then the decoder will be the /// same as the [ProcessPool.encoding] that was set on the pool. - ProcessRunnerResult result; + ProcessRunnerResult? result; @override String toString() { @@ -92,8 +91,8 @@ /// (presumably single-threaded) processes are finished. class ProcessPool { ProcessPool({ - int numWorkers, - ProcessRunner processRunner, + int? numWorkers, + ProcessRunner? processRunner, this.printReport = defaultPrintReport, this.encoding = const SystemEncoding(), }) : processRunner = processRunner ?? ProcessRunner(decoder: encoding), @@ -105,7 +104,7 @@ /// /// Defaults to [defaultProgressReport], which prints the progress report to /// stdout. - final ProcessPoolProgressReporter printReport; + final ProcessPoolProgressReporter? printReport; /// The decoder to use for decoding the stdout, stderr, and output of a /// process, and encoding the stdin from the job. @@ -182,7 +181,7 @@ job.command, workingDirectory: job.workingDirectory, printOutput: job.printOutput, - stdin: job.stdinRaw ?? (job.stdin != null ? encoding.encoder.bind(job.stdin) : null), + stdin: job.stdinRaw ?? encoding.encoder.bind(job.stdin ?? const Stream<String>.empty()), failOk: false, // Must be false so that we can catch the exception below. ); _completedJobs.add(job);
diff --git a/lib/src/process_runner.dart b/lib/src/process_runner.dart index 35aefb5..7ace328 100644 --- a/lib/src/process_runner.dart +++ b/lib/src/process_runner.dart
@@ -5,7 +5,7 @@ import 'dart:async' show Completer; import 'dart:convert' show Encoding; import 'dart:io' - show Process, ProcessResult, ProcessException, Directory, stderr, stdout, SystemEncoding + show Process, ProcessException, Directory, stderr, stdout, SystemEncoding hide Platform; import 'package:platform/platform.dart' show Platform, LocalPlatform; @@ -19,7 +19,7 @@ ProcessRunnerException(this.message, {this.result}); final String message; - final ProcessRunnerResult result; + final ProcessRunnerResult? result; int get exitCode => result?.exitCode ?? -1; @@ -80,19 +80,19 @@ /// [decoder]. String get stdout { _stdout ??= decoder.decode(stdoutRaw); - return _stdout; + return _stdout!; } - String _stdout; + String? _stdout; /// Returns a lazily-decoded version of the data in [stderrRaw], decoded using /// [decoder]. String get stderr { _stderr ??= decoder.decode(stderrRaw); - return _stderr; + return _stderr!; } - String _stderr; + String? _stderr; /// Returns a lazily-decoded version of the data in [outputRaw], decoded using /// [decoder]. @@ -100,10 +100,10 @@ /// Information appears in the order supplied by the process. String get output { _output ??= decoder.decode(outputRaw); - return _output; + return _output!; } - String _output; + String? _output; } /// A helper class for classes that want to run a process, optionally have the @@ -111,9 +111,9 @@ /// the stdout, stderr, and interleaved output properly without dropping any. class ProcessRunner { ProcessRunner({ - Directory defaultWorkingDirectory, + Directory? defaultWorkingDirectory, this.processManager = const LocalProcessManager(), - Map<String, String> environment, + Map<String, String>? environment, this.includeParentEnvironment = true, this.printOutputDefault = false, this.decoder = const SystemEncoding(), @@ -176,10 +176,10 @@ /// The `printOutput` argument defaults to the value of [printOutputDefault]. Future<ProcessRunnerResult> runProcess( List<String> commandLine, { - Directory workingDirectory, - bool printOutput, + Directory? workingDirectory, + bool? printOutput, bool failOk = false, - Stream<List<int>> stdin, + Stream<List<int>>? stdin, }) async { workingDirectory ??= defaultWorkingDirectory; printOutput ??= printOutputDefault; @@ -193,15 +193,15 @@ final Completer<void> stderrComplete = Completer<void>(); final Completer<void> stdinComplete = Completer<void>(); - Process process; + late Process process; Future<int> allComplete() async { if (stdin != null) { await stdinComplete.future; - await process?.stdin?.close(); + await process.stdin.close(); } await stderrComplete.future; await stdoutComplete.future; - return process?.exitCode ?? Future<int>.value(0); + return process.exitCode; } try { @@ -213,14 +213,14 @@ ); if (stdin != null) { stdin.listen((List<int> data) { - process?.stdin?.add(data); + process.stdin.add(data); }, onDone: () async => stdinComplete.complete()); } process.stdout.listen( (List<int> event) { stdoutOutput.addAll(event); combinedOutput.addAll(event); - if (printOutput) { + if (printOutput!) { stdout.add(event); } }, @@ -230,7 +230,7 @@ (List<int> event) { stderrOutput.addAll(event); combinedOutput.addAll(event); - if (printOutput) { + if (printOutput!) { stderr.add(event); } },
diff --git a/pubspec.yaml b/pubspec.yaml index 64c5514..452d416 100644 --- a/pubspec.yaml +++ b/pubspec.yaml
@@ -3,21 +3,20 @@ # found in the LICENSE file. name: process_runner -version: 3.0.0 +version: 4.0.0-nullsafety description: A process invocation astraction for Dart that manages a multiprocess queue. homepage: https://github.com/google/process_runner dependencies: - file: ^5.0.0 - meta: ^1.1.2 - path: ^1.5.1 - platform: ^2.2.0 - process: ^3.0.13 - async: ^2.4.2 + file: ^6.0.0-nullsafety.1 + meta: ^1.3.0-nullsafety.2 + path: ^1.8.0-nullsafety + platform: ^3.0.0-nullsafety.1 + process: ^4.0.0-nullsafety.1 + async: ^2.5.0-nullsafety dev_dependencies: - test: ^1.0.0 - mockito: ^4.1.1 + test: ^1.16.0-nullsafety.1 environment: - sdk: '>=2.3.0 <3.0.0' + sdk: '>=2.10.0-4.0.dev <2.10.0'
diff --git a/test/src/fake_process_manager.dart b/test/src/fake_process_manager.dart index e476cfb..6198907 100644 --- a/test/src/fake_process_manager.dart +++ b/test/src/fake_process_manager.dart
@@ -7,7 +7,6 @@ import 'dart:io'; import 'package:process/process.dart'; -import 'package:mockito/mockito.dart'; import 'package:test/test.dart' as test_package show TypeMatcher; import 'package:test/test.dart' hide TypeMatcher, isInstanceOf; @@ -20,10 +19,8 @@ /// each command line (with arguments). /// /// Call [verifyCalls] to verify that each desired call occurred. -class FakeProcessManager extends Mock implements ProcessManager { - FakeProcessManager(this.stdinResults) { - _setupMock(); - } +class FakeProcessManager implements ProcessManager { + FakeProcessManager(this.stdinResults); /// The callback that will be called each time stdin input is supplied to /// a call. @@ -42,7 +39,7 @@ } /// The list of invocations that occurred, in the order they occurred. - List<Invocation> invocations = <Invocation>[]; + List<List<String>> invocations = <List<String>>[]; /// Verify that the given command lines were called, in the given order, and /// that the parameters were in the same order. @@ -50,15 +47,15 @@ int index = 0; expect(invocations.length, equals(calls.length)); for (final List<String> call in calls) { - expect(call, orderedEquals(invocations[index].positionalArguments[0] as Iterable<dynamic>)); + expect(call, orderedEquals(invocations[index])); index++; } } ProcessResult _popResult(List<String> command) { expect(fakeResults, isNotEmpty); - List<ProcessResult> foundResult; - List<String> foundCommand; + late List<ProcessResult> foundResult; + late List<String> foundCommand; for (final List<String> fakeCommand in fakeResults.keys) { if (fakeCommand.length != command.length) { continue; @@ -71,69 +68,73 @@ } } if (listsIdentical) { - foundResult = fakeResults[fakeCommand]; + foundResult = fakeResults[fakeCommand]!; foundCommand = fakeCommand; break; } } expect(foundResult, isNotNull, reason: '$command not found in expected results.'); expect(foundResult, isNotEmpty); - return fakeResults[foundCommand]?.removeAt(0); + return fakeResults[foundCommand]?.removeAt(0) ?? ProcessResult(0, 0, '', ''); } FakeProcess _popProcess(List<String> command) => FakeProcess(_popResult(command), stdinResults); - Future<Process> _nextProcess(Invocation invocation) async { + Future<Process> _nextProcess(List<String> invocation) async { invocations.add(invocation); - return Future<Process>.value(_popProcess(invocation.positionalArguments[0] as List<String>)); + return Future<Process>.value(_popProcess(invocation)); } - ProcessResult _nextResultSync(Invocation invocation) { + ProcessResult _nextResultSync(List<String> invocation) { invocations.add(invocation); - return _popResult(invocation.positionalArguments[0] as List<String>); + return _popResult(invocation); } - Future<ProcessResult> _nextResult(Invocation invocation) async { + Future<ProcessResult> _nextResult(List<String> invocation) async { invocations.add(invocation); - return Future<ProcessResult>.value( - _popResult(invocation.positionalArguments[0] as List<String>)); + return Future<ProcessResult>.value(_popResult(invocation)); } - void _setupMock() { - // Not all possible types of invocations are covered here, just the ones - // expected to be called by ProcessManager. - // TODO(gspencergoog): make this more general so that any call will be captured. - when(start( - any, - // ignore: dead_code - environment: anyNamed('environment'), - // ignore: dead_code - workingDirectory: anyNamed('workingDirectory'), - // ignore: dead_code - runInShell: anyNamed('runInShell'), - )).thenAnswer(_nextProcess); + @override + bool canRun(dynamic executable, {String? workingDirectory}) { + return true; + } - when(start(any)).thenAnswer(_nextProcess); + @override + bool killPid(int pid, [ProcessSignal signal = ProcessSignal.sigterm]) { + return true; + } - when(run( - any, - environment: anyNamed('environment'), - workingDirectory: anyNamed('workingDirectory'), - )).thenAnswer(_nextResult); + @override + Future<ProcessResult> run(List<dynamic> command, + {String? workingDirectory, + Map<String, String>? environment, + bool includeParentEnvironment = true, + bool runInShell = false, + Encoding stdoutEncoding = systemEncoding, + Encoding stderrEncoding = systemEncoding}) { + return _nextResult(command as List<String>); + } - when(run(any)).thenAnswer(_nextResult); + @override + ProcessResult runSync(List<dynamic> command, + {String? workingDirectory, + Map<String, String>? environment, + bool includeParentEnvironment = true, + bool runInShell = false, + Encoding stdoutEncoding = systemEncoding, + Encoding stderrEncoding = systemEncoding}) { + return _nextResultSync(command as List<String>); + } - when(runSync( - any, - environment: anyNamed('environment'), - workingDirectory: anyNamed('workingDirectory'), - )).thenAnswer(_nextResultSync); - - when(runSync(any)).thenAnswer(_nextResultSync); - - when(killPid(any, any)).thenReturn(true); - - when(canRun(any, workingDirectory: anyNamed('workingDirectory'))).thenReturn(true); + @override + Future<Process> start(List<dynamic> command, + {String? workingDirectory, + Map<String, String>? environment, + bool includeParentEnvironment = true, + bool runInShell = false, + ProcessStartMode mode = ProcessStartMode.normal}) { + return _nextProcess(command as List<String>); } } @@ -141,25 +142,18 @@ /// A fake process that can be used to interact with a process "started" by the /// FakeProcessManager. -class FakeProcess extends Mock implements Process { +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), desiredExitCode = result.exitCode, - stdinSink = IOSink(StringStreamConsumer(stdinResults)) { - _setupMock(); - } + stdinSink = IOSink(StringStreamConsumer(stdinResults)); final IOSink stdinSink; final Stream<List<int>> stdoutStream; final Stream<List<int>> stderrStream; final int desiredExitCode; - void _setupMock() { - // ignore: dead_code - when(kill(any)).thenReturn(true); - } - @override Future<int> get exitCode => Future<int>.value(desiredExitCode); @@ -174,6 +168,11 @@ @override Stream<List<int>> get stdout => stdoutStream; + + @override + bool kill([ProcessSignal signal = ProcessSignal.sigterm]) { + return true; + } } /// Callback used to receive stdin input when it occurs.
diff --git a/test/src/process_pool_test.dart b/test/src/process_pool_test.dart index dfd8fd0..fa7c310 100644 --- a/test/src/process_pool_test.dart +++ b/test/src/process_pool_test.dart
@@ -60,10 +60,10 @@ WorkerJob(<String>['command', 'arg1', 'arg2'], name: 'job 1'), ]; final List<WorkerJob> 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')); + 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')); }); }); }