Beefy Boxes and Bandwidth Generously Provided by pair Networks
Clear questions and runnable code
get the best and fastest answer
 
PerlMonks  

Testing Input and Output

by jaldhar (Vicar)
on Sep 02, 2002 at 04:32 UTC ( [id://194522]=perlquestion: print w/replies, xml ) Need Help??

jaldhar has asked for the wisdom of the Perl Monks concerning the following question:

So I'm almost finished with my Acme::Brainfck module and like a good boy should, I'm providing a full test suite. Once I assimiliated the various Test::* man ages, this was pretty easy to do for the most part. One thing has me stuck. In some places I write out stuff to STDOUT and STDERR or read it from STDIN. How do I do this in a perl test harness? My initial thought is to redirect STDIN, STDOUT, and STDERR to files and then read the files to see if I got the right values. Is there any simpler way? Some function in one of the Test::* modules I may have overlooked?

--
જલધર

Replies are listed 'Best First'.
Re: Testing Input and Output
by Joost (Canon) on Sep 02, 2002 at 11:55 UTC
    I haven't worked with it myself, but maybe you can take a look at IO::Scalar.

    from the manpage:

    use 5.005; use IO::Scalar; $data = "My message:\n"; ### Open a handle on a string, and append to it: $SH = new IO::Scalar \$data; print $SH "Hello"; print $SH ", world!\nBye now!\n"; print "The string is now: ", $data, "\n"; ### Open a handle on a string, read it line-by-line, then c +lose it: $SH = new IO::Scalar \$data; while (<$SH>) { print "Got line: $_"; } close $SH;

    So IMHO you should be able to redirect STDOUT and STDERR to an IO::Scalar object, and read the data back when you need it.

    Isn't perl nice? :-)

    -- Joost downtime n. The period during which a system is error-free and immune from user input.
Re: Testing Input and Output
by jcleland (Acolyte) on Sep 02, 2002 at 06:11 UTC
    I don't know _anything_ about the composition of a perl module or how to develop tests therefore, but perhaps you can use this:

    Ever tried redirecting a child stdin or stdout with open()?

    open( HANDLE, "ls -l |" ) or die "Unable to exec 'ls'\n";
    while( <HANDLE> ) {
    #Process each line of output '$_'
    }
    close( HANDLE );

    I do this all the time when I want to execute a child process and evaluate it's output.

    HTH,
    James

      I do this all the time when I want to execute a child process and evaluate it's output.

      There is a cleaner (IMO) way to do this - the qx or backtick operator handles all that work for you.

      #!/usr/bin/perl -w my @files = qx/ls -l/; ##### or # my @files = `ls -l`; print for @files ;


      grep
      Mynd you, mønk bites Kan be pretti nasti...
Re: Testing Input and Output
by greenFox (Vicar) on Sep 02, 2002 at 07:32 UTC

    print STDOUT "stuff\n"; print STDERR "stuff\n"; chomp(my $stuff = <STDIN>);
    :-)

    Maybe you need to take a look at IPC::Open2 or IPC::Open3

    --
    Until you've lost your reputation, you never realize what a burden it was or what freedom really is. -Margaret Mitchell

      Hmm, this hangs when reading STDIN; got whatever never gets printed.

      #!/usr/bin/perl use strict; use warnings; print STDOUT "stuff\n"; my $whatever = <STDIN>; print "got whatever: $whatever"; exit;

      I'm not too sure why it hangs: it seems to be that STDIN actually isn't tied to STDOUT, as you think it is. For example, we can prevent the above code from hanging forever by playing with alarm:

      #!/usr/bin/perl use strict; use warnings; $SIG{ALRM} = sub { die }; print STDOUT "whatever\n"; my $in; eval { alarm 1; chomp ( $in = <STDIN> ); alarm 0; }; print "got in: $in\n";
      result:
      whatever Use of uninitialized value in concatenation (.) or string at foo.pl li +ne 17. got in:

      1. The first line, "whatever", is us printing to STDOUT.
      2. Next we get a warning about an uninitialized value - this is the warnings pragma in action, helpfully telling us that we're trying to print something that hasn't been set yet.
      3. Finally, there's a "got in: " line where we expect to see "got in: whatever".

      So - what this means is that printing to STDOUT does not automagically make data available to STDIN. And that's good, too: imagine code that looks like this:

      #!/usr/bin/perl use strict; use warnings; print STDOUT "What is your name? : "; chomp ( my $name = <STDIN> ); print STDOUT "Hello, $name!\n"; exit;
      Now, without running the above code, tell me: what do you expect the second print statement to print?
      • Hello, What is your name? : blyman!
      • Hello, blyman!
      Yuck! Imagine if printing to STDOUT automagically made that data available to STDIN: you'd have to do some Olympian coding gymnastics to remove your STDOUT from your STDIN before you could find out what $name was entered as!

      fwiw, I was confused about *why* STDOUT and STDIN aren't tied together - makes sense at first glance! - until I came up with the above code example. Hope this helps.

      Update: realized greenFox was probably joking... but by golly, I learned something.

      blyman
      setenv EXINIT 'set noai ts=2'

        Yes I was joking and no I never meant to suggest that <STDIN> was somehow tied to <STDOUT>... glad you had some fun with it any-way :-)

        --
        Until you've lost your reputation, you never realize what a burden it was or what freedom really is. -Margaret Mitchell

Re: Testing Input and Output
by Dog and Pony (Priest) on Sep 02, 2002 at 19:59 UTC
    Tie::Handle::Scalar and/or IO::Scalar should be able to do this with ease. Just tie the handle in question and print to it, getting the result in a scalar - or the other way around.
    You have moved into a dark place.
    It is pitch black. You are likely to be eaten by a grue.
Re: Testing Input and Output
by jaldhar (Vicar) on Sep 03, 2002 at 19:11 UTC

    Thanks to all who replied. jcleland (with a slight refinement from grep) suggested the typical way for redirecting a stream. However that won't really work in this case as my brainfck instructions are transformed into hardcoded perl statements. greenFox's suggestion has the same problem.

    Joost and dog andPony suggested two other modules. Of those IO::Scalar does the reverse of what I want i.e. it treats a scalar as a filehandle. Tie::Handle::Scalar on the other hand was perfect. The only stumbling block was that it is not part of the Perl core modules and asking people to install it just to run tests seems a bit extreme. Luckily the code is short enough that I could just include it verbatim in my test script. Normally this would be a maintainence problem but i'm not even using all the features of the current module so it won't be a problem if my local copy goes out of sync with the upstream version.

    Here is the actual code from my test script. See the Inline::brainfck thread to understand the interpolated Brainfck code.

    $a = "\t"; tie *STDIN, 'Tie::Handle::Scalar', \$a; my $b = , ; ok ( $b == 9, ' Does , work?'); untie *STDIN; $a = ''; tie *STDOUT, 'Tie::Handle::Scalar', \$a; .. ok ( $a eq "\t\t", ' Does . work?'); untie *STDOUT; $a = ''; tie *STDERR, 'Tie::Handle::Scalar', \$a; # ok ( $a eq "\$p = 1 \$m[\$p]= 9\n", ' Does # work?'); untie *STDERR;

    --
    જલધર

      In the spirit of TMTOWTDI you could also use IO::File to create a temporary file, redirect STDIN to the temporary file, do your stuff, seek back to the start and slurp up the output into a scalar for testing as normal.

      The skip2.t test in Test::Class uses this method.

      One of the advantages is that IO::File is core so you don't have to muck around with other modules.

      I did write a Test::Output module (download Test-Output-0.01.tar.gz) that allowed you to do things like:

      output_is { hello() } "hello world\n", STDOUT, "hello world"; output_isnt { hello() } "goodbye", STDOUT, "not goodbye"; output_unlike { hello() } qr/bye/, STDOUT, "didn't print bye +"; output_like { hello() } qr/hello/, STDOUT, "printed hello"; like(Test::Output->last, qr/world/, "... and world");

      ... but I'm not entirely happy with the API so have not distributed it yet... feedback welcome :-)

Re: Testing Input and Output
by gnu@perl (Pilgrim) on Sep 04, 2002 at 14:19 UTC

    This is a great place to use IPC::Open3. It will open STDOUT, STDIN and STDERR as file handle in you script. The only problem is if what you call performs blocking. To be safe, you should use IO::Select to see if there is data to read on the handles.

    Here is something I have been playing with, it is rough, but you should get the idea from it.

    example:

    #!/usr/bin/perl -w # use strict; use POSIX; use IPC::Open3; use IO qw( Select Handle ); $|++; my $host = "host"; my $login = "login"; my $pword = "password"; my $logfil = "/var/log/syslog"; my $cmd = "telnet"; my $count = 0; my ($infh,$outfh,$errfh); my $pid; my $sel = new IO::Select; open (OUTFILE, ">deneb.log"); OUTFILE->autoflush(1); eval { $pid = open3($infh,$outfh,$errfh, "$cmd $host" ) }; die $@ if $@; $sel->add($outfh,$errfh); while (my @ready = $sel->can_read){ foreach my $fh (@ready) { my $line; my $len = sysread $fh, $line, 4096; if (not defined $len) { die "Error reading from child: $!\n"; } elsif ($len == 0){ $sel->remove($fh); }else { if ($fh == $outfh){ if ($count == 0){ print "$line\n"; print $infh "$login\n"; print $infh "$pword\n"; print $infh "tail $logfil"; $count = 1; }elsif ($count == 1){ print $infh "\n"; $count = 2; }elsif ($count == 2){ print OUTFILE $line; sleep 1; $count = 3; print $infh "exit\n"; }else {exit}; }elsif ($fh == $errfh){ print "From STDERR: $errfh\n"; }else { print "Should never be here!!\n"; } } } }

      The only problem is if what you call performs blocking.

      Internally the brainfck , operator uses getc which does block.

      To be safe, you should use IO::Select to see if there is data to read on the handles.

      Are you by any chance an ex-C programmer? :-) In my case it's a test script so I know exactly what input and output to expect. If you are doing what I think you are doing something like Net::Telnet might save you a lot of bother.

      --
      જલધર

        Yeah, thanks for the tip. I came to that realization on friday. Some kind of brain lapse. I am using Net::Telnet now and basically threw away what I put here, but it was a nice example of open3 though.

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://194522]
Approved by Kanji
Front-paged by grep
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others cooling their heels in the Monastery: (4)
As of 2024-04-23 16:12 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found