Switch to sane subcommand launch in tests
[dbsrgits/SQL-Translator.git] / t / 60roundtrip.t
1 #!/usr/bin/perl
2
3 use warnings;
4 use strict;
5 use Test::More qw/no_plan/;
6 use Test::Exception;
7 use Test::Differences;
8 use FindBin qw/$Bin/;
9
10 use SQL::Translator;
11 use SQL::Translator::Utils qw/ddl_parser_instance/;
12
13
14 ### Set $ENV{SQLTTEST_RT_DEBUG} = 1 for more output
15
16 # What tests to run - parser/producer name, and optional args
17 my $plan = [
18   {
19     engine => 'XML',
20     req => 'XML::LibXML 1.69',
21     no_grammar => 1,
22   },
23   {
24     engine => 'YAML',
25     no_grammar => 1,
26   },
27
28   {
29     engine => 'SQLite',
30     producer_args => {},
31     parser_args => {},
32   },
33   {
34     engine => 'MySQL',
35     producer_args => {},
36     parser_args => {},
37   },
38   {
39     engine => 'MySQL',
40     name => 'MySQL 5.0',
41     producer_args => { mysql_version => 5 },
42     parser_args => { mysql_parser_version => 5 },
43   },
44   {
45     engine => 'MySQL',
46     name => 'MySQL 5.1',
47     producer_args => { mysql_version => '5.1' },
48     parser_args => { mysql_parser_version => '5.1' },
49   },
50   {
51     engine => 'PostgreSQL',
52     producer_args => {},
53     parser_args => {},
54   },
55   {
56     engine => 'SQLServer',
57     producer_args => {},
58     parser_args => {},
59   },
60
61   {
62     engine => 'Oracle',
63     producer_args => {},
64     parser_args => {},
65     todo => 'Needs volunteers',
66   },
67   {
68     engine => 'Sybase',
69     producer_args => {},
70     parser_args => {},
71     todo => 'Needs volunteers',
72   },
73 # too noisy under new P::RD
74 #  {
75 #    engine => 'DB2',
76 #    producer_args => {},
77 #    parser_args => {},
78 #    todo => 'Needs volunteers',
79 #  },
80
81 # There is no Access producer
82 #  {
83 #    engine => 'Access',
84 #    producer_args => {},
85 #    parser_args => {},
86 #  },
87 ];
88
89
90 # This data file has the right mix of table/view/procedure/trigger
91 # definitions, and lists enough quirks to trip up most combos
92 my $base_file = "$Bin/data/roundtrip_autogen.yaml";
93 open (my $base_fh, '<', $base_file) or die "$base_file: $!";
94
95 my $base_t = SQL::Translator->new;
96 $base_t->$_ (1) for qw/add_drop_table no_comments/;
97
98 my $base_schema = $base_t->translate (
99   parser => 'YAML',
100   data => do { local $/; <$base_fh>; },
101 ) or die $base_t->error;
102
103
104 #assume there is at least one table
105 my $string_re = {
106   XML => qr/<tables>\s*<table/,
107   YAML => qr/\A---\n.+tables\:/s,
108   SQL => qr/^\s*CREATE TABLE/m,
109 };
110
111 for my $args (@$plan) {
112   SKIP: {
113     $args->{name} ||= $args->{engine};
114
115     my @req = ref $args->{req} ? @{$args->{req}} : $args->{req}||();
116     my @missing;
117     for (@req) {
118       eval "use $_ ()";
119       push @missing, $_ if ($@);
120     }
121     if (@missing) {
122       skip sprintf ('Need %s for %s roundtrip test',
123         join (', ', @missing),
124         $args->{name},
125       );
126     }
127
128     use_ok("SQL::Translator::Producer::$args->{engine}");
129     use_ok("SQL::Translator::Parser::$args->{engine}");
130
131     ok(ddl_parser_instance($args->{engine}), 'Got proper parser instance')
132       unless $args->{no_grammar};
133
134     TODO: {
135       local $TODO = $args->{todo} if $args->{todo};
136
137       no warnings 'once';
138       # silence PR::D from spewing on STDERR
139       local $::RD_ERRORS = 0 if $args->{todo};
140       local $::RD_WARN = 0 if $args->{todo};
141       local $::RD_HINT = 0 if $args->{todo};
142
143       lives_ok (
144         sub { check_roundtrip ($args, $base_schema) },
145         "Round trip for $args->{name} did not throw an exception",
146       );
147     }
148   }
149 }
150
151
152 sub check_roundtrip {
153   my ($args, $base_schema) = @_;
154   my $base_t = $base_schema->translator;
155
156 # create some output from the submitted schema
157   my $base_out = $base_t->translate (
158     data => $base_schema,
159     producer => $args->{engine},
160     producer_args => $args->{producer_args},
161   );
162
163   like (
164     $base_out,
165     $string_re->{$args->{engine}} || $string_re->{SQL},
166     "Received some meaningful output from the first $args->{name} production",
167   ) or do {
168     diag ( _gen_diag ($base_t->error) );
169     return;
170   };
171
172 # parse the sql back
173   my $parser_t = SQL::Translator->new;
174   $parser_t->$_ (1) for qw/add_drop_table no_comments/;
175   my $mid_schema = $parser_t->translate (
176     data => $base_out,
177     parser => $args->{engine},
178     parser_args => $args->{parser_args},
179   );
180
181   isa_ok ($mid_schema, 'SQL::Translator::Schema', "First $args->{name} parser pass produced a schema:")
182     or do {
183       diag (_gen_diag ( $parser_t->error, $base_out ) );
184       my $i;
185       note join ("\n" . ( '=' x 76) . "\n",
186         'Unparseable DDL:',
187         (join ("\n", map { ++$i . ":\t$_" } split /\n/, $base_out) ),
188         ''
189       );
190       return;
191     };
192
193 # schemas should be comparable at least as far as table/field numbers go
194   is_deeply (
195     _get_table_info ($mid_schema->get_tables),
196     _get_table_info ($base_schema->get_tables),
197     "Schema tables generally match afer $args->{name} parser trip",
198   ) or return;
199
200 # and produce sql once again
201
202 # Producing a schema with a Translator different from the one the schema was generated
203 # from does not work. This is arguably a bug, 61translator_agnostic.t works with that
204 #  my $producer_t = SQL::Translator->new;
205 #  $producer_t->$_ (1) for qw/add_drop_table no_comments/;
206
207 #  my $rt_sql = $producer_t->translate (
208 #    data => $mid_schema,
209 #    producer => $args->{engine},
210 #    producer_args => $args->{producer_args},
211 #  );
212
213   my $rt_out = $parser_t->translate (
214     data => $mid_schema,
215     producer => $args->{engine},
216     producer_args => $args->{producer_args},
217   );
218
219   like (
220     $rt_out,
221     $string_re->{$args->{engine}} || $string_re->{SQL},
222     "Received some meaningful output from the second $args->{name} production",
223   ) or do {
224     diag ( _gen_diag ( $parser_t->error ) );
225     return;
226   };
227
228 # the two sql strings should be identical
229   my $msg = "$args->{name} SQL roundtrip successful - SQL statements match";
230   $ENV{SQLTTEST_RT_DEBUG}     #stringify below because IO::Scalar does not behave nice
231     ? eq_or_diff ("$rt_out", "$base_out", $msg)
232     : ok ("$rt_out" eq "$base_out", $msg)
233   ;
234 }
235
236 sub _get_table_info {
237   my @tables = @_;
238
239   my @info;
240
241   for my $t (@tables) {
242     push @info, {
243       name => $t->name,
244       fields => [
245         map { $_->name } ($t->get_fields),
246       ],
247     };
248   }
249
250   return \@info;
251 }
252
253 # takes an error string and an optional output block
254 # returns the string conctenated with a line-numbered block for
255 # easier reading
256 sub _gen_diag {
257   my ($err, $out) = @_;
258
259   return 'Unknown error' unless $err;
260
261
262   if ($out and $ENV{SQLTTEST_RT_DEBUG}) {
263     my @lines;
264     for (split /\n/, $out) {
265       push @lines, sprintf ('%03d: %s',
266         scalar @lines + 1,
267         $_,
268       );
269     }
270
271     return "$err\n\n" . join ("\n", @lines);
272   }
273
274   return $err;
275 }