File: pgTAP.pm

package info (click to toggle)
libtap-parser-sourcehandler-pgtap-perl 3.37-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 160 kB
  • sloc: perl: 789; makefile: 2
file content (404 lines) | stat: -rw-r--r-- 11,023 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
package TAP::Parser::SourceHandler::pgTAP;

use strict;
use vars qw($VERSION @ISA);

use TAP::Parser::IteratorFactory   ();
use TAP::Parser::Iterator::Process ();

@ISA = qw(TAP::Parser::SourceHandler);
TAP::Parser::IteratorFactory->register_handler(__PACKAGE__);

our $VERSION = '3.37';

=head1 Name

TAP::Parser::SourceHandler::pgTAP - Stream TAP from pgTAP test scripts

=head1 Synopsis

In F<Build.PL> for your application with pgTAP tests in F<t/*.pg>:

  Module::Build->new(
      module_name        => 'MyApp',
      test_file_exts     => [qw(.t .pg)],
      use_tap_harness    => 1,
      tap_harness_args   => {
          sources => {
              Perl  => undef,
              pgTAP => {
                  dbname   => 'try',
                  username => 'postgres',
                  suffix   => '.pg',
              },
          }
      },
      build_requires     => {
          'Module::Build'                     => '0.30',
          'TAP::Parser::SourceHandler::pgTAP' => '3.19',
      },
  )->create_build_script;

If you're using L<C<prove>|prove>:

  prove --source Perl \
        --ext .t --ext .pg \
        --source pgTAP --pgtap-option dbname=try \
                       --pgtap-option username=postgres \
                       --pgtap-option suffix=.pg

If you have only pgTAP tests, just use C<pg_prove>:

  pg_prove --dbname try --username postgres

Direct use:

  use TAP::Parser::Source;
  use TAP::Parser::SourceHandler::pgTAP;

  my $source = TAP::Parser::Source->new->raw(\'mytest.pg');
  $source->config({ pgTAP => {
      dbname   => 'testing',
      username => 'postgres',
      suffix   => '.pg',
  }});
  $source->assemble_meta;

  my $class = 'TAP::Parser::SourceHandler::pgTAP';
  my $vote  = $class->can_handle( $source );
  my $iter  = $class->make_iterator( $source );

=head1 Description

This source handler executes pgTAP tests. It does two things:

=over

=item 1.

Looks at the L<TAP::Parser::Source> passed to it to determine whether or not
the source in question is in fact a pgTAP test (L</can_handle>).

=item 2.

Creates an iterator that will call C<psql> to run the pgTAP tests
(L</make_iterator>).

=back

Unless you're writing a plugin or subclassing L<TAP::Parser>, you probably
won't need to use this module directly.

=head2 Testing with pgTAP

If you just want to write tests with L<pgTAP|https://pgtap.org/>, here's how:

=over

=item *

Build your test database, including pgTAP. It's best to install it in its own
schema. To build it and install it in the schema "tap", do this (assuming your
database is named "try"):

  make TAPSCHEMA=tap
  make install
  psql -U postgres -d try -f pgtap.sql

=item *

Write your tests in files ending in F<.pg> in the F<t> directory, right
alongside your normal Perl F<.t> tests. Here's a simple pgTAP test to get you
started:

  BEGIN;

  SET search_path = public,tap,pg_catalog;

  SELECT plan(1);

  SELECT pass('This should pass!');

  SELECT * FROM finish();
  ROLLBACK;

Note how C<search_path> has been set so that the pgTAP functions can be found
in the "tap" schema. Consult the extensive L<pgTAP
documentation|https://pgtap.org/documentation.html> for a comprehensive list of
test functions.

=item *

Run your tests with C<prove> like so:

  prove --source Perl \
        --ext .t --ext .pg \
        --source pgTAP --pgtap-option dbname=try \
                       --pgtap-option username=postgres \
                       --pgtap-option suffix=.pg

This will run both your Perl F<.t> tests and your pgTAP F<.pg> tests all
together. You can also use L<pg_prove> to run just the pgTAP tests like so:

  pg_prove -d try -U postgres t/

=item *

Once you're sure that you've got the pgTAP tests working, modify your
F<Build.PL> script to allow F<./Build test> to run both the Perl and the pgTAP
tests, like so:

  Module::Build->new(
      module_name        => 'MyApp',
      test_file_exts     => [qw(.t .pg)],
      use_tap_harness    => 1,
      configure_requires => { 'Module::Build' => '0.30', },
      tap_harness_args   => {
          sources => {
              Perl  => undef,
              pgTAP => {
                  dbname   => 'try',
                  username => 'postgres',
                  suffix   => '.pg',
              },
          }
      },
      build_requires     => {
          'Module::Build'                     => '0.30',
          'TAP::Parser::SourceHandler::pgTAP' => '3.19',
      },
  )->create_build_script;

The C<use_tap_harness> parameter is optional, since it's implicitly set by the
use of the C<tap_harness_args> parameter. All the other parameters are
required as you see here. See the documentation for C<make_iterator()> for a
complete list of options to the C<pgTAP> key under C<sources>.

And that's it. Now get testing!

=back

=head1 METHODS

=head2 Class Methods

=head3 C<can_handle>

  my $vote = $class->can_handle( $source );

Looks at the source to determine whether or not it's a pgTAP test and returns
a score for how likely it is in fact a pgTAP test file. The scores are as
follows:

  1    if it's not a file and starts with "pgsql:".
  1    if it has a suffix equal to that in a "suffix" config
  0.9  if its suffix is ".pg"
  0.8  if its suffix is ".sql"
  0.75 if its suffix is ".s"

The latter two scores are subject to change, so try to name your pgTAP tests
ending in ".pg" or specify a suffix in the configuration to be sure.

=cut

sub can_handle {
    my ( $class, $source ) = @_;
    my $meta = $source->meta;

    unless ($meta->{is_file}) {
        my $test = ref $source->raw ? ${ $source->raw } : $source->raw;
        return 1 if $test =~ /^pgsql:/;
        return 0;
    }

    my $suf = $meta->{file}{lc_ext};

    # If the config specifies a suffix, it's required.
    if ( my $config = $source->config_for('pgTAP') ) {
        if ( my $suffix = $config->{suffix} ) {
            if (ref $suffix) {
                return (grep { $suf eq $_ } @{ $suffix }) ? 1 : 0;
            }
            return $suf eq $config->{suffix} ? 1 : 0;
        }
    }

    # Otherwise, return a score for our supported suffixes.
    my %score_for = (
        '.pg'  => 0.9,
        '.sql' => 0.8,
        '.s'   => 0.75,
    );
    return $score_for{$suf} || 0;
}

=head3 C<make_iterator>

  my $iterator = $class->make_iterator( $source );

Returns a new L<TAP::Parser::Iterator::Process> for the source.
C<< $source->raw >> must be either a file name or a scalar reference to the
file name -- or a string starting with "pgsql:", in which case the remainder
of the string is assumed to be SQL to be executed inside the database.

The pgTAP tests are run by executing C<psql>, the PostgreSQL command-line
utility. A number of arguments are passed to it, many of which you can affect
by setting up the source source configuration. The configuration must be a
hash reference, and supports the following keys:

=over

=item C<psql>

The path to the C<psql> command. Defaults to simply "psql", which should work
well enough if it's in your path.

=item C<dbname>

The database to which to connect to run the tests. Defaults to the value of
the C<$PGDATABASE> environment variable or, if not set, to the system
username.

=item C<username>

The PostgreSQL username to use to connect to PostgreSQL. If not specified, no
username will be used, in which case C<psql> will fall back on either the
C<$PGUSER> environment variable or, if not set, the system username.

=item C<host>

Specifies the host name of the machine to which to connect to the PostgreSQL
server. 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<$PGDATABASE> environment
variable or, if not set, the local host.

=item C<port>

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 the
time C<psql> was compiled, usually 5432.

=item C<pset>

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

=begin comment

=item C<search_path>

The schema search path to use during the execution of the tests. Useful for
overriding the default search path and you have pgTAP installed in a schema
not included in that search path.

=end comment

=back

=cut

sub make_iterator {
    my ( $class, $source ) = @_;
    my $config = $source->config_for('pgTAP');

    my @command = ( $config->{psql} || 'psql' );
    push @command, qw(
      --no-psqlrc
      --no-align
      --quiet
      --pset pager=off
      --pset tuples_only=true
      --set ON_ERROR_STOP=1
    );

    for (qw(username host port dbname)) {
        push @command, "--$_" => $config->{$_} if defined $config->{$_};
    }

    if (my $pset = $config->{pset}) {
        while (my ($k, $v) = each %{ $pset }) {
            push @command, '--pset', "$k=$v";
        }
    }

    if (my $set = $config->{set}) {
        while (my ($k, $v) = each %{ $set }) {
            push @command, '--set', "$k=$v";
        }
    }

    my $fn = ref $source->raw ? ${ $source->raw } : $source->raw;

    if ($fn && $fn =~ s/^pgsql:\s*//) {
        push @command, '--command', $fn;
    } else {
        $class->_croak(
            'No such file or directory: ' . ( defined $fn ? $fn : '' ) )
            unless $fn && -e $fn;
        push @command, '--file', $fn;
    }

    # XXX I'd like a way to be able to specify environment variables to set when
    # the iterator executes the command...
    # local $ENV{PGOPTIONS} = "--search_path=$config->{search_path}"
    #     if $config->{search_path};

    return TAP::Parser::Iterator::Process->new({
        command => \@command,
        merge   => $source->merge
    });
}

=head1 See Also

=over

=item * L<TAP::Object>

=item * L<TAP::Parser>

=item * L<TAP::Parser::IteratorFactory>

=item * L<TAP::Parser::SourceHandler>

=item * L<TAP::Parser::SourceHandler::Executable>

=item * L<TAP::Parser::SourceHandler::Perl>

=item * L<TAP::Parser::SourceHandler::File>

=item * L<TAP::Parser::SourceHandler::Handle>

=item * L<TAP::Parser::SourceHandler::RawTAP>

=item * L<pgTAP|https://pgtap.org/>

=back

=head1 Support

This module is managed in an open
L<GitHub repository|https://github.com/theory/tap-parser-sourcehandler-pgtap/>.
Feel free to fork and contribute, or to clone
C<git://github.com/theory/tap-parser-sourcehandler-pgtap.git> and send
patches!

Found a bug? Please
L<post|https://github.com/theory/tap-parser-sourcehandler-pgtap/issues> or
L<email|mailto:bug-tap-parser-sourcehandler-pgtap@rt.cpan.org> a report!

=head1 Author

David E. Wheeler <dwheeler@cpan.org>

=head1 Copyright and License

Copyright (c) 2010-2025 David E. Wheeler. Some Rights Reserved.

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

=cut