8f48199191edbf5197257fb94766658477c5efd4
[dbsrgits/DBIx-Class-Schema-Loader.git] / t / 23dumpmore.t
1 use strict;
2 use Test::More;
3 use File::Path;
4 use IPC::Open3;
5 use Data::Dumper::Concise;
6 use DBIx::Class::Schema::Loader ();
7 use DBIx::Class::Schema::Loader::Optional::Dependencies ();
8 use File::Temp 'tempfile';
9 use lib qw(t/lib);
10
11 my $DUMP_PATH = './t/_dump';
12
13 my $TEST_DB_CLASS = 'make_dbictest_db';
14
15 sub dump_directly {
16     my %tdata = @_;
17
18     my $schema_class = $tdata{classname};
19
20     no strict 'refs';
21     @{$schema_class . '::ISA'} = ('DBIx::Class::Schema::Loader');
22     $schema_class->loader_options(%{$tdata{options}});
23
24     my @warns;
25     eval {
26         local $SIG{__WARN__} = sub { push(@warns, @_) };
27         $schema_class->connect(get_dsn(\%tdata));
28     };
29     my $err = $@;
30     $schema_class->storage->disconnect if !$err && $schema_class->storage;
31     undef *{$schema_class};
32
33     check_error($err, $tdata{error});
34
35     return @warns;
36 }
37
38 sub dump_dbicdump {
39     my %tdata = @_;
40
41     # use $^X so we execute ./script/dbicdump with the same perl binary that the tests were executed with
42     my @cmd = ($^X, qw(./script/dbicdump));
43
44     while (my ($opt, $val) = each(%{ $tdata{options} })) {
45         $val = Dumper($val) if ref $val;
46         push @cmd, '-o', "$opt=$val";
47     }
48
49     push @cmd, $tdata{classname}, get_dsn(\%tdata);
50
51     # make sure our current @INC gets used by dbicdump
52     use Config;
53     local $ENV{PERL5LIB} = join $Config{path_sep}, @INC, ($ENV{PERL5LIB} || '');
54
55     my ($in, $out, $err);
56     my $pid = open3($in, $out, $err, @cmd);
57
58     my @out = <$out>;
59     waitpid($pid, 0);
60
61     my ($error, @warns);
62
63     if ($? >> 8 != 0) {
64         $error = $out[0];
65         check_error($error, $tdata{error});
66     }
67     else {
68         @warns = @out;
69     }
70
71     return @warns;
72 }
73
74 sub get_dsn {
75     my $opts = shift;
76
77     my $test_db_class = $opts->{test_db_class} || $TEST_DB_CLASS;
78
79     eval "require $test_db_class;";
80     die $@ if $@;
81
82     my $dsn = do {
83         no strict 'refs';
84         ${$test_db_class . '::dsn'};
85     };
86
87     return $dsn;
88 }
89
90 sub check_error {
91     my ($got, $expected) = @_;
92
93     return unless $got;
94
95     if (not $expected) {
96         fail "Unexpected error in " . ((caller(1))[3]) . ": $got";
97         return;
98     }
99
100     if (ref $expected eq 'Regexp') {
101         like $got, $expected, 'error matches expected pattern';
102         return;
103     }
104
105     is $got, $expected, 'error matches';
106 }
107
108 sub do_dump_test {
109     my %tdata = @_;
110     
111     $tdata{options}{dump_directory} = $DUMP_PATH;
112     $tdata{options}{use_namespaces} ||= 0;
113
114     for my $dumper (\&dump_directly, \&dump_dbicdump) {
115         test_dumps(\%tdata, $dumper->(%tdata));
116     }
117 }
118
119 sub test_dumps {
120     my ($tdata, @warns) = @_;
121
122     my %tdata = %{$tdata};
123
124     my $schema_class = $tdata{classname};
125     my $check_warns = $tdata{warnings};
126     is(@warns, @$check_warns, "$schema_class warning count");
127
128     for(my $i = 0; $i <= $#$check_warns; $i++) {
129         like($warns[$i], $check_warns->[$i], "$schema_class warning $i");
130     }
131
132     my $file_regexes = $tdata{regexes};
133     my $file_neg_regexes = $tdata{neg_regexes} || {};
134     my $schema_regexes = delete $file_regexes->{schema};
135     
136     my $schema_path = $DUMP_PATH . '/' . $schema_class;
137     $schema_path =~ s{::}{/}g;
138
139     dump_file_like($schema_path . '.pm', @$schema_regexes) if $schema_regexes;
140
141     foreach my $src (keys %$file_regexes) {
142         my $src_file = $schema_path . '/' . $src . '.pm';
143         dump_file_like($src_file, @{$file_regexes->{$src}});
144     }
145     foreach my $src (keys %$file_neg_regexes) {
146         my $src_file = $schema_path . '/' . $src . '.pm';
147         dump_file_not_like($src_file, @{$file_neg_regexes->{$src}});
148     }
149 }
150
151 sub dump_file_like {
152     my $path = shift;
153     open(my $dumpfh, '<', $path) or die "Failed to open '$path': $!";
154     my $contents = do { local $/; <$dumpfh>; };
155     close($dumpfh);
156     like($contents, $_, "$path matches $_") for @_;
157 }
158
159 sub dump_file_not_like {
160     my $path = shift;
161     open(my $dumpfh, '<', $path) or die "Failed to open '$path': $!";
162     my $contents = do { local $/; <$dumpfh>; };
163     close($dumpfh);
164     unlike($contents, $_, "$path does not match $_") for @_;
165 }
166
167 sub append_to_class {
168     my ($class, $string) = @_;
169     $class =~ s{::}{/}g;
170     $class = $DUMP_PATH . '/' . $class . '.pm';
171     open(my $appendfh, '>>', $class) or die "Failed to open '$class' for append: $!";
172     print $appendfh $string;
173     close($appendfh);
174 }
175
176 rmtree($DUMP_PATH, 1, 1);
177
178 # test loading external content
179 do_dump_test(
180     classname => 'DBICTest::Schema::13',
181     warnings => [
182         qr/Dumping manual schema for DBICTest::Schema::13 to directory /,
183         qr/Schema dump completed/,
184     ],
185     regexes => {
186         Foo => [
187 qr/package DBICTest::Schema::13::Foo;\nour \$skip_me = "bad mojo";\n1;/
188         ],
189     },
190 );
191
192 # test skipping external content
193 do_dump_test(
194     classname => 'DBICTest::Schema::14',
195     options => { skip_load_external => 1 },
196     warnings => [
197         qr/Dumping manual schema for DBICTest::Schema::14 to directory /,
198         qr/Schema dump completed/,
199     ],
200     neg_regexes => {
201         Foo => [
202 qr/package DBICTest::Schema::14::Foo;\nour \$skip_me = "bad mojo";\n1;/
203         ],
204     },
205 );
206
207 rmtree($DUMP_PATH, 1, 1);
208
209 # test config_file
210
211 my ($fh, $config_file) = tempfile;
212
213 print $fh <<'EOF';
214 { skip_relationships => 1 }
215 EOF
216 close $fh;
217
218 do_dump_test(
219     classname => 'DBICTest::Schema::14',
220     options => { config_file => $config_file },
221     warnings => [
222         qr/Dumping manual schema for DBICTest::Schema::14 to directory /,
223         qr/Schema dump completed/,
224     ],
225     neg_regexes => {
226         Foo => [
227             qr/has_many/,
228         ],
229     },
230 );
231
232 unlink $config_file;
233
234 rmtree($DUMP_PATH, 1, 1);
235
236 if (DBIx::Class::Schema::Loader::Optional::Dependencies->req_ok_for('use_moose')) {
237
238 # first dump a fresh use_moose=1 schema
239
240 do_dump_test(
241     classname => 'DBICTest::DumpMore::1',
242     options => {
243         use_moose => 1,
244         result_base_class => 'My::ResultBaseClass',
245         schema_base_class => 'My::SchemaBaseClass',
246     },
247     warnings => [
248         qr/Dumping manual schema for DBICTest::DumpMore::1 to directory /,
249         qr/Schema dump completed/,
250     ],
251     regexes => {
252         schema => [
253 qr/\nuse Moose;\nuse MooseX::NonMoose;\nuse namespace::autoclean;\nextends 'My::SchemaBaseClass';\n\n/,
254 qr/\n__PACKAGE__->meta->make_immutable;\n1;(?!\n1;\n)\n.*/,
255         ],
256         Foo => [
257 qr/\nuse Moose;\nuse MooseX::NonMoose;\nuse namespace::autoclean;\nextends 'My::ResultBaseClass';\n\n/,
258 qr/\n__PACKAGE__->meta->make_immutable;\n1;(?!\n1;\n)\n.*/,
259         ],
260         Bar => [
261 qr/\nuse Moose;\nuse MooseX::NonMoose;\nuse namespace::autoclean;\nextends 'My::ResultBaseClass';\n\n/,
262 qr/\n__PACKAGE__->meta->make_immutable;\n1;(?!\n1;\n)\n.*/,
263         ],
264     },
265 );
266
267 # now upgrade a non-moose schema to use_moose=1
268
269 rmtree($DUMP_PATH, 1, 1);
270
271 do_dump_test(
272     classname => 'DBICTest::DumpMore::1',
273     options => {
274         result_base_class => 'My::ResultBaseClass',
275         schema_base_class => 'My::SchemaBaseClass',
276     },
277     warnings => [
278         qr/Dumping manual schema for DBICTest::DumpMore::1 to directory /,
279         qr/Schema dump completed/,
280     ],
281     regexes => {
282         schema => [
283             qr/\nuse base 'My::SchemaBaseClass';\n/,
284         ],
285         Foo => [
286             qr/\nuse base 'My::ResultBaseClass';\n/,
287         ],
288         Bar => [
289             qr/\nuse base 'My::ResultBaseClass';\n/,
290         ],
291     },
292 );
293
294 # check that changed custom content is upgraded for Moose bits
295 append_to_class('DBICTest::DumpMore::1::Foo', q{# XXX This is my custom content XXX});
296
297 do_dump_test(
298     classname => 'DBICTest::DumpMore::1',
299     options => {
300         use_moose => 1,
301         result_base_class => 'My::ResultBaseClass',
302         schema_base_class => 'My::SchemaBaseClass',
303     },
304     warnings => [
305         qr/Dumping manual schema for DBICTest::DumpMore::1 to directory /,
306         qr/Schema dump completed/,
307     ],
308     regexes => {
309         schema => [
310 qr/\nuse Moose;\nuse MooseX::NonMoose;\nuse namespace::autoclean;\nextends 'My::SchemaBaseClass';\n\n/,
311 qr/\n__PACKAGE__->meta->make_immutable;\n1;(?!\n1;\n)\n.*/,
312         ],
313         Foo => [
314 qr/\nuse Moose;\nuse MooseX::NonMoose;\nuse namespace::autoclean;\nextends 'My::ResultBaseClass';\n\n/,
315 qr/\n__PACKAGE__->meta->make_immutable;\n1;(?!\n1;\n)\n.*/,
316         ],
317         Bar => [
318 qr/\nuse Moose;\nuse MooseX::NonMoose;\nuse namespace::autoclean;\nextends 'My::ResultBaseClass';\n\n/,
319 qr/\n__PACKAGE__->meta->make_immutable;\n1;(?!\n1;\n)\n.*/,
320         ],
321     },
322 );
323
324 # now add the Moose custom content to unapgraded schema, and make sure it is not repeated
325
326 rmtree($DUMP_PATH, 1, 1);
327
328 do_dump_test(
329     classname => 'DBICTest::DumpMore::1',
330     options => {
331         result_base_class => 'My::ResultBaseClass',
332         schema_base_class => 'My::SchemaBaseClass',
333     },
334     warnings => [
335         qr/Dumping manual schema for DBICTest::DumpMore::1 to directory /,
336         qr/Schema dump completed/,
337     ],
338     regexes => {
339         schema => [
340             qr/\nuse base 'My::SchemaBaseClass';\n/,
341         ],
342         Foo => [
343             qr/\nuse base 'My::ResultBaseClass';\n/,
344         ],
345         Bar => [
346             qr/\nuse base 'My::ResultBaseClass';\n/,
347         ],
348     },
349 );
350
351 # add Moose custom content then check it is not repeated
352
353 append_to_class('DBICTest::DumpMore::1::Foo', qq{__PACKAGE__->meta->make_immutable;\n1;\n});
354
355 do_dump_test(
356     classname => 'DBICTest::DumpMore::1',
357     options => {
358         use_moose => 1,
359         result_base_class => 'My::ResultBaseClass',
360         schema_base_class => 'My::SchemaBaseClass',
361     },
362     warnings => [
363         qr/Dumping manual schema for DBICTest::DumpMore::1 to directory /,
364         qr/Schema dump completed/,
365     ],
366     regexes => {
367         schema => [
368 qr/\nuse Moose;\nuse MooseX::NonMoose;\nuse namespace::autoclean;\nextends 'My::SchemaBaseClass';\n\n/,
369 qr/\n__PACKAGE__->meta->make_immutable;\n1;(?!\n1;\n)\n.*/,
370         ],
371         Foo => [
372 qr/\nuse Moose;\nuse MooseX::NonMoose;\nuse namespace::autoclean;\nextends 'My::ResultBaseClass';\n\n/,
373 qr/\n__PACKAGE__->meta->make_immutable;\n1;(?!\n1;\n)\n.*/,
374         ],
375         Bar => [
376 qr/\nuse Moose;\nuse MooseX::NonMoose;\nuse namespace::autoclean;\nextends 'My::ResultBaseClass';\n\n/,
377 qr/\n__PACKAGE__->meta->make_immutable;\n1;(?!\n1;\n)\n.*/,
378         ],
379     },
380     neg_regexes => {
381         Foo => [
382 qr/\n__PACKAGE__->meta->make_immutable;\n.*\n__PACKAGE__->meta->make_immutable;/s,
383         ],
384     },
385 );
386
387
388 }
389 else {
390     SKIP: { skip 'use_moose=1 deps not installed', 1 };
391 }
392
393 rmtree($DUMP_PATH, 1, 1);
394
395 do_dump_test(
396     classname => 'DBICTest::Schema::14',
397     test_db_class => 'make_dbictest_db_clashing_monikers',
398     error => qr/tables 'bar', 'bars' reduced to the same source moniker 'Bar'/,
399 );
400
401 rmtree($DUMP_PATH, 1, 1);
402
403 # test out the POD
404
405 do_dump_test(
406     classname => 'DBICTest::DumpMore::1',
407     options => {
408         custom_column_info => sub {
409             my ($table, $col, $info) = @_;
410             return +{ extra => { is_footext => 1 } } if $col eq 'footext';
411         }
412     },
413     warnings => [
414         qr/Dumping manual schema for DBICTest::DumpMore::1 to directory /,
415         qr/Schema dump completed/,
416     ],
417     regexes => {
418         schema => [
419             qr/package DBICTest::DumpMore::1;/,
420             qr/->load_classes/,
421         ],
422         Foo => [
423 qr/package DBICTest::DumpMore::1::Foo;/,
424 qr/=head1 NAME\n\nDBICTest::DumpMore::1::Foo\n\n=cut\n\n/,
425 qr/=head1 ACCESSORS\n\n/,
426 qr/=head2 fooid\n\n  data_type: 'integer'\n  is_auto_increment: 1\n  is_nullable: 0\n\n/,
427 qr/=head2 footext\n\n  data_type: 'text'\n  default_value: 'footext'\n  extra: {is_footext => 1}\n  is_nullable: 1\n\n/,
428 qr/->set_primary_key/,
429 qr/=head1 RELATIONS\n\n/,
430 qr/=head2 bars\n\nType: has_many\n\nRelated object: L<DBICTest::DumpMore::1::Bar>\n\n=cut\n\n/,
431 qr/1;\n$/,
432         ],
433         Bar => [
434 qr/package DBICTest::DumpMore::1::Bar;/,
435 qr/=head1 NAME\n\nDBICTest::DumpMore::1::Bar\n\n=cut\n\n/,
436 qr/=head1 ACCESSORS\n\n/,
437 qr/=head2 barid\n\n  data_type: 'integer'\n  is_auto_increment: 1\n  is_nullable: 0\n\n/,
438 qr/=head2 fooref\n\n  data_type: 'integer'\n  is_foreign_key: 1\n  is_nullable: 1\n\n/,
439 qr/->set_primary_key/,
440 qr/=head1 RELATIONS\n\n/,
441 qr/=head2 fooref\n\nType: belongs_to\n\nRelated object: L<DBICTest::DumpMore::1::Foo>\n\n=cut\n\n/,
442 qr/1;\n$/,
443         ],
444     },
445 );
446
447 append_to_class('DBICTest::DumpMore::1::Foo',q{# XXX This is my custom content XXX});
448
449 do_dump_test(
450     classname => 'DBICTest::DumpMore::1',
451     warnings => [
452         qr/Dumping manual schema for DBICTest::DumpMore::1 to directory /,
453         qr/Schema dump completed/,
454     ],
455     regexes => {
456         schema => [
457             qr/package DBICTest::DumpMore::1;/,
458             qr/->load_classes/,
459         ],
460         Foo => [
461             qr/package DBICTest::DumpMore::1::Foo;/,
462             qr/->set_primary_key/,
463             qr/1;\n# XXX This is my custom content XXX/,
464         ],
465         Bar => [
466             qr/package DBICTest::DumpMore::1::Bar;/,
467             qr/->set_primary_key/,
468             qr/1;\n$/,
469         ],
470     },
471 );
472
473 do_dump_test(
474     classname => 'DBICTest::DumpMore::1',
475     options => { really_erase_my_files => 1 },
476     warnings => [
477         qr/Dumping manual schema for DBICTest::DumpMore::1 to directory /,
478         qr/Deleting existing file /,
479         qr/Deleting existing file /,
480         qr/Deleting existing file /,
481         qr/Schema dump completed/,
482     ],
483     regexes => {
484         schema => [
485             qr/package DBICTest::DumpMore::1;/,
486             qr/->load_classes/,
487         ],
488         Foo => [
489             qr/package DBICTest::DumpMore::1::Foo;/,
490             qr/->set_primary_key/,
491             qr/1;\n$/,
492         ],
493         Bar => [
494             qr/package DBICTest::DumpMore::1::Bar;/,
495             qr/->set_primary_key/,
496             qr/1;\n$/,
497         ],
498     },
499     neg_regexes => {
500         Foo => [
501             qr/# XXX This is my custom content XXX/,
502         ],
503     },
504 );
505
506 rmtree($DUMP_PATH, 1, 1);
507
508 do_dump_test(
509     classname => 'DBICTest::DumpMore::1',
510     options => { use_namespaces => 1, generate_pod => 0 },
511     warnings => [
512         qr/Dumping manual schema for DBICTest::DumpMore::1 to directory /,
513         qr/Schema dump completed/,
514     ],
515     neg_regexes => {
516         'Result/Foo' => [
517             qr/^=/m,
518         ],
519     },
520 );
521
522 rmtree($DUMP_PATH, 1, 1);
523
524 do_dump_test(
525     classname => 'DBICTest::DumpMore::1',
526     options => { db_schema => 'foo_schema', qualify_objects => 1, use_namespaces => 1 },
527     warnings => [
528         qr/Dumping manual schema for DBICTest::DumpMore::1 to directory /,
529         qr/Schema dump completed/,
530     ],
531     regexes => {
532         'Result/Foo' => [
533             qr/^\Q__PACKAGE__->table("foo_schema.foo");\E/m,
534             # the has_many relname should not have the schema in it!
535             qr/^__PACKAGE__->has_many\(\n  "bars"/m,
536         ],
537     },
538 );
539
540 rmtree($DUMP_PATH, 1, 1);
541
542 do_dump_test(
543     classname => 'DBICTest::DumpMore::1',
544     options => { use_namespaces => 1 },
545     warnings => [
546         qr/Dumping manual schema for DBICTest::DumpMore::1 to directory /,
547         qr/Schema dump completed/,
548     ],
549     regexes => {
550         schema => [
551             qr/package DBICTest::DumpMore::1;/,
552             qr/->load_namespaces/,
553         ],
554         'Result/Foo' => [
555             qr/package DBICTest::DumpMore::1::Result::Foo;/,
556             qr/->set_primary_key/,
557             qr/1;\n$/,
558         ],
559         'Result/Bar' => [
560             qr/package DBICTest::DumpMore::1::Result::Bar;/,
561             qr/->set_primary_key/,
562             qr/1;\n$/,
563         ],
564     },
565 );
566
567 rmtree($DUMP_PATH, 1, 1);
568
569 do_dump_test(
570     classname => 'DBICTest::DumpMore::1',
571     options => { use_namespaces => 1,
572                  result_namespace => 'Res',
573                  resultset_namespace => 'RSet',
574                  default_resultset_class => 'RSetBase',
575              },
576     warnings => [
577         qr/Dumping manual schema for DBICTest::DumpMore::1 to directory /,
578         qr/Schema dump completed/,
579     ],
580     regexes => {
581         schema => [
582             qr/package DBICTest::DumpMore::1;/,
583             qr/->load_namespaces/,
584             qr/result_namespace => 'Res'/,
585             qr/resultset_namespace => 'RSet'/,
586             qr/default_resultset_class => 'RSetBase'/,
587         ],
588         'Res/Foo' => [
589             qr/package DBICTest::DumpMore::1::Res::Foo;/,
590             qr/->set_primary_key/,
591             qr/1;\n$/,
592         ],
593         'Res/Bar' => [
594             qr/package DBICTest::DumpMore::1::Res::Bar;/,
595             qr/->set_primary_key/,
596             qr/1;\n$/,
597         ],
598     },
599 );
600
601 rmtree($DUMP_PATH, 1, 1);
602
603 do_dump_test(
604     classname => 'DBICTest::DumpMore::1',
605     options => { use_namespaces => 1,
606                  result_namespace => '+DBICTest::DumpMore::1::Res',
607                  resultset_namespace => 'RSet',
608                  default_resultset_class => 'RSetBase',
609                  result_base_class => 'My::ResultBaseClass',
610                  schema_base_class => 'My::SchemaBaseClass',
611              },
612     warnings => [
613         qr/Dumping manual schema for DBICTest::DumpMore::1 to directory /,
614         qr/Schema dump completed/,
615     ],
616     regexes => {
617         schema => [
618             qr/package DBICTest::DumpMore::1;/,
619             qr/->load_namespaces/,
620             qr/result_namespace => '\+DBICTest::DumpMore::1::Res'/,
621             qr/resultset_namespace => 'RSet'/,
622             qr/default_resultset_class => 'RSetBase'/,
623             qr/use base 'My::SchemaBaseClass'/,
624         ],
625         'Res/Foo' => [
626             qr/package DBICTest::DumpMore::1::Res::Foo;/,
627             qr/use base 'My::ResultBaseClass'/,
628             qr/->set_primary_key/,
629             qr/1;\n$/,
630         ],
631         'Res/Bar' => [
632             qr/package DBICTest::DumpMore::1::Res::Bar;/,
633             qr/use base 'My::ResultBaseClass'/,
634             qr/->set_primary_key/,
635             qr/1;\n$/,
636         ],
637     },
638 );
639
640 rmtree($DUMP_PATH, 1, 1);
641
642 do_dump_test(
643     classname => 'DBICTest::DumpMore::1',
644     options   => {
645         use_namespaces    => 1,
646         result_base_class => 'My::MissingResultBaseClass',
647     },
648     error => qr/My::MissingResultBaseClass.*is not installed/,
649 );
650
651 done_testing;
652
653 END { rmtree($DUMP_PATH, 1, 1) unless $ENV{SCHEMA_LOADER_TESTS_NOCLEANUP} }
654 # vim:et sts=4 sw=4 tw=0: