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   802701 use strict;
  20         191  
6 20     20   1410 use warnings;
  20         35  
  20         362  
7 20     20   76 use Test::More 0.98;
  20         33  
  20         1868  
8 20     20   4872  
  20         818322  
  20         129  
9             use Data::Dump qw(dump);
10 20     20   13065 use Data::Sah qw(gen_validator);
  20         113261  
  20         1090  
11 20     20   8369 use File::chdir;
  20         63  
  20         1031  
12 20     20   8691 use File::Slurper qw(read_text);
  20         51083  
  20         1575  
13 20     20   7758  
  20         303680  
  20         1139  
14             use Exporter qw(import);
15 20     20   143  
  20         32  
  20         37439  
16             our $AUTHORITY = 'cpan:PERLANCAR'; # AUTHORITY
17             our $DATE = '2022-10-19'; # DATE
18             our $DIST = 'Data-Sah'; # DIST
19             our $VERSION = '0.914'; # 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 1834930  
33 26   100     148 my $sah = Data::Sah->new;
34             my $plc = $sah->get_compiler('perl');
35 26         296  
36 26         1166 my $gvopts = $opts->{gen_validator_opts} // {};
37             my $rt = $gvopts->{return_type} // 'bool_valid';
38 26   100     164  
39 26   100     164 for my $test (@$tests) {
40             my $v = gen_validator($test->{schema}, $gvopts);
41 26         78 my $res = $v->($test->{input});
42 278         951 my $name = $test->{name} //
43 278         5709 "data " . dump($test->{input}) . " should".
44             ($test->{valid} ? " pass" : " not pass"). " schema " .
45             dump($test->{schema});
46             my $testres;
47 278 100 33     1503 if ($test->{valid}) {
48 278         148786 if ($rt eq 'bool_valid') {
49 278 100       642 $testres = ok($res, $name);
50 162 100       408 } elsif ($rt eq 'str_errmsg') {
    50          
    0          
51 161         519 $testres = is($res, "", $name) or diag explain $res;
52             } elsif ($rt eq 'hash_details') {
53 1 50       6 $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       325 } elsif ($rt eq 'str_errmsg') {
    0          
    0          
59 116         401 $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       180652 # 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   44132 };
85 1         1516 $json->decode(@_);
86 1         9174 }
87              
88 14         10017 require File::ShareDir;
89             require File::ShareDir::Tarball;
90             require Sah;
91              
92 1     1 1 366 my %args = @_;
93 1         21612  
94 1         90220 my $sah = Data::Sah->new;
95              
96 1         17 my $dir;
97             if (version->parse($Sah::VERSION) == version->parse("0.9.27")) {
98 1         9 # this version of Sah temporarily uses ShareDir instead of
99             # ShareDir::Tarball due to garbled output problem of tarball.
100 1         47 $dir = File::ShareDir::dist_dir("Sah");
101 1 50       18 } 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     75928 my @specfiles;
109 1 50       14 {
110             local $CWD = "$dir/spectest";
111             @specfiles = glob("*.json");
112 1         5 }
113              
114 1         4 # to test certain files only
  1         6  
115 1         269 my @files;
116             if ($ENV{TEST_SAH_SPECTEST_FILES}) {
117             @files = split /\s*,\s*|\s+/, $ENV{TEST_SAH_SPECTEST_FILES};
118             } else {
119 1         28 @files = @ARGV;
120 1 50       7 }
121 0         0  
122             # to test certain types only
123 1         3 my @types;
124             if ($ENV{TEST_SAH_SPECTEST_TYPES}) {
125             @types = split /\s*,\s*|\s+/, $ENV{TEST_SAH_SPECTEST_TYPES};
126             }
127 1         4  
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         6 }
134 1 50       7  
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         2 }
141 1 50       11  
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   2706 return "contains excluded tag(s) (".
148             join(", ", @exclude_tags).")";
149 1503 50 33     9722 }
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       4577 }
156 0 0 0     0 }
157 0         0 "";
158             };
159              
160             {
161 1503         3370 last unless $args{test_normalize_schema};
162 1         17  
163             for my $file ("00-normalize_schema.json") {
164             unless (!@files || grep { $_ eq $file } @files) {
165 1 50       10 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       2 unless (!@files || grep { $_ eq $file } @files) {
  1         4  
200             diag "Skipping file $file";
201 1         10 next;
202 1         6 }
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       7 }
  1         5  
  1         7  
  24         77  
235 14 50 33     896579 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   17488 if ($args{tests_func}) {
241 14         7608 $args{tests_func}->($tests, {
242 14         119 parent_args => \%args,
243 14         8503 code_test_excluded => $code_test_excluded,
244 14 50       89 });
    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         34 $skip_reason = $args{skip_if}->($test);
251 1584         3919794 last if $skip_reason;
252             }
253 1584 50       4206 $skip_reason = $code_test_excluded->($test);
  1584         7121  
254 1584         6406 last if $skip_reason;
255 1584 100       4416 }
256             my $tname = "(tags=".join(", ", sort @{ $test->{tags} // [] }).
257 1503         5468 ") $test->{name}";
258 1503 50       4014 if ($skip_reason) {
259             diag "Skipping test $tname: $skip_reason";
260 1584   50     2688 next;
  1584         11764  
261             }
262 1584 100       4557 note explain $test;
263 81         293 subtest $tname => sub {
264 81         41288 $args{test_func}->($test);
265             };
266 1503         6650 } # for $test
267             ok 1; # an extra dummy ok to pass even if all spectest is skipped
268 1503         1535351 } else {
269 1503         1178719 die "Please specify 'test_func' or 'tests_func'";
270             }
271 14         30508 }; # subtest $file
272             } # for $file
273 0         0 }
274              
275 14         2622 }
276              
277             my ($list1, $list2) = @_;
278              
279             for my $el (@$list1) {
280             return 0 unless grep { $_ eq $el } @$list2;
281             }
282 18582     18582 1 26467 1;
283             }
284 18582         24327  
285 18761 100       22721 my ($list1, $list2) = @_;
  75880         132528  
286              
287 60         207 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.914 of Test::Data::Sah (from Perl distribution Data-Sah), released on 2022-10-19.
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