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   819525 use strict;
  20         1532  
6 20     20   89 use warnings;
  20         33  
  20         353  
7 20     20   1439 use Test::More 0.98;
  20         37  
  20         565  
8 20     20   5218  
  20         857229  
  20         127  
9             use Data::Dump qw(dump);
10 20     20   13438 use Data::Sah qw(gen_validator);
  20         116191  
  20         1069  
11 20     20   8811 use File::chdir;
  20         76  
  20         1003  
12 20     20   8982 use File::Slurper qw(read_text);
  20         53302  
  20         1647  
13 20     20   8218  
  20         317462  
  20         1027  
14             use Exporter qw(import);
15 20     20   157  
  20         39  
  20         37686  
16             our $AUTHORITY = 'cpan:PERLANCAR'; # AUTHORITY
17             our $DATE = '2022-09-30'; # DATE
18             our $DIST = 'Data-Sah'; # DIST
19             our $VERSION = '0.913'; # 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 1926226  
33 26   100     157 my $sah = Data::Sah->new;
34             my $plc = $sah->get_compiler('perl');
35 26         184  
36 26         1112 my $gvopts = $opts->{gen_validator_opts} // {};
37             my $rt = $gvopts->{return_type} // 'bool_valid';
38 26   100     153  
39 26   100     150 for my $test (@$tests) {
40             my $v = gen_validator($test->{schema}, $gvopts);
41 26         73 my $res = $v->($test->{input});
42 278         1068 my $name = $test->{name} //
43 278         5934 "data " . dump($test->{input}) . " should".
44             ($test->{valid} ? " pass" : " not pass"). " schema " .
45             dump($test->{schema});
46             my $testres;
47 278 100 33     1434 if ($test->{valid}) {
48 278         151642 if ($rt eq 'bool_valid') {
49 278 100       635 $testres = ok($res, $name);
50 162 100       364 } elsif ($rt eq 'str_errmsg') {
    50          
    0          
51 161         480 $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       289 } elsif ($rt eq 'str_errmsg') {
    0          
    0          
59 116         375 $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       162467 # 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   42661 };
85 1         1678 $json->decode(@_);
86 1         9514 }
87              
88 14         9644 require File::ShareDir;
89             require File::ShareDir::Tarball;
90             require Sah;
91              
92 1     1 1 400 my %args = @_;
93 1         21851  
94 1         118589 my $sah = Data::Sah->new;
95              
96 1         19 my $dir;
97             if (version->parse($Sah::VERSION) == version->parse("0.9.27")) {
98 1         8 # 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       16 } 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     59207 my @specfiles;
109 1 50       22 {
110             local $CWD = "$dir/spectest";
111             @specfiles = glob("*.json");
112 1         4 }
113              
114 1         7 # to test certain files only
  1         6  
115 1         277 my @files;
116             if ($ENV{TEST_SAH_SPECTEST_FILES}) {
117             @files = split /\s*,\s*|\s+/, $ENV{TEST_SAH_SPECTEST_FILES};
118             } else {
119 1         29 @files = @ARGV;
120 1 50       14 }
121 0         0  
122             # to test certain types only
123 1         10 my @types;
124             if ($ENV{TEST_SAH_SPECTEST_TYPES}) {
125             @types = split /\s*,\s*|\s+/, $ENV{TEST_SAH_SPECTEST_TYPES};
126             }
127 1         7  
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         5 }
134 1 50       3  
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       16  
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   2438 return "contains excluded tag(s) (".
148             join(", ", @exclude_tags).")";
149 1503 50 33     7231 }
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       3189 }
156 0 0 0     0 }
157 0         0 "";
158             };
159              
160             {
161 1503         2874 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       4 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         10  
200             diag "Skipping file $file";
201 1         7 next;
202 1         5 }
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         2  
  1         3  
  24         70  
235 14 50 33     915034 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   18262 if ($args{tests_func}) {
241 14         8242 $args{tests_func}->($tests, {
242 14         115 parent_args => \%args,
243 14         8682 code_test_excluded => $code_test_excluded,
244 14 50       74 });
    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         36 $skip_reason = $args{skip_if}->($test);
251 1584         3795789 last if $skip_reason;
252             }
253 1584 50       3873 $skip_reason = $code_test_excluded->($test);
  1584         5129  
254 1584         5175 last if $skip_reason;
255 1584 100       3877 }
256             my $tname = "(tags=".join(", ", sort @{ $test->{tags} // [] }).
257 1503         3541 ") $test->{name}";
258 1503 50       3588 if ($skip_reason) {
259             diag "Skipping test $tname: $skip_reason";
260 1584   50     2844 next;
  1584         11114  
261             }
262 1584 100       3645 note explain $test;
263 81         335 subtest $tname => sub {
264 81         41913 $args{test_func}->($test);
265             };
266 1503         5217 } # for $test
267             ok 1; # an extra dummy ok to pass even if all spectest is skipped
268 1503         1509781 } else {
269 1503         1091536 die "Please specify 'test_func' or 'tests_func'";
270             }
271 14         30701 }; # subtest $file
272             } # for $file
273 0         0 }
274              
275 14         2313 }
276              
277             my ($list1, $list2) = @_;
278              
279             for my $el (@$list1) {
280             return 0 unless grep { $_ eq $el } @$list2;
281             }
282 18582     18582 1 23757 1;
283             }
284 18582         22184  
285 18761 100       22336 my ($list1, $list2) = @_;
  75880         127767  
286              
287 60         202 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.913 of Test::Data::Sah (from Perl distribution Data-Sah), released on 2022-09-30.
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