[null-safety] migrate package:process to _almost_ null safety (#47)
Migrate to null-safety, except for the path/file/platform dependencies which are not yet migrated. Removes record/replay functionality.
diff --git a/CHANGELOG.md b/CHANGELOG.md
index c4e881b..59dbf53 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,7 +1,9 @@
#### 4.0.0-dev
-* Remove implicit casts in preparation for null-safety
-* Remove dependency on `package:intl`
+* Migrate to null-safety.
+* Remove record/replay functionality.
+* Remove implicit casts in preparation for null-safety.
+* Remove dependency on `package:intl` and `package:meta`.
#### 3.0.13
diff --git a/analysis_options.yaml b/analysis_options.yaml
index 2b88268..2c72e4b 100644
--- a/analysis_options.yaml
+++ b/analysis_options.yaml
@@ -1,4 +1,6 @@
analyzer:
+ enable-experiment:
+ - non-nullable
strong-mode:
implicit-dynamic: false
implicit-casts: false
diff --git a/dev/travis.sh b/dev/travis.sh
index 866b361..192b2e8 100755
--- a/dev/travis.sh
+++ b/dev/travis.sh
@@ -27,4 +27,4 @@
set -e
# Run the tests.
-pub run test
+pub run --enable-experiment=non-nullable test
diff --git a/lib/process.dart b/lib/process.dart
index dfeb1d0..a407c48 100644
--- a/lib/process.dart
+++ b/lib/process.dart
@@ -2,6 +2,7 @@
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
+// @dart=2.10
export 'src/interface/local_process_manager.dart';
export 'src/interface/process_manager.dart';
export 'src/interface/process_wrapper.dart';
diff --git a/lib/record_replay.dart b/lib/record_replay.dart
deleted file mode 100644
index e9211bb..0000000
--- a/lib/record_replay.dart
+++ /dev/null
@@ -1,7 +0,0 @@
-// Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-export 'src/record_replay/command_element.dart';
-export 'src/record_replay/recording_process_manager.dart';
-export 'src/record_replay/replay_process_manager.dart';
diff --git a/lib/src/interface/common.dart b/lib/src/interface/common.dart
index 89c8036..f2db1c3 100644
--- a/lib/src/interface/common.dart
+++ b/lib/src/interface/common.dart
@@ -2,6 +2,7 @@
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
+// @dart=2.10
import 'package:file/file.dart';
import 'package:file/local.dart';
import 'package:path/path.dart' show Context;
@@ -48,9 +49,9 @@
/// could not be found.
///
/// If [platform] is not specified, it will default to the current platform.
-String getExecutablePath(
+String? getExecutablePath(
String command,
- String workingDirectory, {
+ String? workingDirectory, {
Platform platform = const LocalPlatform(),
FileSystem fs = const LocalFileSystem(),
}) {
@@ -72,7 +73,7 @@
List<String> extensions = <String>[];
if (platform.isWindows && context.extension(command).isEmpty) {
- extensions = platform.environment['PATHEXT'].split(pathSeparator);
+ extensions = platform.environment['PATHEXT']!.split(pathSeparator);
}
List<String> candidates = <String>[];
@@ -80,11 +81,16 @@
candidates = _getCandidatePaths(
command, <String>[workingDirectory], extensions, context);
} else {
- List<String> searchPath = platform.environment['PATH'].split(pathSeparator);
+ List<String> searchPath =
+ platform.environment['PATH']!.split(pathSeparator);
candidates = _getCandidatePaths(command, searchPath, extensions, context);
}
- return candidates.firstWhere((String path) => fs.file(path).existsSync(),
- orElse: () => null);
+ for (String path in candidates) {
+ if (fs.file(path).existsSync()) {
+ return path;
+ }
+ }
+ return null;
}
/// Returns all possible combinations of `$searchPath\$command.$ext` for
diff --git a/lib/src/interface/local_process_manager.dart b/lib/src/interface/local_process_manager.dart
index 268bcf7..648540a 100644
--- a/lib/src/interface/local_process_manager.dart
+++ b/lib/src/interface/local_process_manager.dart
@@ -2,6 +2,7 @@
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
+// @dart=2.10
import 'dart:async';
import 'dart:convert';
import 'dart:io'
@@ -31,8 +32,8 @@
@override
Future<Process> start(
covariant List<Object> command, {
- String workingDirectory,
- Map<String, String> environment,
+ String? workingDirectory,
+ Map<String, String>? environment,
bool includeParentEnvironment = true,
bool runInShell = false,
ProcessStartMode mode = ProcessStartMode.normal,
@@ -55,8 +56,8 @@
@override
Future<ProcessResult> run(
covariant List<Object> command, {
- String workingDirectory,
- Map<String, String> environment,
+ String? workingDirectory,
+ Map<String, String>? environment,
bool includeParentEnvironment = true,
bool runInShell = false,
Encoding stdoutEncoding = systemEncoding,
@@ -81,8 +82,8 @@
@override
ProcessResult runSync(
covariant List<Object> command, {
- String workingDirectory,
- Map<String, String> environment,
+ String? workingDirectory,
+ Map<String, String>? environment,
bool includeParentEnvironment = true,
bool runInShell = false,
Encoding stdoutEncoding = systemEncoding,
@@ -105,7 +106,7 @@
}
@override
- bool canRun(covariant String executable, {String workingDirectory}) =>
+ bool canRun(covariant String executable, {String? workingDirectory}) =>
getExecutablePath(executable, workingDirectory) != null;
@override
@@ -115,12 +116,12 @@
}
String _getExecutable(
- List<dynamic> command, String workingDirectory, bool runInShell) {
+ List<dynamic> command, String? workingDirectory, bool runInShell) {
String commandName = command.first.toString();
if (runInShell) {
return commandName;
}
- String exe = getExecutablePath(commandName, workingDirectory);
+ String? exe = getExecutablePath(commandName, workingDirectory);
if (exe == null) {
throw ArgumentError('Cannot find executable for $commandName.');
}
diff --git a/lib/src/interface/process_manager.dart b/lib/src/interface/process_manager.dart
index 67fd079..8c0e5b7 100644
--- a/lib/src/interface/process_manager.dart
+++ b/lib/src/interface/process_manager.dart
@@ -2,6 +2,7 @@
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
+// @dart=2.10
import 'dart:async';
import 'dart:convert';
import 'dart:io'
diff --git a/lib/src/interface/process_wrapper.dart b/lib/src/interface/process_wrapper.dart
index e2ebee4..9c275f5 100644
--- a/lib/src/interface/process_wrapper.dart
+++ b/lib/src/interface/process_wrapper.dart
@@ -2,6 +2,7 @@
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
+// @dart=2.10
import 'dart:async';
import 'dart:io' as io;
@@ -53,7 +54,7 @@
///
/// The future returned here will complete with the exit code of the process.
Future<int> get done async {
- int result;
+ late int result;
await Future.wait<void>(<Future<void>>[
_stdoutDone.future,
_stderrDone.future,
@@ -61,7 +62,6 @@
result = value;
}),
]);
- assert(result != null);
return result;
}
diff --git a/lib/src/record_replay/can_run_manifest_entry.dart b/lib/src/record_replay/can_run_manifest_entry.dart
deleted file mode 100644
index ec9a64a..0000000
--- a/lib/src/record_replay/can_run_manifest_entry.dart
+++ /dev/null
@@ -1,34 +0,0 @@
-import 'manifest_entry.dart';
-
-/// An entry in the process invocation manifest for `canRun`.
-class CanRunManifestEntry extends ManifestEntry {
- /// Creates a new manifest entry with the given properties.
- CanRunManifestEntry({this.executable, this.result});
-
- /// Creates a new manifest entry populated with the specified JSON [data].
- ///
- /// If any required fields are missing from the JSON data, this will throw
- /// a [FormatException].
- factory CanRunManifestEntry.fromJson(Map<String, dynamic> data) {
- checkRequiredField(data, 'executable');
- checkRequiredField(data, 'result');
- CanRunManifestEntry entry = CanRunManifestEntry(
- executable: data['executable'] as String,
- result: data['result'] as bool,
- );
- return entry;
- }
-
- @override
- final String type = 'can_run';
-
- /// The name of the executable for which the run-ability is checked.
- final String executable;
-
- /// The result of the check.
- final bool result;
-
- @override
- Map<String, dynamic> toJson() =>
- JsonBuilder().add('executable', executable).add('result', result).entry;
-}
diff --git a/lib/src/record_replay/command_element.dart b/lib/src/record_replay/command_element.dart
deleted file mode 100644
index c38a948..0000000
--- a/lib/src/record_replay/command_element.dart
+++ /dev/null
@@ -1,60 +0,0 @@
-// Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-import 'package:process/process.dart';
-
-/// Callback used to [sanitize](CommandElement.sanitized) a [CommandElement]
-/// for the purpose of recording.
-typedef CommandSanitizer = String Function(String rawValue);
-
-/// A command element capable of holding both a raw and sanitized value.
-///
-/// Instances of this type can be used in the `command` list of the
-/// [ProcessManager.start], [ProcessManager.run], and [ProcessManager.runSync]
-/// methods.
-///
-/// Each command element has:
-/// - A raw value, which is the value that should passed to the underlying
-/// operating system to invoke the process.
-/// - A sanitized value, which is the value that's serialized when used with
-/// [RecordingProcessManager] and looked up in the replay log when used
-/// with [ReplayProcessManager]. Sanitized values typically will remove
-/// user-specific segments (such as the user's home directory) or random
-/// segments (such as temporary file names). Sanitizing values allows you
-/// to guarantee determinism in your process invocation lookups, thus
-/// removing flakiness in tests.
-///
-/// This class implements [toString] to return the element's raw value, meaning
-/// instances of this class can be passed directly to [LocalProcessManager]
-/// and will work as intended.
-class CommandElement {
- /// Creates a new command element with the specified [raw] value.
- ///
- /// If a [sanitizer] is specified, it will be used to generate this command
- /// element's [sanitized] value. If it is unspecified, the raw value will be
- /// used as the sanitized value.
- CommandElement(this.raw, {CommandSanitizer sanitizer})
- : _sanitizer = sanitizer;
-
- final CommandSanitizer _sanitizer;
-
- /// This command element's raw, unsanitized, value.
- ///
- /// This value is liable to contain non-deterministic segments, such as
- /// OS-generated temporary file names. It is suitable for passing to the
- /// operating system to invoke a process, but it is not suitable for
- /// record/replay.
- final String raw;
-
- /// This command element's sanitized value.
- ///
- /// This value has been stripped of any non-deterministic segments, such as
- /// OS-generated temporary file names or user-specific values. It is suitable
- /// for record/replay, but it is not suitable for passing to the operating
- /// system to invoke a process.
- String get sanitized => _sanitizer == null ? raw : _sanitizer(raw);
-
- @override
- String toString() => raw;
-}
diff --git a/lib/src/record_replay/common.dart b/lib/src/record_replay/common.dart
deleted file mode 100644
index fae84a4..0000000
--- a/lib/src/record_replay/common.dart
+++ /dev/null
@@ -1,19 +0,0 @@
-// Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-import 'command_element.dart';
-
-/// Sanitizes the specified [command] by running any non-deterministic
-/// segments through a [sanitizer](CommandSanitizer) if possible.
-List<String> sanitize(List<dynamic> command) {
- return command
- .map((dynamic element) {
- if (element is CommandElement) {
- return element.sanitized;
- }
- return element.toString();
- })
- .toList()
- .cast<String>();
-}
diff --git a/lib/src/record_replay/constants.dart b/lib/src/record_replay/constants.dart
deleted file mode 100644
index 732d607..0000000
--- a/lib/src/record_replay/constants.dart
+++ /dev/null
@@ -1,6 +0,0 @@
-// Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-/// The name of the invocation manifest file.
-const String kManifestName = 'MANIFEST.txt';
diff --git a/lib/src/record_replay/manifest.dart b/lib/src/record_replay/manifest.dart
deleted file mode 100644
index 9aae5fd..0000000
--- a/lib/src/record_replay/manifest.dart
+++ /dev/null
@@ -1,128 +0,0 @@
-// Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file
-// for details. 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:convert';
-import 'dart:io' show ProcessStartMode;
-
-import 'can_run_manifest_entry.dart';
-import 'manifest_entry.dart';
-import 'run_manifest_entry.dart';
-
-/// Tests if two lists contain pairwise equal elements.
-bool _areListsEqual<T>(List<T> list1, List<T> list2) {
- int i = 0;
- return list1 != null &&
- list2 != null &&
- list1.length == list2.length &&
- list1.every((dynamic element) => element == list2[i++]);
-}
-
-/// Tells whether [testValue] is non-null and equal to [entryValue]. If
-/// [isEqual] is specified, it will be used to determine said equality.
-bool _isHit<T>(
- T entryValue,
- T testValue, [
- bool isEqual(T value1, T value2),
-]) {
- if (testValue == null) {
- return true;
- } else if (isEqual != null) {
- return isEqual(entryValue, testValue);
- }
- return entryValue == testValue;
-}
-
-/// The process invocation manifest, holding metadata about every recorded
-/// process invocation.
-class Manifest {
- /// Creates a new manifest.
- Manifest();
-
- /// Creates a new manifest populated with the specified [json] data.
- ///
- /// If [json] does not represent a valid JSON string (matching the format of
- /// [toJson]), a [FormatException] will be thrown.
- factory Manifest.fromJson(String json) {
- List<Map<String, dynamic>> decoded =
- (jsonDecode(json) as List<dynamic>).cast<Map<String, dynamic>>();
- Manifest manifest = Manifest();
- decoded.forEach((Map<String, dynamic> entry) {
- switch (entry['type'] as String) {
- case 'run':
- manifest._entries.add(
- RunManifestEntry.fromJson(entry['body'] as Map<String, dynamic>));
- break;
- case 'can_run':
- manifest._entries.add(CanRunManifestEntry.fromJson(
- entry['body'] as Map<String, dynamic>));
- break;
- default:
- throw UnsupportedError('Manifest type ${entry['type']} is unkown.');
- }
- });
- return manifest;
- }
-
- final List<ManifestEntry> _entries = <ManifestEntry>[];
-
- /// Adds the specified [entry] to this manifest.
- void add(ManifestEntry entry) => _entries.add(entry);
-
- /// The number of entries currently in the manifest.
- int get length => _entries.length;
-
- /// Gets the entry whose [RunManifestEntry.pid] matches the specified [pid].
- ManifestEntry getRunEntry(int pid) {
- return _entries.firstWhere(
- (ManifestEntry entry) => entry is RunManifestEntry && entry.pid == pid);
- }
-
- /// Finds the first manifest entry that has not been invoked and whose
- /// metadata matches the specified criteria. If no arguments are specified,
- /// this will simply return the first entry that has not yet been invoked.
- ManifestEntry findPendingRunEntry({
- List<String> command,
- ProcessStartMode mode,
- Encoding stdoutEncoding,
- Encoding stderrEncoding,
- }) {
- return _entries.firstWhere(
- (ManifestEntry entry) {
- return entry is RunManifestEntry &&
- !entry.invoked &&
- _isHit(entry.command, command, _areListsEqual) &&
- _isHit(entry.mode, mode) &&
- _isHit(entry.stdoutEncoding, stdoutEncoding) &&
- _isHit(entry.stderrEncoding, stderrEncoding);
- },
- orElse: () => null,
- );
- }
-
- /// Finds the first manifest entry that has not been invoked and whose
- /// metadata matches the specified criteria. If no arguments are specified,
- /// this will simply return the first entry that has not yet been invoked.
- ManifestEntry findPendingCanRunEntry({
- String executable,
- }) {
- return _entries.firstWhere(
- (ManifestEntry entry) {
- return entry is CanRunManifestEntry &&
- !entry.invoked &&
- _isHit(entry.executable, executable);
- },
- orElse: () => null,
- );
- }
-
- /// Returns a JSON-encoded representation of this manifest.
- String toJson() {
- List<Map<String, dynamic>> list = <Map<String, dynamic>>[];
- _entries.forEach((ManifestEntry entry) => list.add(JsonBuilder()
- .add('type', entry.type)
- .add('body', entry.toJson())
- .entry));
- return const JsonEncoder.withIndent(' ').convert(list);
- }
-}
diff --git a/lib/src/record_replay/manifest_entry.dart b/lib/src/record_replay/manifest_entry.dart
deleted file mode 100644
index 78d0314..0000000
--- a/lib/src/record_replay/manifest_entry.dart
+++ /dev/null
@@ -1,46 +0,0 @@
-import 'manifest.dart';
-import 'replay_process_manager.dart';
-
-/// An entry in the process invocation manifest.
-///
-/// Each entry in the [Manifest] represents a single recorded process
-/// invocation.
-abstract class ManifestEntry {
- /// Whether this entry has been "invoked" by [ReplayProcessManager].
- bool get invoked => _invoked;
- bool _invoked = false;
-
- /// Marks this entry as having been "invoked" by [ReplayProcessManager].
- void setInvoked() {
- _invoked = true;
- }
-
- /// The type of this [ManifestEntry].
- String get type;
-
- /// Returns a JSON-encodable representation of this manifest entry.
- Map<String, dynamic> toJson();
-}
-
-/// A lightweight class that provides a means of building a manifest entry
-/// JSON object.
-class JsonBuilder {
- /// The JSON-encodable object.
- final Map<String, dynamic> entry = <String, dynamic>{};
-
- /// Adds the specified key/value pair to the manifest entry iff the value
- /// is non-null. If [jsonValue] is specified, its value will be used instead
- /// of the raw value.
- JsonBuilder add(String name, dynamic value, [dynamic jsonValue()]) {
- if (value != null) {
- entry[name] = jsonValue == null ? value : jsonValue();
- }
- return this;
- }
-}
-
-/// Throws a [FormatException] if [data] does not contain [key].
-void checkRequiredField(Map<String, dynamic> data, String key) {
- if (!data.containsKey(key))
- throw FormatException('Required field missing: $key');
-}
diff --git a/lib/src/record_replay/recording_process_manager.dart b/lib/src/record_replay/recording_process_manager.dart
deleted file mode 100644
index a2e1c41..0000000
--- a/lib/src/record_replay/recording_process_manager.dart
+++ /dev/null
@@ -1,416 +0,0 @@
-// Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file
-// for details. 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:convert';
-import 'dart:io' as io
- show
- IOSink,
- Process,
- ProcessResult,
- ProcessSignal,
- ProcessStartMode,
- systemEncoding;
-
-import 'package:file/file.dart';
-import 'package:path/path.dart' as path;
-
-import '../interface/process_manager.dart';
-import 'can_run_manifest_entry.dart';
-import 'common.dart';
-import 'constants.dart';
-import 'manifest.dart';
-import 'replay_process_manager.dart';
-import 'run_manifest_entry.dart';
-
-/// Records process invocation activity and serializes it to disk.
-///
-/// A `RecordingProcessManager` decorates another `ProcessManager` instance by
-/// recording all process invocation activity (including the stdout and stderr
-/// of the associated processes) before delegating to the underlying manager.
-///
-/// This class enables "record / replay" tests, where you record the process
-/// invocation activity during a real program run, serialize the activity to
-/// disk, then fake all invocation activity during tests by replaying the
-/// serialized recording.
-///
-/// See also:
-///
-/// * [ReplayProcessManager].
-class RecordingProcessManager implements ProcessManager {
- /// Constructs a new `RecordingProcessManager`.
- ///
- /// This manager will record all process invocations and serialize them to
- /// the specified [destination]. The underlying `ProcessManager` functionality
- /// will be delegated to [delegate].
- ///
- /// If [destination] does not already exist, or if it exists and is not empty,
- /// a [StateError] will be thrown.
- ///
- /// [destination] should be treated as opaque. Its contents are intended to
- /// be consumed only by [ReplayProcessManager] and are subject to change
- /// between versions of `package:process`.
- RecordingProcessManager(this.delegate, this.destination) {
- if (!destination.existsSync() || destination.listSync().isNotEmpty) {
- throw StateError('Cannot record to ${destination.path}');
- }
- }
-
- static const List<String> _kSkippableExecutables = <String>[
- 'env',
- 'xcrun',
- ];
-
- /// The manager to which this manager delegates.
- final ProcessManager delegate;
-
- /// The directory to which serialized invocation metadata will be written.
- final Directory destination;
-
- /// List of invocation metadata. Will be serialized as [kManifestName].
- final Manifest _manifest = Manifest();
-
- /// Maps process IDs of running processes to exit code futures.
- final Map<int, Future<int>> _runningProcesses = <int, Future<int>>{};
-
- /// The file system in which this manager will create recording files.
- FileSystem get fs => destination.fileSystem;
-
- @override
- Future<io.Process> start(
- List<dynamic> command, {
- String workingDirectory,
- Map<String, String> environment,
- bool includeParentEnvironment = true,
- bool runInShell = false,
- io.ProcessStartMode mode = io.ProcessStartMode.normal,
- }) async {
- io.Process process = await delegate.start(
- command,
- workingDirectory: workingDirectory,
- environment: environment,
- includeParentEnvironment: includeParentEnvironment,
- runInShell: runInShell,
- mode: mode,
- );
-
- List<String> sanitizedCommand = sanitize(command);
- String basename = _getBasename(process.pid, sanitizedCommand);
- RunManifestEntry entry = RunManifestEntry(
- pid: process.pid,
- basename: basename,
- command: sanitizedCommand,
- workingDirectory: workingDirectory,
- environment: environment,
- includeParentEnvironment: includeParentEnvironment,
- runInShell: runInShell,
- mode: mode,
- );
- _manifest.add(entry);
-
- _RecordingProcess result = _RecordingProcess(
- manager: this,
- basename: basename,
- delegate: process,
- );
- await result.startRecording();
- _runningProcesses[process.pid] = result.exitCode.then((int exitCode) {
- _runningProcesses.remove(process.pid);
- entry.exitCode = exitCode;
- return exitCode;
- });
-
- return result;
- }
-
- @override
- Future<io.ProcessResult> run(
- List<dynamic> command, {
- String workingDirectory,
- Map<String, String> environment,
- bool includeParentEnvironment = true,
- bool runInShell = false,
- Encoding stdoutEncoding = io.systemEncoding,
- Encoding stderrEncoding = io.systemEncoding,
- }) async {
- io.ProcessResult result = await delegate.run(
- command,
- workingDirectory: workingDirectory,
- environment: environment,
- includeParentEnvironment: includeParentEnvironment,
- runInShell: runInShell,
- stdoutEncoding: stdoutEncoding,
- stderrEncoding: stderrEncoding,
- );
-
- List<String> sanitizedCommand = sanitize(command);
- String basename = _getBasename(result.pid, sanitizedCommand);
- _manifest.add(RunManifestEntry(
- pid: result.pid,
- basename: basename,
- command: sanitizedCommand,
- workingDirectory: workingDirectory,
- environment: environment,
- includeParentEnvironment: includeParentEnvironment,
- runInShell: runInShell,
- stdoutEncoding: stdoutEncoding,
- stderrEncoding: stderrEncoding,
- exitCode: result.exitCode,
- ));
-
- await _recordData(result.stdout, stdoutEncoding, '$basename.stdout');
- await _recordData(result.stderr, stderrEncoding, '$basename.stderr');
-
- return result;
- }
-
- Future<Null> _recordData(
- dynamic data, Encoding encoding, String basename) async {
- File file = fs.file('${destination.path}/$basename');
- IOSink recording = file.openWrite(encoding: encoding);
- try {
- if (encoding == null)
- recording.add(data as List<int>);
- else
- recording.write(data);
- await recording.flush();
- } finally {
- await recording.close();
- }
- }
-
- @override
- io.ProcessResult runSync(
- List<dynamic> command, {
- String workingDirectory,
- Map<String, String> environment,
- bool includeParentEnvironment = true,
- bool runInShell = false,
- Encoding stdoutEncoding = io.systemEncoding,
- Encoding stderrEncoding = io.systemEncoding,
- }) {
- io.ProcessResult result = delegate.runSync(
- command,
- workingDirectory: workingDirectory,
- environment: environment,
- includeParentEnvironment: includeParentEnvironment,
- runInShell: runInShell,
- stdoutEncoding: stdoutEncoding,
- stderrEncoding: stderrEncoding,
- );
-
- List<String> sanitizedCommand = sanitize(command);
- String basename = _getBasename(result.pid, sanitizedCommand);
- _manifest.add(RunManifestEntry(
- pid: result.pid,
- basename: basename,
- command: sanitizedCommand,
- workingDirectory: workingDirectory,
- environment: environment,
- includeParentEnvironment: includeParentEnvironment,
- runInShell: runInShell,
- stdoutEncoding: stdoutEncoding,
- stderrEncoding: stderrEncoding,
- exitCode: result.exitCode,
- ));
-
- _recordDataSync(result.stdout, stdoutEncoding, '$basename.stdout');
- _recordDataSync(result.stderr, stderrEncoding, '$basename.stderr');
-
- return result;
- }
-
- void _recordDataSync(dynamic data, Encoding encoding, String basename) {
- File file = fs.file('${destination.path}/$basename');
- if (encoding == null)
- file.writeAsBytesSync(data as List<int>, flush: true);
- else
- file.writeAsStringSync(data as String, encoding: encoding, flush: true);
- }
-
- @override
- bool canRun(dynamic executable, {String workingDirectory}) {
- bool result =
- delegate.canRun(executable, workingDirectory: workingDirectory);
- _manifest.add(
- CanRunManifestEntry(executable: executable.toString(), result: result));
- return result;
- }
-
- @override
- bool killPid(int pid, [io.ProcessSignal signal = io.ProcessSignal.sigterm]) {
- return delegate.killPid(pid, signal);
- }
-
- /// Returns a human-readable identifier for the specified executable.
- String _getBasename(int pid, List<String> sanitizedCommand) {
- String index = _manifest.length.toString();
- String identifier = 'executable';
- for (String element in sanitizedCommand) {
- if (element.startsWith('-')) {
- // Ignore flags.
- continue;
- }
- identifier = path.basename(element);
- if (!_kSkippableExecutables.contains(identifier)) {
- break;
- }
- }
- return '$index.$identifier.$pid';
- }
-
- /// Flushes pending data to [destination].
- ///
- /// This manager may buffer invocation metadata in memory as it sees fit.
- /// Calling `flush` will force the manager to write any pending data to disk.
- /// This returns a future that completes when all pending data has been
- /// written to disk.
- ///
- /// Failure to call this method before the current process exits will likely
- /// cause invocation data to be lost.
- ///
- /// If [finishRunningProcesses] is true, the returned future will only
- /// complete after all running processes have exited, thus guaranteeing that
- /// no new invocation data will be generated until new processes are invoked.
- /// Any processes that don't exit on their own within the specified [timeout]
- /// will be marked as daemon processes in the serialized metadata and will be
- /// signalled with `SIGTERM`. If such processes *still* don't exit within the
- /// specified [timeout] after being signalled, they'll be marked as not
- /// responding in the serialized metadata.
- ///
- /// If [finishRunningProcesses] is false (the default), then [timeout] is
- /// ignored.
- Future<Null> flush({
- bool finishRunningProcesses = false,
- Duration timeout = const Duration(milliseconds: 20),
- }) async {
- if (finishRunningProcesses) {
- await _waitForRunningProcessesToExit(timeout);
- }
- await _writeManifestToDisk();
- }
-
- /// Waits for all running processes to exit, and records their exit codes in
- /// the process manifest. Any process that doesn't exit within [timeout]
- /// will be marked as a [RunManifestEntry.daemon] and be signalled with
- /// `SIGTERM`. If such processes *still* don't exit within [timeout] after
- /// being signalled, they'll be marked as [RunManifestEntry.notResponding].
- Future<Null> _waitForRunningProcessesToExit(Duration timeout) async {
- await _waitForRunningProcessesToExitWithTimeout(
- timeout: timeout,
- onTimeout: (RunManifestEntry entry) {
- entry.daemon = true;
- delegate.killPid(entry.pid);
- });
- // Now that we explicitly signalled the processes that timed out asking
- // them to shutdown, wait one more time for those processes to exit.
- await _waitForRunningProcessesToExitWithTimeout(
- timeout: timeout,
- onTimeout: (RunManifestEntry entry) {
- entry.notResponding = true;
- });
- }
-
- Future<Null> _waitForRunningProcessesToExitWithTimeout({
- Duration timeout,
- void onTimeout(RunManifestEntry entry),
- }) async {
- void callOnTimeout(int pid) =>
- onTimeout(_manifest.getRunEntry(pid) as RunManifestEntry);
- await Future.wait(List<Future<int>>.from(_runningProcesses.values))
- .timeout(timeout, onTimeout: () {
- _runningProcesses.keys.forEach(callOnTimeout);
- return null;
- });
- }
-
- /// Writes our process invocation manifest to disk in the destination folder.
- Future<Null> _writeManifestToDisk() async {
- File manifestFile = fs.file('${destination.path}/$kManifestName');
- await manifestFile.writeAsString(_manifest.toJson(), flush: true);
- }
-}
-
-/// A [io.Process] implementation that records `stdout` and `stderr` stream events
-/// to disk before forwarding them on to the underlying stream listener.
-class _RecordingProcess implements io.Process {
- _RecordingProcess({this.manager, this.basename, this.delegate});
-
- final io.Process delegate;
- final String basename;
- final RecordingProcessManager manager;
-
- // ignore: close_sinks
- final StreamController<List<int>> _stdout = StreamController<List<int>>();
- // ignore: close_sinks
- final StreamController<List<int>> _stderr = StreamController<List<int>>();
-
- bool _started = false;
-
- Future<Null> startRecording() async {
- assert(!_started);
- _started = true;
- await Future.wait(<Future<Null>>[
- _recordStream(delegate.stdout, _stdout, 'stdout'),
- _recordStream(delegate.stderr, _stderr, 'stderr'),
- ]);
- }
-
- Future<Null> _recordStream(
- Stream<List<int>> stream,
- StreamController<List<int>> controller,
- String suffix,
- ) async {
- String path = '${manager.destination.path}/$basename.$suffix';
- File file = await manager.fs.file(path).create();
- RandomAccessFile recording = await file.open(mode: FileMode.write);
- stream.listen(
- (List<int> data) {
- // Write synchronously to guarantee that the order of data
- // within our recording is preserved across stream notifications.
- recording.writeFromSync(data);
- // Flush immediately so that if the program crashes, forensic
- // data from the recording won't be lost.
- recording.flushSync();
- controller.add(data);
- },
- onError: (dynamic error, StackTrace stackTrace) {
- recording.closeSync();
- controller.addError(error, stackTrace);
- },
- onDone: () {
- recording.closeSync();
- controller.close();
- },
- );
- }
-
- @override
- Future<int> get exitCode => delegate.exitCode;
-
- @override
- Stream<List<int>> get stdout {
- assert(_started);
- return _stdout.stream;
- }
-
- @override
- Stream<List<int>> get stderr {
- assert(_started);
- return _stderr.stream;
- }
-
- @override
- io.IOSink get stdin {
- // We don't currently support recording `stdin`.
- return delegate.stdin;
- }
-
- @override
- int get pid => delegate.pid;
-
- @override
- bool kill([io.ProcessSignal signal = io.ProcessSignal.sigterm]) =>
- delegate.kill(signal);
-}
diff --git a/lib/src/record_replay/replay_process_manager.dart b/lib/src/record_replay/replay_process_manager.dart
deleted file mode 100644
index 53fdfeb..0000000
--- a/lib/src/record_replay/replay_process_manager.dart
+++ /dev/null
@@ -1,323 +0,0 @@
-// Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file
-// for details. 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:convert';
-import 'dart:io' as io
- show
- IOSink,
- Process,
- ProcessException,
- ProcessResult,
- ProcessSignal,
- ProcessStartMode,
- systemEncoding;
-
-import 'package:file/file.dart';
-import 'package:path/path.dart' as path;
-
-import '../interface/process_manager.dart';
-import 'can_run_manifest_entry.dart';
-import 'common.dart';
-import 'constants.dart';
-import 'manifest.dart';
-import 'run_manifest_entry.dart';
-import 'recording_process_manager.dart';
-
-/// Fakes all process invocations by replaying a previously-recorded series
-/// of invocations.
-///
-/// Recordings exist as opaque directories that are produced by
-/// [RecordingProcessManager].
-class ReplayProcessManager implements ProcessManager {
- ReplayProcessManager._(this._manifest, this.location, this.streamDelay);
-
- final Manifest _manifest;
-
- /// The location of the serialized recording that's driving this manager.
- final Directory location;
-
- /// If non-null, processes spawned by this manager will delay their
- /// `stdout` and `stderr` stream production by the this amount. See
- /// description of the associated parameter in [create].
- final Duration streamDelay;
-
- /// Creates a new `ReplayProcessManager` capable of replaying a recording that
- /// was serialized to the specified [location] by [RecordingProcessManager].
- ///
- /// If [location] does not exist, or if it does not represent a valid
- /// recording (as determined by [RecordingProcessManager]), an [ArgumentError]
- /// will be thrown.
- ///
- /// If [streamDelay] is specified, processes spawned by this manager will
- /// delay their `stdout` and `stderr` stream production by the specified
- /// amount. This is useful in cases where the real process invocation had
- /// a necessary delay in stream production, and you need to mirror that
- /// behavior. e.g. you spawn a `tail` process to tail a log file, then in a
- /// follow-on event loop, you invoke a `startServer` process, which starts
- /// producing log output. In this case, you may need to delay the `tail`
- /// output to prevent its stream from flushing all its content before you
- /// start listening.
- static Future<ReplayProcessManager> create(
- Directory location, {
- Duration streamDelay = Duration.zero,
- }) async {
- assert(streamDelay != null);
-
- if (!location.existsSync()) {
- throw ArgumentError.value(location.path, 'location', "Doesn't exist");
- }
-
- FileSystem fs = location.fileSystem;
- File manifestFile = fs.file(path.join(location.path, kManifestName));
- if (!manifestFile.existsSync()) {
- throw ArgumentError.value(
- location, 'location', 'Does not represent a valid recording');
- }
-
- String content = await manifestFile.readAsString();
- try {
- // We don't validate the existence of all stdout and stderr files
- // referenced in the manifest.
- Manifest manifest = Manifest.fromJson(content);
- return ReplayProcessManager._(manifest, location, streamDelay);
- } on FormatException catch (e) {
- throw ArgumentError('$kManifestName is not a valid JSON file: $e');
- }
- }
-
- @override
- Future<io.Process> start(
- List<dynamic> command, {
- String workingDirectory,
- Map<String, String> environment,
- bool includeParentEnvironment = true,
- bool runInShell = false,
- io.ProcessStartMode mode = io.ProcessStartMode.normal,
- }) async {
- RunManifestEntry entry = _popRunEntry(command, mode: mode);
- _ReplayResult result = await _ReplayResult.create(this, entry);
- return result.asProcess(entry.daemon);
- }
-
- @override
- Future<io.ProcessResult> run(
- List<dynamic> command, {
- String workingDirectory,
- Map<String, String> environment,
- bool includeParentEnvironment = true,
- bool runInShell = false,
- Encoding stdoutEncoding = io.systemEncoding,
- Encoding stderrEncoding = io.systemEncoding,
- }) async {
- RunManifestEntry entry = _popRunEntry(command,
- stdoutEncoding: stdoutEncoding, stderrEncoding: stderrEncoding);
- return await _ReplayResult.create(this, entry);
- }
-
- @override
- io.ProcessResult runSync(
- List<dynamic> command, {
- String workingDirectory,
- Map<String, String> environment,
- bool includeParentEnvironment = true,
- bool runInShell = false,
- Encoding stdoutEncoding = io.systemEncoding,
- Encoding stderrEncoding = io.systemEncoding,
- }) {
- RunManifestEntry entry = _popRunEntry(command,
- stdoutEncoding: stdoutEncoding, stderrEncoding: stderrEncoding);
- return _ReplayResult.createSync(this, entry);
- }
-
- /// Finds and returns the next entry in the process manifest that matches
- /// the specified process arguments. Once found, it marks the manifest entry
- /// as having been invoked and thus not eligible for invocation again.
- RunManifestEntry _popRunEntry(
- List<dynamic> command, {
- io.ProcessStartMode mode,
- Encoding stdoutEncoding,
- Encoding stderrEncoding,
- }) {
- List<String> sanitizedCommand = sanitize(command);
- RunManifestEntry entry = _manifest.findPendingRunEntry(
- command: sanitizedCommand,
- mode: mode,
- stdoutEncoding: stdoutEncoding,
- stderrEncoding: stderrEncoding,
- ) as RunManifestEntry;
-
- if (entry == null) {
- throw io.ProcessException(sanitizedCommand.first,
- sanitizedCommand.skip(1).toList(), 'No matching invocation found');
- }
-
- entry.setInvoked();
- return entry;
- }
-
- @override
- bool canRun(dynamic executable, {String workingDirectory}) {
- CanRunManifestEntry entry = _manifest.findPendingCanRunEntry(
- executable: executable.toString(),
- ) as CanRunManifestEntry;
- if (entry == null) {
- throw ArgumentError('No matching invocation found for $executable');
- }
- entry.setInvoked();
- return entry.result;
- }
-
- @override
- bool killPid(int pid, [io.ProcessSignal signal = io.ProcessSignal.sigterm]) {
- throw UnsupportedError(
- "$runtimeType.killPid() has not been implemented because at the time "
- "of its writing, it wasn't needed. If you're hitting this error, you "
- "should implement it.");
- }
-}
-
-/// A [io.ProcessResult] implementation that derives its data from a recording
-/// fragment.
-class _ReplayResult implements io.ProcessResult {
- _ReplayResult._({
- this.manager,
- this.pid,
- this.exitCode,
- this.stdout,
- this.stderr,
- });
-
- final ReplayProcessManager manager;
-
- @override
- final int pid;
-
- @override
- final int exitCode;
-
- @override
- final dynamic stdout;
-
- @override
- final dynamic stderr;
-
- static Future<_ReplayResult> create(
- ReplayProcessManager manager,
- RunManifestEntry entry,
- ) async {
- FileSystem fs = manager.location.fileSystem;
- String basePath = path.join(manager.location.path, entry.basename);
- try {
- return _ReplayResult._(
- manager: manager,
- pid: entry.pid,
- exitCode: entry.exitCode,
- stdout: await _getData(fs, '$basePath.stdout', entry.stdoutEncoding),
- stderr: await _getData(fs, '$basePath.stderr', entry.stderrEncoding),
- );
- } catch (e) {
- throw io.ProcessException(
- entry.executable, entry.arguments, e.toString());
- }
- }
-
- static Future<dynamic> _getData(
- FileSystem fs, String path, Encoding encoding) async {
- File file = fs.file(path);
- return encoding == null
- ? await file.readAsBytes()
- : await file.readAsString(encoding: encoding);
- }
-
- static _ReplayResult createSync(
- ReplayProcessManager manager,
- RunManifestEntry entry,
- ) {
- FileSystem fs = manager.location.fileSystem;
- String basePath = path.join(manager.location.path, entry.basename);
- try {
- return _ReplayResult._(
- manager: manager,
- pid: entry.pid,
- exitCode: entry.exitCode,
- stdout: _getDataSync(fs, '$basePath.stdout', entry.stdoutEncoding),
- stderr: _getDataSync(fs, '$basePath.stderr', entry.stderrEncoding),
- );
- } catch (e) {
- throw io.ProcessException(
- entry.executable, entry.arguments, e.toString());
- }
- }
-
- static dynamic _getDataSync(FileSystem fs, String path, Encoding encoding) {
- File file = fs.file(path);
- return encoding == null
- ? file.readAsBytesSync()
- : file.readAsStringSync(encoding: encoding);
- }
-
- io.Process asProcess(bool daemon) {
- assert(stdout is List<int>);
- assert(stderr is List<int>);
- return _ReplayProcess(this, daemon);
- }
-}
-
-/// A [io.Process] implementation derives its data from a recording fragment.
-class _ReplayProcess implements io.Process {
- _ReplayProcess(_ReplayResult result, bool daemon)
- : pid = result.pid,
- _stdout = result.stdout as List<int>,
- _stderr = result.stderr as List<int>,
- _stdoutController = StreamController<List<int>>(),
- _stderrController = StreamController<List<int>>(),
- _exitCode = result.exitCode,
- _exitCodeCompleter = Completer<int>() {
- // Don't flush our stdio streams until we at least reach the outer event
- // loop. i.e. even if `streamDelay` is zero, we still want to use the timer.
- Timer(result.manager.streamDelay, () {
- if (!_stdoutController.isClosed) {
- _stdoutController.add(_stdout);
- }
- if (!_stderrController.isClosed) {
- _stderrController.add(_stderr);
- }
- if (!daemon) kill();
- });
- }
-
- @override
- final int pid;
-
- final List<int> _stdout;
- final List<int> _stderr;
- final StreamController<List<int>> _stdoutController;
- final StreamController<List<int>> _stderrController;
- final int _exitCode;
- final Completer<int> _exitCodeCompleter;
-
- @override
- Stream<List<int>> get stdout => _stdoutController.stream;
-
- @override
- Stream<List<int>> get stderr => _stderrController.stream;
-
- @override
- Future<int> get exitCode => _exitCodeCompleter.future;
-
- @override
- io.IOSink get stdin => throw UnimplementedError();
-
- @override
- bool kill([io.ProcessSignal signal = io.ProcessSignal.sigterm]) {
- if (!_exitCodeCompleter.isCompleted) {
- _stdoutController.close();
- _stderrController.close();
- _exitCodeCompleter.complete(_exitCode);
- return true;
- }
- return false;
- }
-}
diff --git a/lib/src/record_replay/run_manifest_entry.dart b/lib/src/record_replay/run_manifest_entry.dart
deleted file mode 100644
index a3dfd35..0000000
--- a/lib/src/record_replay/run_manifest_entry.dart
+++ /dev/null
@@ -1,149 +0,0 @@
-// Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file
-// for details. 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:convert';
-import 'dart:io' show ProcessStartMode, systemEncoding;
-
-import 'manifest_entry.dart';
-
-/// Gets a `ProcessStartMode` value by its string name.
-ProcessStartMode _getProcessStartMode(String value) {
- if (value != null) {
- for (ProcessStartMode mode in ProcessStartMode.values) {
- if (mode.toString() == value) {
- return mode;
- }
- }
- throw FormatException('Invalid value for mode: $value');
- }
- return null;
-}
-
-/// Gets an `Encoding` instance by the encoding name.
-Encoding _getEncoding(String encoding) {
- if (encoding == 'system') {
- return systemEncoding;
- } else if (encoding != null) {
- return Encoding.getByName(encoding);
- }
- return null;
-}
-
-/// An entry in the process invocation manifest for running an executable.
-class RunManifestEntry extends ManifestEntry {
- /// Creates a new manifest entry with the given properties.
- RunManifestEntry({
- this.pid,
- this.basename,
- this.command,
- this.workingDirectory,
- this.environment,
- this.includeParentEnvironment,
- this.runInShell,
- this.mode,
- this.stdoutEncoding,
- this.stderrEncoding,
- this.exitCode,
- });
-
- /// Creates a new manifest entry populated with the specified JSON [data].
- ///
- /// If any required fields are missing from the JSON data, this will throw
- /// a [FormatException].
- factory RunManifestEntry.fromJson(Map<String, dynamic> data) {
- checkRequiredField(data, 'pid');
- checkRequiredField(data, 'basename');
- checkRequiredField(data, 'command');
- RunManifestEntry entry = RunManifestEntry(
- pid: data['pid'] as int,
- basename: data['basename'] as String,
- command: (data['command'] as List<dynamic>)?.cast<String>(),
- workingDirectory: data['workingDirectory'] as String,
- environment: (data['environment'] as Map<dynamic, dynamic>)
- ?.cast<String, String>(),
- includeParentEnvironment: data['includeParentEnvironment'] as bool,
- runInShell: data['runInShell'] as bool,
- mode: _getProcessStartMode(data['mode'] as String),
- stdoutEncoding: _getEncoding(data['stdoutEncoding'] as String),
- stderrEncoding: _getEncoding(data['stderrEncoding'] as String),
- exitCode: data['exitCode'] as int,
- );
- entry.daemon = data['daemon'] as bool;
- entry.notResponding = data['notResponding'] as bool;
- return entry;
- }
-
- @override
- final String type = 'run';
-
- /// The process id.
- final int pid;
-
- /// The base file name for this entry. `stdout` and `stderr` files for this
- /// process will be serialized in the recording directory as
- /// `$basename.stdout` and `$basename.stderr`, respectively.
- final String basename;
-
- /// The command that was run. The first element is the executable, and the
- /// remaining elements are the arguments to the executable.
- final List<String> command;
-
- /// The process' working directory when it was spawned.
- final String workingDirectory;
-
- /// The environment variables that were passed to the process.
- final Map<String, String> environment;
-
- /// Whether the invoker's environment was made available to the process.
- final bool includeParentEnvironment;
-
- /// Whether the process was spawned through a system shell.
- final bool runInShell;
-
- /// The mode with which the process was spawned.
- final ProcessStartMode mode;
-
- /// The encoding used for the `stdout` of the process.
- final Encoding stdoutEncoding;
-
- /// The encoding used for the `stderr` of the process.
- final Encoding stderrEncoding;
-
- /// The exit code of the process.
- int exitCode;
-
- /// The executable that was invoked.
- String get executable => command.first;
-
- /// The arguments that were passed to [executable].
- List<String> get arguments => command.skip(1).toList();
-
- /// Indicates that the process is a daemon.
- bool get daemon => _daemon;
- bool _daemon = false;
- set daemon(bool value) => _daemon = value ?? false;
-
- /// Indicates that the process did not respond to `SIGTERM`.
- bool get notResponding => _notResponding;
- bool _notResponding = false;
- set notResponding(bool value) => _notResponding = value ?? false;
-
- /// Returns a JSON-encodable representation of this manifest entry.
- @override
- Map<String, dynamic> toJson() => JsonBuilder()
- .add('pid', pid)
- .add('basename', basename)
- .add('command', command)
- .add('workingDirectory', workingDirectory)
- .add('environment', environment)
- .add('includeParentEnvironment', includeParentEnvironment)
- .add('runInShell', runInShell)
- .add('mode', mode, () => mode.toString())
- .add('stdoutEncoding', stdoutEncoding, () => stdoutEncoding.name)
- .add('stderrEncoding', stderrEncoding, () => stderrEncoding.name)
- .add('daemon', daemon)
- .add('notResponding', notResponding)
- .add('exitCode', exitCode)
- .entry;
-}
diff --git a/pubspec.yaml b/pubspec.yaml
index b6a7362..b25b238 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -5,7 +5,6 @@
dependencies:
file: '^5.0.0'
- meta: ^1.1.2
path: ^1.5.1
platform: '>=1.0.1'
@@ -13,4 +12,4 @@
test: ^1.0.0
environment:
- sdk: '>=2.0.0 <3.0.0'
+ sdk: '>=2.9.0 <3.0.0'
diff --git a/test/record_test.dart b/test/record_test.dart
deleted file mode 100644
index 6f827df..0000000
--- a/test/record_test.dart
+++ /dev/null
@@ -1,174 +0,0 @@
-// Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file
-// for details. 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:convert';
-import 'dart:io' show Platform, Process, ProcessResult, systemEncoding;
-
-import 'package:file/file.dart';
-import 'package:file/local.dart';
-import 'package:path/path.dart' as p;
-import 'package:process/process.dart';
-import 'package:process/record_replay.dart';
-import 'package:test/test.dart';
-
-import 'utils.dart';
-
-void main() {
- FileSystem fs = LocalFileSystem();
- // TODO(goderbauer): refactor when github.com/google/platform.dart/issues/1
- // is available.
- String newline = Platform.isWindows ? '\r\n' : '\n';
-
- group('RecordingProcessManager', () {
- Directory tmp;
- RecordingProcessManager manager;
-
- setUp(() {
- tmp = fs.systemTempDirectory.createTempSync('process_tests_');
- manager = RecordingProcessManager(LocalProcessManager(), tmp);
- });
-
- tearDown(() {
- tmp.deleteSync(recursive: true);
- });
-
- test('start', () async {
- Process process =
- await manager.start(<String>['echo', 'foo'], runInShell: true);
- int pid = process.pid;
- int exitCode = await process.exitCode;
- List<int> stdout = await consume(process.stdout);
- List<int> stderr = await consume(process.stderr);
- expect(exitCode, 0);
- expect(decode(stdout), <String>['foo']);
- expect(stderr, isEmpty);
-
- // Force the recording to be written to disk.
- await manager.flush(finishRunningProcesses: true);
-
- _Recording recording = _Recording(tmp);
- expect(recording.manifest, hasLength(1));
- Map<String, dynamic> entry = recording.manifest.first;
- expect(entry['type'], 'run');
- Map<String, dynamic> body = entry['body'] as Map<String, dynamic>;
- expect(body['pid'], pid);
- expect(body['command'], <String>['echo', 'foo']);
- expect(body['mode'], 'normal');
- expect(body['exitCode'], exitCode);
- expect(recording.stdoutForEntryAt(0), stdout);
- expect(recording.stderrForEntryAt(0), stderr);
- });
-
- test('run', () async {
- ProcessResult result =
- await manager.run(<String>['echo', 'bar'], runInShell: true);
- int pid = result.pid;
- int exitCode = result.exitCode;
- String stdout = result.stdout as String;
- String stderr = result.stderr as String;
- expect(exitCode, 0);
- expect(stdout, 'bar$newline');
- expect(stderr, isEmpty);
-
- // Force the recording to be written to disk.
- await manager.flush(finishRunningProcesses: true);
-
- _Recording recording = _Recording(tmp);
- expect(recording.manifest, hasLength(1));
- Map<String, dynamic> entry = recording.manifest.first;
- expect(entry['type'], 'run');
- Map<String, dynamic> body = entry['body'] as Map<String, dynamic>;
- expect(body['pid'], pid);
- expect(body['command'], <String>['echo', 'bar']);
- expect(body['stdoutEncoding'], 'system');
- expect(body['stderrEncoding'], 'system');
- expect(body['exitCode'], exitCode);
- expect(recording.stdoutForEntryAt(0), stdout);
- expect(recording.stderrForEntryAt(0), stderr);
- });
-
- test('runSync', () async {
- ProcessResult result =
- manager.runSync(<String>['echo', 'baz'], runInShell: true);
- int pid = result.pid;
- int exitCode = result.exitCode;
- String stdout = result.stdout as String;
- String stderr = result.stderr as String;
- expect(exitCode, 0);
- expect(stdout, 'baz$newline');
- expect(stderr, isEmpty);
-
- // Force the recording to be written to disk.
- await manager.flush(finishRunningProcesses: true);
-
- _Recording recording = _Recording(tmp);
- expect(recording.manifest, hasLength(1));
- Map<String, dynamic> entry = recording.manifest.first;
- expect(entry['type'], 'run');
- Map<String, dynamic> body = entry['body'] as Map<String, dynamic>;
- expect(body['pid'], pid);
- expect(body['command'], <String>['echo', 'baz']);
- expect(body['stdoutEncoding'], 'system');
- expect(body['stderrEncoding'], 'system');
- expect(body['exitCode'], exitCode);
- expect(recording.stdoutForEntryAt(0), stdout);
- expect(recording.stderrForEntryAt(0), stderr);
- });
-
- test('canRun', () async {
- String executable = p.join(tmp.path, 'bla.exe');
- fs.file(executable).createSync();
-
- bool result = manager.canRun(executable);
-
- // Force the recording to be written to disk.
- await manager.flush(finishRunningProcesses: true);
-
- _Recording recording = _Recording(tmp);
- expect(recording.manifest, hasLength(1));
- Map<String, dynamic> entry = recording.manifest.first;
- expect(entry['type'], 'can_run');
- Map<String, dynamic> body = entry['body'] as Map<String, dynamic>;
- expect(body['executable'], executable);
- expect(body['result'], result);
- });
- });
-}
-
-/// A testing utility class that encapsulates a recording.
-class _Recording {
- _Recording(this.dir);
- final Directory dir;
-
- List<Map<String, dynamic>> get manifest {
- return (json.decoder
- .convert(_getFileContent('MANIFEST.txt', utf8) as String)
- as List<dynamic>)
- .cast<Map<String, dynamic>>();
- }
-
- dynamic stdoutForEntryAt(int index) => _getStdioContent(
- manifest[index]['body'] as Map<String, dynamic>, 'stdout');
-
- dynamic stderrForEntryAt(int index) => _getStdioContent(
- manifest[index]['body'] as Map<String, dynamic>, 'stderr');
-
- dynamic _getFileContent(String name, Encoding encoding) {
- File file = dir.fileSystem.file('${dir.path}/$name');
- return encoding == null
- ? file.readAsBytesSync()
- : file.readAsStringSync(encoding: encoding);
- }
-
- dynamic _getStdioContent(Map<String, dynamic> entry, String type) {
- String basename = entry['basename'] as String;
- String encodingName = entry['${type}Encoding'] as String;
- Encoding encoding;
- if (encodingName != null)
- encoding = encodingName == 'system'
- ? systemEncoding
- : Encoding.getByName(encodingName);
- return _getFileContent('$basename.$type', encoding);
- }
-}
diff --git a/test/replay_test.dart b/test/replay_test.dart
deleted file mode 100644
index 045f72d..0000000
--- a/test/replay_test.dart
+++ /dev/null
@@ -1,60 +0,0 @@
-// Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file
-// for details. 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:io' show Process, ProcessResult;
-
-import 'package:file/file.dart';
-import 'package:file/local.dart';
-import 'package:process/process.dart';
-import 'package:process/record_replay.dart';
-import 'package:test/test.dart';
-
-import 'utils.dart';
-
-void main() {
- FileSystem fs = LocalFileSystem();
-
- group('ReplayProcessManager', () {
- ProcessManager manager;
-
- setUp(() async {
- Directory dir = fs.directory('test/data/replay');
- manager = await ReplayProcessManager.create(dir);
- });
-
- test('start', () async {
- Process process = await manager.start(<String>['sing', 'ppap']);
- int exitCode = await process.exitCode;
- List<int> stdout = await consume(process.stdout);
- List<int> stderr = await consume(process.stderr);
- expect(process.pid, 100);
- expect(exitCode, 0);
- expect(decode(stdout), <String>['I have a pen', 'I have a pineapple']);
- expect(decode(stderr), <String>['Uh, pineapple pen']);
- });
-
- test('run', () async {
- ProcessResult result =
- await manager.run(<String>['dance', 'gangnam-style']);
- expect(result.pid, 101);
- expect(result.exitCode, 2);
- expect(result.stdout, '');
- expect(result.stderr, 'No one can dance like Psy\n');
- });
-
- test('runSync', () {
- ProcessResult result =
- manager.runSync(<String>['dance', 'gangnam-style']);
- expect(result.pid, 101);
- expect(result.exitCode, 2);
- expect(result.stdout, '');
- expect(result.stderr, 'No one can dance like Psy\n');
- });
-
- test('canRun', () {
- bool result = manager.canRun('marathon');
- expect(result, true);
- });
- });
-}