#Process
Status: standard library specification draft.
This chapter defines the part of std.process that lib/std writes in
ordinary hd over the language tier:
- the
Displaytext ofProcessError; - the traits of
ExitCodeandProcessOutput; - the builder
Commandand its terminalrun!; - the test provider
ScriptedProcess; - the status a signal leaves, and what cancelling
run!does.
The language tier keeps what the compiler and the test runner name:
| Item | Why it stays in the language tier |
|---|---|
ExitCode, Termination |
lang items that an entry point's result reports through (Exit Status) |
Process, Command, ProcessOutput, ProcessError |
the host capability trait that the test runner binds, and the values it takes and returns (Processes) |
#Process Errors
ProcessError says why a program did not start:
use std.process.ProcessError
fn explain(error: ProcessError) -> string:
"cannot run the tool: ${error}"| Rule | Variant | Display text |
|---|---|---|
| std-process.error.display.not-found-namedNot found | NotFound(program) |
program not found: PROGRAM |
| std-process.error.display.permission-namedPermission | PermissionDenied(program) |
permission denied: PROGRAM |
| std-process.error.display.not-granted.plainNot granted | NotGranted |
program not granted |
| std-process.error.display.otherOther | Other(message) |
message, as written |
| std-process.error.display.exited-outputExited | Exited(output) |
program PROGRAM exited with status STATUS, with the stderr suffix below |
- std-process.error.debug-output-fieldsIts
Debugwrites the qualified variant and its argument, asProcessError.NotFound(program="git")andProcessError.Other(message="x"),ProcessError.Exited(output=ProcessOutput { stdout: "", stderr: "boom", status: 2, program: "git" }), andNotGrantedalone, bystd-format.debug.std-types.calls. - std-process.error.equalTwo
ProcessErrorvalues are equal when they are the same variant and their arguments, if any, are equal. - std-process.error.error
ProcessErrorimplementsstd.error.Error, as Standard Error Types requires.
Why.
ExitedfollowsOther, so the host's variant tags keep their numbers. Hosts report every completed status as data;ok()performs the explicit check and retains the complete output. The host's message is the whole text ofOther, asFsErrorkeeps it.NotFoundandPermissionDeniedname the program, asFsError.NotFound(path)names its path, so a message such asprogram not found: gitneeds no caller to add the name.
#Exit Data
ExitCode and ProcessOutput are plain values, so they compare and print
like other std values:
use std.process.{ExitCode, ProcessOutput}
fn clean(output: ProcessOutput) -> bool:
output == ProcessOutput { stdout: "", stderr: "", status: 0 }
fn same(left: ExitCode, right: ExitCode) -> bool:
left == right- std-process.exit-code.eq
ExitCodeimplementsEq; two codes are equal when theiru8values are. - std-process.output.eq-program
ProcessOutputimplementsEq; two outputs are equal whenstdout,stderr,status, andprogramare each equal. - std-process.debug-text
ExitCodeandProcessOutputimplementDebug, bystd-format.debug.std-types. AnExitCodewrites its constructor call, asExitCode(0).
#Checking Output
- std-process.output.ok
ProcessOutput.ok(self) -> Result[ProcessOutput, ProcessError]returns.Ok(self)whenstatusis zero. - std-process.output.ok-nonzeroFor a nonzero status, it returns
.Err(ProcessError.Exited(self))with every output field preserved. - std-process.error.exited-output
ProcessError.Exited(output: ProcessOutput)carries the complete output whose explicit check failed. - std-process.error.exited-last-lineIts
Displayappends: LINEwhen the last line ofoutput.stderr.lines()is nonempty. - std-process.error.exited-empty-lineWhen that last line is empty or absent, the
Displaytext has no stderr suffix. - std-process.error.exited-unnamedAn empty
output.programomitsPROGRAMand its following space from the message. - std-process.error.exited-no-argsThe message never adds command arguments.
- std-process.helper.run-checked
pub fn run!(command: Command) -> Result[ProcessOutput, ProcessError] $ Processcalls the command's neutralrun!and then checks its output withok(). - std-process.helper.start-errorA provider's
.Errpasses through the freerun!unchanged.
Why. A status-neutral method lets a caller inspect an expected status directly. The explicit check and free helper retain failed output without storing command arguments in the error.
#Command
Command is the builder that run! starts a program from. It is a
struct that Command::new returns behind *mut. Each setter takes
*mut self and returns the reference, so calls chain on the one object,
and the one terminal run! starts the program:
use std.path.Path
use std.process.{Command, Process, ProcessError}
fn commit!(repo: Path) -> Result[string, ProcessError] $ Process:
output := Command::new("git").args(["rev-parse", "--short", "HEAD"]).current_dir(repo).run!()?
.Ok(output.stdout.trim())- std-process.command.decl-struct
Commandis the struct type thatmodule.process.decl-structdeclares, with public fieldsprogram,args,env,current_dir, andstdin.std.processgives it the methods below. - std-process.command.new-ref
Command::new(program: string) -> *mut Commandreturns a new object holding a command forprogramwith no arguments, no environment pairs, nocurrent_dir, and an emptystdin. - std-process.command.arg-ref
arg(*mut self, value: string) -> *mut Commandappendsvaluetoargsas one item and returns the reference it was called through. - std-process.command.args-ref
args(*mut self, values: List[string]) -> *mut Commandappends each item ofvaluestoargs, in order, as its own item and returns the reference. - std-process.command.env-ref
env(*mut self, name: string, value: string) -> *mut Commandappends the pair(name, value)toenvand returns the reference. A later pair for a name replaces an earlier one, asmodule.process.envsays. - std-process.command.current-dir-ref
current_dir(*mut self, dir: Path) -> *mut Commandsetscurrent_dirto.Some(dir), replacing an earlier directory, and returns the reference. - std-process.command.stdin-ref
stdin(*mut self, text: string) -> *mut Commandsetsstdintotext, replacing an earlier text, and returns the reference. A command that gets nostdincall passes the empty string. - std-process.command.run
run!(self) -> Result[ProcessOutput, ProcessError] $ Processcallsrun!(self)on the coveringProcessprovider, so a program need not write$.use(Process). Its row is exactlyProcess, and it is a bang function, as the method it calls is. - std-process.stdin.unreadWhen the child exits or closes its standard input before reading all of
stdin, the rest is dropped. The call still returns.Okwith the child's output and status. - std-process.stdin.start-failureA failure to start the child returns
.Err, even whenstdinis nonempty. - std-process.command.eq
CommandimplementsEq; two commands are equal whenprogram,args,env,current_dir, andstdinare each equal. - std-process.command.debug
CommandimplementsDebug, bystd-format.debug.std-types. - std-process.command.compare-refTwo commands that
Command::newreturned are two references, so==between them is an error byref.identity.no-equality. A program compares their values with*left == *right.
Why. Rust's
Commandand Go'sexec.Cmdcarry the arguments, the environment, and the directory beside the program, so no call fixes one combination of them. Go'sexec.Command(...).Output()and Python'ssubprocess.runtake no stdin by default. A non-zero status stays.Ok, asmodule.process.nonzero-oksays.argandenvappend in place, in O(1), and the output text passes on without a copy.
#Scripted Process
ScriptedProcess is the deterministic Process provider. A test queues
the answers it needs, runs the code under test, and reads back the
commands that code started:
use std.process.{Command, Process, ProcessOutput, ScriptedProcess}
fn version!() -> string $ Process:
match Command::new("git").arg("--version").run!():
.Ok(output) => output.stdout
.Err(_) => "no git"
tests:
use std.testing.assert_equal
it("answers the scripted program and records its arguments"):
process := ScriptedProcess::new()
process.reply("git", .Ok(ProcessOutput { stdout: "git 2.0" }))
$.with(Process=process):
assert_equal(version!(), "git 2.0", reason="git is scripted")
assert_equal(process.received()[0].args, ["--version"], reason="the arguments")- std-process.scripted.decl
std.processdeclaresScriptedProcess, which implementsProcessandDebug, with private fields. Code imports it, as inuse std.process.ScriptedProcess. - std-process.scripted.empty-ref
ScriptedProcess::new() -> *mut ScriptedProcessreturns a new object holding a provider with no replies and no recorded commands. Installing it asProcess=processshares the object, so the test reads back what the code under test ran. - std-process.scripted.reply-ref
reply(&mut self, program: string, result: Result[ProcessOutput, ProcessError]) -> voidqueuesresultas an answer forprogram, after the answers already queued for it. - std-process.scripted.answer-program
run!(command)recordscommandbefore serving its answer. - std-process.scripted.next-answerIt returns the first answer queued for
command.programthat no earlier run of that program has taken. - std-process.scripted.repeat-lastWhen every queued answer is taken, it returns the last one again.
- std-process.scripted.command-independentThe arguments, environment, directory, and stdin do not change the selected answer.
- std-process.scripted.output-programAn
.Okoutput hasprogramset to the command's program before it is returned. - std-process.scripted.unqueuedFor a program that has no queued answer,
run!returns.Err(ProcessError.NotFound(program)). - std-process.scripted.received-ref
received(&self) -> List[Command]returns every command thatrun!received, in order, including those answered withNotFound. - std-process.scripted.no-hostA
ScriptedProcessstarts no host program.
Why. Every host capability trait has a deterministic provider beside it, so a test never touches the host. A queue per program with a repeating last answer is how Python's
responseslibrary serves them.receivedlets a test check the arguments, which a table of outputs could not. A constructor keeps the fields private, asMemoryConsole::newandManualClock::newdo.
#Child Processes
A program that run! starts on the host is a child of the program that
calls it:
use std.process.{Command, Process, ProcessError}
fn killed!(tool: string) -> Result[bool, ProcessError] $ Process:
output := Command::new(tool).run!()?
.Ok(output.status == 128 + 9) # SIGKILL, signal 9, ended the tool- std-process.child.signalWhen a signal ends a child, its
statusis 128 plus the signal number. - std-process.cancelCancelling the suspension of
run!kills the child and its process group.
Why. 128 plus the signal number is the shell's convention, so a status reads the same in hd and in a shell script.
See also: Processes, Running Executables.