aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--Capture.xs28
-rw-r--r--Changes26
-rw-r--r--MANIFEST67
-rw-r--r--Makefile.PL8
-rw-r--r--README4
-rw-r--r--capture.c152
-rw-r--r--capture.h12
-rw-r--r--lib/POSIX/Run/Capture.pm65
m---------runcap0
-rw-r--r--t/09linemon.t35
-rw-r--r--t/10file.t24
-rw-r--r--t/11env.t35
13 files changed, 308 insertions, 149 deletions
diff --git a/.gitignore b/.gitignore
index 7c6f9ca..e0aaffd 100644
--- a/.gitignore
+++ b/.gitignore
@@ -15,3 +15,4 @@
*.o
Makefile.old
/capture.o
+/MANIFEST
diff --git a/Capture.xs b/Capture.xs
index 19da27b..a2c1d21 100644
--- a/Capture.xs
+++ b/Capture.xs
@@ -8,6 +8,7 @@ capture_new(package, ...)
char *package;
PREINIT:
ARGV argv = NULL;
+ ARGV env = NULL;
unsigned timeout = 0;
SV *cb[2] = { &PL_sv_undef, &PL_sv_undef };
SV *prog = &PL_sv_undef;
@@ -37,13 +38,15 @@ capture_new(package, ...)
argv = XS_unpack_ARGV(val);
} else
croak("argv must be an array ref");
- } else if (strcmp(kw, "stdout") == 0
- || strcmp(kw, "stderr") == 0) {
+ } else if (strcmp(kw, "env") == 0) {
if (SvROK(val)
- && SvTYPE(SvRV(val)) == SVt_PVCV) {
- cb[kw[3] == 'o' ? 0 : 1] = SvRV(val);
+ && SvTYPE(SvRV(val)) == SVt_PVAV) {
+ env = XS_unpack_ARGV(val);
} else
- croak("%s must be a code ref", kw);
+ croak("env must be an array ref");
+ } else if (strcmp(kw, "stdout") == 0
+ || strcmp(kw, "stderr") == 0) {
+ cb[kw[3] == 'o' ? 0 : 1] = val;
} else if (strcmp(kw, "timeout") == 0) {
if (SvIOK(val)) {
timeout = SvUV(val);
@@ -61,7 +64,7 @@ capture_new(package, ...)
croak("unknown keyword argument %s", kw);
}
}
- RETVAL = capture_new(prog, argv, timeout, cb, input);
+ RETVAL = capture_new(prog, argv, env, timeout, cb, input);
OUTPUT:
RETVAL
@@ -83,6 +86,11 @@ capture_set_argv_ref(cp, argv)
ARGV argv;
void
+capture_set_env_ref(cp, env)
+ POSIX::Run::Capture cp;
+ ARGV env;
+
+void
capture_set_program(cp, prog)
POSIX::Run::Capture cp;
char *prog = NO_INIT;
@@ -122,6 +130,14 @@ capture_argv(cp)
OUTPUT:
RETVAL
+ARGV
+capture_env(cp)
+ POSIX::Run::Capture cp;
+ CODE:
+ RETVAL = cp->rc.rc_env;
+ OUTPUT:
+ RETVAL
+
void
capture_program(cp)
POSIX::Run::Capture cp;
diff --git a/Changes b/Changes
index fce6b88..92a0966 100644
--- a/Changes
+++ b/Changes
@@ -1,6 +1,30 @@
Revision history for Perl extension POSIX::Run::Capture.
-0.02 Fri Dec 1 08:54:18
+1.05 Fri Mar 15 13:47:40 2024
+ - New constructor argument 'env' allows you to supply environment
+ to the command to be run.
+ - Fix packaging
+
+1.04 Fri Mar 15 13:31:50 2024
+ - New constructor argument 'env' allows you to supply environment
+ to the command to be run.
+
+1.03 Tue Feb 23 12:21:17 2021
+ - Fix packaging
+
+1.02 Sat Feb 13 09:30:49 2021
+ - change bugtracker address
+
+1.01 Fri Jan 31 09:16:30 2020
+ - Argument to the 'stderr' or 'stdout' can be a code reference,
+ glob (file handle) or scalar string. In the latter two cases,
+ the stream is redirected to the file handle or the named file.
+
+1.00 Wed Aug 14 08:11:53 2019
+ - line monitor is called for every line, whether or not terminated
+ with a newline
+
+0.02 Fri Dec 1 08:54:18 2017
- first release
0.01 Thu Jul 20 18:17:18 2017
diff --git a/MANIFEST b/MANIFEST
deleted file mode 100644
index 9cb5b09..0000000
--- a/MANIFEST
+++ /dev/null
@@ -1,67 +0,0 @@
-capture.c
-capture.h
-Capture.xs
-Changes
-lib/POSIX/Run/Capture.pm
-LICENSE
-Makefile.PL
-MANIFEST This list of files
-MANIFEST.SKIP
-ppport.h
-README
-runcap/aclocal.m4
-runcap/compile
-runcap/config.guess
-runcap/config.sub
-runcap/configure
-runcap/configure.ac
-runcap/depcomp
-runcap/getc.c
-runcap/getl.c
-runcap/install-sh
-runcap/install.am
-runcap/install.in
-runcap/libtool
-runcap/ltmain.sh
-runcap/Make.am
-runcap/Makefile.am
-runcap/Makefile.in
-runcap/missing
-runcap/runcap.3
-runcap/runcap.c
-runcap/runcap.h
-runcap/runcap.m4
-runcap/seek.c
-runcap/shared.am
-runcap/static.am
-runcap/static.in
-runcap/t/atlocal.in
-runcap/t/genout.c
-runcap/t/INPUT
-runcap/t/linemon00.at
-runcap/t/linemon01.at
-runcap/t/lines.at
-runcap/t/longout.at
-runcap/t/Makefile.am
-runcap/t/Makefile.in
-runcap/t/package.m4
-runcap/t/pipe.at
-runcap/t/rt.c
-runcap/t/seek00.at
-runcap/t/seek01.at
-runcap/t/simple.at
-runcap/t/stdin.at
-runcap/t/testsuite
-runcap/t/two.at
-runcap/tell.c
-t/00use.t
-t/01argv.t
-t/02prog.t
-t/03timeout.t
-t/04init.t
-t/05simple.t
-t/06lines.t
-t/07two.t
-t/08input.t
-t/TestCapture.pm
-typemap
diff --git a/Makefile.PL b/Makefile.PL
index 4aee252..b41b5c8 100644
--- a/Makefile.PL
+++ b/Makefile.PL
@@ -9,6 +9,10 @@ WriteMakefile(
VERSION_FROM => 'lib/POSIX/Run/Capture.pm',
LICENSE => 'gpl_3',
PREREQ_PM => {}, # e.g., Module::Name => 1.1
+ TEST_REQUIRES => {
+ 'File::Temp' => 0.23,
+ 'File::Cmp' => 1.06
+ },
MIN_PERL_VERSION => 5.006,
ABSTRACT_FROM => 'lib/POSIX/Run/Capture.pm',
AUTHOR => 'Sergey Poznyakoff <gray@gnu.org>',
@@ -25,6 +29,10 @@ WriteMakefile(
url => 'git://git.gnu.org.ua/posixruncapture.git',
web => 'http://git.gnu.org.ua/cgit/posixruncapture.git/',
},
+ bugtracker => {
+ web => 'https://puszcza.gnu.org.ua/bugs/?group=posixruncapture',
+ mailto => 'gray+posixruncapture@gnu.org.ua'
+ }
},
provides => Module::Metadata->provides(version => '1.4',
dir => 'lib')
diff --git a/README b/README
index 65527e4..be0cb9f 100644
--- a/README
+++ b/README
@@ -1,4 +1,4 @@
-POSIX-Run-Capture version 0.01
+POSIX-Run-Capture version 1.03
==============================
POSIX::Run::Capture runs an external command and captures its output. Both
@@ -22,7 +22,7 @@ To install this module type the following:
COPYRIGHT AND LICENCE
-Copyright (C) 2017 by Sergey Poznyakoff
+Copyright (C) 2017-2024 by Sergey Poznyakoff
This library is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by the
diff --git a/capture.c b/capture.c
index 2d2bfd0..f42bd0c 100644
--- a/capture.c
+++ b/capture.c
@@ -30,7 +30,12 @@ line_monitor(const char *ptr, size_t sz, void *closure)
{
struct line_closure *lc = closure;
- if (lc->len || ptr[sz-1] != '\n') {
+ if (sz == 0) {
+ if (lc->len) {
+ call_monitor(lc->cv, lc->str, lc->len);
+ lc->len = 0;
+ }
+ } else if (lc->len || ptr[sz-1] != '\n') {
size_t newsz = lc->len + sz + 1;
if (newsz > lc->size) {
@@ -102,20 +107,75 @@ XS_unpack_ARGV(SV *sv)
}
static void
-free_argv(struct capture *cp)
+free_argv(char **argv)
{
- if (cp->rc.rc_argv) {
+ if (argv) {
size_t i;
- for (i = 0; cp->rc.rc_argv[i]; i++) {
- free(cp->rc.rc_argv[i]);
+ for (i = 0; argv[i]; i++) {
+ free(argv[i]);
}
- free(cp->rc.rc_argv);
- cp->rc.rc_argv = NULL;
+ free(argv);
}
}
+static void
+capture_set_output(struct capture *cp, SV *cb[2], int strno)
+{
+ SV *sv = cb[strno-1];
+
+ if (sv == &PL_sv_undef)
+ return;
+
+ if (SvROK(sv)) {
+ sv = SvRV(sv);
+
+ // FIXME: Do we need SvGETMAGIC (sv);?
+ if (SvTYPE(sv) == SVt_PVCV) {
+ SvREFCNT_inc(sv);
+ cp->closure[strno-1].cv = sv;
+ cp->rc.rc_cap[strno].sc_linemon = line_monitor;
+ cp->rc.rc_cap[strno].sc_monarg = &cp->closure[strno-1];
+ cp->flags |= RCF_SC_TO_FLAG(RCF_SC_LINEMON, strno);
+ } else if (SvTYPE(sv) == SVt_PVGV) {
+ int fd;
+
+ // FIXME: Check if sv is writable
+ SvREFCNT_inc(sv);
+ cp->closure[strno-1].cv = sv;
+ cp->rc.rc_cap[strno].sc_storfd =
+ PerlIO_fileno(IoOFP(sv_2io(sv)));
+ cp->flags |= RCF_SC_TO_FLAG(RCF_SC_STORFD, strno);
+ } else {
+ static char *what[] = { "stdout", "stderr" };
+ croak("%s must be scalar, glob or code ref",
+ what[strno-1]);
+ }
+ } else {
+ char *filename = SvPV_nolen(sv);
+ int fd = open(filename, O_CREAT|O_TRUNC|O_RDWR, 0666);
+ if (fd == -1) {
+ croak("can't open file %s for writing: %s",
+ filename, strerror(errno));
+ }
+ cp->rc.rc_cap[strno].sc_storfd = fd;
+ cp->flags |= RCF_SC_TO_FLAG(RCF_SC_STORFD, strno);
+ cp->closure[strno-1].fd = fd;
+ }
+}
+
+static void
+capture_close_output(struct capture *cp, int strno)
+{
+ strno--;
+ free(cp->closure[strno].str);
+ if (cp->closure[strno].cv != &PL_sv_undef)
+ SvREFCNT_dec(cp->closure[strno].cv);
+ if (cp->closure[strno].fd != -1)
+ close(cp->closure[strno].fd);
+}
+
struct capture *
-capture_new(SV *program, ARGV argv, unsigned timeout, SV *cb[2], SV *input)
+capture_new(SV *program, ARGV argv, ARGV env, unsigned timeout, SV *cb[2], SV *input)
{
struct capture *cp;
I32 i, n;
@@ -124,7 +184,8 @@ capture_new(SV *program, ARGV argv, unsigned timeout, SV *cb[2], SV *input)
if (!cp)
croak_nomem();
memset(cp, 0, sizeof *cp);
-
+ cp->closure[0].fd = cp->closure[1].fd = -1;
+
cp->rc.rc_argv = argv;
cp->program = program;
@@ -133,28 +194,19 @@ capture_new(SV *program, ARGV argv, unsigned timeout, SV *cb[2], SV *input)
cp->rc.rc_program = SvPV_nolen(program);
cp->flags |= RCF_PROGRAM;
}
+
+ if (env) {
+ cp->rc.rc_env = env;
+ cp->flags |= RCF_ENV;
+ }
if (timeout) {
cp->rc.rc_timeout = timeout;
cp->flags |= RCF_TIMEOUT;
}
- cp->closure[0].cv = cb[0];
- if (cb[0] != &PL_sv_undef) {
- SvREFCNT_inc(cb[0]);
- cp->rc.rc_cap[RUNCAP_STDOUT].sc_linemon = line_monitor;
- cp->rc.rc_cap[RUNCAP_STDOUT].sc_monarg = &cp->closure[0];
- cp->flags |= RCF_STDOUT_LINEMON;
- }
-
- cp->closure[1].cv = cb[1];
- if (cb[1] != &PL_sv_undef) {
- SvREFCNT_inc(cb[1]);
- cp->closure[1].cv = cb[1];
- cp->rc.rc_cap[RUNCAP_STDERR].sc_linemon = line_monitor;
- cp->rc.rc_cap[RUNCAP_STDERR].sc_monarg = &cp->closure[1];
- cp->flags |= RCF_STDERR_LINEMON;
- }
+ capture_set_output(cp, cb, RUNCAP_STDOUT);
+ capture_set_output(cp, cb, RUNCAP_STDERR);
cp->input = &PL_sv_undef;
capture_set_input(cp, input);
@@ -174,16 +226,14 @@ capture_DESTROY(struct capture *cp)
*/
cp->rc.rc_cap[RUNCAP_STDIN].sc_base = NULL;
cp->rc.rc_cap[RUNCAP_STDIN].sc_fd = -1;
-
- free(cp->closure[0].str);
- if (cp->closure[0].cv != &PL_sv_undef)
- SvREFCNT_dec(cp->closure[0].cv);
- free(cp->closure[1].str);
- if (cp->closure[1].cv != &PL_sv_undef)
- SvREFCNT_dec(cp->closure[1].cv);
+ capture_close_output(cp, RUNCAP_STDOUT);
+ capture_close_output(cp, RUNCAP_STDERR);
- free_argv(cp);
+ free_argv(cp->rc.rc_env);
+ cp->rc.rc_env = NULL;
+ free_argv(cp->rc.rc_argv);
+ cp->rc.rc_argv = NULL;
runcap_free(&cp->rc);
free(cp);
@@ -231,10 +281,21 @@ capture_set_input(struct capture *cp, SV *inp)
void
capture_set_argv_ref(struct capture *cp, ARGV argv)
{
- free_argv(cp);
+ free_argv(cp->rc.rc_argv);
cp->rc.rc_argv = argv;
}
+void
+capture_set_env_ref(struct capture *cp, ARGV env)
+{
+ free_argv(cp->rc.rc_env);
+ cp->rc.rc_env = env;
+ if (cp->rc.rc_env == NULL)
+ cp->flags &= ~RCF_ENV;
+ else
+ cp->flags |= RCF_ENV;
+}
+
char *
capture_next_line(struct capture *cp, int fd)
{
@@ -256,27 +317,8 @@ capture_next_line(struct capture *cp, int fd)
int
capture_run(struct capture *cp)
{
- int res;
-
if (!cp->rc.rc_argv)
- croak("no command line given");
-
- res = runcap(&cp->rc, cp->flags);
-
- if (cp->flags & RCF_STDOUT_LINEMON && cp->closure[0].len) {
- call_monitor(cp->closure[0].cv,
- cp->closure[0].str,
- cp->closure[0].len);
- cp->closure[0].len = 0;
- }
-
- if (cp->flags & RCF_STDERR_LINEMON && cp->closure[1].len) {
- call_monitor(cp->closure[1].cv,
- cp->closure[1].str,
- cp->closure[1].len);
- cp->closure[1].len = 0;
- }
-
- return res == 0;
+ croak("no command line given");
+ return runcap(&cp->rc, cp->flags) == 0;
}
diff --git a/capture.h b/capture.h
index cad375d..cb6fe68 100644
--- a/capture.h
+++ b/capture.h
@@ -9,10 +9,11 @@
struct line_closure
{
- char *str;
- size_t len;
- size_t size;
- SV *cv;
+ char *str; /* Line buffer */
+ size_t len; /* Length of the collected line in buffer */
+ size_t size; /* Line buffer size */
+ SV *cv; /* Ref to Perl callback sub or file handle */
+ int fd; /* Close this descriptor at destroy, unless -1 */
};
struct capture {
@@ -29,9 +30,10 @@ typedef char** ARGV;
ARGV XS_unpack_ARGV(SV *sv);
void XS_pack_ARGV(SV *const sv, ARGV argv);
-struct capture *capture_new(SV *pn, ARGV argv, unsigned timeout, SV *cb[2], SV *input);
+struct capture *capture_new(SV *pn, ARGV argv, ARGV env, unsigned timeout, SV *cb[2], SV *input);
void capture_DESTROY(struct capture *rc);
char *capture_next_line(struct capture *rc, int fd);
int capture_run(struct capture *cp);
void capture_set_argv_ref(struct capture *cp, ARGV av);
+void capture_set_env_ref(struct capture *cp, ARGV av);
void capture_set_input(struct capture *cp, SV *inp);
diff --git a/lib/POSIX/Run/Capture.pm b/lib/POSIX/Run/Capture.pm
index ca1446a..25fc8be 100644
--- a/lib/POSIX/Run/Capture.pm
+++ b/lib/POSIX/Run/Capture.pm
@@ -28,7 +28,7 @@ our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } );
#our @EXPORT = qw();
-our $VERSION = '0.02';
+our $VERSION = '1.05';
require XSLoader;
XSLoader::load('POSIX::Run::Capture', $VERSION);
@@ -55,6 +55,11 @@ sub set_argv {
$self->set_argv_ref([@_]);
}
+sub set_env {
+ my $self = shift;
+ $self->set_env_ref([@_]);
+}
+
1;
__END__
=head1 NAME
@@ -67,9 +72,10 @@ POSIX::Run::Capture - run command and capture its output
$obj = new POSIX::Run::Capture(argv => [ $command, @args ],
program => $prog,
+ env => [ @environment ],
stdin => $fh_or_string,
- stdout => sub { ... },
- stderr => sub { ... },
+ stdout => $ref_or_string,
+ stderr => $ref_or_string,
timeout => $n);
$obj->run;
@@ -82,19 +88,22 @@ POSIX::Run::Capture - run command and capture its output
$aref = $obj->get_lines($chan);
$obj->rewind($chan)
+ $obj->set_argv(@argv);
$obj->set_program($prog);
+ $obj->set_env(@argv);
$obj->set_timeout($n);
$obj->set_input($fh_or_string);
$aref = $obj->argv;
$str = $obj->program
+ $aref = $obj->env;
$num = $obj->timeout;
=head1 DESCRIPTION
Runs an external command and captures its output. Both standard error and
output can be captured. Standard input can be supplied as either a
-filehandle or a text. Upon exit, the captured streams can be accessed line
+filehandle or a string. Upon exit, the captured streams can be accessed line
by line or in one chunk. Callback routines can be supplied that will be
called for each complete line of output read, providing a way for synchronous
processing.
@@ -108,9 +117,10 @@ Creates a new capture object. There are three possible invocation modes.
new POSIX::Run::Capture(argv => [ $command, @args ],
program => $prog,
+ env => [ @environment ],
stdin => $fh_or_string,
- stdout => sub { ... },
- stderr => sub { ... },
+ stdout => $ref_or_string,
+ stderr => $ref_or_string,
timeout => $n)
When named arguments are used, the following keywords are allowed:
@@ -126,12 +136,17 @@ B<program> argument, B<$argv[0]> will be run.
Sets the pathname of binary file to run.
+=item B<env>
+
+Defines execution environment. By default, environment variables are
+inherited from the calling process.
+
=item B<stdin> or B<input>
Supplies standard input for the command. The argument can be a string or
a file handle.
-=item B<stdout>
+=item B<stdout> =E<gt> I<$coderef>
Sets the I<line monitor> function for standard output. Line monitor is
invoked each time a complete line is read, or the EOF is hit on the standard
@@ -145,10 +160,22 @@ following example monitor function prints its argument to STDOUT:
Notice that the last line read can lack the teminating newline character.
-=item B<stderr>
+=item B<stdout> =E<gt> I<FH>
+
+Redirect standard output to file handle I<FH>. Obviously, the handle should
+be writable.
+
+=item B<stdout> =E<gt> I<NAME>
+
+Capture standard output and write it to the file I<NAME>. If the file
+exists, it will be truncated. Otherwise, it will be created with permissions
+of 0666 modified by the process' "umask" value.
-Sets the I<line monitor> function for standard error stream. See the
-description above.
+=item B<stderr> =E<gt> I<$arg>
+
+Sets the I<line monitor> function for standard error or redirects it to
+the file handle or file, depending on the type of I<$arg> (CODE reference,
+GLOB or scalar string). For details, see the description of B<stdout> above.
=item B<timeout>
@@ -169,19 +196,27 @@ A simplified way of creating the object, equivalent to
Crates an empty capture object.
Whatever constructor is used, the necessary parameters can be set
-or changed later, using B<set_argv>, B<set_program>, B<set_input>,
+or changed later, using B<set_argv>, B<set_program>, B<set_env>, B<set_input>,
and B<set_timeout>.
-Monitors can be defined only when creating the object.
+Monitors and redirections can be defined only when creating the object.
=head2 Modifying the object.
The following methods modify the object:
+
+=head3 $obj->set_argv(@argv)
+
+Set arguments array.
=head3 $obj->set_program($prog)
Sets the pathname of the command to run.
+=head3 $obj->set_env(@env)
+
+Set environment variables.
+
=head3 $obj->set_timeout($n)
Sets runtime timeout, in seconds.
@@ -204,6 +239,10 @@ Returns a reference to the B<argv> array associated with the object.
Returns the pathname of the executable program.
+=head3 $obj->env
+
+Returns a reference to the array of environment variables.
+
=head3 $obj->timeout
Returns the runtime timeout or B<0> if no timeout is set.
@@ -283,7 +322,7 @@ Sergey Poznyakoff, E<lt>gray@gnu.orgE<gt>
=head1 COPYRIGHT AND LICENSE
-Copyright (C) 2017 by Sergey Poznyakoff
+Copyright (C) 2017-2024 by Sergey Poznyakoff
This library is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by the
diff --git a/runcap b/runcap
-Subproject 048800a78f64808116bb9b943837062a67f586a
+Subproject e879cd3085ff09f2ff7aff88b9a725d69f209a2
diff --git a/t/09linemon.t b/t/09linemon.t
new file mode 100644
index 0000000..1f1666a
--- /dev/null
+++ b/t/09linemon.t
@@ -0,0 +1,35 @@
+# -*- perl -*-
+
+use lib 't', 'lib';
+
+use strict;
+use warnings;
+use TestCapture;
+use Test::More tests => 3;
+
+our($catbin, $input, $content);
+
+my @lines;
+
+TestCapture({ argv => [$catbin, $input],
+ stdout => sub { push @lines, shift } });
+
+is($content, join('', @lines));
+
+@lines = ();
+
+TestCapture({ argv => [$catbin, '-l', 31, $input],
+ stdout => sub { push @lines, shift } });
+
+is_deeply(\@lines, ['CHAPTER I. Down the Rabbit-Hole']);
+
+@lines = ();
+
+TestCapture({ argv => [$catbin, '-l', 102, $input],
+ stdout => sub { push @lines, shift } });
+
+is_deeply(\@lines, [ "CHAPTER I. Down the Rabbit-Hole\n",
+ "\n",
+ 'Alice was beginning to get very tired of sitting by her sister on the' ]);
+
+
diff --git a/t/10file.t b/t/10file.t
new file mode 100644
index 0000000..a5f9045
--- /dev/null
+++ b/t/10file.t
@@ -0,0 +1,24 @@
+# -*- perl -*-
+
+use lib 't', 'lib';
+
+use strict;
+use warnings;
+use TestCapture;
+use Test::More tests => 2;
+use File::Temp;
+use File::Cmp qw/fcmp/;
+use File::Spec;
+
+our($catbin, $input, $content);
+
+my $fh = new File::Temp;
+TestCapture({ argv => [$catbin, $input],
+ stdout => $fh });
+ok(fcmp($fh->filename, $input));
+
+$fh = new File::Temp;
+TestCapture({ argv => [$catbin, $input],
+ stdout => $fh->filename });
+ok(fcmp($fh->filename, $input));
+
diff --git a/t/11env.t b/t/11env.t
new file mode 100644
index 0000000..866369c
--- /dev/null
+++ b/t/11env.t
@@ -0,0 +1,35 @@
+# -*- perl -*-
+
+use lib 't';
+
+use strict;
+use warnings;
+use TestCapture;
+use Test::More tests => 6;
+use POSIX::Run::Capture qw(:std);
+
+my $obj = new POSIX::Run::Capture(['/bin/sh', '-c', 'echo $FOO-$BAZ']);
+
+$ENV{FOO}='foo';
+$ENV{BAZ}='baz';
+$obj->run;
+is($obj->next_line(SD_STDOUT), "foo-baz\n");
+
+# Set environment
+$obj->set_env('FOO=bar', 'BAZ=quux');
+is(0+@{$obj->env}, 2);
+is_deeply($obj->env, [ 'FOO=bar', 'BAZ=quux' ]);
+
+$obj->run;
+is($obj->next_line(SD_STDOUT), "bar-quux\n");
+
+# Unset environment
+$obj->set_env();
+$obj->run;
+is($obj->next_line(SD_STDOUT), "foo-baz\n");
+
+ok(TestCapture({ argv => ['/bin/sh', '-c', 'echo $FOO-$BAZ'],
+ env => ['FOO=bar', 'BAZ=quux'] },
+ stdout => {
+ content => "bar-quux\n"
+ }));

Return to:

Send suggestions and report system problems to the System administrator.