fix some edge cases for use_moose option, and more tests
[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 File::Temp 'tempfile';
8 use lib qw(t/lib);
9
10 my $DUMP_PATH = './t/_dump';
11
12 my $TEST_DB_CLASS = 'make_dbictest_db';
13
14 sub dump_directly {
15     my %tdata = @_;
16
17     my $schema_class = $tdata{classname};
18
19     no strict 'refs';
20     @{$schema_class . '::ISA'} = ('DBIx::Class::Schema::Loader');
21     $schema_class->loader_options(%{$tdata{options}});
22
23     my @warns;
24     eval {
25         local $SIG{__WARN__} = sub { push(@warns, @_) };
26         $schema_class->connect(get_dsn(\%tdata));
27     };
28     my $err = $@;
29     $schema_class->storage->disconnect if !$err && $schema_class->storage;
30     undef *{$schema_class};
31
32     check_error($err, $tdata{error});
33
34     return @warns;
35 }
36
37 sub dump_dbicdump {
38     my %tdata = @_;
39
40     # use $^X so we execute ./script/dbicdump with the same perl binary that the tests were executed with
41     my @cmd = ($^X, qw(./script/dbicdump));
42
43     while (my ($opt, $val) = each(%{ $tdata{options} })) {
44         $val = Dumper($val) if ref $val;
45         push @cmd, '-o', "$opt=$val";
46     }
47
48     push @cmd, $tdata{classname}, get_dsn(\%tdata);
49
50     # make sure our current @INC gets used by dbicdump
51     use Config;
52     local $ENV{PERL5LIB} = join $Config{path_sep}, @INC, ($ENV{PERL5LIB} || '');
53
54     my ($in, $out, $err);
55     my $pid = open3($in, $out, $err, @cmd);
56
57     my @out = <$out>;
58     waitpid($pid, 0);
59
60     my ($error, @warns);
61
62     if ($? >> 8 != 0) {
63         $error = $out[0];
64         check_error($error, $tdata{error});
65     }
66     else {
67         @warns = @out;
68     }
69
70     return @warns;
71 }
72
73 sub get_dsn {
74     my $opts = shift;
75
76     my $test_db_class = $opts->{test_db_class} || $TEST_DB_CLASS;
77
78     eval "require $test_db_class;";
79     die $@ if $@;
80
81     my $dsn = do {
82         no strict 'refs';
83         ${$test_db_class . '::dsn'};
84     };
85
86     return $dsn;
87 }
88
89 sub check_error {
90     my ($got, $expected) = @_;
91
92     return unless $got;
93
94     if (not $expected) {
95         fail "Unexpected error in " . ((caller(1))[3]) . ": $got";
96         return;
97     }
98
99     if (ref $expected eq 'Regexp') {
100         like $got, $expected, 'error matches expected pattern';
101         return;
102     }
103
104     is $got, $expected, 'error matches';
105 }
106
107 sub do_dump_test {
108     my %tdata = @_;
109     
110     $tdata{options}{dump_directory} = $DUMP_PATH;
111     $tdata{options}{use_namespaces} ||= 0;
112
113     for my $dumper (\&dump_directly, \&dump_dbicdump) {
114         test_dumps(\%tdata, $dumper->(%tdata));
115     }
116 }
117
118 sub test_dumps {
119     my ($tdata, @warns) = @_;
120
121     my %tdata = %{$tdata};
122
123     my $schema_class = $tdata{classname};
124     my $check_warns = $tdata{warnings};
125     is(@warns, @$check_warns, "$schema_class warning count");
126
127     for(my $i = 0; $i <= $#$check_warns; $i++) {
128         like($warns[$i], $check_warns->[$i], "$schema_class warning $i");
129     }
130
131     my $file_regexes = $tdata{regexes};
132     my $file_neg_regexes = $tdata{neg_regexes} || {};
133     my $schema_regexes = delete $file_regexes->{schema};
134     
135     my $schema_path = $DUMP_PATH . '/' . $schema_class;
136     $schema_path =~ s{::}{/}g;
137
138     dump_file_like($schema_path . '.pm', @$schema_regexes) if $schema_regexes;
139
140     foreach my $src (keys %$file_regexes) {
141         my $src_file = $schema_path . '/' . $src . '.pm';
142         dump_file_like($src_file, @{$file_regexes->{$src}});
143     }
144     foreach my $src (keys %$file_neg_regexes) {
145         my $src_file = $schema_path . '/' . $src . '.pm';
146         dump_file_not_like($src_file, @{$file_neg_regexes->{$src}});
147     }
148 }
149
150 sub dump_file_like {
151     my $path = shift;
152     open(my $dumpfh, '<', $path) or die "Failed to open '$path': $!";
153     my $contents = do { local $/; <$dumpfh>; };
154     close($dumpfh);
155     like($contents, $_, "$path matches $_") for @_;
156 }
157
158 sub dump_file_not_like {
159     my $path = shift;
160     open(my $dumpfh, '<', $path) or die "Failed to open '$path': $!";
161     my $contents = do { local $/; <$dumpfh>; };
162     close($dumpfh);
163     unlike($contents, $_, "$path does not match $_") for @_;
164 }
165
166 sub append_to_class {
167     my ($class, $string) = @_;
168     $class =~ s{::}{/}g;
169     $class = $DUMP_PATH . '/' . $class . '.pm';
170     open(my $appendfh, '>>', $class) or die "Failed to open '$class' for append: $!";
171     print $appendfh $string;
172     close($appendfh);
173 }
174
175 rmtree($DUMP_PATH, 1, 1);
176
177 # test loading external content
178 do_dump_test(
179     classname => 'DBICTest::Schema::13',
180     warnings => [
181         qr/Dumping manual schema for DBICTest::Schema::13 to directory /,
182         qr/Schema dump completed/,
183     ],
184     regexes => {
185         Foo => [
186 qr/package DBICTest::Schema::13::Foo;\nour \$skip_me = "bad mojo";\n1;/
187         ],
188     },
189 );
190
191 # test skipping external content
192 do_dump_test(
193     classname => 'DBICTest::Schema::14',
194     options => { skip_load_external => 1 },
195     warnings => [
196         qr/Dumping manual schema for DBICTest::Schema::14 to directory /,
197         qr/Schema dump completed/,
198     ],
199     neg_regexes => {
200         Foo => [
201 qr/package DBICTest::Schema::14::Foo;\nour \$skip_me = "bad mojo";\n1;/
202         ],
203     },
204 );
205
206 rmtree($DUMP_PATH, 1, 1);
207
208 # test config_file
209
210 my ($fh, $config_file) = tempfile;
211
212 print $fh <<'EOF';
213 { skip_relationships => 1 }
214 EOF
215 close $fh;
216
217 do_dump_test(
218     classname => 'DBICTest::Schema::14',
219     options => { config_file => $config_file },
220     warnings => [
221         qr/Dumping manual schema for DBICTest::Schema::14 to directory /,
222         qr/Schema dump completed/,
223     ],
224     neg_regexes => {
225         Foo => [
226             qr/has_many/,
227         ],
228     },
229 );
230
231 unlink $config_file;
232
233 rmtree($DUMP_PATH, 1, 1);
234
235 eval "use Moose; use MooseX::NonMoose; use namespace::autoclean;";
236 if (not $@) {
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: