better expand stability testing
[dbsrgits/SQL-Abstract.git] / lib / SQL / Abstract / Test.pm
CommitLineData
fffe6900 1package SQL::Abstract::Test; # see doc at end of file
2
3use strict;
4use warnings;
ba3f5e40 5use base qw(Test::Builder::Module);
4abea32b 6use Test::Builder;
328c5eac 7use Test::Deep ();
01dd4e4f 8use SQL::Abstract::Tree;
fffe6900 9
c7e9c808 10{
11 my $class;
12 if ($class = $ENV{SQL_ABSTRACT_TEST_AGAINST}) {
13 my $mod = join('/', split '::', $class).".pm";
14 require $mod;
15 eval qq{sub SQL::Abstract () { "\Q${class}\E" }; 1}
16 or die "Failed to create const sub for ${class}: $@";
17 }
18 if ($ENV{SQL_ABSTRACT_TEST_EXPAND_STABILITY}) {
19 $class ||= do { require SQL::Abstract; 'SQL::Abstract' };
20 my $orig = $class->can('expand_expr');
21 require Data::Dumper::Concise;
22 my $wrapped = sub {
23 my ($self, @args) = @_;
24 my $e1 = $self->$orig(@args);
656b11c3 25 return $e1 if our $Stab_Check_Rec;
26 local $Stab_Check_Rec = 1;
c7e9c808 27 my $e2 = $self->$orig($e1);
656b11c3 28 my ($d1, $d2) = map Data::Dumper::Concise::Dumper($_), $e1, $e2;
c7e9c808 29 (our $tb)->is_eq(
656b11c3 30 $d2, $d1,
c7e9c808 31 'expand_expr stability ok'
656b11c3 32 ) or do {
33 require Path::Tiny;
34 Path::Tiny->new('e1')->spew($d1);
35 Path::Tiny->new('e2')->spew($d2);
36 die "Wrote e1 and e2, bailing out";
37 };
c7e9c808 38 return $e1;
39 };
40 no strict 'refs'; no warnings 'redefine';
41 *{"${class}::expand_expr"} = $wrapped;
42 }
c9383a7b 43}
44
2fadf08e 45our @EXPORT_OK = qw(
46 is_same_sql_bind is_same_sql is_same_bind
47 eq_sql_bind eq_sql eq_bind dumper diag_where
48 $case_sensitive $sql_differ
49);
fffe6900 50
a24cc3a0 51my $sqlat = SQL::Abstract::Tree->new;
52
fffe6900 53our $case_sensitive = 0;
e40f5df9 54our $parenthesis_significant = 0;
0c2de280 55our $order_by_asc_significant = 0;
56
fffe6900 57our $sql_differ; # keeps track of differing portion between SQLs
70af236b 58our $tb; # not documented, but someone might be overriding it anyway
fffe6900 59
70c6f0e9 60sub _unpack_arrayrefref {
61
62 my @args;
63 for (1,2) {
64 my $chunk = shift @_;
65
ca4f826a 66 if (ref $chunk eq 'REF' and ref $$chunk eq 'ARRAY') {
70c6f0e9 67 my ($sql, @bind) = @$$chunk;
68 push @args, ($sql, \@bind);
69 }
70 else {
71 push @args, $chunk, shift @_;
72 }
73
74 }
75
76 # maybe $msg and ... stuff
77 push @args, @_;
78
79 @args;
80}
81
fffe6900 82sub is_same_sql_bind {
70c6f0e9 83 my ($sql1, $bind_ref1, $sql2, $bind_ref2, $msg) = &_unpack_arrayrefref;
fffe6900 84
85 # compare
25823711 86 my $same_sql = eq_sql($sql1, $sql2);
fffe6900 87 my $same_bind = eq_bind($bind_ref1, $bind_ref2);
88
a6daa642 89 # call Test::Builder::ok
70af236b 90 my $tb = $tb || __PACKAGE__->builder;
1a828f61 91 my $ret = $tb->ok($same_sql && $same_bind, $msg);
fffe6900 92
93 # add debugging info
94 if (!$same_sql) {
e7827ba2 95 _sql_differ_diag($sql1, $sql2);
fffe6900 96 }
97 if (!$same_bind) {
e7827ba2 98 _bind_differ_diag($bind_ref1, $bind_ref2);
fffe6900 99 }
1a828f61 100
101 # pass ok() result further
102 return $ret;
fffe6900 103}
104
e7827ba2 105sub is_same_sql {
106 my ($sql1, $sql2, $msg) = @_;
107
108 # compare
70c6f0e9 109 my $same_sql = eq_sql($sql1, $sql2);
e7827ba2 110
111 # call Test::Builder::ok
70af236b 112 my $tb = $tb || __PACKAGE__->builder;
1a828f61 113 my $ret = $tb->ok($same_sql, $msg);
e7827ba2 114
115 # add debugging info
116 if (!$same_sql) {
117 _sql_differ_diag($sql1, $sql2);
118 }
1a828f61 119
120 # pass ok() result further
121 return $ret;
e7827ba2 122}
123
124sub is_same_bind {
125 my ($bind_ref1, $bind_ref2, $msg) = @_;
126
127 # compare
128 my $same_bind = eq_bind($bind_ref1, $bind_ref2);
129
130 # call Test::Builder::ok
70af236b 131 my $tb = $tb || __PACKAGE__->builder;
1a828f61 132 my $ret = $tb->ok($same_bind, $msg);
e7827ba2 133
134 # add debugging info
135 if (!$same_bind) {
136 _bind_differ_diag($bind_ref1, $bind_ref2);
137 }
1a828f61 138
139 # pass ok() result further
140 return $ret;
e7827ba2 141}
142
2fadf08e 143sub dumper {
70c6f0e9 144 # FIXME
145 # if we save the instance, we will end up with $VARx references
146 # no time to figure out how to avoid this (Deepcopy is *not* an option)
147 require Data::Dumper;
148 Data::Dumper->new([])->Terse(1)->Indent(1)->Useqq(1)->Deparse(1)->Quotekeys(0)->Sortkeys(1)->Maxdepth(0)
149 ->Values([@_])->Dump;
2fadf08e 150}
151
152sub diag_where{
70af236b 153 my $tb = $tb || __PACKAGE__->builder;
ca4f826a 154 $tb->diag("Search term:\n" . &dumper);
2fadf08e 155}
156
e7827ba2 157sub _sql_differ_diag {
70c6f0e9 158 my $sql1 = shift || '';
159 my $sql2 = shift || '';
e7827ba2 160
70af236b 161 my $tb = $tb || __PACKAGE__->builder;
9a9b40f8 162
163 if (my $profile = $ENV{SQL_ABSTRACT_TEST_TREE_PROFILE}) {
164 my $sqlat = SQL::Abstract::Tree->new(profile => $profile);
165 $_ = $sqlat->format($_) for ($sql1, $sql2);
166 }
167
ca4f826a 168 $tb->${\($tb->in_todo ? 'note' : 'diag')} (
b3437526 169 "SQL expressions differ\n"
70c6f0e9 170 ." got: $sql1\n"
171 ."want: $sql2\n"
172 ."\nmismatch around\n$sql_differ\n"
b3437526 173 );
e7827ba2 174}
175
176sub _bind_differ_diag {
177 my ($bind_ref1, $bind_ref2) = @_;
178
e026e02a 179 my $tb = $tb || __PACKAGE__->builder;
ca4f826a 180 $tb->${\($tb->in_todo ? 'note' : 'diag')} (
70c6f0e9 181 "BIND values differ " . dumper({ got => $bind_ref1, want => $bind_ref2 })
182 );
e7827ba2 183}
184
185sub eq_sql_bind {
70c6f0e9 186 my ($sql1, $bind_ref1, $sql2, $bind_ref2) = &_unpack_arrayrefref;
e7827ba2 187
188 return eq_sql($sql1, $sql2) && eq_bind($bind_ref1, $bind_ref2);
189}
190
191
328c5eac 192sub eq_bind { goto &Test::Deep::eq_deeply };
fffe6900 193
194sub eq_sql {
25823711 195 my ($sql1, $sql2) = @_;
196
197 # parse
a24cc3a0 198 my $tree1 = $sqlat->parse($sql1);
199 my $tree2 = $sqlat->parse($sql2);
25823711 200
6f2a5b66 201 undef $sql_differ;
1b17d1b0 202 return 1 if _eq_sql($tree1, $tree2);
25823711 203}
204
205sub _eq_sql {
fffe6900 206 my ($left, $right) = @_;
207
939db550 208 # one is defined the other not
ca4f826a 209 if ((defined $left) xor (defined $right)) {
210 $sql_differ = sprintf ("[%s] != [%s]\n", map { defined $_ ? $sqlat->unparse($_) : 'N/A' } ($left, $right) );
939db550 211 return 0;
212 }
6f2a5b66 213
939db550 214 # one is undefined, then so is the other
215 elsif (not defined $left) {
d15c14cc 216 return 1;
217 }
6f2a5b66 218
219 # both are empty
220 elsif (@$left == 0 and @$right == 0) {
0769ac0e 221 return 1;
222 }
6f2a5b66 223
224 # one is empty
225 if (@$left == 0 or @$right == 0) {
ca4f826a 226 $sql_differ = sprintf ("left: %s\nright: %s\n", map { @$_ ? $sqlat->unparse($_) : 'N/A'} ($left, $right) );
6f2a5b66 227 return 0;
228 }
229
1b17d1b0 230 # one is a list, the other is an op with a list
231 elsif (ref $left->[0] xor ref $right->[0]) {
6f2a5b66 232 $sql_differ = sprintf ("[%s] != [%s]\nleft: %s\nright: %s\n", map
ca4f826a 233 { ref $_ ? $sqlat->unparse($_) : $_ }
6f2a5b66 234 ($left->[0], $right->[0], $left, $right)
235 );
fffe6900 236 return 0;
237 }
6f2a5b66 238
239 # both are lists
1b17d1b0 240 elsif (ref $left->[0]) {
241 for (my $i = 0; $i <= $#$left or $i <= $#$right; $i++ ) {
6f2a5b66 242 if (not _eq_sql ($left->[$i], $right->[$i]) ) {
243 if (! $sql_differ or $sql_differ !~ /left\:\s .+ right:\s/xs) {
244 $sql_differ ||= '';
245 $sql_differ .= "\n" unless $sql_differ =~ /\n\z/;
ca4f826a 246 $sql_differ .= sprintf ("left: %s\nright: %s\n", map { $sqlat->unparse($_) } ($left, $right) );
6f2a5b66 247 }
248 return 0;
249 }
1b17d1b0 250 }
251 return 1;
252 }
6f2a5b66 253
254 # both are ops
1b17d1b0 255 else {
256
e40f5df9 257 # unroll parenthesis if possible/allowed
ca4f826a 258 unless ($parenthesis_significant) {
6f2a5b66 259 $sqlat->_parenthesis_unroll($_) for $left, $right;
260 }
1b17d1b0 261
0c2de280 262 # unroll ASC order by's
263 unless ($order_by_asc_significant) {
264 $sqlat->_strip_asc_from_order_by($_) for $left, $right;
265 }
266
ca4f826a 267 if ($left->[0] ne $right->[0]) {
1b17d1b0 268 $sql_differ = sprintf "OP [$left->[0]] != [$right->[0]] in\nleft: %s\nright: %s\n",
a24cc3a0 269 $sqlat->unparse($left),
6f2a5b66 270 $sqlat->unparse($right)
271 ;
1b17d1b0 272 return 0;
273 }
6f2a5b66 274
275 # literals have a different arg-sig
276 elsif ($left->[0] eq '-LITERAL') {
277 (my $l = " $left->[1][0] " ) =~ s/\s+/ /g;
278 (my $r = " $right->[1][0] ") =~ s/\s+/ /g;
279 my $eq = $case_sensitive ? $l eq $r : uc($l) eq uc($r);
280 $sql_differ = "[$l] != [$r]\n" if not $eq;
281 return $eq;
282 }
283
284 # if operators are identical, compare operands
01dd4e4f 285 else {
6f2a5b66 286 my $eq = _eq_sql($left->[1], $right->[1]);
ca4f826a 287 $sql_differ ||= sprintf ("left: %s\nright: %s\n", map { $sqlat->unparse($_) } ($left, $right) ) if not $eq;
6f2a5b66 288 return $eq;
fffe6900 289 }
290 }
291}
292
7853a177 293sub parse { $sqlat->parse(@_) }
fffe6900 2941;
295
296
297__END__
298
299=head1 NAME
300
301SQL::Abstract::Test - Helper function for testing SQL::Abstract
302
303=head1 SYNOPSIS
304
305 use SQL::Abstract;
306 use Test::More;
e7827ba2 307 use SQL::Abstract::Test import => [qw/
308 is_same_sql_bind is_same_sql is_same_bind
309 eq_sql_bind eq_sql eq_bind
310 /];
ec9af79e 311
fffe6900 312 my ($sql, @bind) = SQL::Abstract->new->select(%args);
e7827ba2 313
01dd4e4f 314 is_same_sql_bind($given_sql, \@given_bind,
fffe6900 315 $expected_sql, \@expected_bind, $test_msg);
316
e7827ba2 317 is_same_sql($given_sql, $expected_sql, $test_msg);
318 is_same_bind(\@given_bind, \@expected_bind, $test_msg);
319
01dd4e4f 320 my $is_same = eq_sql_bind($given_sql, \@given_bind,
e7827ba2 321 $expected_sql, \@expected_bind);
322
323 my $sql_same = eq_sql($given_sql, $expected_sql);
324 my $bind_same = eq_bind(\@given_bind, \@expected_bind);
325
fffe6900 326=head1 DESCRIPTION
327
328This module is only intended for authors of tests on
329L<SQL::Abstract|SQL::Abstract> and related modules;
330it exports functions for comparing two SQL statements
331and their bound values.
332
333The SQL comparison is performed on I<abstract syntax>,
334ignoring differences in spaces or in levels of parentheses.
335Therefore the tests will pass as long as the semantics
336is preserved, even if the surface syntax has changed.
337
ec9af79e 338B<Disclaimer> : the semantic equivalence handling is pretty limited.
339A lot of effort goes into distinguishing significant from
340non-significant parenthesis, including AND/OR operator associativity.
341Currently this module does not support commutativity and more
70c6f0e9 342intelligent transformations like L<De Morgan's laws
343|http://en.wikipedia.org/wiki/De_Morgan's_laws>, etc.
ec9af79e 344
70c6f0e9 345For a good overview of what this test framework is currently capable of refer
ec9af79e 346to C<t/10test.t>
fffe6900 347
348=head1 FUNCTIONS
349
350=head2 is_same_sql_bind
351
70c6f0e9 352 is_same_sql_bind(
353 $given_sql, \@given_bind,
354 $expected_sql, \@expected_bind,
355 $test_msg
356 );
fffe6900 357
70c6f0e9 358 is_same_sql_bind(
359 \[$given_sql, @given_bind],
360 \[$expected_sql, @expected_bind],
361 $test_msg
362 );
363
364 is_same_sql_bind(
365 $dbic_rs->as_query
366 $expected_sql, \@expected_bind,
367 $test_msg
368 );
369
370Compares given and expected pairs of C<($sql, \@bind)> by unpacking C<@_>
371as shown in the examples above and passing the arguments to L</eq_sql> and
372L</eq_bind>. Calls L<Test::Builder/ok> with the combined result, with
373C<$test_msg> as message.
374If the test fails, a detailed diagnostic is printed.
e7827ba2 375
376=head2 is_same_sql
377
70c6f0e9 378 is_same_sql(
379 $given_sql,
380 $expected_sql,
381 $test_msg
382 );
e7827ba2 383
70c6f0e9 384Compares given and expected SQL statements via L</eq_sql>, and calls
385L<Test::Builder/ok> on the result, with C<$test_msg> as message.
386If the test fails, a detailed diagnostic is printed.
e7827ba2 387
388=head2 is_same_bind
389
70c6f0e9 390 is_same_bind(
391 \@given_bind,
392 \@expected_bind,
393 $test_msg
394 );
e7827ba2 395
70c6f0e9 396Compares given and expected bind values via L</eq_bind>, and calls
397L<Test::Builder/ok> on the result, with C<$test_msg> as message.
398If the test fails, a detailed diagnostic is printed.
e7827ba2 399
400=head2 eq_sql_bind
401
70c6f0e9 402 my $is_same = eq_sql_bind(
403 $given_sql, \@given_bind,
404 $expected_sql, \@expected_bind,
405 );
e7827ba2 406
70c6f0e9 407 my $is_same = eq_sql_bind(
408 \[$given_sql, @given_bind],
409 \[$expected_sql, @expected_bind],
410 );
411
412 my $is_same = eq_sql_bind(
413 $dbic_rs->as_query
414 $expected_sql, \@expected_bind,
415 );
416
417Unpacks C<@_> depending on the given arguments and calls L</eq_sql> and
418L</eq_bind>, returning their combined result.
fffe6900 419
420=head2 eq_sql
421
422 my $is_same = eq_sql($given_sql, $expected_sql);
423
e7827ba2 424Compares the abstract syntax of two SQL statements. Similar to L</is_same_sql>,
425but it just returns a boolean value and does not print diagnostics or talk to
426L<Test::Builder>. If the result is false, the global variable L</$sql_differ>
427will contain the SQL portion where a difference was encountered; this is useful
428for printing diagnostics.
fffe6900 429
430=head2 eq_bind
431
432 my $is_same = eq_sql(\@given_bind, \@expected_bind);
433
e7827ba2 434Compares two lists of bind values, taking into account the fact that some of
435the values may be arrayrefs (see L<SQL::Abstract/bindtype>). Similar to
436L</is_same_bind>, but it just returns a boolean value and does not print
437diagnostics or talk to L<Test::Builder>.
fffe6900 438
439=head1 GLOBAL VARIABLES
440
e7827ba2 441=head2 $case_sensitive
fffe6900 442
443If true, SQL comparisons will be case-sensitive. Default is false;
444
e40f5df9 445=head2 $parenthesis_significant
446
447If true, SQL comparison will preserve and report difference in nested
48d9f5f8 448parenthesis. Useful while testing C<IN (( x ))> vs C<IN ( x )>.
449Defaults to false;
e40f5df9 450
0c2de280 451=head2 $order_by_asc_significant
452
453If true SQL comparison will consider C<ORDER BY foo ASC> and
454C<ORDER BY foo> to be different. Default is false;
455
e7827ba2 456=head2 $sql_differ
fffe6900 457
458When L</eq_sql> returns false, the global variable
459C<$sql_differ> contains the SQL portion
460where a difference was encountered.
461
fffe6900 462=head1 SEE ALSO
463
a6daa642 464L<SQL::Abstract>, L<Test::More>, L<Test::Builder>.
fffe6900 465
25823711 466=head1 AUTHORS
fffe6900 467
70c6f0e9 468Laurent Dami <laurent.dami AT etat geneve ch>
fffe6900 469
25823711 470Norbert Buchmuller <norbi@nix.hu>
471
e96c510a 472Peter Rabbitson <ribasushi@cpan.org>
473
fffe6900 474=head1 COPYRIGHT AND LICENSE
475
476Copyright 2008 by Laurent Dami.
477
478This library is free software; you can redistribute it and/or modify
01dd4e4f 479it under the same terms as Perl itself.