blob: 64fd687da5278bd9a9e1f976fdc51fa638289db2 [file] [log] [blame]
// 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, SYSTEM_ENCODING;
import 'manifest.dart';
import 'replay_process_manager.dart';
/// Throws a [FormatException] if [data] does not contain [key].
void _checkRequiredField(Map<String, dynamic> data, String key) {
if (!data.containsKey(key))
throw new FormatException('Required field missing: $key');
}
/// 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 new FormatException('Invalid value for mode: $value');
}
return null;
}
/// Gets an `Encoding` instance by the encoding name.
Encoding _getEncoding(String encoding) {
if (encoding == 'system') {
return SYSTEM_ENCODING;
} else if (encoding != null) {
return Encoding.getByName(encoding);
}
return null;
}
/// An entry in the process invocation manifest.
///
/// Each entry in the [Manifest] represents a single recorded process
/// invocation.
class ManifestEntry {
/// 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 name of the executable that spawned the process.
final String executable;
/// The list of arguments to [executable].
final List<String> arguments;
/// 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;
/// Creates a new manifest entry with the given properties.
ManifestEntry({
this.pid,
this.basename,
this.executable,
this.arguments,
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 ManifestEntry.fromJson(Map<String, dynamic> data) {
_checkRequiredField(data, 'pid');
_checkRequiredField(data, 'basename');
_checkRequiredField(data, 'executable');
_checkRequiredField(data, 'arguments');
ManifestEntry entry = new ManifestEntry(
pid: data['pid'],
basename: data['basename'],
executable: data['executable'],
arguments: data['arguments'],
workingDirectory: data['workingDirectory'],
environment: data['environment'],
includeParentEnvironment: data['includeParentEnvironment'],
runInShell: data['runInShell'],
mode: _getProcessStartMode(data['mode']),
stdoutEncoding: _getEncoding(data['stdoutEncoding']),
stderrEncoding: _getEncoding(data['stderrEncoding']),
exitCode: data['exitCode'],
);
entry.daemon = data['daemon'];
entry.notResponding = data['notResponding'];
return entry;
}
/// 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;
/// 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;
}
/// Returns a JSON-encodable representation of this manifest entry.
Map<String, dynamic> toJson() => new _JsonBuilder()
.add('pid', pid)
.add('basename', basename)
.add('executable', executable)
.add('arguments', arguments)
.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;
}
/// A lightweight class that provides a means of building a manifest entry
/// JSON object.
class _JsonBuilder {
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;
}
}