1 package Test::SQL::Translator;
3 # ----------------------------------------------------------------------
4 # Copyright (C) 2003 The SQLFairy Authors
6 # This program is free software; you can redistribute it and/or
7 # modify it under the terms of the GNU General Public License as
8 # published by the Free Software Foundation; version 2.
10 # This program is distributed in the hope that it will be useful, but
11 # WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 # General Public License for more details.
15 # You should have received a copy of the GNU General Public License
16 # along with this program; if not, write to the Free Software
17 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
19 # -------------------------------------------------------------------
25 Test::SQL::Translator - Test::More test functions for the Schema objects.
32 #use SQL::Translator::Schema::Constants;
33 use SQL::Translator::Constants qw(:sqlt_constants);
35 use base qw(Exporter);
36 use vars qw($VERSION @EXPORT @EXPORT_OK);
50 # $ATTRIBUTES{ <schema_object_name> } = { <attribname> => <default>, ... }
55 default_value => undef,
61 is_auto_increment => 0,
64 # foreign_key_reference,
79 reference_fields => [],
80 reference_table => '',
100 perform_action_when => undef,
101 database_events => undef,
118 #primary_key => undef, # pkey constraint
122 constraints => undef,
130 procedures => undef, # [] when set
131 tables => undef, # [] when set
132 triggers => undef, # [] when set
133 views => undef, # [] when set
139 # Given a test hash and schema object name set any attribute keys not present in
140 # the test hash to their default value for that schema object type.
141 # e.g. default_attribs( $test, "field" );
142 sub default_attribs {
143 my ($hashref, $object_type) = @_;
145 if ( !exists $ATTRIBUTES{ $object_type } ) {
146 die "Can't add default attribs for unknown Schema "
147 . "object type '$object_type'.";
151 grep { !exists $hashref->{ $_ } }
152 keys %{ $ATTRIBUTES{ $object_type } }
154 $hashref->{ $attr } = $ATTRIBUTES{ $object_type }{ $attr }
160 # Format test name so it will prepend the test names used below.
164 $name = "$name - " if $name;
169 my ($f1,$test,$name) = @_;
170 my $t_name = t_name($name);
171 default_attribs($test,"field");
174 fail " Field '$test->{name}' doesn't exist!";
175 # TODO Do a skip on the following tests. Currently the test counts wont
176 # match at the end. So at least it fails.
180 my $full_name = $f1->table->name.".".$test->{name};
182 is( $f1->name, $test->{name}, "${t_name}Field '$full_name'" );
184 is( $f1->is_valid, $test->{is_valid},
185 "$t_name is ".($test->{is_valid} ? '' : 'not ').'valid' );
187 is( $f1->data_type, $test->{data_type},
188 "$t_name type is '$test->{data_type}'" );
190 is( $f1->size, $test->{size}, "$t_name size is '$test->{size}'" );
192 is( $f1->default_value, $test->{default_value},
193 "$t_name default value is "
194 .(defined($test->{default_value}) ? "'$test->{default_value}'" : "UNDEF" )
197 is( $f1->is_nullable, $test->{is_nullable},
198 "$t_name ".($test->{is_nullable} ? 'can' : 'cannot').' be null' );
200 is( $f1->is_unique, $test->{is_unique},
201 "$t_name ".($test->{is_unique} ? 'can' : 'cannot').' be unique' );
203 is( $f1->is_primary_key, $test->{is_primary_key},
204 "$t_name is ".($test->{is_primary_key} ? '' : 'not ').'a primary_key' );
206 is( $f1->is_foreign_key, $test->{is_foreign_key},
207 "$t_name is ".($test->{is_foreign_key} ? '' : 'not').' a foreign_key' );
209 is( $f1->is_auto_increment, $test->{is_auto_increment},
211 .($test->{is_auto_increment} ? '' : 'not ').'an auto_increment' );
213 is( $f1->comments, $test->{comments}, "$t_name comments" );
215 is_deeply( { $f1->extra }, $test->{extra}, "$t_name extra" );
219 my ($obj,$test,$name) = @_;
220 my $t_name = t_name($name);
221 default_attribs($test,"constraint");
223 is( $obj->name, $test->{name}, "${t_name}Constraint '$test->{name}'" );
225 is( $obj->type, $test->{type}, "$t_name type is '$test->{type}'" );
227 is( $obj->deferrable, $test->{deferrable},
228 "$t_name ".($test->{deferrable} ? 'can' : 'cannot').' be deferred' );
230 is( $obj->is_valid, $test->{is_valid},
231 "$t_name is ".($test->{is_valid} ? '' : 'not ').'valid' );
233 is($obj->table->name,$test->{table},"$t_name table is '$test->{table}'" );
235 is( $obj->expression, $test->{expression},
236 "$t_name expression is '$test->{expression}'" );
238 is_deeply( [$obj->fields], $test->{fields},
239 "$t_name fields are '".join(",",@{$test->{fields}})."'" );
241 is( $obj->reference_table, $test->{reference_table},
242 "$t_name reference_table is '$test->{reference_table}'" );
244 is_deeply( [$obj->reference_fields], $test->{reference_fields},
245 "$t_name reference_fields are '".join(",",@{$test->{reference_fields}})."'" );
247 is( $obj->match_type, $test->{match_type},
248 "$t_name match_type is '$test->{match_type}'" );
250 is( $obj->on_delete, $test->{on_delete},
251 "$t_name on_delete is '$test->{on_delete}'" );
253 is( $obj->on_update, $test->{on_update},
254 "$t_name on_update is '$test->{on_update}'" );
256 is_deeply( [$obj->options], $test->{options},
257 "$t_name options are '".join(",",@{$test->{options}})."'" );
259 is_deeply( { $obj->extra }, $test->{extra}, "$t_name extra" );
263 my ($obj,$test,$name) = @_;
264 my $t_name = t_name($name);
265 default_attribs($test,"index");
267 is( $obj->name, $test->{name}, "${t_name}Index '$test->{name}'" );
269 is( $obj->is_valid, $test->{is_valid},
270 "$t_name is ".($test->{is_valid} ? '' : 'not ').'valid' );
272 is( $obj->type, $test->{type}, "$t_name type is '$test->{type}'" );
274 is_deeply( [$obj->fields], $test->{fields},
275 "$t_name fields are '".join(",",@{$test->{fields}})."'" );
277 is_deeply( [$obj->options], $test->{options},
278 "$t_name options are '".join(",",@{$test->{options}})."'" );
280 is_deeply( { $obj->extra }, $test->{extra}, "$t_name extra" );
284 my ($obj,$test,$name) = @_;
285 my $t_name = t_name($name);
286 default_attribs($test,"index");
288 is( $obj->name, $test->{name}, "${t_name}Trigger '$test->{name}'" );
290 is( $obj->is_valid, $test->{is_valid},
291 "$t_name is ".($test->{is_valid} ? '' : 'not ').'valid' );
293 is( $obj->perform_action_when, $test->{perform_action_when},
294 "$t_name perform_action_when is '$test->{perform_action_when}'" );
296 is( join(',', $obj->database_events), $test->{database_events},
297 sprintf("%s database_events is '%s'",
299 $test->{'database_events'},
303 is( $obj->on_table, $test->{on_table},
304 "$t_name on_table is '$test->{on_table}'" );
306 is( $obj->action, $test->{action}, "$t_name action is '$test->{action}'" );
308 is_deeply( { $obj->extra }, $test->{extra}, "$t_name extra" );
312 my ($obj,$test,$name) = @_;
313 my $t_name = t_name($name);
314 default_attribs($test,"index");
316 #isa_ok( $v, 'SQL::Translator::Schema::View', 'View' );
318 is( $obj->name, $test->{name}, "${t_name}View '$test->{name}'" );
320 is( $obj->is_valid, $test->{is_valid},
321 "$t_name is ".($test->{is_valid} ? '' : 'not ').'valid' );
323 is( $obj->sql, $test->{sql}, "$t_name sql is '$test->{sql}'" );
325 is_deeply( [$obj->fields], $test->{fields},
326 "$t_name fields are '".join(",",@{$test->{fields}})."'" );
328 is_deeply( { $obj->extra }, $test->{extra}, "$t_name extra" );
332 my ($obj,$test,$name) = @_;
333 my $t_name = t_name($name);
334 default_attribs($test,"index");
336 #isa_ok( $v, 'SQL::Translator::Schema::View', 'View' );
338 is( $obj->name, $test->{name}, "${t_name}Procedure '$test->{name}'" );
340 is( $obj->sql, $test->{sql}, "$t_name sql is '$test->{sql}'" );
342 is_deeply( [$obj->parameters], $test->{parameters},
343 "$t_name parameters are '".join(",",@{$test->{parameters}})."'" );
345 is( $obj->comments, $test->{comments},
346 "$t_name comments is '$test->{comments}'" );
348 is( $obj->owner, $test->{owner}, "$t_name owner is '$test->{owner}'" );
350 is_deeply( { $obj->extra }, $test->{extra}, "$t_name extra" );
354 my ($obj,$test,$name) = @_;
355 my $t_name = t_name($name);
356 default_attribs($test,"table");
359 my $tbl_name = $arg{name} || die "Need a table name to test.";
360 is( $obj->{name}, $arg{name}, "${t_name}Table '$arg{name}'" );
362 is_deeply( [$obj->options], $test->{options},
363 "$t_name options are '".join(",",@{$test->{options}})."'" );
365 is_deeply( { $obj->extra }, $test->{extra}, "$t_name extra" );
368 if ( $arg{fields} ) {
369 my @fldnames = map {$_->{name}} @{$arg{fields}};
371 [ map {$_->name} $obj->get_fields ],
373 "${t_name} field names are ".join(", ",@fldnames)
375 foreach ( @{$arg{fields}} ) {
376 my $f_name = $_->{name} || die "Need a field name to test.";
377 next unless my $fld = $obj->get_field($f_name);
378 field_ok( $fld, $_, $name );
382 is(scalar($obj->get_fields), undef,
383 "${t_name} has no fields.");
386 # Constraints and Indices
387 _test_kids($obj, $test, $name, {
388 constraint => 'constraints',
394 my ( $obj, $test, $name, $kids ) = @_;
395 my $t_name = t_name($name);
396 my $obj_name = ref $obj;
397 ($obj_name) = $obj_name =~ m/^.*::(.*)$/;
399 while ( my ( $object_type, $plural ) = each %$kids ) {
400 next unless defined $test->{ $plural };
402 if ( my @tests = @{ $test->{ $plural } } ) {
403 my $meth = "get_$plural";
404 my @objects = $obj->$meth;
405 is( scalar(@objects), scalar(@tests),
406 "${t_name}$obj_name has " . scalar(@tests) . " $plural"
409 for my $object (@objects) {
410 my $ans = { lc($obj_name) => $obj->name, %{ shift @tests } };
412 my $meth = "${object_type}_ok";
415 $meth->( $object, $ans, $name );
423 my ($obj,$test,$name) = @_;
424 my $t_name = t_name($name);
425 default_attribs($test,"schema");
427 is( $obj->name, $test->{name}, "${t_name}Schema name is '$test->{name}'" );
429 is( $obj->database, $test->{database},
430 "$t_name database is '$test->{database}'" );
432 is_deeply( { $obj->extra }, $test->{extra}, "$t_name extra" );
434 is( $obj->is_valid, $test->{is_valid},
435 "$t_name is ".($test->{is_valid} ? '' : 'not ').'valid' );
438 if ( $test->{tables} ) {
439 is_deeply( [ map {$_->name} $obj->get_tables ],
440 [ map {$_->{name}} @{$test->{tables}} ],
441 "${t_name} table names match" );
442 foreach ( @{$test->{tables}} ) {
443 my $t_name = $_->{name} || die "Need a table name to test.";
444 table_ok( $obj->get_table($t_name), $_, $name );
448 is(scalar($obj->get_tables), undef,
449 "${t_name} has no tables.");
452 # Procedures, Triggers, Views
453 _test_kids($obj, $test, $name, {
454 procedure => 'procedures',
455 trigger => 'triggers',
460 # maybe_plan($ntests, @modules)
462 # Calls plan $ntests if @modules can all be loaded; otherwise,
463 # calls skip_all with an explanation of why the tests were skipped.
465 my ($ntests, @modules) = @_;
468 for my $module (@modules) {
470 if ($@ && $@ =~ /Can't locate (\S+)/) {
479 my $msg = sprintf "Missing dependenc%s: %s",
480 @errors == 1 ? 'y' : 'ies',
482 plan skip_all => $msg;
484 elsif ($ntests and $ntests ne 'no_plan') {
485 plan tests => $ntests;
492 1; # compile please ===========================================================
503 use Test::SQL::Translator;
506 my $sqlt = SQL::Translator->new(
508 filename => "$Bin/data/magic/test.magic",
512 my $schema = $sqlt->schema;
514 # Test the table it produced.
515 table_ok( $schema->get_table("Customer"), {
519 name => "CustomerID",
522 default_value => undef,
528 data_type => "VARCHAR",
535 type => "PRIMARY KEY",
536 fields => "CustomerID",
549 Provides a set of Test::More tests for Schema objects. Testing a parsed
550 schema is then as easy as writing a perl data structure describing how you
551 expect the schema to look. Also provides maybe_plan for conditionally running
552 tests based on their dependencies.
554 The data structures given to the test subs don't have to include all the
555 possible values, only the ones you expect to have changed. Any left out will be
556 tested to make sure they are still at their default value. This is a usefull
557 check that you your parser hasn't accidentally set schema values you didn't
560 For an example of the output run the t/16xml-parser.t test.
564 All the tests take a first arg of the schema object to test, followed by a
565 hash ref describing how you expect that object to look (you only need give the
566 attributes you expect to have changed from the default).
567 The 3rd arg is an optional test name to pre-pend to all the generated test
584 =head1 CONDITIONAL TESTS
586 The C<maybe_plan> function handles conditionally running an individual
587 test. It is here to enable running the test suite even when dependencies
588 are missing; not having (for example) GraphViz installed should not keep
589 the test suite from passing.
591 C<maybe_plan> takes the number of tests to (maybe) run, and a list of
592 modules on which test execution depends:
594 maybe_plan(180, 'SQL::Translator::Parser::MySQL');
596 If one of C<SQL::Translator::Parser::MySQL>'s dependencies does not exist,
597 then the test will be skipped.
601 table_ok, field_ok, constraint_ok, index_ok, view_ok, trigger_ok, procedure_ok,
608 =item Test the tests!
610 =item Test Count Constants
612 Constants to give the number of tests each *_ok sub uses. e.g. How many tests
613 does field_ok run? Can then use these to set up the test plan easily.
617 As the test subs wrap up lots of tests in one call you can't skip idividual
618 tests only whole sets e.g. a whole table or field.
619 We could add skip_* items to the test hashes to allow per test skips. e.g.
621 skip_is_primary_key => "Need to fix primary key parsing.",
623 =item yaml test specs
625 Maybe have the test subs also accept yaml for the test hash ref as its a much
626 nicer for writing big data structures. We can then define tests as in input
627 schema file and test yaml file to compare it against.
635 Mark D. Addison E<lt>mark.addison@itn.co.ukE<gt>,
636 Darren Chamberlain <darren@cpan.org>.
638 Thanks to Ken Y. Clark for the original table and field test code taken from
643 perl(1), SQL::Translator, SQL::Translator::Schema, Test::More.