File Coverage

blib/lib/Test/Data/Sah.pm
Criterion Covered Total %
statement 114 187 60.9
branch 34 92 36.9
condition 11 28 39.2
subroutine 15 19 78.9
pod 5 5 100.0
total 179 331 54.0


line stmt bran cond sub pod time code
1             ## no critic: (ControlStructures::ProhibitUnreachableCode)
2              
3              
4             use 5.010;
5 20     20   815646 use strict;
  20         194  
6 20     20   106 use warnings;
  20         30  
  20         367  
7 20     20   1759 use Test::More 0.98;
  20         39  
  20         574  
8 20     20   5137  
  20         852460  
  20         123  
9             use Data::Dump qw(dump);
10 20     20   13537 use Data::Sah qw(gen_validator);
  20         115454  
  20         1126  
11 20     20   8584 use File::chdir;
  20         73  
  20         1011  
12 20     20   8718 use File::Slurper qw(read_text);
  20         53093  
  20         1670  
13 20     20   7993  
  20         312500  
  20         1050  
14             use Exporter qw(import);
15 20     20   125  
  20         39  
  20         37377  
16             our $AUTHORITY = 'cpan:PERLANCAR'; # AUTHORITY
17             our $DATE = '2022-08-20'; # DATE
18             our $DIST = 'Data-Sah'; # DIST
19             our $VERSION = '0.912'; # VERSION
20              
21             our @EXPORT_OK = qw(
22             test_sah_cases
23             run_spectest
24             all_match
25             any_match
26             none_match
27             );
28              
29             # XXX support js & human testing too
30             my $tests = shift;
31             my $opts = shift // {};
32 26     26 1 1871469  
33 26   100     150 my $sah = Data::Sah->new;
34             my $plc = $sah->get_compiler('perl');
35 26         176  
36 26         1147 my $gvopts = $opts->{gen_validator_opts} // {};
37             my $rt = $gvopts->{return_type} // 'bool_valid';
38 26   100     147  
39 26   100     152 for my $test (@$tests) {
40             my $v = gen_validator($test->{schema}, $gvopts);
41 26         73 my $res = $v->($test->{input});
42 278         1001 my $name = $test->{name} //
43 278         5721 "data " . dump($test->{input}) . " should".
44             ($test->{valid} ? " pass" : " not pass"). " schema " .
45             dump($test->{schema});
46             my $testres;
47 278 100 33     1378 if ($test->{valid}) {
48 278         150605 if ($rt eq 'bool_valid') {
49 278 100       597 $testres = ok($res, $name);
50 162 100       366 } elsif ($rt eq 'str_errmsg') {
    50          
    0          
51 161         465 $testres = is($res, "", $name) or diag explain $res;
52             } elsif ($rt eq 'hash_details') {
53 1 50       4 $testres = is(scalar keys(%{$res->{errors}}), 0, $name) or diag explain $res;
54             }
55 0 0       0 } else {
  0         0  
56             if ($rt eq 'bool_valid') {
57             $testres = ok(!$res, $name);
58 116 50       292 } elsif ($rt eq 'str_errmsg') {
    0          
    0          
59 116         363 $testres = isnt($res, "", $name) or diag explain $res;
60             } elsif ($rt eq 'hash_details') {
61 0 0       0 $testres = isnt(scalar keys(%{$res->{errors}}), 0, $name) or diag explain $res;
62             }
63 0 0       0 }
  0         0  
64             next if $testres;
65              
66 278 50       155005 # when test fails, show the validator generated code to help debugging
67             my $cd = $plc->compile(schema => $test->{schema});
68             diag "schema compilation result:\n----begin generated code----\n",
69 0         0 explain($cd->{result}), "\n----end generated code----\n",
70             "that code should return ", ($test->{valid} ? "true":"false"),
71             " when fed \$data=", dump($test->{input}),
72             " but instead returns ", dump($res);
73 0 0       0  
74             # also show the result for return_type=hash_details
75             my $vhash = gen_validator($test->{schema}, {return_type=>"hash_details"});
76             diag "\nvalidator result (hash_details):\n----begin result----\n",
77 0         0 explain($vhash->($test->{input})), "----end result----";
78             }
79 0         0 }
80              
81             state $json = do {
82             require JSON;
83             JSON->new->allow_nonref;
84 14     14   39730 };
85 1         2157 $json->decode(@_);
86 1         9762 }
87              
88 14         9442 require File::ShareDir;
89             require File::ShareDir::Tarball;
90             require Sah;
91              
92 1     1 1 388 my %args = @_;
93 1         21598  
94 1         96163 my $sah = Data::Sah->new;
95              
96 1         22 my $dir;
97             if (version->parse($Sah::VERSION) == version->parse("0.9.27")) {
98 1         10 # this version of Sah temporarily uses ShareDir instead of
99             # ShareDir::Tarball due to garbled output problem of tarball.
100 1         53 $dir = File::ShareDir::dist_dir("Sah");
101 1 50       19 } else {
102             $dir = File::ShareDir::Tarball::dist_dir("Sah");
103             }
104 0         0 $dir && (-d $dir) or die "Can't find spectest, have you installed Sah?";
105             (-f "$dir/spectest/00-normalize_schema.json")
106 1         4 or die "Something's wrong, spectest doesn't contain the correct files";
107              
108 1 50 33     57553 my @specfiles;
109 1 50       21 {
110             local $CWD = "$dir/spectest";
111             @specfiles = glob("*.json");
112 1         5 }
113              
114 1         6 # to test certain files only
  1         10  
115 1         281 my @files;
116             if ($ENV{TEST_SAH_SPECTEST_FILES}) {
117             @files = split /\s*,\s*|\s+/, $ENV{TEST_SAH_SPECTEST_FILES};
118             } else {
119 1         31 @files = @ARGV;
120 1 50       10 }
121 0         0  
122             # to test certain types only
123 1         4 my @types;
124             if ($ENV{TEST_SAH_SPECTEST_TYPES}) {
125             @types = split /\s*,\s*|\s+/, $ENV{TEST_SAH_SPECTEST_TYPES};
126             }
127 1         5  
128 1 50       6 # to test only tests that have all matching tags
129 0         0 my @include_tags;
130             if ($ENV{TEST_SAH_SPECTEST_INCLUDE_TAGS}) {
131             @include_tags = split /\s*,\s*|\s+/,
132             $ENV{TEST_SAH_SPECTEST_INCLUDE_TAGS};
133 1         2 }
134 1 50       8  
135             # to skip tests that have all matching tags
136 0         0 my @exclude_tags;
137             if ($ENV{TEST_SAH_SPECTEST_EXCLUDE_TAGS}) {
138             @exclude_tags = split /\s*,\s*|\s+/,
139             $ENV{TEST_SAH_SPECTEST_EXCLUDE_TAGS};
140 1         3 }
141 1 50       12  
142             my $code_test_excluded = sub {
143 0         0 my $test = shift;
144              
145             if ($test->{tags} && @exclude_tags) {
146             if (any_match(\@exclude_tags, $test->{tags})) {
147 1503     1503   2460 return "contains excluded tag(s) (".
148             join(", ", @exclude_tags).")";
149 1503 50 33     7444 }
150 0 0       0 }
151 0         0 if (@include_tags) {
152             if (!all_match(\@include_tags, $test->{tags} // [])) {
153             return "does not contain all include tags (".
154             join(", ", @include_tags).")";
155 1503 50       4446 }
156 0 0 0     0 }
157 0         0 "";
158             };
159              
160             {
161 1503         2759 last unless $args{test_normalize_schema};
162 1         19  
163             for my $file ("00-normalize_schema.json") {
164             unless (!@files || grep { $_ eq $file } @files) {
165 1 50       5 diag "Skipping file $file";
166             next;
167 0         0 }
168 0 0 0     0 subtest $file => sub {
  0         0  
169 0         0 my $tspec = _decode_json(read_text("$dir/spectest/$file"));
170 0         0 for my $test (@{ $tspec->{tests} }) {
171             subtest $test->{name} => sub {
172             if (my $reason = $code_test_excluded->($test)) {
173 0     0   0 plan skip_all => "Skipping test $test->{name}: $reason";
174 0         0 return;
  0         0  
175             }
176 0 0       0 eval {
177 0         0 is_deeply(normalize_schema($test->{input}),
178 0         0 $test->{result}, "result");
179             };
180 0         0 my $eval_err = $@;
181             if ($test->{dies}) {
182 0         0 ok($eval_err, "dies");
183             } else {
184 0         0 ok(!$eval_err, "doesn't die")
185 0 0       0 or diag $eval_err;
186 0         0 }
187             };
188 0 0       0 }
189             ok 1; # an extra dummy ok to pass even if all spectest is skipped
190             };
191 0         0 }
192             }
193 0         0  
194 0         0 {
195             last unless $args{test_merge_clause_sets};
196              
197             for my $file ("01-merge_clause_sets.json") {
198             last; # we currently remove _merge_clause_sets() from Data::Sah
199 1 50       7 unless (!@files || grep { $_ eq $file } @files) {
  1         11  
200             diag "Skipping file $file";
201 1         7 next;
202 1         3 }
203 0 0 0     0 subtest $file => sub {
  0         0  
204 0         0 my $tspec = _decode_json(scalar read_text("$dir/spectest/$file"));
205 0         0 for my $test (@{ $tspec->{tests} }) {
206             subtest $test->{name} => sub {
207             if (my $reason = $code_test_excluded->($test)) {
208 0     0   0 plan skip_all => "Skipping test $test->{name}: $reason";
209 0         0 return;
  0         0  
210             }
211 0 0       0 eval {
212 0         0 is_deeply($sah->_merge_clause_sets(@{ $test->{input} }),
213 0         0 $test->{result}, "result");
214             };
215 0         0 my $eval_err = $@;
216 0         0 if ($test->{dies}) {
217 0         0 ok($eval_err, "dies");
218             } else {
219 0         0 ok(!$eval_err, "doesn't die")
220 0 0       0 or diag $eval_err;
221 0         0 }
222             };
223 0 0       0 }
224             ok 1; # an extra dummy ok to pass even if all spectest is skipped
225             };
226 0         0 }
227             }
228 0         0  
229 0         0 {
230             for my $file (grep {/^10-type-/ || /^20-clause-(prefilters)/} @specfiles) {
231             unless (!@files || grep { $_ eq $file } @files) {
232             diag "Skipping file $file";
233             next;
234 1 100       2 }
  1         5  
  1         4  
  24         83  
235 14 50 33     942953 subtest $file => sub {
  0         0  
236 0         0 diag "Loading $file ...";
237 0         0 my $tspec = _decode_json(read_text("$dir/spectest/$file"));
238             note "Test version: ", $tspec->{version};
239             my $tests = $tspec->{tests};
240 14     14   18627 if ($args{tests_func}) {
241 14         7925 $args{tests_func}->($tests, {
242 14         117 parent_args => \%args,
243 14         8801 code_test_excluded => $code_test_excluded,
244 14 50       87 });
    50          
245 0         0 } elsif ($args{test_func}) {
246             for my $test (@$tests) {
247             my $skip_reason;
248             {
249             if ($args{skip_if}) {
250 14         35 $skip_reason = $args{skip_if}->($test);
251 1584         3801786 last if $skip_reason;
252             }
253 1584 50       3113 $skip_reason = $code_test_excluded->($test);
  1584         6203  
254 1584         5239 last if $skip_reason;
255 1584 100       4608 }
256             my $tname = "(tags=".join(", ", sort @{ $test->{tags} // [] }).
257 1503         4590 ") $test->{name}";
258 1503 50       3760 if ($skip_reason) {
259             diag "Skipping test $tname: $skip_reason";
260 1584   50     2391 next;
  1584         11732  
261             }
262 1584 100       4656 note explain $test;
263 81         380 subtest $tname => sub {
264 81         40549 $args{test_func}->($test);
265             };
266 1503         6356 } # for $test
267             ok 1; # an extra dummy ok to pass even if all spectest is skipped
268 1503         1507644 } else {
269 1503         1145345 die "Please specify 'test_func' or 'tests_func'";
270             }
271 14         30954 }; # subtest $file
272             } # for $file
273 0         0 }
274              
275 14         2443 }
276              
277             my ($list1, $list2) = @_;
278              
279             for my $el (@$list1) {
280             return 0 unless grep { $_ eq $el } @$list2;
281             }
282 18582     18582 1 23630 1;
283             }
284 18582         22285  
285 18761 100       21792 my ($list1, $list2) = @_;
  75880         128347  
286              
287 60         203 for my $el (@$list1) {
288             return 1 if grep { $_ eq $el } @$list2;
289             }
290             0;
291 0     0 1   }
292              
293 0           my ($list1, $list2) = @_;
294 0 0          
  0            
295             for my $el (@$list1) {
296 0           return 0 if grep { $_ eq $el } @$list2;
297             }
298             1;
299             }
300 0     0 1    
301             1;
302 0           # ABSTRACT: Test routines for Data::Sah
303 0 0          
  0            
304              
305 0           =pod
306              
307             =encoding UTF-8
308              
309             =head1 NAME
310              
311             Test::Data::Sah - Test routines for Data::Sah
312              
313             =head1 VERSION
314              
315             This document describes version 0.912 of Test::Data::Sah (from Perl distribution Data-Sah), released on 2022-08-20.
316              
317             =head1 FUNCTIONS
318              
319             =head2 test_sah_cases(\@tests)
320              
321             =head2 run_spectest(\@tests, \%opts)
322              
323             =head2 all_match(\@array1, \@array2) => bool
324              
325             A utility routine. Probably will be moved to another module in the future.
326              
327             Return true if all of the elements in C<@array1> is in C<@array2>.
328              
329             =head2 any_match(\@array1, \@array2) => bool
330              
331             A utility routine. Probably will be moved to another module in the future.
332              
333             Return true if any element in C<@array1> is in C<@array2>.
334              
335             =head2 none_match(\@array1, \@array2) => bool
336              
337             A utility routine. Probably will be moved to another module in the future.
338              
339             Return true if none of the elements in C<@array1> is in C<@array2>.
340              
341             =head1 ENVIRONMENT
342              
343             =head2 TEST_SAH_SPECTEST_FILES => str
344              
345             Comma-separated list of files in spectest to test. Default is all files. If you
346             only want to test certain spectest files, use this.
347              
348             =head2 TEST_SAH_SPECTEST_TYPES => str
349              
350             Comma-separated list of types to test. Default is all types. If you only want to
351             test certain types, use this.
352              
353             =head2 TEST_SAH_SPECTEST_INCLUDE_TAGS => str
354              
355             Comma-separated list of tags to include. If you only want to include tests that
356             have certain tags, use this.
357              
358             =head2 TEST_SAH_SPECTEST_EXCLUDE_TAGS => str
359              
360             Comma-separated list of tags to exclude. If you want to exclude tests that have
361             certain tags, use this.
362              
363             =head1 HOMEPAGE
364              
365             Please visit the project's homepage at L<https://metacpan.org/release/Data-Sah>.
366              
367             =head1 SOURCE
368              
369             Source repository is at L<https://github.com/perlancar/perl-Data-Sah>.
370              
371             =head1 AUTHOR
372              
373             perlancar <perlancar@cpan.org>
374              
375             =head1 CONTRIBUTING
376              
377              
378             To contribute, you can send patches by email/via RT, or send pull requests on
379             GitHub.
380              
381             Most of the time, you don't need to build the distribution yourself. You can
382             simply modify the code, then test via:
383              
384             % prove -l
385              
386             If you want to build the distribution (e.g. to try to install it locally on your
387             system), you can install L<Dist::Zilla>,
388             L<Dist::Zilla::PluginBundle::Author::PERLANCAR>,
389             L<Pod::Weaver::PluginBundle::Author::PERLANCAR>, and sometimes one or two other
390             Dist::Zilla- and/or Pod::Weaver plugins. Any additional steps required beyond
391             that are considered a bug and can be reported to me.
392              
393             =head1 COPYRIGHT AND LICENSE
394              
395             This software is copyright (c) 2022, 2021, 2020, 2019, 2018, 2017, 2016, 2015, 2014, 2013, 2012 by perlancar <perlancar@cpan.org>.
396              
397             This is free software; you can redistribute it and/or modify it under
398             the same terms as the Perl 5 programming language system itself.
399              
400             =head1 BUGS
401              
402             Please report any bugs or feature requests on the bugtracker website L<https://rt.cpan.org/Public/Dist/Display.html?Name=Data-Sah>
403              
404             When submitting a bug or request, please include a test-file or a
405             patch to an existing test-file that illustrates the bug or desired
406             feature.
407              
408             =cut