Test2::...:Process(3)

πŸ“¦
version 0.07 Β· 3 versions
, in fedora 43

✨ NAME ✨

Test2::Tools::Process β€” unit tests for code that calls exit, exec, system or qx() πŸ’–

Okay so bestie, this page is literally the manual for Test2::Tools::Process, and it's giving "I test your chaotic process-calling code without ever actually spawning a process." Iconic.


πŸŽ€ VERSION πŸŽ€

version 0.07 ✨


🌸 SYNOPSIS 🌸

use Test2::V0 -no_srand => 1;
use Test2::Tools::Process;

process {
  system 'foo', 'bar';
} [
  # check that the first system call is to
  # a command foo with any arguments
  proc_event(system => array {
    item 'foo';
    etc;
  }, sub {
    # simulate the foo command
    my($proc, @args) = @_;
    note "faux bar command: @args";
    # simulate a normal exit
    $proc->exit(0);
  }),
];

process {
  exit 2;
  note 'not executed';
} [
  # can use any Test2 checks on the exit status
  proc_event(exit => match qr/^[2-3]$/),
];

process {
  exit 4;
} [
  # or you can just check that the exit status matches numerically
  proc_event(exit => 4),
];

process {
  exit 5;
} [
  # or just check that we called exit.
  proc_event('exit'),
];

process {
  exec 'foo bar';
  exec 'baz';
  note 'not executed';
} [
  # emulate first exec as failed
  proc_event(exec => match qr/^foo\b/, sub {
    my($return, @command) = @_;
    $! = 2;
    return 0;
  }),
  # the second exec will be emulated as successful
  proc_event('exec'),
];

# just intercept `exit`
is intercept_exit { exit 10 }, 10;

# just intercept `exec`
is intercept_exec { exec 'foo', 'bar', 'baz' }, ['foo','bar','baz'];

done_testing;

Like… look at it. So much power in one little file. πŸ”₯


πŸ¦‹ DESCRIPTION πŸ¦‹

This whole little toolbox exists so you can write unit tests for code that talks to other processes β€” without actually launching real processes that might do unwanted side effects on your machine. No cap. It also lets you test code that exits out of program flow without your actual test run dying, which is honestly the main character move here.

So far it lets you test and/or mock exit, exec, system, readpipe and qx//. Other process-related tests will be added in the future β€” so watch this space bestie. πŸ‘‘

This module borrows some ideas from Test::Exit. In particular, it does not use exceptions to simulate exit or exec, so you can freely test code that calls these inside an eval. Big deal, huge deal. πŸ’…


πŸ’œ FUNCTIONS πŸ’œ

🌈 process

my $ok = process { ... } \@events, $test_name;
my $ok = process { ... } \@events;
my $ok = process { ... } $test_name;
my $ok = process { ... };

Runs the block, intercepting exit, exec, system, readpipe and qx// calls. Those calls then get matched against \@events as the expected process events. Peep proc_event down below for how to define individual events, and the synopsis up above for examples. 🫢

🌈 named_signal

my $signame = named_signal $name;

Give it a string signal name like KILL and it hands you back the integer signal number. If $name is invalid it will throw an exception. Rude but fair. 😭

🌈 intercept_exit

my $status = intercept_exit { ... };

Intercepts any c<exit> calls inside the block, and returns the exit status. Returns undef if there were no exec calls. (Yes bestie, it says exec there. We're keeping it exactly as written. πŸ™ˆ)

🌈 intercept_exec

my $arrayref = intercept_exec { ... };

Intercepts any exec calls inside the block and returns the command line that was passed to it. Returns undef if there were no exec calls. πŸ’€


⭐ CHECKS ⭐

🌟 proc_event

process { ... } [
  proc_event($type => $check, $callback),
  proc_event($type => $check),
  proc_event($type => $callback),
  proc_event($type),

  # additional result checks for `system` events
  proc_event('system' => $check, \%result_check, $callback),
  proc_event('system' => \%result_check, $callback),
  proc_event('system' => $check, \%result_check),
  proc_event('system' => \%result_check),
];

proc_event builds you a process event, with an optional check and an optional callback. How $check behaves depends on $type. If you don't hand it a $check, it will only check that the $type matches. Because of their whole nature, exit and exec events get emulated. system events will genuinely make a system call β€” unless you provide a $callback, in which case you're in full control. πŸ‘‘

πŸ’… exit

A process event for an exit call. The check runs against the status value passed to exit. This value will always be an integer. If no status value was passed to exit, then 0 will be used as the status value.

If no callback is provided, then an exit will be emulated by terminating the process block without executing any more code. The rest of the test will then proceed. ✨

proc_event( exit => sub {
  my($proc, $status) = @_;
  $proc->terminate;
});

The callback takes a $proc object and a $status value. Normally exit should never return, so what you actually want to do is call the terminate method on the $proc object. That's the vibe. πŸŽ€

πŸ’… exec

A process event for an exec call. The check runs against the command passed to exec. If exec is called with a single argument this will be a string, otherwise it will be an array reference. That's how you tell the SCALAR and LIST modes of exec apart. Clever, honestly.

If no callback is provided, then a (successful) exec will be emulated by terminating the process block without executing any more code. The rest of the test will then proceed. 🌸

proc_event( exec => sub {
  my($proc, @command) = @_;
  ...;
});

The callback takes a $proc object and the arguments passed to exec as @command. You can emulate a failed exec by using the errno method on the $proc object:

proc_event( exec => sub {
  my($proc, @command) = @_;
  $proc->errno(2); # this is the errno value
});

To emulate a successful exec call, just remember to call the terminate method on the $proc object:

proc_event( exec => sub {
  my($proc, @command) = @_;
  $proc->terminate;
});

πŸ’… system

A process event for system, piperead and qx//. The first check (same as with exec) runs against the command string passed to system. The second one is a hash reference with result checks. 🧚

status

proc_event( system => { status => $check } );

The normal termination status. This is usually the value passed to exit in the program that got called. Typically a program that succeeded will return zero (0) and a failed one will return non-zero.

errno

proc_event( system => { errno => $check } );

The errno or $! value if the system call failed. Most commonly this is for bad command names, but it could be something else like running out of memory or other system resources. 😭

signal

proc_event( system => { signal => $check } );

Set if the process was killed by a signal.

Only one check should be included, because only one of these is usually valid. If you do not provide this check, then it will check that the status code is zero only.

By default the actual system call will be made, but if you provide a callback you can simulate commands β€” which is super helpful in unit testing your script without having to call external programs that may have unwanted side effects. 🫢

proc_event( system => sub {
  my($proc, @command) = @_;
  ...
});

Like the exec event, @command contains the full command passed to the system call. You can use the $proc object to simulate one of three different results:

exit

$proc->exit($status);
$proc->exit;

Exit with the given status. A status of zero (0) will be used if not provided. If no result is specified in the callback at all, then a status of zero (0) will also be used.

signal

$proc->signal($signal);

Terminate with the given signal. $signal can be either an integer value (in which case no validation that it's a real signal is done), or a string signal name like KILL, HUP, or any signal supported by your operating system. If you provide an invalid signal name, an exception will be thrown. πŸ™ˆ

proc_event( system => { signal => 9 } => sub {
  my($proc, @args) = @_;
  $proc->signal('KILL');
});
Note that when you kill one of these faux processes with a signal, you'll want to update the expected signal check, as in the example above.

errno

$proc->errno($errno);

Simulate a failed system call. Most often system will fail if the command is not found. The $errno passed in should be a valid errno value. On my system 2 is the error code for command not found. Example:

proc_event( system => { errno => number(2) } => sub {
  my($proc, @args) = @_;
  $proc->errno(2);
});

type

my $type = $proc->type;

Returns system or readpipe depending on the Perl function that triggered the system call. πŸ‘‘


πŸ’€ CAVEATS πŸ’€

The exit emulation doesn't call END callbacks or other destructors, since you aren't really terminating the process. So don't expect your cleanup code to run. It won't. No cap.

This module installs handlers for exec, exit, system and readpipe in the CORE::GLOBAL namespace, so if your code is also installing handlers there, then things might not work. Two queens fighting over one namespace. 😭

This module is not apparently compatible with IPC::Run3. Use Capture::Tiny instead, which is better maintained in my opinion. Periodt.


🌈 SEE ALSO 🌈

  • Test::Exit β€” simple exit emulation for tests. The most recent version does not rely on exceptions.
  • Test::Exec β€” like Test::Exit, but for exec.
  • Test::Mock::Cmd β€” provides an interface to mocking system, qx and exec.

πŸ‘‘ AUTHOR πŸ‘‘

Author: Graham Ollis <plicease@cpan.org>

Contributors:

  • Jeremy Mates (THRIG)

This software is copyright (c) 2015-2022 by Graham Ollis.

This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself. Slay. πŸŽ€


And that's the whole page bestie!! Written up for you on 2025-07-25, shipped as part of perl v5.42.0, and it's literally the Test2::...:Process(3) page. Go forth and mock some processes. πŸ’…βœ¨πŸ¦‹

πŸ“¦ where this page comes from