remove default Pg dsn
[dbsrgits/SQL-Translator.git] / lib / Test / SQL / Translator.pm
CommitLineData
9bbbf403 1package Test::SQL::Translator;
2
9bbbf403 3=pod
4
5=head1 NAME
6
7Test::SQL::Translator - Test::More test functions for the Schema objects.
8
9=cut
10
11use strict;
12use warnings;
11fee3e0 13use Test::More;
14use SQL::Translator::Schema::Constants;
9bbbf403 15
16use base qw(Exporter);
0c04c5a2 17our @EXPORT_OK;
18our $VERSION = '1.59';
19our @EXPORT = qw(
a0eb602d 20 schema_ok
9bbbf403 21 table_ok
22 field_ok
23 constraint_ok
24 index_ok
25 view_ok
26 trigger_ok
27 procedure_ok
49133ae7 28 maybe_plan
9bbbf403 29);
9bbbf403 30
9bbbf403 31# $ATTRIBUTES{ <schema_object_name> } = { <attribname> => <default>, ... }
11fee3e0 32my %ATTRIBUTES = (
33 field => {
34 name => undef,
35 data_type => '',
36 default_value => undef,
37 size => '0',
38 is_primary_key => 0,
39 is_unique => 0,
40 is_nullable => 1,
41 is_foreign_key => 0,
42 is_auto_increment => 0,
43 comments => '',
44 extra => {},
45 # foreign_key_reference,
46 is_valid => 1,
47 # order
48 },
49 constraint => {
50 name => '',
51 type => '',
52 deferrable => 1,
53 expression => '',
54 is_valid => 1,
55 fields => [],
56 match_type => '',
57 options => [],
58 on_delete => '',
59 on_update => '',
60 reference_fields => [],
61 reference_table => '',
62 extra => {},
63 },
64 index => {
65 fields => [],
66 is_valid => 1,
67 name => "",
68 options => [],
69 type => NORMAL,
70 extra => {},
71 },
72 view => {
73 name => "",
74 sql => "",
75 fields => [],
76 is_valid => 1,
77 extra => {},
78 },
79 trigger => {
80 name => '',
81 perform_action_when => undef,
82 database_events => undef,
83 on_table => undef,
84 action => undef,
85 is_valid => 1,
86 extra => {},
87 },
88 procedure => {
89 name => '',
90 sql => '',
91 parameters => [],
92 owner => '',
93 comments => '',
94 extra => {},
95 },
96 table => {
97 comments => undef,
98 name => '',
99 #primary_key => undef, # pkey constraint
100 options => [],
101 #order => 0,
102 fields => undef,
103 constraints => undef,
104 indices => undef,
105 is_valid => 1,
106 extra => {},
107 },
108 schema => {
109 name => '',
110 database => '',
111 procedures => undef, # [] when set
112 tables => undef, # [] when set
113 triggers => undef, # [] when set
114 views => undef, # [] when set
115 is_valid => 1,
116 extra => {},
117 }
118);
9bbbf403 119
120# Given a test hash and schema object name set any attribute keys not present in
121# the test hash to their default value for that schema object type.
122# e.g. default_attribs( $test, "field" );
123sub default_attribs {
11fee3e0 124 my ($hashref, $object_type) = @_;
125
126 if ( !exists $ATTRIBUTES{ $object_type } ) {
ea93df61 127 die "Can't add default attribs for unknown Schema "
11fee3e0 128 . "object type '$object_type'.";
129 }
130
ea93df61 131 for my $attr (
11fee3e0 132 grep { !exists $hashref->{ $_ } }
ea93df61 133 keys %{ $ATTRIBUTES{ $object_type } }
11fee3e0 134 ) {
135 $hashref->{ $attr } = $ATTRIBUTES{ $object_type }{ $attr }
136 }
137
138 return $hashref;
9bbbf403 139}
140
141# Format test name so it will prepend the test names used below.
142sub t_name {
143 my $name = shift;
144 $name ||= "";
145 $name = "$name - " if $name;
146 return $name;
147}
148
149sub field_ok {
150 my ($f1,$test,$name) = @_;
151 my $t_name = t_name($name);
152 default_attribs($test,"field");
153
154 unless ($f1) {
155 fail " Field '$test->{name}' doesn't exist!";
a0eb602d 156 # TODO Do a skip on the following tests. Currently the test counts wont
157 # match at the end. So at least it fails.
9bbbf403 158 return;
159 }
160
a0eb602d 161 my $full_name = $f1->table->name.".".$test->{name};
162
163 is( $f1->name, $test->{name}, "${t_name}Field '$full_name'" );
9bbbf403 164
165 is( $f1->is_valid, $test->{is_valid},
a0eb602d 166 "$t_name is ".($test->{is_valid} ? '' : 'not ').'valid' );
9bbbf403 167
168 is( $f1->data_type, $test->{data_type},
169 "$t_name type is '$test->{data_type}'" );
170
171 is( $f1->size, $test->{size}, "$t_name size is '$test->{size}'" );
172
173 is( $f1->default_value, $test->{default_value},
174 "$t_name default value is "
175 .(defined($test->{default_value}) ? "'$test->{default_value}'" : "UNDEF" )
176 );
177
178 is( $f1->is_nullable, $test->{is_nullable},
179 "$t_name ".($test->{is_nullable} ? 'can' : 'cannot').' be null' );
180
181 is( $f1->is_unique, $test->{is_unique},
182 "$t_name ".($test->{is_unique} ? 'can' : 'cannot').' be unique' );
183
184 is( $f1->is_primary_key, $test->{is_primary_key},
185 "$t_name is ".($test->{is_primary_key} ? '' : 'not ').'a primary_key' );
186
187 is( $f1->is_foreign_key, $test->{is_foreign_key},
188 "$t_name is ".($test->{is_foreign_key} ? '' : 'not').' a foreign_key' );
189
190 is( $f1->is_auto_increment, $test->{is_auto_increment},
191 "$t_name is "
192 .($test->{is_auto_increment} ? '' : 'not ').'an auto_increment' );
193
194 is( $f1->comments, $test->{comments}, "$t_name comments" );
195
196 is_deeply( { $f1->extra }, $test->{extra}, "$t_name extra" );
197}
198
199sub constraint_ok {
200 my ($obj,$test,$name) = @_;
201 my $t_name = t_name($name);
202 default_attribs($test,"constraint");
203
204 is( $obj->name, $test->{name}, "${t_name}Constraint '$test->{name}'" );
205
206 is( $obj->type, $test->{type}, "$t_name type is '$test->{type}'" );
207
208 is( $obj->deferrable, $test->{deferrable},
209 "$t_name ".($test->{deferrable} ? 'can' : 'cannot').' be deferred' );
210
211 is( $obj->is_valid, $test->{is_valid},
212 "$t_name is ".($test->{is_valid} ? '' : 'not ').'valid' );
213
214 is($obj->table->name,$test->{table},"$t_name table is '$test->{table}'" );
215
216 is( $obj->expression, $test->{expression},
217 "$t_name expression is '$test->{expression}'" );
218
219 is_deeply( [$obj->fields], $test->{fields},
220 "$t_name fields are '".join(",",@{$test->{fields}})."'" );
221
222 is( $obj->reference_table, $test->{reference_table},
223 "$t_name reference_table is '$test->{reference_table}'" );
224
225 is_deeply( [$obj->reference_fields], $test->{reference_fields},
226 "$t_name reference_fields are '".join(",",@{$test->{reference_fields}})."'" );
227
228 is( $obj->match_type, $test->{match_type},
229 "$t_name match_type is '$test->{match_type}'" );
230
231 is( $obj->on_delete, $test->{on_delete},
232 "$t_name on_delete is '$test->{on_delete}'" );
233
234 is( $obj->on_update, $test->{on_update},
235 "$t_name on_update is '$test->{on_update}'" );
236
237 is_deeply( [$obj->options], $test->{options},
238 "$t_name options are '".join(",",@{$test->{options}})."'" );
ea93df61 239
b1789409 240 is_deeply( { $obj->extra }, $test->{extra}, "$t_name extra" );
9bbbf403 241}
242
243sub index_ok {
244 my ($obj,$test,$name) = @_;
245 my $t_name = t_name($name);
246 default_attribs($test,"index");
247
248 is( $obj->name, $test->{name}, "${t_name}Index '$test->{name}'" );
249
250 is( $obj->is_valid, $test->{is_valid},
251 "$t_name is ".($test->{is_valid} ? '' : 'not ').'valid' );
252
253 is( $obj->type, $test->{type}, "$t_name type is '$test->{type}'" );
254
255 is_deeply( [$obj->fields], $test->{fields},
256 "$t_name fields are '".join(",",@{$test->{fields}})."'" );
257
258 is_deeply( [$obj->options], $test->{options},
259 "$t_name options are '".join(",",@{$test->{options}})."'" );
ea93df61 260
b1789409 261 is_deeply( { $obj->extra }, $test->{extra}, "$t_name extra" );
9bbbf403 262}
263
264sub trigger_ok {
265 my ($obj,$test,$name) = @_;
266 my $t_name = t_name($name);
267 default_attribs($test,"index");
268
14ad260a 269 is( $obj->name, $test->{name}, "${t_name}Trigger '$test->{name}'" );
9bbbf403 270
271 is( $obj->is_valid, $test->{is_valid},
272 "$t_name is ".($test->{is_valid} ? '' : 'not ').'valid' );
273
274 is( $obj->perform_action_when, $test->{perform_action_when},
275 "$t_name perform_action_when is '$test->{perform_action_when}'" );
276
11fee3e0 277 is( join(',', $obj->database_events), $test->{database_events},
278 sprintf("%s database_events is '%s'",
279 $t_name,
280 $test->{'database_events'},
281 )
282 );
9bbbf403 283
284 is( $obj->on_table, $test->{on_table},
285 "$t_name on_table is '$test->{on_table}'" );
286
287 is( $obj->action, $test->{action}, "$t_name action is '$test->{action}'" );
ea93df61 288
b1789409 289 is_deeply( { $obj->extra }, $test->{extra}, "$t_name extra" );
9bbbf403 290}
291
292sub view_ok {
293 my ($obj,$test,$name) = @_;
294 my $t_name = t_name($name);
295 default_attribs($test,"index");
296
297 #isa_ok( $v, 'SQL::Translator::Schema::View', 'View' );
298
299 is( $obj->name, $test->{name}, "${t_name}View '$test->{name}'" );
300
301 is( $obj->is_valid, $test->{is_valid},
302 "$t_name is ".($test->{is_valid} ? '' : 'not ').'valid' );
303
304 is( $obj->sql, $test->{sql}, "$t_name sql is '$test->{sql}'" );
305
306 is_deeply( [$obj->fields], $test->{fields},
307 "$t_name fields are '".join(",",@{$test->{fields}})."'" );
ea93df61 308
b1789409 309 is_deeply( { $obj->extra }, $test->{extra}, "$t_name extra" );
9bbbf403 310}
311
312sub procedure_ok {
313 my ($obj,$test,$name) = @_;
314 my $t_name = t_name($name);
315 default_attribs($test,"index");
316
317 #isa_ok( $v, 'SQL::Translator::Schema::View', 'View' );
318
319 is( $obj->name, $test->{name}, "${t_name}Procedure '$test->{name}'" );
320
321 is( $obj->sql, $test->{sql}, "$t_name sql is '$test->{sql}'" );
322
323 is_deeply( [$obj->parameters], $test->{parameters},
324 "$t_name parameters are '".join(",",@{$test->{parameters}})."'" );
325
ea93df61 326 is( $obj->comments, $test->{comments},
9bbbf403 327 "$t_name comments is '$test->{comments}'" );
328
329 is( $obj->owner, $test->{owner}, "$t_name owner is '$test->{owner}'" );
ea93df61 330
b1789409 331 is_deeply( { $obj->extra }, $test->{extra}, "$t_name extra" );
9bbbf403 332}
333
334sub table_ok {
335 my ($obj,$test,$name) = @_;
336 my $t_name = t_name($name);
337 default_attribs($test,"table");
338 my %arg = %$test;
339
340 my $tbl_name = $arg{name} || die "Need a table name to test.";
a0eb602d 341 is( $obj->{name}, $arg{name}, "${t_name}Table '$arg{name}'" );
9bbbf403 342
343 is_deeply( [$obj->options], $test->{options},
344 "$t_name options are '".join(",",@{$test->{options}})."'" );
345
b1789409 346 is_deeply( { $obj->extra }, $test->{extra}, "$t_name extra" );
347
9bbbf403 348 # Fields
349 if ( $arg{fields} ) {
a0eb602d 350 my @fldnames = map {$_->{name}} @{$arg{fields}};
ea93df61 351 is_deeply(
a0eb602d 352 [ map {$_->name} $obj->get_fields ],
353 [ @fldnames ],
354 "${t_name} field names are ".join(", ",@fldnames)
355 );
9bbbf403 356 foreach ( @{$arg{fields}} ) {
357 my $f_name = $_->{name} || die "Need a field name to test.";
a0eb602d 358 next unless my $fld = $obj->get_field($f_name);
359 field_ok( $fld, $_, $name );
9bbbf403 360 }
361 }
362 else {
363 is(scalar($obj->get_fields), undef,
a0eb602d 364 "${t_name} has no fields.");
9bbbf403 365 }
366
a0eb602d 367 # Constraints and Indices
368 _test_kids($obj, $test, $name, {
11fee3e0 369 constraint => 'constraints',
370 index => 'indices',
a0eb602d 371 });
372}
373
374sub _test_kids {
11fee3e0 375 my ( $obj, $test, $name, $kids ) = @_;
376 my $t_name = t_name($name);
a0eb602d 377 my $obj_name = ref $obj;
378 ($obj_name) = $obj_name =~ m/^.*::(.*)$/;
379
11fee3e0 380 while ( my ( $object_type, $plural ) = each %$kids ) {
381 next unless defined $test->{ $plural };
382
383 if ( my @tests = @{ $test->{ $plural } } ) {
9bbbf403 384 my $meth = "get_$plural";
11fee3e0 385 my @objects = $obj->$meth;
386 is( scalar(@objects), scalar(@tests),
ea93df61 387 "${t_name}$obj_name has " . scalar(@tests) . " $plural"
11fee3e0 388 );
389
390 for my $object (@objects) {
391 my $ans = { lc($obj_name) => $obj->name, %{ shift @tests } };
392
393 my $meth = "${object_type}_ok";
394 {
395 no strict 'refs';
396 $meth->( $object, $ans, $name );
9bbbf403 397 }
398 }
399 }
400 }
401}
402
403sub schema_ok {
404 my ($obj,$test,$name) = @_;
405 my $t_name = t_name($name);
406 default_attribs($test,"schema");
a0eb602d 407
408 is( $obj->name, $test->{name}, "${t_name}Schema name is '$test->{name}'" );
409
410 is( $obj->database, $test->{database},
411 "$t_name database is '$test->{database}'" );
ea93df61 412
b1789409 413 is_deeply( { $obj->extra }, $test->{extra}, "$t_name extra" );
a0eb602d 414
415 is( $obj->is_valid, $test->{is_valid},
416 "$t_name is ".($test->{is_valid} ? '' : 'not ').'valid' );
417
418 # Tables
419 if ( $test->{tables} ) {
420 is_deeply( [ map {$_->name} $obj->get_tables ],
421 [ map {$_->{name}} @{$test->{tables}} ],
422 "${t_name} table names match" );
423 foreach ( @{$test->{tables}} ) {
424 my $t_name = $_->{name} || die "Need a table name to test.";
425 table_ok( $obj->get_table($t_name), $_, $name );
426 }
427 }
428 else {
429 is(scalar($obj->get_tables), undef,
430 "${t_name} has no tables.");
431 }
432
433 # Procedures, Triggers, Views
434 _test_kids($obj, $test, $name, {
11fee3e0 435 procedure => 'procedures',
436 trigger => 'triggers',
437 view => 'views',
a0eb602d 438 });
9bbbf403 439}
440
49133ae7 441# maybe_plan($ntests, @modules)
442#
443# Calls plan $ntests if @modules can all be loaded; otherwise,
444# calls skip_all with an explanation of why the tests were skipped.
445sub maybe_plan {
446 my ($ntests, @modules) = @_;
447 my @errors;
448
449 for my $module (@modules) {
450 eval "use $module;";
559aa8a9 451 next if !$@;
452
453 if ($@ =~ /Can't locate (\S+)/) {
49133ae7 454 my $mod = $1;
455 $mod =~ s/\.pm$//;
456 $mod =~ s#/#::#g;
457 push @errors, $mod;
458 }
559aa8a9 459 elsif ($@ =~ /([\w\:]+ version [\d\.]+) required.+?this is only version/) {
460 push @errors, $1;
461 }
49133ae7 462 }
463
464 if (@errors) {
465 my $msg = sprintf "Missing dependenc%s: %s",
466 @errors == 1 ? 'y' : 'ies',
467 join ", ", @errors;
468 plan skip_all => $msg;
469 }
dc34f950 470 elsif ($ntests and $ntests ne 'no_plan') {
49133ae7 471 plan tests => $ntests;
472 }
dc34f950 473 else {
474 plan 'no_plan';
475 }
49133ae7 476}
477
9bbbf403 4781; # compile please ===========================================================
479__END__
480
481=pod
482
483=head1 SYNOPSIS
484
485 # t/magic.t
486
487 use FindBin '$Bin';
488 use Test::More;
489 use Test::SQL::Translator;
490
491 # Run parse
492 my $sqlt = SQL::Translator->new(
493 parser => "Magic",
494 filename => "$Bin/data/magic/test.magic",
e2a489c2 495 ...
9bbbf403 496 );
497 ...
498 my $schema = $sqlt->schema;
e2a489c2 499
9bbbf403 500 # Test the table it produced.
501 table_ok( $schema->get_table("Customer"), {
502 name => "Customer",
503 fields => [
504 {
505 name => "CustomerID",
506 data_type => "INT",
507 size => 12,
508 default_value => undef,
509 is_nullable => 0,
510 is_primary_key => 1,
511 },
512 {
513 name => "bar",
514 data_type => "VARCHAR",
515 size => 255,
516 is_nullable => 0,
517 },
518 ],
519 constraints => [
520 {
521 type => "PRIMARY KEY",
522 fields => "CustomerID",
523 },
524 ],
525 indices => [
526 {
527 name => "barindex",
528 fields => ["bar"],
529 },
530 ],
531 });
532
533=head1 DESCSIPTION
534
e2a489c2 535Provides a set of Test::More tests for Schema objects. Testing a parsed
9bbbf403 536schema is then as easy as writing a perl data structure describing how you
e2a489c2 537expect the schema to look. Also provides maybe_plan for conditionally running
538tests based on their dependencies.
9bbbf403 539
e2a489c2 540The data structures given to the test subs don't have to include all the
9bbbf403 541possible values, only the ones you expect to have changed. Any left out will be
10f70490 542tested to make sure they are still at their default value. This is a useful
9bbbf403 543check that you your parser hasn't accidentally set schema values you didn't
e2a489c2 544expect it to.
9bbbf403 545
546For an example of the output run the t/16xml-parser.t test.
547
548=head1 Tests
549
e2a489c2 550All the tests take a first arg of the schema object to test, followed by a
9bbbf403 551hash ref describing how you expect that object to look (you only need give the
552attributes you expect to have changed from the default).
e2a489c2 553The 3rd arg is an optional test name to pre-pend to all the generated test
9bbbf403 554names.
555
556=head2 table_ok
557
558=head2 field_ok
559
560=head2 constraint_ok
561
562=head2 index_ok
563
564=head2 view_ok
565
566=head2 trigger_ok
567
568=head2 procedure_ok
569
49133ae7 570=head1 CONDITIONAL TESTS
571
572The C<maybe_plan> function handles conditionally running an individual
573test. It is here to enable running the test suite even when dependencies
574are missing; not having (for example) GraphViz installed should not keep
575the test suite from passing.
576
577C<maybe_plan> takes the number of tests to (maybe) run, and a list of
578modules on which test execution depends:
579
580 maybe_plan(180, 'SQL::Translator::Parser::MySQL');
581
582If one of C<SQL::Translator::Parser::MySQL>'s dependencies does not exist,
583then the test will be skipped.
584
9bbbf403 585=head1 EXPORTS
586
49133ae7 587table_ok, field_ok, constraint_ok, index_ok, view_ok, trigger_ok, procedure_ok,
588maybe_plan
9bbbf403 589
590=head1 TODO
591
592=over 4
593
594=item Test the tests!
595
19458c2f 596=item Test Count Constants
597
e2a489c2 598Constants to give the number of tests each *_ok sub uses. e.g. How many tests
19458c2f 599does field_ok run? Can then use these to set up the test plan easily.
600
9bbbf403 601=item Test skipping
602
4b2d9113 603As the test subs wrap up lots of tests in one call you can't skip individual
9bbbf403 604tests only whole sets e.g. a whole table or field.
605We could add skip_* items to the test hashes to allow per test skips. e.g.
606
607 skip_is_primary_key => "Need to fix primary key parsing.",
608
609=item yaml test specs
610
611Maybe have the test subs also accept yaml for the test hash ref as its a much
612nicer for writing big data structures. We can then define tests as in input
613schema file and test yaml file to compare it against.
614
615=back
616
617=head1 BUGS
618
619=head1 AUTHOR
620
ea93df61 621Mark D. Addison E<lt>mark.addison@itn.co.ukE<gt>,
11fee3e0 622Darren Chamberlain <darren@cpan.org>.
9bbbf403 623
11ad2df9 624Thanks to Ken Y. Clark for the original table and field test code taken from
625his mysql test.
9bbbf403 626
627=head1 SEE ALSO
628
629perl(1), SQL::Translator, SQL::Translator::Schema, Test::More.
630
631=cut