NAME

IO::Capture::Extended - Extend functionality of IO::Capture

SYNOPSIS

The programmer interface consists of two classes:

IO::Capture::Stdout::Extended

use IO::Capture::Stdout::Extended;

$capture = IO::Capture::Stdout::Extended->new();
$capture->start();
# some code that prints to STDOUT
$capture->stop();

# scalar context:  return number of print statements with 'fox'
$matches = $capture->grep_print_statements('fox');

# list context:  return list of print statements with 'fox'
@matches = $capture->grep_print_statements('fox');

# return number of print statements
$matches = $capture->statements;

# scalar context:  return number of pattern matches
$regex = qr/some regular expression/;
$matches = $capture->matches($regex);

# list context:  return list of pattern matches
@matches = $capture->matches($regex);

# return reference to array holding list of pattern matches
$matchesref = $capture->matches_ref($regex);

# scalar context:  return number of 'screen' lines printed
$screen_lines = $capture->all_screen_lines();

# list context:  return list of 'screen' lines printed
@all_screen_lines = $capture->all_screen_lines();

IO::Capture::Stderr::Extended

$capture = IO::Capture::Stderr::Extended->new();
$capture->start();
# some code that prints to STDERR
$capture->stop();

... and then use all the methods defined above for IO::Capture::Stdout::Extended.

DESCRIPTION

IO::Capture::Extended is a distribution consisting of two classes, each of which is a collection of subroutines which are useful in extending the functionality of CPAN modules IO::Capture::Stdout and IO::Capture::Stderr, particularly when used in a testing context such as that provided by Test::Simple, Test::More or other modules built on Test::Builder.

USAGE

Requirements

IO::Capture distribution, available from CPAN http://search.cpan.org/~reynolds/IO-Capture-0.05/. Use version 0.05 or later to take advantage of important bug fixes. The IO::Capture distribution includes base class IO::Capture, IO::Capture::Stdout, IO::Capture::Stderr and other packages. It also includes useful documentation in IO::Capture::Overview.

General Comments

The IO::Capture::Stdout::Extended and IO::Capture::Stdout::Extended methods are designed to provide return values which work nicely as arguments to Test::More functions such as ok(), is() and like(). The examples below illustrate that objective. Suggestions are welcome for additional methods which would fulfill that objective.

Individual Methods

Note: Since IO::Capture::Extended is structured so as to make exactly the same methods available for IO::Capture::Stdout::Extended and IO::Capture::Stderr::Extended, whenver there appears below a reference to, e.g., IO::Capture::Stdout::Extended::grep_print_statements(), you should assume that the remarks apply equally to IO::Capture::Stderr::Extended::grep_print_statements(). Wherever reference is made to STDOUT, the remarks apply to STDERR as well.

grep_print_statements()

  • Scalar Context

    Problem: You wish to test a function that prints to STDOUT. You can predict the number of print statements that match a pattern and wish to test that prediction. (The example below is adapted from IO::Capture::Overview.)

    sub print_fox {
        print "The quick brown fox jumped over ...";
        print "garden wall";
        print "The quick red fox jumped over ...";
        print "garden wall";
    }
    
    $capture->start;
    print_fox();
    $capture->stop;
    $matches = $capture->grep_print_statements('fox');
    is($capture->grep_print_statements('fox'), 2, 
        "correct no. of print statements grepped");

    Solution: Precede the function call with a call to the IO::Capture::Stdout::Extended start() method and follow it with a call to the stop() method. Call grep_print_statements. Use its return value as one of two arguments to Test::More::is(). Use your prediction as the other argument. Add a useful comment to is().

    Potential Pitfall: The number of print statements captured between IO::Capture::Stdout::Extended::start() and stop() is not necessarily the number of lines that would appear to be printed to standard output by a given block of code. If your subroutine or other code block prints partial lines -- i.e., lines lacking \n newline characters -- the number of print statements will be greater than the number of ''screen lines.'' This is illustrated by the following:

    sub print_fox_long {
        print "The quick brown fox jumped over ...";
        print "a less adept fox\n";
        print "The quick red fox jumped over ...";
        print "the garden wall\n";
    }
    
    $capture->start;
    print_fox_long();
    $capture->stop;
    $matches = $capture->grep_print_statements("fox");
    is($capture->grep_print_statements("fox"), 3, 
        "correct no. of print statements grepped");

    The number of print statements matching fox is three -- even though the number of lines on the screen which appear on STDOUT containing fox is only two.

  • List Context

    Problem: As above, you wish to test a function that prints to STDOUT. This time, you can predict the content of print statements that match a pattern and wish to test that prediction.

    %matches = map { $_, 1 } $capture->grep_print_statements('fox');
    is(keys %matches, 2, "correct no. of print statements grepped");
    ok($matches{'The quick brown fox jumped over ...'}, 
        'print statement correctly grepped');
    ok($matches{'The quick red fox jumped over ...'}, 
        'print statement correctly grepped');

    Solution: As above, call grep_print_statements, but map its output to a 'seen-hash'. You can then use the number of keys in that seen-hash as an argument to Test::More::is(). You can use ok() to test whether the keys of that hash are as predicted.

statements()

Problem: You've written a function which prints to STDOUT. You can make a prediction as to the number of screen lines which should be printed. You want to test that prediction with Test::More::is().

sub print_greek {
    local $_;
    print "$_\n" for (qw| alpha beta gamma delta |);
}

$capture->start();
print_greek();
$capture->stop();
is($capture->statements, 4, 
    "number of print statements is correct");

Solution: Precede the function call with a call to the IO::Capture::Stdout start() method and follow it with a call to the stop() method. Call IO::Capture::Stdout::Extended::statements() and use its return value as the first argument to is(). Use your prediction as the second argument to is(). Be sure to write a useful comment for your test.

Potential Pitfall: The number of print statements returned by statements is not necessarily the number of lines that would appear to be printed to standard output by a given block of code. If your subroutine or other code block prints partial lines -- i.e., lines lacking \n newline characters -- the number of print statements will be greater than the number of ''screen lines.'' This is illustrated by the following:

sub print_greek_long {
    local $_;
    for (qw| alpha beta gamma delta |) {
        print $_;
        print "\n";
    }
}

$capture->start();
print_greek_long();
$capture->stop();
is($capture->statements, 8, 
    "number of print statements is correct");

This pitfall can be avoided by using all_screen_lines() below.

all_screen_lines()

  • Scalar Context

    Returns the number of lines which would normally be counted by eye on STDOUT. This number is not necessarily equal to the number of print() statements found in the captured output. This method avoids the 'pitfall' found when using statements() above.

    $capture->start();
    print_greek_long();
    $capture->stop();
    $screen_lines = $capture->all_screen_lines;
    is($screen_lines, 4, "correct no. of lines printed to screen");
  • List Context

    Returns an array holding lines as normally viewed on STDOUT. The size of this array is not necessarily equal to the number of print() statements found in the captured output. This method avoids the 'pitfall' found when using statements() above.

    $capture->start();
    print_greek_long();
    $capture->stop();
    @all_screen_lines = $capture->all_screen_lines;
    is($all_screen_lines[0], 
        "alpha", 
        "line correctly printed to screen");
    is($all_screen_lines[1], 
        "beta", 
        "line correctly printed to screen");

    Any newline (\n) appearing at the end of a screen line is not included in the list of lines returned by this method, i.e., the lines are chomped.

matches()

  • Scalar Context

    Problem: You've written a function which, much like the ''mail merge'' function in word processing programs, extracts data from some data source, merges the data with text in a standard form, and prints the result to STDOUT. You make a prediction as to the number of forms which are printed to STDOUT and wish to confirm that prediction.

    my @week = (
        [ qw| Monday     Lundi    Lunes     | ],
        [ qw| Tuesday    Mardi    Martes    | ],
        [ qw| Wednesday  Mercredi Miercoles | ],
        [ qw| Thursday   Jeudi    Jueves    | ],
        [ qw| Friday     Vendredi Viernes   | ],
        [ qw| Saturday   Samedi   Sabado    | ],
        [ qw| Sunday     Dimanche Domingo   | ],
    );
    
    sub print_week {
        my $weekref = shift;
        my @week = @{$weekref}; 
        for (my $day=0; $day<=$#week; $day++) {
            print "English:  $week[$day][0]\n";
            print "French:   $week[$day][1]\n";
            print "Spanish:  $week[$day][2]\n";
            print "\n";
        }
    }
    
    $capture->start();
    print_week(\@week);
    $capture->stop();
    $regex = qr/English:.*?French:.*?Spanish:/s;
    
    is($capture->matches($regex), 7,
        "correct number of forms printed to screen");

    Solution: Precede the function call with a call to the IO::Capture::Stdout start() method and follow it with a call to the stop() method. Write a Perl regular expression and assign it to a variable using the qr// notation. (Remember to use the /s modifier if the text you are testing crosses screen lines.) Pass the regex variable to IO::Capture::Stdout::Extended::matches() and use the return value of that method call as one argument to Test::More::is(). Use your prediction as the second argument to is(). Be sure to write a useful comment for your test.

  • List Context

    Problem: As above, you've written a function which, much like the ''mail merge'' function in word processing programs, extracts data from some data source, merges the data with text in a standard form, and prints the result to STDOUT. This time, however, you wish to do a quick test on the results by examining a sample form.

    $capture->start();
    print_week(\@week); # as defined above
    $capture->stop();
    
    @matches = $capture->matches($regex);
    $predicted = "English:  Monday\nFrench:   Lundi\nSpanish:";
    is($matches[0], $predicted, "first form matches test portion");

    Solution: Same as above, but capture the output of matches() in a list or array. Write a string which predicts typical contents of one instance of your merged form. Use the contents of one form (one element in the list output) and the prediction string as arguments to is(). Be sure to write a useful comment for your test.

    Problem: As above, but now you wish to make sure that a form was generated for each required field in the data source.

    $regex = qr/French:\s+(.*?)\n/s;
    @predicted = qw| Lundi Mardi Mercredi Jeudi 
        Vendredi Samedi Dimanche |;
    ok(eq_array( [ $capture->matches($regex) ], \@predicted ), 
        "all predicted matches found");

    Solution: Similar to above, but this time you predict which data points are going to be present in the output. Store that prediction in a list or array. Take references to (a) the array holding that prediction; and (b) the result of $capture-matches($regex)>. Pass those two references to Test::More's utility function eq_array, which will return a true value if the underlying arrays are identical element-by-element. Pass that value in turn to <ok()>. As always, be sure to write a useful comment for your test.

matches_ref()

Problem: Same as the first ''List Context'' example above, but now you would prefer to work with a method that returns an array reference rather than all the elements in a list.

$matchesref = $capture->matches_ref($regex);
is(${$matchesref}[0], $predicted, "first form matches test portion");

Solution: Call IO::Capture::Stdout::Extended::matches_ref() instead of matches. You will have to rephrase your test in terms of an element of a dereferenced array.

BUGS

As Paul Johnson says, ''Did I mention that this is alpha code?''.

SUPPORT

Contact the author or post to the perl-qa mailing list.

ACKNOWLEDGEMENTS

Thanks go first and foremost to the two authors of the IO::Capture distribution, Mark Reynolds and Jon Morgan. Mark Reynolds was responsive to this module's author's suggestions for bug fixes and additional tests. The documentation found in IO::Capture::Overview was particularly helpful in showing me how to extend IO::Capture's functionality. This distribution is maintained independently but will be updated as needed if and when IO::Capture is revised.

The methods in IO::Capture::Extended are offered to the Perl community in gratitude for being turned on to IO::Capture by David Cantrell on the perl.qa mailing list in February 2005 (http://www.nntp.perl.org/group/perl.qa/3567).

Other contributors to that discussion thread whose suggestions are reflected in this module were David H. Adler, David Golden, Michael G. Schwern and Tels. Fergal Daly also made suggestions in separate communications.

The structure for this module was created with my own hacked-up version of R. Geoffrey Avery's modulemaker utility, based on his CPAN module ExtUtils::ModuleMaker.

AUTHOR

James E Keenan. CPAN ID: JKEENAN. jkeenan [at] cpan [dot] org.

COPYRIGHT

Copyright 2005-15 James E Keenan.

This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself.

The full text of the license can be found in the LICENSE file included with this module.

SEE ALSO

perl(1). IO::Capture; IO::Capture::Stdout; IO::Capture::Overview. Test::Simple; Test::More.