Create new Process instance to run specified program.
Add arguments to the process.
Copy the process configuration. Could be useful when needed to run command multipe times with slightly different configuration. Returns new instance of process.
Execute the configured process and capture output.
Replace current process by executing program as configured by Process instance.
Return a new Process with the working directory set to the provided path, leaving the original unchanged.
Append a single argument, returning a new Process (non-mutating). Equivalent to withArgs.
Append multiple arguments, returning a new Process (non-mutating). Equivalent to withArgs.
Append a single argument in place (mutating). Equivalent to addArgs.
Append multiple arguments in place (mutating). Equivalent to addArgs.
Pipe process
Set arguments for the process
Set process configuration
Set environemnt for the process to be started. Could be called multiple times to update environment.
Set environment variable (specified by key) to provided value
Set configuration flag for process to be started
Set GID to run process with
Run process with new environment (do not inherit environment variables from parent process)
Apply Config.stderrPassThrough flag. With this flag, stderr will not be captured, but instead directly passed to console or terminal.
Set UID to run process with
Run process as specified user
Set work directory for the process to be started
Spawn process
Return string representation of process to be started
Return a new Process with the provided arguments appended, leaving the original unchanged.
Return a new Process with the process configuration set to the provided value, leaving the original unchanged.
Return a new Process with the environment updated with the provided key-value pairs, leaving the original unchanged.
Return a new Process with the given configuration flag set, leaving the original unchanged.
Return a new Process configured to run with the given GID, leaving the original unchanged.
Return a new Process configured to start with a fresh environment (not inheriting parent environment variables), leaving the original unchanged.
Return a new Process with Config.stderrPassThrough set, leaving the original unchanged.
Return a new Process configured to run with the given UID, leaving the original unchanged.
Return a new Process configured to run as the given user, leaving the original unchanged.
// It is possible to run process in following way: auto result = Process("my-program") .withArgs("--verbose", "--help") .withEnv("MY_ENV_VAR", "MY_VALUE") .inWorkDir("my/working/directory") .execute() .ensureStatus!MyException("My error message on failure"); writeln(result.output);
// Also, in Posix system it is possible to run command as different user: auto result = Process("my-program") .withUser("bob") .execute() .ensureStatus!MyException("My error message on failure"); writeln(result.output);
Test simple execution of the script
import std.string; import std.ascii : newline; import unit_threaded.assertions; auto temp_root = createTempPath(); scope(exit) temp_root.remove(); version(Posix) { import std.conv: octal; auto script_path = temp_root.join("test-script.sh"); script_path.writeFile( "#!" ~ nativeShell ~ newline ~ `echo "Test out: $1 $2"` ~ newline); // Add permission to run this script script_path.setAttributes(octal!755); } else version(Windows) { auto script_path = temp_root.join("test-script.cmd"); script_path.writeFile( "@echo off" ~ newline ~ "echo Test out: %1 %2" ~ newline); } // Test the case when process executes fine auto result = Process(script_path) .withArgs("Hello", "World", "test") .execute .ensureOk; result.status.should == 0; result.output.chomp.should == "Test out: Hello World"; result.isOk.shouldBeTrue; result.isNotOk.shouldBeFalse; // When we expect different successful exit-code result.isOk(42).shouldBeFalse; result.isNotOk(42).shouldBeTrue; result.ensureOk(42).shouldThrow!ProcessException;
Test simple execution of the script that handles environment variables
1 import std.string; 2 import std.ascii : newline; 3 4 import unit_threaded.assertions; 5 6 auto temp_root = createTempPath(); 7 scope(exit) temp_root.remove(); 8 9 /* Do similar trick as in Phobos for portable newline output 10 * 11 * To avoid printing the newline characters, we use the echo|set trick on 12 * Windows, and printf on POSIX (neither echo -n nor echo \c are portable). 13 */ 14 version(Posix) { 15 import std.conv: octal; 16 auto script_path = temp_root.join("test-script.sh"); 17 script_path.writeFile( 18 "#!" ~ nativeShell ~ newline ~ 19 `printf "Test out: $1 $2, $MY_PARAM_1 $MY_PARAM_2"` ~ newline); 20 // Add permission to run this script 21 script_path.setAttributes(octal!755); 22 } else version(Windows) { 23 auto script_path = temp_root.join("test-script.cmd"); 24 script_path.writeFile( 25 `@echo off` ~ newline ~ 26 `echo|set /p DUMMY="Test out: %1 %2, %MY_PARAM_1% %MY_PARAM_2%"` ~ newline); 27 } 28 29 // Test the case when process executes fine 30 auto result = Process(script_path) 31 .withArgs("Hello") 32 .withArgs("World") 33 .withEnv("MY_PARAM_1", "the") 34 .withEnv("MY_PARAM_2", "Void") 35 .execute 36 .ensureOk; 37 result.status.should == 0; 38 result.output.chomp.should == "Test out: Hello World, the Void"; 39 result.isOk.shouldBeTrue; 40 result.isNotOk.shouldBeFalse; 41 // When we expect different successful exit-code 42 result.isOk(42).shouldBeFalse; 43 result.isNotOk(42).shouldBeTrue; 44 45 // Ensure that status is ok, if not ok, then raise error 46 result.ensureOk(42).shouldThrow!ProcessException; 47 48 // Optionally allow to print command output on failure with custom error message or with standard one. 49 result.ensureOk("Custom error message", 42).shouldThrowWithMessage!ProcessException( 50 "Custom error message"); 51 result.ensureOk("Error message", true, 42).shouldThrowWithMessage!ProcessException( 52 "Error message\nOutput: %s".format("Test out: Hello World, the Void")); 53 result.ensureOk(true, 42).shouldThrowWithMessage!ProcessException( 54 "Program %s with args %s failed! Expected exit-code %s, got %s.\nOutput: %s".format( 55 result._program, result._args, 42, 0, "Test out: Hello World, the Void"));
Test simple execution of the script with user (use current user)
import std.string; import std.ascii : newline; import unit_threaded.assertions; auto temp_root = createTempPath(); scope(exit) temp_root.remove(); import std.conv: octal; auto script_path = temp_root.join("test-script.sh"); script_path.writeFile( "#!" ~ nativeShell ~ newline ~ `echo "Test out: $1 $2"` ~ newline); // Add permission to run this script script_path.setAttributes(octal!755); auto username = Process("whoami").execute.ensureOk(true).output.strip; // Test the case when process executes fine auto result = Process(script_path) .withArgs("Hello", "World", "test") .withUser(username) .execute .ensureOk; result.status.should == 0; result.output.chomp.should == "Test out: Hello World"; result.isOk.shouldBeTrue; result.isNotOk.shouldBeFalse; // When we expect different successful exit-code result.isOk(42).shouldBeFalse; result.isNotOk(42).shouldBeTrue; result.ensureOk(42).shouldThrow!ProcessException;
Test simple execution of the script within user's home directory
import std.string; import std.ascii : newline; import unit_threaded.assertions; // Change current working dir to /tmp Path.tempDir.chdir; auto current_user = Process("whoami").execute.ensureOk(true).output.strip; auto workdir = Process("pwd") .withUser(current_user) .execute .ensureOk(true) .output.strip; Path(workdir).realPath.should == Path.tempDir.realPath; workdir = Process("pwd") .withUser(current_user, true) .execute .ensureOk(true) .output.strip; Path(workdir).realPath.should == Path("~").realPath;
This struct is used to prepare configuration for process and run it.
The following methods of running a process are supported:
- execute: run process and catch its output and exit code. - spawn: spawn the process in background, and optionally pipe its output. - pipe: spawn the process and attach configurable pipes to catch output.
The configuration of a process can be done like so:
1. Create the Process instance specifying the program to run. 2. Apply your desired configuration (args, env, workDir) via calls to one of the corresponding methods. 3. Run one of execute, spawn or pipe methods, that will actually start the process.
Configuration methods come in two families with distinct semantics:
- set* / add* methods mutate the current instance in place and return void, making them suitable for conditional modification of an already-stored Process variable. - with* / in* methods return a new Process by value, leaving the original unchanged, making them safe to use in chained expressions. Most set* / add* method has a with* counterpart: addArgs ↔ withArgs, setWorkDir ↔ inWorkDir, setEnv ↔ withEnv, etc.