Beefy Boxes and Bandwidth Generously Provided by pair Networks
Think about Loose Coupling
 
PerlMonks  

Re: Pre-empting STDIN during Testing

by tmoertel (Chaplain)
on Feb 15, 2005 at 03:57 UTC ( [id://431019]=note: print w/replies, xml ) Need Help??


in reply to Pre-empting STDIN during Testing

If you are on a Unix system, you can redirect STDIN to pull input from a file. You can also open the file a second time for appending to obtain an independent file descriptor. You can write into this descriptor to inject input into STDIN. The OS will keep track of the input and output file positions separately, effectively turning the file into a fifo queue with a history.

Using this approach, you can inject on the fly, interleave injections and reads, and also simulate EOF input conditions.

The following code, which should work back to Perl 5.6, shows one way to do it.

#! perl use warnings; use strict; use File::Temp qw( tempfile ); # this function encapsulates the technique. # it returns a function that can be used to # inject input into the targeted file handle sub inject_input { my $target_fh = shift; my ($temp_fh, $temp_fn) = tempfile(); my $temp_fd = fileno $temp_fh; local *SAVED; local *TARGET = $target_fh; local *INJECT; open SAVED, "<&TARGET" or die "can't remember target"; open TARGET, "<&=$temp_fd" or die "can't redirect target"; open INJECT, "+>>$temp_fn" or die "can't open injector"; unlink $temp_fn; # we don't need the directory entry any more select((select(INJECT), $| = 1)[0]); my $saved_fh = *SAVED; my $inject_fh = *INJECT; return sub { if (@_) { print $inject_fh @_; } else { seek $inject_fh, 0, 0 or die "can't seek"; # rewind my $injected_output = do { local $/; <$inject_fh> }; close $temp_fh or die "can't close temp file handle"; local (*SAVED, *TARGET, *INJECT) = ($saved_fh, $target_fh, $inject_fh); open TARGET, "<&SAVED" or die "can't restore target"; close SAVED or die "can't close SAVED"; close INJECT or die "can't close INJECT"; return $injected_output; } } }

Here is an example of how to use the function:

# let us inject input into STDIN my $injector = inject_input(*STDIN); # the resulting injector takes a string to inject $injector->("1234\n"); # inject 1234 print "r: ", scalar <STDIN>; # read 1234 back in # now we can inject 5678, a, and b # we can read them back in via a loop $injector->("5678\n"); # inject 5678 $injector->("a\n", "b\n"); # inject a and b while (<>) { print "R: $_"; } # after the read loop reaches the EOF, we # can still inject more input and read it $injector->("we're outta here\n"); print "r: ", <>; # when we are done, we call the injector with # no arguments; this will close down the injection # apparatus, restore STDIN to its pre-injection # condition, and return a copy of what was injected # for flight-recording purposes print "\n\nwe injected:\n\n", $injector->(); # now STDIN is restored print "\n\nEnter text by hand:\n"; while (<>) { print "You entered: $_"; } # actual output follows: __END__ r: 1234 R: 5678 R: a R: b r: we're outta here we injected: 1234 5678 a b we're outta here Enter text by hand: blah You entered: blah blah You entered: blah blah You entered: blah

I hope this helps.

Cheers,
Tom

Replies are listed 'Best First'.
Re^2: Pre-empting STDIN during Testing
by jkeenan1 (Deacon) on Feb 15, 2005 at 14:13 UTC
    Tom Moertel wrote:

    If you are on a Unix system, you can redirect STDIN to pull input from a file. You can also open the file a second time for appending to obtain an independent file descriptor. You can write into this descriptor to inject input into STDIN. ... Using this approach, you can inject on the fly, interleave injections and reads, and also simulate EOF input conditions.

    Yesterday I was frustrated by the fact that, while there are several ways to redirect where STDIN is going to (by the command-line; by IO::Handle; etc.), there didn't seem to be a robust way of changing where it is coming from. After I logged off, I got to thinking about File::Temp (a module I've been using a lot lately as part of test building in the Phalanx project). I realized that there probably would be a way to use File::Temp to, in effect, fake out STDIN. But I then thought, "That's a lot of work for a simple problem."

    Well, your subroutine does do a lot of work, but it has the virtue of hiding the work in a subroutine. I'll try to check it out today. Thanks for taking the time to present your approach.

    Jim Keenan

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: note [id://431019]
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others pondering the Monastery: (4)
As of 2024-03-29 11:17 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found