#!/usr/bin/perl -w

use strict;
use App::Prove;
use Getopt::Long;

our $VERSION = '3.31';
$|++;

Getopt::Long::Configure(qw(no_ignore_case bundling pass_through));

my $opts = { color => 1, comments => 1 };

Getopt::Long::GetOptions(
    'psql-bin|b=s' => \$opts->{psql},
    'dbname|d=s'   => \$opts->{dbname},
    'username|U=s' => \$opts->{username},
    'host|h=s'     => \$opts->{host},
    'port|p=s'     => \$opts->{port},
    'pset|P=s%'    => \$opts->{pset},
    'set|S=s%'     => \$opts->{set},
    'runtests|R'   => \$opts->{runtests},
    'schema|s=s'   => \$opts->{schema},
    'match|x=s'    => \$opts->{match},
    'timer|t!'     => \$opts->{timer},
    'version|V'    => \$opts->{version},
    'ext=s@'       => \$opts->{ext},
    'comments|o!'  => \$opts->{comments},
    'help|H|?'     => \$opts->{help},
    'man|m'        => \$opts->{man},
) or require Pod::Usage && Pod::Usage::pod2usage(2);

if ($opts->{version}) {
    print 'pg_prove ', main->VERSION, $/;
    exit;
}

if ( $opts->{help} or $opts->{man} ) {
    require Pod::Usage;
    Pod::Usage::pod2usage(
        '-sections' => $opts->{man} ? '.+' : '(?i:(Usage|Options))',
        '-verbose'  => 99,
        '-exitval'  => 0,
    )
}

if ($opts->{version}) {
    print 'pg_prove ', main->VERSION, $/;
    exit;
}

my $prove_class = 'App::Prove';
my $runtests_call;

# --schema and --match assume --runtests.
if ($opts->{runtests} || $opts->{schema} || $opts->{match}) {
    # We're just going to call `runtests()`.
    $prove_class .= '::pgTAP';
    my @args;
    for my $key (qw(schema match)) {
        next unless $opts->{$key};
        (my $arg = $opts->{$key}) =~ s/'/\\'/g;
        # Gotta cast the arguments.
        push @args, "'$arg'::" . ($key eq 'schema' ? 'name' : 'text');
    }

    $runtests_call = 'runtests(' . join( ', ', @args ) . ');'
}

my $app = $prove_class->new;
$app->process_args(
    @ARGV,
    (map { ('--ext' => $_) } @{ $opts->{ext} || ['.pg'] }),
    (map { ('--pgtap-option' => "suffix=$_") } @{ $opts->{ext} || [] }),
    qw(--source pgTAP),
    ($opts->{comments} ? ('--comments') : ()),
    ($opts->{timer} ? ('--timer') : ()),
    (map {
        ('--pgtap-option' => "$_=$opts->{$_}")
    } grep {
        $opts->{$_}
    } qw(psql dbname username host port)),
    (map {
        ('--pgtap-option' => "pset=$_=$opts->{pset}{$_}")
    } keys %{ $opts->{pset} }),
    (map {
        ('--pgtap-option' => "set=$_=$opts->{set}{$_}")
    } keys %{ $opts->{set} })
);

exit($app->run ? 0 : 1);

PGPROVE: {
    package # Hide from indexer.
        App::Prove::pgTAP;
    use base 'App::Prove';
    sub _get_tests {
        return [
            "pgsql: SELECT * FROM $runtests_call",
            $runtests_call,
        ]
    }
}

__END__

=encoding utf8

=head1 Name

pg_prove - A command-line tool for running and harnessing pgTAP tests

=head1 Usage

  pg_prove tests/
  pg_prove --dbname template1 test*.sql
  pg_prove -d testdb --runtests

=head1 Description

C<pg_prove> is a command-line application to run one or more
L<pgTAP|http://pgtap.org/> tests in a PostgreSQL database. The output of the
tests is harvested and processed by L<TAP::Harness|TAP::Harness> in order to
summarize the results of the test.

Tests can be written and run in one of two ways, as SQL scripts or as
xUnit-style database functions.

=head2 Test Scripts

pgTAP test scripts should consist of a series of SQL statements that output
TAP. Here's a simple example that assumes that the pgTAP functions have been
installed in the database:

    -- Start transaction and plan the tests.
    BEGIN;
    SELECT plan(1);

    -- Run the tests.
    SELECT pass( 'My test passed, w00t!' );

    -- Finish the tests and clean up.
    SELECT * FROM finish();
    ROLLBACK;

Now run the tests by passing the list of SQL script names or the name of a
test directory to C<pg_prove>. Here's what it looks like when the pgTAP tests
are run with C<pg_prove>

    % pg_prove -U postgres tests/
    tests/coltap.....ok
    tests/hastap.....ok
    tests/moretap....ok
    tests/pg73.......ok
    tests/pktap......ok
    All tests successful.
    Files=5, Tests=216,  1 wallclock secs ( 0.06 usr  0.02 sys +  0.08 cusr  0.07 csys =  0.23 CPU)
    Result: PASS

=head2 xUnit Test Functions

pgTAP test functions should return a set of text, and then simply return the
values returned by pgTAP functions, like so:

    CREATE OR REPLACE FUNCTION setup_insert(
    ) RETURNS SETOF TEXT AS $$
        RETURN NEXT is( MAX(nick), NULL, 'Should have no users') FROM users;
        INSERT INTO users (nick) VALUES ('theory');
    $$ LANGUAGE plpgsql;

    Create OR REPLACE FUNCTION test_user(
    ) RETURNS SETOF TEXT AS $$
        SELECT is( nick, 'theory', 'Should have nick') FROM users;
    END;
    $$ LANGUAGE sql;

Once you have these functions defined in your database, you can run them with
C<pg_prove> by using the C<--runtests> option.

    % pg_prove --dbname mydb --runtests
    runtests()....ok
    All tests successful.
    Files=1, Tests=32,  0 wallclock secs ( 0.02 usr  0.01 sys +  0.01 cusr  0.00 csys =  0.04 CPU)
    Result: PASS

Be sure to pass the C<--schema> option if your test functions are all in one
schema, and the C<--match> option if they have names that don't start with
"test". For example, if you have all of your test functions in the "test"
schema and I<ending> with "test," run the tests like so:

    pg_prove --dbname mydb --schema test --match 'test$'

=head1 Options

 -b   --psql-bin PSQL        Location of the psql client.
 -d,  --dbname DBNAME        Database to which to connect.
 -U,  --username USERNAME    User with which to connect.
 -h,  --host HOST            Host to which to connect.
 -p,  --port PORT            Port to which to connect.
 -P,  --pset OPTION=VALUE    Set psql key/value printing option.
 -S,  --set VAR=VALUE        Set variables for psql session.
 -R   --runtests             Run xUnit test using runtests().
 -s,  --schema SCHEMA        Schema in which to find xUnit tests.
 -x,  --match REGEX          Regular expression to find xUnit tests.

      --ext                  Set the extension for tests (default .pg)
 -r,  --recurse              Recursively descend into directories.
      --ignore-exit          Ignore exit status from test scripts.
      --trap                 Trap Ctrl-C and print summary on interrupt.
      --harness              Define test harness to use.
 -j,  --jobs N               Run N test jobs in parallel (try 9.)
      --rc RCFILE            Process options from rcfile
      --norc                 Don't process default .proverc
      --state OPTION=VALUE   Set persistent state options.

 -v,  --verbose              Print all test lines.
 -f,  --failures             Show failed tests.
 -o,  --comments             Show comments and diagnostics.
      --directives           Only show results with TODO or SKIP directives.
 -q,  --quiet                Suppress some test output while running tests.
 -Q,  --QUIET                Only print summary results.
      --parse                Show full list of TAP parse errors, if any.
      --normalize            Normalize TAP output in verbose output
 -D   --dry                  Dry run. Show test that would have run.
      --merge                Merge test scripts' STDERR and STDOUT.
 -t   --timer                Print elapsed time after each test.
 -c,  --color                Colored test output (default).
      --nocolor              Do not color test output.
      --shuffle              Run the tests in random order.
      --reverse              Run the tests in reverse order.
 -a,  --archive FILENAME     Store the resulting TAP in an archive file.
      --formatter            Result formatter to use.
      --count                Show X/Y test count when not verbose (default)
      --nocount              Disable the X/Y test count.

 -H,  --help                 Print a usage statement and exit.
 -?,                         Print a usage statement and exit.
 -m,  --man                  Print the complete documentation and exit.
 -V,  --version              Print the version number and exit.

=head1 Options Details

=head2 Database Options

=over

=item C<-b>

=item C<--psql-bin>

  pg_prove --psql-bin /usr/local/pgsql/bin/psql
  pg_prove -b /usr/local/bin/psql

Path to the C<psql> program, which will be used to actually run the tests.
Defaults to F<psql>, which should work well, when it is in your path.

=item C<-d>

=item C<--dbname>

  pg_prove --dbname try
  pg_prove -d postgres

The name of database to which to connect. Defaults to the value of the
C<$PGDATABASE> environment variable or to the system username.

=item C<-U>

=item C<--username>

  pg_prove --username foo
  pg_prove -U postgres

PostgreSQL user name to connect as. Defaults to the value of the C<$PGUSER>
environment variable or to the operating system name of the user running the
application.

=item C<-h>

=item C<--host>

  pg_prove --host pg.example.com
  pg_prove -h dev.local

Specifies the host name of the machine on which the server is running. If the
value begins with a slash, it is used as the directory for the Unix-domain
socket. Defaults to the value of the C<$PGHOST> environment variable or
localhost.

=item C<-p>

=item C<--port>

  pg_prove --port 1234
  pg_prove -p 666

Specifies the TCP port or the local Unix-domain socket file extension on which
the server is listening for connections. Defaults to the value of the
C<$PGPORT> environment variable or, if not set, to the port specified at
compile time, usually 5432.

=item C<-P>

=item C<--pset>

  pg_prove --pset tuples_only=0
  pg_prove -P null=[NULL]

Specifies printing options in the style of C<\pset> in the C<psql> program.
See L<http://www.postgresql.org/docs/current/static/app-psql.html> for details
on the supported options.

=item C<-S>

=item C<--set>

  pg_prove --set MY_CONTRACT=321
  pg_prove -S TEST_SEARCH_PATH=test,public

Sets local variables for psql in the style of C<\set> in the C<psql> program.
See L<http://www.postgresql.org/docs/current/static/app-psql.html> for details
on the supported options.

=item C<--runtests>

  pg_prove --runtests
  pg_prove -r

Don't run any test scripts, but just use the C<runtests()> pgTAP function to
run xUnit tests. This ends up looking like a single test script has been run,
when in fact no test scripts have been run. Instead, C<pg_prove> tells C<psql>
to run something like:

  psql --command 'SELECT * FROM runtests()'

You should use this option when you've written your tests in xUnit style,
where they're all defined in test functions already loaded in the database.

=item C<-s>

=item C<--schema>

  pg_prove --schema test
  pg_prove -s mytest

Used with C<--runtests>, and, in fact, implicitly forces C<--runtests> to be
true. This option can be used to specify the name of a schema in which to find
xUnit functions to run. Basically, it tells C<psql> to run something like:

  psql --command "SELECT * FROM runtests('test'::name)"

=item C<-x>

=item C<--match>

  pg_prove --match 'test$'
  pg_prove -x _test_

Used with C<--runtests>, and, in fact, implicitly forces C<--runtests> to be
true. This option can be used to specify a POSIX regular expression that will
be used to search for xUnit functions to run. Basically, it tells C<psql> to
run something like:

  psql --command "SELECT * FROM runtests('_test_'::text)"

This will run any visible functions with the string "_test_" in their names.
This can be especially useful if you just want to run a single test in a
given schema. For example, this:

  pg_prove --schema testing --match '^test_widgets$'

Will have C<psql> execute the C<runtests()> function like so:

 SELECT * FROM runtests('testing'::name, '^test_widgets$'::text);

=back

=head2 Behavioral Options

=over

=item C<--ext>

  pg_prove --ext .sql tests/

Set the extension for test files (default F<.pg>). May be specified multiple
times if you have test scripts with multiple extensions, though all must be
pgTAP tests:

  pg_prove --ext .sql --ext .pg --ext .pgt

If you want to mix pgTAP tests with other TAP-emitting tests, like Perl tests,
use C<prove> instead, where C<--ext> identifies any test file, and
C<--pgtap-option suffix=> lets you specify one or more extensions for pgTAP
tests.

  prove --source Perl \
        --ext .t --ext .pg \
        --source pgTAP --pgtap-option suffix=.pg

=item C<-r>

=item C<--recurse>

  pg_prove --recurse tests/
  pg_prove --recurse sql/

Recursively descend into directories when searching for tests. Be sure to
specify C<--ext> if your tests do not end in C<.pg>. Not relevant with
C<--runtests>.

=item C<--ignore-exit>

  pg_prove --ignore-exit

Ignore exit status from test scripts. Normally if a script triggers a database
exception, C<psql> will exit with an error code and, even if all tests passed,
the test will be considered a failure. Use C<--ignore-exit> to ignore such
situations (at your own peril).

=item C<--trap>

  pg_prove --trap

Trap C<Ctrl-C> and print a summary on interrupt.

=item C<--harness>

  pg_prove --harness TAP::Harness::Color

Specify a subclass of L<TAP::Harness> to use for the test harness. Defaults to
TAP::Harness (unless C<--archive> is specified, in which case it uses
L<TAP::Harness::Archive>).

=item C<-j>

=item C<-jobs>

Run N test jobs in parallel (try 9.)

=item C<--rc>

  pg_prove --rc pg_prove.rc

Process options from the specified configuration file.

If C<--rc> is not specified and F<./.proverc> or F<~/.proverc> exist, they
will be read and the options they contain processed before the command line
options. Options in configuration files are specified in the same way as
command line options:

           # .proverc
           --state=hot,fast,save
           -j9

Under Windows and VMS the option file is named F<_proverc> rather than
F<.proverc> and is sought only in the current directory.

Due to how options are loaded you cannot use F<.proverc> for
C<pg_prove>-specific options, only C<prove> options. However, <pg_prove> does
support all of the usual libpq
L<Environment Variables|http://www.postgresql.org/docs/current/static/libpq-envars.html>.

=item C<--norc>

Do not process F<./.proverc> or F<~/.proverc>.

=item C<--state>

You can ask C<pg_prove> to remember the state of previous test runs and select
and/or order the tests to be run based on that saved state.

The C<--state> switch requires an argument which must be a comma separated
list of one or more of the following options.

=over

=item C<last>

Run the same tests as the last time the state was saved. This makes it
possible, for example, to recreate the ordering of a shuffled test.

    # Run all tests in random order
    pg_prove --state save --shuffle

    # Run them again in the same order
    pg_prove --state last

=item C<failed>

Run only the tests that failed on the last run.

    # Run all tests
    pg_prove --state save

    # Run failures
    pg_prove --state failed

If you also specify the C<save> option newly passing tests will be
excluded from subsequent runs.

    # Repeat until no more failures
    pg_prove --state failed,save

=item C<passed>

Run only the passed tests from last time. Useful to make sure that no new
problems have been introduced.

=item C<all>

Run all tests in normal order. Multiple options may be specified, so to run
all tests with the failures from last time first:

    pg_prove --state failed,all,save

=item C<hot>

Run the tests that most recently failed first. The last failure time of each
test is stored. The C<hot> option causes tests to be run in most-recent-
failure order.

    pg_prove --state hot,save

Tests that have never failed will not be selected. To run all tests with the
most recently failed first use

    pg_prove --state hot,all,save

This combination of options may also be specified thus

    pg_prove --state adrian

=item C<todo>

Run any tests with todos.

=item C<slow>

Run the tests in slowest to fastest order. This is useful in conjunction with
the C<-j> parallel testing switch to ensure that your slowest tests start
running first.

    pg_prove --state slow -j9

=item C<fast>

Run test tests in fastest to slowest order.

=item C<new>

Run the tests in newest to oldest order based on the modification times of the
test scripts.

=item C<old>

Run the tests in oldest to newest order.

=item C<fresh>

Run those test scripts that have been modified since the last test run.

=item C<save>

Save the state on exit. The state is stored in a file called F<.pg_prove>
(F<_pg_prove> on Windows and VMS) in the current directory.

=back

The C<--state> switch may be used more than once.

    pg_prove --state hot --state all,save

=back

=head2 Display Options

=over

=item C<-v>

=item C<--verbose>

  pg_prove --verbose
  pg_prove -v

Display standard output of test scripts while running them. This behavior can
also be triggered by setting the C<$TEST_VERBOSE> environment variable to a
true value.

=item C<-f>

=item C<--failures>

  pg_prove --failures
  pg_prove -f

Show failed tests.

=item C<-o>

=item C<--comments>

Show comments, such as diagnostics output by C<diag()>. Enabled by default.
use C<--no-comments> to disable.

=item C<--directives>

  pg_prove --directives

Only show results with TODO or SKIP directives.

=item C<-q>

=item C<--quiet>

  pg_prove --quiet
  pg_prove -q

Suppress some test output while running tests.

=item C<-Q>

=item C<--QUIET>

  pg_prove --QUIET
  pg_prove -Q

Only print summary results.

=item C<--parse>

  pg_prove --parse

Enables the display of any TAP parsing errors as tests run. Useful for
debugging new TAP emitters.

=item C<--normalize>

  pg_prove --normalize

Normalize TAP output in verbose output. Errors in the harnessed TAP corrected
by the parser will be corrected.

=item C<--dry>

=item C<-D>

  pg_prove --dry tests/
  pg_prove -D

Dry run. Just outputs a list of the tests that would have been run.

=item C<--merge>

Merge test scripts' C<STDERR> with their C<STDOUT>. Not really relevant to
pgTAP tests, which only print to C<STDERR> when an exception is thrown.

=item C<-t>

=item C<--timer>

  pg_prove --timer
  pg_prove -t

Print elapsed time after each test file.

=item C<-c>

=item C<--color>

  pg_prove --color
  pg_prove -c

Display test results in color. Colored test output is the default, but if
output is not to a terminal, color is disabled.

Requires L<Term::ANSIColor|Term::ANSIColor> on Unix-like platforms and
L<Win32::Console|Win32::Console> on Windows. If the necessary module is not
installed colored output will not be available.

=item C<--nocolor>

Do not display test results in color.

=item C<--shuffle>

  pg_prove --shuffle tests/

Test scripts are normally run in alphabetical order. Use C<--reverse> to run
them in in random order. Not relevant when used with C<--runtests>.

=item C<--reverse>

  pg_prove --reverse tests/

Test scripts are normally run in alphabetical order. Use C<--reverse> to run
them in reverse order. Not relevant when used with C<--runtests>.

=item C<-a>

=item C<--archive>

  pg_prove --archive tap.tar.gz
  pg_prove -a test_output.tar

=item C<-f>

=item C<--formatter>

  pg_prove --formatter TAP::Formatter::File
  pg_prove -f TAP::Formatter::Console

The name of the class to use to format output. The default is
L<TAP::Formatter::Console|TAP::Formatter::Console>, or
L<TAP::Formatter::File|TAP::Formatter::File> if the output isn't a TTY.

=item C<--count>

  pg_prove --count

Show the X/Y test count as tests run when not verbose (default).

=item C<--nocount>

  pg_prove --nocount

Disable the display of the X/Y test count as tests run.

Send the TAP output to a TAP archive file as well as to the normal output
destination. The archive formats supported are F<.tar> and F<.tar.gz>.

=back

=head2 Metadata Options

=over

=item C<-H>

=item C<-?>

=item C<--help>

  pg_prove --help
  pg_prove -H

Outputs a brief description of the options supported by C<pg_prove> and exits.

=item C<-m>

=item C<--man>

  pg_prove --man
  pg_prove -m

Outputs this documentation and exits.

=item C<-V>

=item C<--version>

  pg_prove --version
  pg_prove -V

Outputs the program name and version and exits.

=back

=head1 Author

David E. Wheeler <dwheeler@cpan.org>

=head1 Copyright

Copyright (c) 2008-2015 David E. Wheeler. Some Rights Reserved.

=cut