Testing

CommandChecker wraps a Command for testing. It captures stdout, stderr, and exit codes in-process. No subprocess spawning, no shell quoting issues.

Basic usage

Create a CommandChecker from your Command, call run() with argument strings, and inspect the RunResult:

from face import Command, CommandChecker

def hello(name='world'):
    print(f'Hello, {name}')

cmd = Command(hello)
cmd.add('--name')

cc = CommandChecker(cmd)
result = cc.run('hello --name Alice')
assert result.stdout == 'Hello, Alice\n'
assert result.exit_code == 0

run() returns a RunResult. If the command exits with an unexpected exit code, CheckError is raised. By default, run() expects exit code 0.

Testing failures

Use fail() to assert that a command exits with a non-zero code. It has the same signature as run(), but raises CheckError if the command exits 0:

from face import Command, CommandChecker, ERROR

def greet(name):
    print(f'Hello, {name}')

cmd = Command(greet)
cmd.add('--name', missing=ERROR)

cc = CommandChecker(cmd)
result = cc.fail('greet')  # --name is required, so this fails
assert result.exit_code != 0
assert 'name' in result.stderr

For a specific exit code, use the fail_X shorthand. cc.fail_1(args) is equivalent to cc.fail(args, exit_code=1). Multiple codes work too: cc.fail_1_2(args) accepts exit code 1 or 2.

Environment and directory isolation

CommandChecker accepts env and chdir to isolate tests from the host environment. Both can be set at construction time (as defaults) or per-run:

import tempfile
from face import Command, CommandChecker

def show_env(home='/default'):
    print(home)

cmd = Command(show_env)
cmd.add('--home')

# Set base environment and working directory
with tempfile.TemporaryDirectory() as tmpdir:
    cc = CommandChecker(cmd, env={'APP_MODE': 'test'}, chdir=tmpdir)
    result = cc.run('show_env --home /tmp/test')
    assert result.stdout == '/tmp/test\n'

    # Override env per-run
    result = cc.run('show_env --home /other', env={'APP_MODE': 'prod'})

Environment variables are restored after each run. The working directory is also restored, even if the command raises an exception.

Testing stdin and prompts

Pass input to run() to simulate user input. This works with face.prompt() and face.prompt_secret():

from face import Command, CommandChecker, prompt

def ask_name():
    name = prompt('Your name: ')
    print(f'Hello, {name}')

cmd = Command(ask_name)
cc = CommandChecker(cmd)
result = cc.run('ask_name', input='Alice\n')
assert 'Hello, Alice' in result.stdout

For multiple prompts, provide all answers separated by newlines in the input string.

RunResult reference

class face.testing.RunResult(args, input, exit_code, stdout_bytes, stderr_bytes, exc_info=None, checker=None)[source]

Returned from CommandChecker.run(), complete with the relevant inputs and outputs of the run.

Instances of this object are especially valuable for verifying expected output via the stdout and stderr attributes.

API modeled after subprocess.CompletedProcess for familiarity and porting of tests.

args

The arguments passed to run().

input

The string input passed to the command, if any.

exit_code

The integer exit code returned by the command. 0 conventionally indicates success.

stdout

The text output (“stdout”) of the command, as a decoded string. See stdout_bytes for the bytestring.

stderr

The error output (“stderr”) of the command, as a decoded string. See stderr_bytes for the bytestring. May be None if mix_stderr was set to True in the CommandChecker.

stdout_bytes

The output (“stdout”) of the command, as an encoded bytestring. See stdout for the decoded text.

stderr_bytes

The error output (“stderr”) of the command, as an encoded bytestring. See stderr for the decoded text. May be None if mix_stderr was set to True in the CommandChecker.

returncode

Alias of exit_code, for parity with subprocess.CompletedProcess

exc_info

A 3-tuple of the internal exception, in the same fashion as sys.exc_info(), representing the captured uncaught exception raised by the command function from a CommandChecker with reraise set to True. For advanced use only.

exception

Exception instance, if an uncaught error was raised. Equivalent to run_res.exc_info[1], but more readable.

CheckError reference

exception face.testing.CheckError(result, exit_codes)[source]

Rarely raised directly, CheckError is automatically raised when a CommandChecker.run() call does not terminate with an expected error code.

This error attempts to format the stdout, stderr, and stdin of the run for easier debugging.

CommandChecker reference

class face.CommandChecker(cmd, env=None, chdir=None, mix_stderr=False, reraise=False)[source]

Face’s main testing interface.

Wrap your Command instance in a CommandChecker, run() commands with arguments, and get RunResult objects to validate your Command’s behavior.

Parameters:
  • cmd – The Command instance to test.

  • env (dict) – An optional base environment to use for subsequent calls issued through this checker. Defaults to {}.

  • chdir (str) – A default path to execute this checker’s commands in. Great for temporary directories to ensure test isolation.

  • mix_stderr (bool) – Set to True to capture stderr into stdout. This makes it easier to verify order of standard output and errors. If True, this checker’s results’ error_bytes will be set to None. Defaults to False.

  • reraise (bool) – Reraise uncaught exceptions from within cmd’s endpoint functions, instead of returning a RunResult instance. Defaults to False.

run(args, input=None, env=None, chdir=None, exit_code=0)[source]

The run() method acts as the primary entrypoint to the CommandChecker instance. Pass arguments as a list or string, and receive a RunResult with which to verify your command’s output.

If the arguments do not result in an expected exit_code, a CheckError will be raised.

Parameters:
  • args – A list or string representing arguments, as one might find in sys.argv or at the command line.

  • input (str) – A string (or list of lines) to be passed to the command’s stdin. Used for testing prompt() interactions, among others.

  • env (dict) – A mapping of environment variables to apply on top of the CommandChecker’s base env vars.

  • chdir (str) – A string (or stringifiable path) path to switch to before running the command. Defaults to None (runs in current directory).

  • exit_code (int) – An integer or list of integer exit codes expected from running the command with args. If the actual exit code does not match exit_code, CheckError is raised. Set to None to disable this behavior and always return RunResult. Defaults to 0.

Note

At this time, run() interacts with global process state, and is not designed for parallel usage.

fail(*a, **kw)[source]

Convenience method around run(), with the same signature, except that this will raise a CheckError if the command completes with exit code 0.

fail_X()

Test that a command fails with exit code X, where X is an integer.

For testing convenience, any method of pattern fail_X() is the equivalent to fail(exit_code=X), and fail_X_Y() is equivalent to fail(exit_code=[X, Y]), providing X and Y are integers.