1 package Test::SQL::Translator;
3 # ----------------------------------------------------------------------
5 # ----------------------------------------------------------------------
6 # Copyright (C) 2003 The SQLFairy Authors
8 # This program is free software; you can redistribute it and/or
9 # modify it under the terms of the GNU General Public License as
10 # published by the Free Software Foundation; version 2.
12 # This program is distributed in the hope that it will be useful, but
13 # WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 # General Public License for more details.
17 # You should have received a copy of the GNU General Public License
18 # along with this program; if not, write to the Free Software
19 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
21 # -------------------------------------------------------------------
27 Test::SQL::Translator - Test::More test functions for the Schema objects.
34 use base qw(Exporter);
36 use vars qw($VERSION @EXPORT @EXPORT_OK);
37 $VERSION = sprintf "%d.%02d", q$Revision$ =~ /(\d+)\.(\d+)/;
51 use SQL::Translator::Schema::Constants;
53 # $ATTRIBUTES{ <schema_object_name> } = { <attribname> => <default>, ... }
55 $ATTRIBUTES{field} = {
58 default_value => undef,
64 is_auto_increment => 0,
67 # foreign_key_reference,
71 $ATTRIBUTES{constraint} = {
82 reference_fields => [],
83 reference_table => '',
86 $ATTRIBUTES{'index'} = {
94 $ATTRIBUTES{'view'} = {
101 $ATTRIBUTES{'trigger'} = {
103 perform_action_when => undef,
104 database_event => undef,
110 $ATTRIBUTES{'procedure'} = {
118 $ATTRIBUTES{table} = {
121 #primary_key => undef, # pkey constraint
125 constraints => undef,
130 $ATTRIBUTES{schema} = {
133 procedures => undef, # [] when set
134 tables => undef, # [] when set
135 triggers => undef, # [] when set
136 views => undef, # [] when set
143 # Given a test hash and schema object name set any attribute keys not present in
144 # the test hash to their default value for that schema object type.
145 # e.g. default_attribs( $test, "field" );
146 sub default_attribs {
147 my ($foo, $what) = @_;
148 die "Can't add default attibs - unkown Scheam object type '$what'."
149 unless exists $ATTRIBUTES{$what};
150 $foo->{$_} = $ATTRIBUTES{$what}{$_}
151 foreach grep !exists($foo->{$_}), keys %{$ATTRIBUTES{$what}};
155 # Format test name so it will prepend the test names used below.
159 $name = "$name - " if $name;
164 my ($f1,$test,$name) = @_;
165 my $t_name = t_name($name);
166 default_attribs($test,"field");
169 fail " Field '$test->{name}' doesn't exist!";
170 # TODO Do a skip on the following tests. Currently the test counts wont
171 # match at the end. So at least it fails.
175 my $full_name = $f1->table->name.".".$test->{name};
177 is( $f1->name, $test->{name}, "${t_name}Field '$full_name'" );
179 is( $f1->is_valid, $test->{is_valid},
180 "$t_name is ".($test->{is_valid} ? '' : 'not ').'valid' );
182 is( $f1->data_type, $test->{data_type},
183 "$t_name type is '$test->{data_type}'" );
185 is( $f1->size, $test->{size}, "$t_name size is '$test->{size}'" );
187 is( $f1->default_value, $test->{default_value},
188 "$t_name default value is "
189 .(defined($test->{default_value}) ? "'$test->{default_value}'" : "UNDEF" )
192 is( $f1->is_nullable, $test->{is_nullable},
193 "$t_name ".($test->{is_nullable} ? 'can' : 'cannot').' be null' );
195 is( $f1->is_unique, $test->{is_unique},
196 "$t_name ".($test->{is_unique} ? 'can' : 'cannot').' be unique' );
198 is( $f1->is_primary_key, $test->{is_primary_key},
199 "$t_name is ".($test->{is_primary_key} ? '' : 'not ').'a primary_key' );
201 is( $f1->is_foreign_key, $test->{is_foreign_key},
202 "$t_name is ".($test->{is_foreign_key} ? '' : 'not').' a foreign_key' );
204 is( $f1->is_auto_increment, $test->{is_auto_increment},
206 .($test->{is_auto_increment} ? '' : 'not ').'an auto_increment' );
208 is( $f1->comments, $test->{comments}, "$t_name comments" );
210 is_deeply( { $f1->extra }, $test->{extra}, "$t_name extra" );
214 my ($obj,$test,$name) = @_;
215 my $t_name = t_name($name);
216 default_attribs($test,"constraint");
218 is( $obj->name, $test->{name}, "${t_name}Constraint '$test->{name}'" );
220 is( $obj->type, $test->{type}, "$t_name type is '$test->{type}'" );
222 is( $obj->deferrable, $test->{deferrable},
223 "$t_name ".($test->{deferrable} ? 'can' : 'cannot').' be deferred' );
225 is( $obj->is_valid, $test->{is_valid},
226 "$t_name is ".($test->{is_valid} ? '' : 'not ').'valid' );
228 is($obj->table->name,$test->{table},"$t_name table is '$test->{table}'" );
230 is( $obj->expression, $test->{expression},
231 "$t_name expression is '$test->{expression}'" );
233 is_deeply( [$obj->fields], $test->{fields},
234 "$t_name fields are '".join(",",@{$test->{fields}})."'" );
236 is( $obj->reference_table, $test->{reference_table},
237 "$t_name reference_table is '$test->{reference_table}'" );
239 is_deeply( [$obj->reference_fields], $test->{reference_fields},
240 "$t_name reference_fields are '".join(",",@{$test->{reference_fields}})."'" );
242 is( $obj->match_type, $test->{match_type},
243 "$t_name match_type is '$test->{match_type}'" );
245 is( $obj->on_delete, $test->{on_delete},
246 "$t_name on_delete is '$test->{on_delete}'" );
248 is( $obj->on_update, $test->{on_update},
249 "$t_name on_update is '$test->{on_update}'" );
251 is_deeply( [$obj->options], $test->{options},
252 "$t_name options are '".join(",",@{$test->{options}})."'" );
254 is_deeply( { $obj->extra }, $test->{extra}, "$t_name extra" );
258 my ($obj,$test,$name) = @_;
259 my $t_name = t_name($name);
260 default_attribs($test,"index");
262 is( $obj->name, $test->{name}, "${t_name}Index '$test->{name}'" );
264 is( $obj->is_valid, $test->{is_valid},
265 "$t_name is ".($test->{is_valid} ? '' : 'not ').'valid' );
267 is( $obj->type, $test->{type}, "$t_name type is '$test->{type}'" );
269 is_deeply( [$obj->fields], $test->{fields},
270 "$t_name fields are '".join(",",@{$test->{fields}})."'" );
272 is_deeply( [$obj->options], $test->{options},
273 "$t_name options are '".join(",",@{$test->{options}})."'" );
275 is_deeply( { $obj->extra }, $test->{extra}, "$t_name extra" );
279 my ($obj,$test,$name) = @_;
280 my $t_name = t_name($name);
281 default_attribs($test,"index");
283 is( $obj->name, $test->{name}, "${t_name}Trigger '$test->{name}'" );
285 is( $obj->is_valid, $test->{is_valid},
286 "$t_name is ".($test->{is_valid} ? '' : 'not ').'valid' );
288 is( $obj->perform_action_when, $test->{perform_action_when},
289 "$t_name perform_action_when is '$test->{perform_action_when}'" );
291 is( $obj->database_event, $test->{database_event},
292 "$t_name database_event is '$test->{database_event}'" );
294 is( $obj->on_table, $test->{on_table},
295 "$t_name on_table is '$test->{on_table}'" );
297 is( $obj->action, $test->{action}, "$t_name action is '$test->{action}'" );
299 is_deeply( { $obj->extra }, $test->{extra}, "$t_name extra" );
303 my ($obj,$test,$name) = @_;
304 my $t_name = t_name($name);
305 default_attribs($test,"index");
307 #isa_ok( $v, 'SQL::Translator::Schema::View', 'View' );
309 is( $obj->name, $test->{name}, "${t_name}View '$test->{name}'" );
311 is( $obj->is_valid, $test->{is_valid},
312 "$t_name is ".($test->{is_valid} ? '' : 'not ').'valid' );
314 is( $obj->sql, $test->{sql}, "$t_name sql is '$test->{sql}'" );
316 is_deeply( [$obj->fields], $test->{fields},
317 "$t_name fields are '".join(",",@{$test->{fields}})."'" );
319 is_deeply( { $obj->extra }, $test->{extra}, "$t_name extra" );
323 my ($obj,$test,$name) = @_;
324 my $t_name = t_name($name);
325 default_attribs($test,"index");
327 #isa_ok( $v, 'SQL::Translator::Schema::View', 'View' );
329 is( $obj->name, $test->{name}, "${t_name}Procedure '$test->{name}'" );
331 is( $obj->sql, $test->{sql}, "$t_name sql is '$test->{sql}'" );
333 is_deeply( [$obj->parameters], $test->{parameters},
334 "$t_name parameters are '".join(",",@{$test->{parameters}})."'" );
336 is( $obj->comments, $test->{comments},
337 "$t_name comments is '$test->{comments}'" );
339 is( $obj->owner, $test->{owner}, "$t_name owner is '$test->{owner}'" );
341 is_deeply( { $obj->extra }, $test->{extra}, "$t_name extra" );
345 my ($obj,$test,$name) = @_;
346 my $t_name = t_name($name);
347 default_attribs($test,"table");
350 my $tbl_name = $arg{name} || die "Need a table name to test.";
351 is( $obj->{name}, $arg{name}, "${t_name}Table '$arg{name}'" );
353 is_deeply( [$obj->options], $test->{options},
354 "$t_name options are '".join(",",@{$test->{options}})."'" );
356 is_deeply( { $obj->extra }, $test->{extra}, "$t_name extra" );
359 if ( $arg{fields} ) {
360 my @fldnames = map {$_->{name}} @{$arg{fields}};
362 [ map {$_->name} $obj->get_fields ],
364 "${t_name} field names are ".join(", ",@fldnames)
366 foreach ( @{$arg{fields}} ) {
367 my $f_name = $_->{name} || die "Need a field name to test.";
368 next unless my $fld = $obj->get_field($f_name);
369 field_ok( $fld, $_, $name );
373 is(scalar($obj->get_fields), undef,
374 "${t_name} has no fields.");
377 # Constraints and Indices
378 _test_kids($obj, $test, $name, {
379 constraint => "constraints",
380 'index' => "indices",
385 my ($obj, $test, $name, $kids) = @_;
386 my $t_name = t_name($name);
387 my $obj_name = ref $obj;
388 ($obj_name) = $obj_name =~ m/^.*::(.*)$/;
390 while ( my($foo,$plural) = each %$kids ) {
391 next unless defined $test->{$plural};
392 if ( my @tfoo = @{$test->{$plural}} ) {
393 my $meth = "get_$plural";
394 my @foo = $obj->$meth;
395 is(scalar(@foo), scalar(@tfoo),
396 "${t_name}$obj_name has ".scalar(@tfoo)." $plural");
398 my $ans = { lc($obj_name) => $obj->name, %{shift @tfoo}};
399 #my $ans = shift @tfoo;
400 my $meth = "${foo}_ok";
402 $meth->( $_, $ans, $name );
410 my ($obj,$test,$name) = @_;
411 my $t_name = t_name($name);
412 default_attribs($test,"schema");
414 is( $obj->name, $test->{name}, "${t_name}Schema name is '$test->{name}'" );
416 is( $obj->database, $test->{database},
417 "$t_name database is '$test->{database}'" );
419 is_deeply( { $obj->extra }, $test->{extra}, "$t_name extra" );
421 is( $obj->is_valid, $test->{is_valid},
422 "$t_name is ".($test->{is_valid} ? '' : 'not ').'valid' );
425 if ( $test->{tables} ) {
426 is_deeply( [ map {$_->name} $obj->get_tables ],
427 [ map {$_->{name}} @{$test->{tables}} ],
428 "${t_name} table names match" );
429 foreach ( @{$test->{tables}} ) {
430 my $t_name = $_->{name} || die "Need a table name to test.";
431 table_ok( $obj->get_table($t_name), $_, $name );
435 is(scalar($obj->get_tables), undef,
436 "${t_name} has no tables.");
439 # Procedures, Triggers, Views
440 _test_kids($obj, $test, $name, {
441 procedure => "procedures",
442 trigger => "triggers",
447 # maybe_plan($ntests, @modules)
449 # Calls plan $ntests if @modules can all be loaded; otherwise,
450 # calls skip_all with an explanation of why the tests were skipped.
452 my ($ntests, @modules) = @_;
455 for my $module (@modules) {
457 if ($@ && $@ =~ /Can't locate (\S+)/) {
466 my $msg = sprintf "Missing dependenc%s: %s",
467 @errors == 1 ? 'y' : 'ies',
469 plan skip_all => $msg;
472 plan tests => $ntests;
476 1; # compile please ===========================================================
487 use Test::SQL::Translator;
490 my $sqlt = SQL::Translator->new(
492 filename => "$Bin/data/magic/test.magic",
496 my $schema = $sqlt->schema;
498 # Test the table it produced.
499 table_ok( $schema->get_table("Customer"), {
503 name => "CustomerID",
506 default_value => undef,
512 data_type => "VARCHAR",
519 type => "PRIMARY KEY",
520 fields => "CustomerID",
533 Provides a set of Test::More tests for Schema objects. Testing a parsed
534 schema is then as easy as writing a perl data structure describing how you
535 expect the schema to look. Also provides maybe_plan for conditionally running
536 tests based on their dependencies.
538 The data structures given to the test subs don't have to include all the
539 possible values, only the ones you expect to have changed. Any left out will be
540 tested to make sure they are still at their default value. This is a usefull
541 check that you your parser hasn't accidentally set schema values you didn't
544 For an example of the output run the t/16xml-parser.t test.
548 All the tests take a first arg of the schema object to test, followed by a
549 hash ref describing how you expect that object to look (you only need give the
550 attributes you expect to have changed from the default).
551 The 3rd arg is an optional test name to pre-pend to all the generated test
568 =head1 CONDITIONAL TESTS
570 The C<maybe_plan> function handles conditionally running an individual
571 test. It is here to enable running the test suite even when dependencies
572 are missing; not having (for example) GraphViz installed should not keep
573 the test suite from passing.
575 C<maybe_plan> takes the number of tests to (maybe) run, and a list of
576 modules on which test execution depends:
578 maybe_plan(180, 'SQL::Translator::Parser::MySQL');
580 If one of C<SQL::Translator::Parser::MySQL>'s dependencies does not exist,
581 then the test will be skipped.
585 table_ok, field_ok, constraint_ok, index_ok, view_ok, trigger_ok, procedure_ok,
592 =item Test the tests!
594 =item Test Count Constants
596 Constants to give the number of tests each *_ok sub uses. e.g. How many tests
597 does field_ok run? Can then use these to set up the test plan easily.
601 As the test subs wrap up lots of tests in one call you can't skip idividual
602 tests only whole sets e.g. a whole table or field.
603 We could add skip_* items to the test hashes to allow per test skips. e.g.
605 skip_is_primary_key => "Need to fix primary key parsing.",
607 =item yaml test specs
609 Maybe have the test subs also accept yaml for the test hash ref as its a much
610 nicer for writing big data structures. We can then define tests as in input
611 schema file and test yaml file to compare it against.
619 Mark D. Addison E<lt>mark.addison@itn.co.ukE<gt>, Darren Chamberlain <darren@cpan.org>.
621 Thanks to Ken Y. Clark for the original table and field test code taken from
626 perl(1), SQL::Translator, SQL::Translator::Schema, Test::More.