.\" -*- mode: troff; coding: utf-8 -*- .\" Automatically generated by Pod::Man 5.01 (Pod::Simple 3.43) .\" .\" Standard preamble: .\" ======================================================================== .de Sp \" Vertical space (when we can't use .PP) .if t .sp .5v .if n .sp .. .de Vb \" Begin verbatim text .ft CW .nf .ne \\$1 .. .de Ve \" End verbatim text .ft R .fi .. .\" \*(C` and \*(C' are quotes in nroff, nothing in troff, for use with C<>. .ie n \{\ . ds C` "" . ds C' "" 'br\} .el\{\ . ds C` . ds C' 'br\} .\" .\" Escape single quotes in literal strings from groff's Unicode transform. .ie \n(.g .ds Aq \(aq .el .ds Aq ' .\" .\" If the F register is >0, we'll generate index entries on stderr for .\" titles (.TH), headers (.SH), subsections (.SS), items (.Ip), and index .\" entries marked with X<> in POD. Of course, you'll have to process the .\" output yourself in some meaningful fashion. .\" .\" Avoid warning from groff about undefined register 'F'. .de IX .. .nr rF 0 .if \n(.g .if rF .nr rF 1 .if (\n(rF:(\n(.g==0)) \{\ . if \nF \{\ . de IX . tm Index:\\$1\t\\n%\t"\\$2" .. . if !\nF==2 \{\ . nr % 0 . nr F 2 . \} . \} .\} .rr rF .\" ======================================================================== .\" .IX Title "Test2::Tools::Process 3" .TH Test2::Tools::Process 3 2023-07-26 "perl v5.38.0" "User Contributed Perl Documentation" .\" For nroff, turn off justification. Always turn off hyphenation; it makes .\" way too many mistakes in technical documents. .if n .ad l .nh .SH NAME Test2::Tools::Process \- Unit tests for code that calls exit, exec, system or qx() .SH VERSION .IX Header "VERSION" version 0.07 .SH SYNOPSIS .IX Header "SYNOPSIS" .Vb 2 \& use Test2::V0 \-no_srand => 1; \& use Test2::Tools::Process; \& \& process { \& system \*(Aqfoo\*(Aq, \*(Aqbar\*(Aq; \& } [ \& # check that the first system call is to \& # a command foo with any arguments \& proc_event(system => array { \& item \*(Aqfoo\*(Aq; \& 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 \*(Aqnot executed\*(Aq; \& } [ \& # 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(\*(Aqexit\*(Aq), \& ]; \& \& process { \& exec \*(Aqfoo bar\*(Aq; \& exec \*(Aqbaz\*(Aq; \& note \*(Aqnot executed\*(Aq; \& } [ \& # emulate first exec as failed \& proc_event(exec => match qr/^foo\eb/, sub { \& my($return, @command) = @_; \& $! = 2; \& return 0; \& }), \& # the second exec will be emulated as successful \& proc_event(\*(Aqexec\*(Aq), \& ]; \& \& # just intercept \`exit\` \& is intercept_exit { exit 10 }, 10; \& \& # just intercept \`exec\` \& is intercept_exec { exec \*(Aqfoo\*(Aq, \*(Aqbar\*(Aq, \*(Aqbaz\*(Aq }, [\*(Aqfoo\*(Aq,\*(Aqbar\*(Aq,\*(Aqbaz\*(Aq]; \& \& done_testing; .Ve .SH DESCRIPTION .IX Header "DESCRIPTION" This set of testing tools is intended for writing unit tests for code that interacts with other processes without using real processes that might have unwanted side effects. It also lets you test code that exits program flow without actually terminating your test. So far it allows you to test and/or mock \f(CW\*(C`exit\*(C'\fR, \f(CW\*(C`exec\*(C'\fR, \f(CW\*(C`system\*(C'\fR, \&\f(CW\*(C`readpipe\*(C'\fR and \f(CW\*(C`qx//\*(C'\fR. Other process related tests will be added in the future. .PP This module borrows some ideas from Test::Exit. In particular it does not use exceptions to simulate \f(CW\*(C`exit\*(C'\fR or \f(CW\*(C`exec\*(C'\fR, so you can freely test code that calls these in an \f(CW\*(C`eval\*(C'\fR. .SH FUNCTIONS .IX Header "FUNCTIONS" .SS process .IX Subsection "process" .Vb 4 \& my $ok = process { ... } \e@events, $test_name; \& my $ok = process { ... } \e@events; \& my $ok = process { ... } $test_name; \& my $ok = process { ... }; .Ve .PP Runs the block, intercepting \f(CW\*(C`exit\*(C'\fR, \f(CW\*(C`exec\*(C'\fR, \f(CW\*(C`system\*(C'\fR, \f(CW\*(C`readpipe\*(C'\fR and \&\f(CW\*(C`qx//\*(C'\fR calls. The calls are then matched against \f(CW\*(C`\e@events\*(C'\fR as the expected process events. See \f(CW\*(C`proc_event\*(C'\fR below for defining individual events, and the synopsis above for examples. .SS named_signal .IX Subsection "named_signal" .Vb 1 \& my $signame = named_signal $name; .Ve .PP Given a string signal name like \f(CW\*(C`KILL\*(C'\fR, this will return the integer signal number. It will throw an exception if the \f(CW$name\fR is invalid. .SS intercept_exit .IX Subsection "intercept_exit" .Vb 1 \& my $status = intercept_exit { ... }; .Ve .PP Intercept any c calls inside the block, and return the exit status. Returns \f(CW\*(C`undef\*(C'\fR if there were no \f(CW\*(C`exec\*(C'\fR calls. .SS intercept_exec .IX Subsection "intercept_exec" .Vb 1 \& my $arrayref = intercept_exec { ... }; .Ve .PP Intercept any \f(CW\*(C`exec\*(C'\fR calls inside the block and return the command line that a was passed to it. Returns \f(CW\*(C`undef\*(C'\fR if there were no \f(CW\*(C`exec\*(C'\fR calls. .SH CHECKS .IX Header "CHECKS" .SS proc_event .IX Subsection "proc_event" .Vb 5 \& 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(\*(Aqsystem\*(Aq => $check, \e%result_check, $callback), \& proc_event(\*(Aqsystem\*(Aq => \e%result_check, $callback), \& proc_event(\*(Aqsystem\*(Aq => $check, \e%result_check), \& proc_event(\*(Aqsystem\*(Aq => \e%result_check), \& ]; .Ve .PP The \f(CW\*(C`proc_event\*(C'\fR function creates a process event, with an optional check and callback. How the \&\f(CW$check\fR works depends on the \f(CW$type\fR. If no \f(CW$check\fR is provided then it will only check that the \f(CW$type\fR matches. Due to their nature, \f(CW\*(C`exit\*(C'\fR and \f(CW\*(C`exec\*(C'\fR events are emulated. \f(CW\*(C`system\*(C'\fR events will actually make a system call, unless a \f(CW$callback\fR is provided. .IP exit 4 .IX Item "exit" A process event for an \f(CW\*(C`exit\*(C'\fR call. The check is against the status value passed to \f(CW\*(C`exit\*(C'\fR. This value will always be an integer. If no status value was passed to \f(CW\*(C`exit\*(C'\fR, \f(CW0\fR will be used as the status value. .Sp If no callback is provided then an \f(CW\*(C`exit\*(C'\fR will be emulated by terminating the process block without executing any more code. The rest of the test will then proceed. .Sp .Vb 4 \& proc_event( exit => sub { \& my($proc, $status) = @_; \& $proc\->terminate; \& }); .Ve .Sp The callback takes a \f(CW$proc\fR object and a \f(CW$status\fR value. Normally \f(CW\*(C`exit\*(C'\fR should never return, so what you want to do is call the \f(CW\*(C`terminate\*(C'\fR method on the \f(CW$proc\fR object. .IP exec 4 .IX Item "exec" A process event for an \f(CW\*(C`exec\*(C'\fR call. The check is against the command passed to \f(CW\*(C`exec\*(C'\fR. If \f(CW\*(C`exec\*(C'\fR is called with a single argument this will be a string, otherwise it will be an array reference. This way you can differentiate between the SCALAR and LIST modes of \f(CW\*(C`exec\*(C'\fR. .Sp If no callback is provided then a (successful) \f(CW\*(C`exec\*(C'\fR will be emulated by terminating the process block without executing any more code. The rest of the test will then proceed. .Sp .Vb 4 \& proc_event( exec => sub { \& my($proc, @command) = @_; \& ...; \& }); .Ve .Sp The callback takes a \f(CW$proc\fR object and the arguments passed to \f(CW\*(C`exec\*(C'\fR as \f(CW@command\fR. You can emulate a failed \f(CW\*(C`exec\*(C'\fR by using the \f(CW\*(C`errno\*(C'\fR method on the \f(CW$proc\fR object: .Sp .Vb 4 \& proc_event( exec => sub { \& my($proc, @command) = @_; \& $proc\->errno(2); # this is the errno value \& }); .Ve .Sp To emulate a successful \f(CW\*(C`exec\*(C'\fR call you want to just remember to call the \f(CW\*(C`terminate\*(C'\fR method on the \f(CW$proc\fR object. .Sp .Vb 4 \& proc_event( exec => sub { \& my($proc, @command) = @_; \& $proc\->terminate; \& }); .Ve .IP system 4 .IX Item "system" A process event for \f(CW\*(C`system\*(C'\fR, \f(CW\*(C`piperead\*(C'\fR and \f(CW\*(C`qx//\*(C'\fR. The first check (as with \f(CW\*(C`exec\*(C'\fR) is against the command string passed to \f(CW\*(C`system\*(C'\fR. The second is a hash reference with result checks. .RS 4 .IP status 4 .IX Item "status" .Vb 1 \& proc_event( system => { status => $check } ); .Ve .Sp The normal termination status. This is usually the value passed to \f(CW\*(C`exit\*(C'\fR in the program called. Typically a program that succeeded will return zero (\f(CW0\fR) and a failed on will return non-zero. .IP errno 4 .IX Item "errno" .Vb 1 \& proc_event( system => { errno => $check } ); .Ve .Sp The \f(CW\*(C`errno\*(C'\fR or \f(CW$!\fR 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. .IP signal 4 .IX Item "signal" .Vb 1 \& proc_event( system => { signal => $check } ); .Ve .Sp Set if the process was killed by a signal. .RE .RS 4 .Sp 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. .Sp By default the actual system call will be made, but if you provide a callback you can simulate commands, which is helpful in unit testing your script without having to call external programs which may have unwanted side effects. .Sp .Vb 4 \& proc_event( system => sub { \& my($proc, @command) = @_; \& ... \& }); .Ve .Sp Like the \f(CW\*(C`exec\*(C'\fR event, \f(CW@command\fR contains the full command passed to the \f(CW\*(C`system\*(C'\fR call. You can use the \&\f(CW$proc\fR object to simulate one of three different results: .IP exit 4 .IX Item "exit" .Vb 2 \& $proc\->exit($status); \& $proc\->exit; .Ve .Sp 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. .IP signal 4 .IX Item "signal" .Vb 1 \& $proc\->signal($signal); .Ve .Sp Terminate with the given signal. \f(CW$signal\fR can be either an integer value (in which case no validation that it is a real signal is done), or a string signal name like \f(CW\*(C`KILL\*(C'\fR, \f(CW\*(C`HUP\*(C'\fR or any signal supported by your operating system. If you provide an invalid signal name an exception will be thrown. .Sp .Vb 4 \& proc_event( system => { signal => 9 } => sub { \& my($proc, @args) = @_; \& $proc\->signal(\*(AqKILL\*(Aq); \& }); .Ve .Sp Note that when you kill one of these faux processes with a signal you will want to update the expected signal check, as in the example above. .IP errno 4 .IX Item "errno" .Vb 1 \& $proc\->errno($errno); .Ve .Sp Simulate a failed \f(CW\*(C`system\*(C'\fR call. Most often \f(CW\*(C`system\*(C'\fR will fail if the command is not found. The \f(CW$errno\fR passed in should be a valid \f(CW\*(C`errno\*(C'\fR value. On my system \f(CW2\fR is the error code for command not found. Example: .Sp .Vb 4 \& proc_event( system => { errno => number(2) } => sub { \& my($proc, @args) = @_; \& $proc\->errno(2); \& }); .Ve .IP type 4 .IX Item "type" .Vb 1 \& my $type = $proc\->type; .Ve .Sp Returns \f(CW\*(C`system\*(C'\fR or \f(CW\*(C`readpipe\*(C'\fR depending on the Perl function that triggered the system call. .RE .RS 4 .RE .SH CAVEATS .IX Header "CAVEATS" The \f(CW\*(C`exit\*(C'\fR emulation, doesn't call \f(CW\*(C`END\*(C'\fR callbacks or other destructors, since you aren't really terminating the process. .PP This module installs handlers for \f(CW\*(C`exec\*(C'\fR, \f(CW\*(C`exit\*(C'\fR, \f(CW\*(C`system\*(C'\fR and \f(CW\*(C`readpipe\*(C'\fR, in the \f(CW\*(C`CORE::GLOBAL\*(C'\fR namespace, so if your code is also installing handlers there then things might not work. .PP This module is not apparently compatible with IPC::Run3. Use Capture::Tiny instead, which is better maintained in my opinion. .SH "SEE ALSO" .IX Header "SEE ALSO" .IP Test::Exit 4 .IX Item "Test::Exit" Simple \f(CW\*(C`exit\*(C'\fR emulation for tests. The most recent version does not rely on exceptions. .IP Test::Exec 4 .IX Item "Test::Exec" Like Test::Exit, but for \f(CW\*(C`exec\*(C'\fR .IP Test::Mock::Cmd 4 .IX Item "Test::Mock::Cmd" Provides an interface to mocking \f(CW\*(C`system\*(C'\fR, \f(CW\*(C`qx\*(C'\fR and \f(CW\*(C`exec\*(C'\fR. .SH AUTHOR .IX Header "AUTHOR" Author: Graham Ollis .PP Contributors: .PP Jeremy Mates (THRIG) .SH "COPYRIGHT AND LICENSE" .IX Header "COPYRIGHT AND LICENSE" This software is copyright (c) 2015\-2022 by Graham Ollis. .PP This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself.