File Coverage

blib/lib/Test/Data/Sah/JS.pm
Criterion Covered Total %
statement 32 116 27.5
branch 0 34 0.0
condition 0 25 0.0
subroutine 11 15 73.3
pod 1 1 100.0
total 44 191 23.0


line stmt bran cond sub pod time code
1             package Test::Data::Sah::JS;
2              
3             our $DATE = '2016-09-14'; # DATE
4             our $VERSION = '0.87'; # VERSION
5              
6 1     1   12680 use 5.010001;
  1         3  
7 1     1   4 use strict;
  1         0  
  1         15  
8 1     1   3 use warnings;
  1         0  
  1         25  
9              
10 1     1   412 use Test::Data::Sah qw(run_spectest all_match);
  1         27539  
  1         55  
11 1     1   6 use Test::More 0.98;
  1         14  
  1         6  
12              
13 1     1   620 use Capture::Tiny qw(tee_merged);
  1         17770  
  1         54  
14 1     1   6 use Data::Sah qw(gen_validator);
  1         1  
  1         37  
15 1     1   3 use File::Temp qw(tempdir tempfile);
  1         1  
  1         35  
16 1     1   746 use Nodejs::Util qw(get_nodejs_path);
  1         732  
  1         45  
17 1     1   370 use String::Indent qw(indent);
  1         241  
  1         49  
18              
19 1     1   4 use Exporter qw(import);
  1         1  
  1         931  
20             our @EXPORT_OK = qw(run_spectest_for_js);
21              
22             sub _encode_json {
23 0     0     state $json = do {
24 0           require JSON::MaybeXS;
25 0           JSON::MaybeXS->new->allow_nonref;
26             };
27 0           $json->encode(@_);
28             }
29              
30             sub run_spectest_for_js {
31 0     0 1   my %args = @_;
32              
33             run_spectest(
34             tests_func => sub {
35 0     0     my ($tests, $opts) = @_;
36              
37             # we compile all the schemas (plus some control code) to a single js
38             # file then execute it using nodejs. the js file is supposed to
39             # produce TAP output.
40              
41             my $node_path = $opts->{node_path} // $args{nodejs_path} //
42 0   0       get_nodejs_path();
      0        
43 0           my $sah = Data::Sah->new;
44 0           my $js = $sah->get_compiler('js');
45              
46 0           my %names; # key: json(schema)
47             my %counters; # key: type name
48              
49 0           my @js_code;
50              
51             # controller/tap code
52 0           push @js_code, <<'_';
53             String.prototype.repeat = function(n) { return new Array(isNaN(n) ? 1 : ++n).join(this) }
54              
55             // BEGIN TAP
56              
57             var indent = " "
58             var tap_indent_level = 2
59             var tap_counter = 0
60             var tap_num_nok = 0
61              
62             function tap_esc(name) {
63             return name.replace(/#/g, '\\#').replace(/\n/g, '\n' + indent.repeat(tap_indent_level+1) + '#')
64             }
65              
66             function tap_print_oknok(is_ok, name) {
67             if (!is_ok) tap_num_nok++
68             console.log(
69             indent.repeat(tap_indent_level) +
70             (is_ok ? "ok " : "not ok ") +
71             ++tap_counter +
72             (name ? " - " + tap_esc(name) : "")
73             )
74             }
75              
76             function tap_print_summary() {
77             if (tap_num_nok > 0) {
78             console.log(indent.repeat(tap_indent_level) + '# ' + tap_num_nok + ' failed test(s)')
79             }
80             console.log(
81             indent.repeat(tap_indent_level) + "1.." + tap_counter
82             )
83             }
84              
85             function ok(cond, name) {
86             tap_print_oknok(cond, name)
87             }
88              
89             function subtest(name, code) {
90             var save_counter = tap_counter
91             var save_num_nok = tap_num_nok
92              
93             tap_num_nok = 0
94             tap_counter = 0
95             tap_indent_level++
96             code()
97             tap_print_summary()
98             tap_indent_level--
99              
100             tap_counter = save_counter
101             var save2_num_nok = tap_num_nok
102             tap_num_nok = save_num_nok
103             tap_print_oknok(save2_num_nok == 0, name)
104             }
105              
106             function done_testing() {
107             tap_print_summary()
108             }
109              
110             // END TAP
111              
112             var res;
113              
114             _
115              
116             TEST:
117 0           for my $test (@$tests) {
118 0   0       my $tname = "(tags=".join(", ", sort @{ $test->{tags} // [] }).
  0            
119             ") $test->{name}";
120 0 0 0       if ($opts->{parent_args}{skip_if} &&
121             (my $reason = $opts->{parent_args}{skip_if}->($test))) {
122 0           diag "Skipping test $tname: $reason";
123 0           next TEST;
124             }
125 0 0 0       if ($opts->{code_test_excluded} &&
126             (my $reason = $opts->{code_test_excluded}->($test))) {
127 0           diag "Skipping test $tname: $reason";
128 0           next TEST;
129             }
130 0           my $k = _encode_json($test->{schema});
131 0           my $ns = $sah->normalize_schema($test->{schema});
132 0           $test->{nschema} = $ns;
133              
134 0           my $fn = $names{$k};
135 0 0         if (!$fn) {
136 0           $fn = "sahv_" . $ns->[0] . ++$counters{$ns->[0]};
137 0           $names{$k} = $fn;
138              
139 0           push @js_code, "\n\n",
140             indent("// ", "schema: " . _encode_json($ns)), "\n\n";
141              
142 0           for my $rt (qw/bool str full/) {
143 0           my $code;
144 0           eval {
145 0           $code = $js->expr_validator_sub(
146             schema => $ns,
147             schema_is_normalized => 1,
148             return_type => $rt,
149             );
150             };
151 0           my $err = $@;
152 0 0         if ($test->{dies}) {
153             #note "schema = ", explain($ns);
154 0           ok($err, $tname);
155 0           next TEST;
156             } else {
157 0 0         ok(!$err, "compile ok ($tname}, $rt)") or do {
158 0           diag $err;
159 0           next TEST;
160             };
161             }
162 0           push @js_code, "var $fn\_$rt = $code;\n\n";
163             } # rt
164             }
165              
166 0           push @js_code,
167             "subtest("._encode_json($tname).", function() {\n";
168              
169             # bool
170 0 0         if ($test->{valid_inputs}) {
    0          
171             # test multiple inputs, currently done for rt=bool only
172 0           for my $i (0..@{ $test->{valid_inputs} }-1) {
  0            
173 0           my $data = $test->{valid_inputs}[$i];
174 0           push @js_code,
175             " ok($fn\_bool("._encode_json($data).")".
176             ", 'valid input [$i]');\n";
177             }
178 0           for my $i (0..@{ $test->{invalid_inputs} }-1) {
  0            
179 0           my $data = $test->{invalid_inputs}[$i];
180 0           push @js_code,
181             " ok(!$fn\_bool("._encode_json($data).")".
182             ", 'invalid input [$i]');\n";
183             }
184             } elsif (exists $test->{valid}) {
185 0 0         if ($test->{valid}) {
186             # XXX test output
187             push @js_code,
188 0           " ok($fn\_bool("._encode_json($test->{input}).")".
189             ", 'valid (rt=bool)');\n";
190             } else {
191             push @js_code,
192 0           " ok(!$fn\_bool("._encode_json($test->{input}).")".
193             ", 'invalid (rt=bool)');\n";
194             }
195             }
196              
197             # str
198 0 0         if (exists $test->{valid}) {
199 0 0         if ($test->{valid}) {
200             push @js_code,
201 0           " ok($fn\_str("._encode_json($test->{input}).")".
202             "=='', 'valid (rt=str)');\n";
203             } else {
204             push @js_code,
205 0           " ok($fn\_str("._encode_json($test->{input}).")".
206             ".match(/\\S/), 'invalid (rt=str)');\n";
207             }
208             }
209              
210             # full
211 0 0 0       if (exists($test->{errors}) || exists($test->{warnings}) ||
      0        
212             exists($test->{valid})) {
213 0 0 0       my $errors = $test->{errors} // ($test->{valid} ? 0 : 1);
214 0   0       my $warnings = $test->{warnings} // 0;
215             push @js_code, (
216 0           " res = $fn\_full("._encode_json($test->{input}).");\n",
217             " ok(typeof(res)=='object', ".
218             "'validator (rt=full) returns object');\n",
219             " ok(Object.keys(res['errors'] ? res['errors'] : {}).length==$errors, 'errors (rt=full)');\n",
220             " ok(Object.keys(res['warnings'] ? res['warnings'] : {}).length==$warnings, ".
221             "'warnings (rt=full)');\n",
222             );
223             }
224              
225 0           push @js_code, "});\n\n";
226             } # for test
227              
228 0           push @js_code, <<'_';
229             done_testing();
230             process.exit(code = tap_num_nok == 0 ? 0:1);
231             _
232              
233 0           state $tempdir = tempdir();
234 0           my ($jsh, $jsfn) = tempfile('jsXXXXXXXX', DIR=>$tempdir);
235 0           note "js filename $jsfn";
236 0           print $jsh @js_code;
237              
238             # finally we execute the js file, which should produce TAP
239 0           my ($status, $errno);
240             my ($merged, @result) = tee_merged {
241 0           system($node_path, $jsfn);
242 0           ($status, $errno) = ($?, $!);
243 0           };
244             # when node fails, we want to know the actual output
245 0 0         ok(!$status, "js file executed successfully")
246             or diag "output=<<$merged>>, exit status (\$?)=$status, ".
247             "errno (\$!)=$errno, result=", explain(@result);
248             }, # tests_func
249              
250             skip_if => sub {
251 0     0     my $t = shift;
252 0 0         return 0 unless $t->{tags};
253              
254 0           for (qw/
255              
256             check
257             check_each_elem
258             check_each_index
259             check_each_key
260             check_each_value
261             check_prop
262             exists
263             if
264             postfilters
265             prefilters
266             prop
267             uniq
268              
269             /) {
270             return "clause $_ not yet implemented"
271 0 0         if all_match(["clause:$_"], $t->{tags});
272              
273             }
274              
275 0           for (qw/isa/) {
276             return "obj clause $_ not yet implemented"
277 0 0         if all_match(["type:obj", "clause:$_"], $t->{tags});
278             }
279              
280             return "properties are not yet implemented"
281 0 0         if grep {/^prop:/} @{ $t->{tags} };
  0            
  0            
282              
283 0           0;
284             },
285 0           );
286             }
287              
288             1;
289             # ABSTRACT: Routines for testing Data::Sah (js compiler)
290              
291             __END__
292              
293             =pod
294              
295             =encoding UTF-8
296              
297             =head1 NAME
298              
299             Test::Data::Sah::JS - Routines for testing Data::Sah (js compiler)
300              
301             =head1 VERSION
302              
303             This document describes version 0.87 of Test::Data::Sah::JS (from Perl distribution Data-Sah-JS), released on 2016-09-14.
304              
305             =head1 FUNCTIONS
306              
307             =head2 run_spectest_for_js()
308              
309             =head1 HOMEPAGE
310              
311             Please visit the project's homepage at L<https://metacpan.org/release/Data-Sah-JS>.
312              
313             =head1 SOURCE
314              
315             Source repository is at L<https://github.com/perlancar/perl-Data-Sah-JS>.
316              
317             =head1 BUGS
318              
319             Please report any bugs or feature requests on the bugtracker website L<https://rt.cpan.org/Public/Dist/Display.html?Name=Data-Sah-JS>
320              
321             When submitting a bug or request, please include a test-file or a
322             patch to an existing test-file that illustrates the bug or desired
323             feature.
324              
325             =head1 AUTHOR
326              
327             perlancar <perlancar@cpan.org>
328              
329             =head1 COPYRIGHT AND LICENSE
330              
331             This software is copyright (c) 2016 by perlancar@cpan.org.
332              
333             This is free software; you can redistribute it and/or modify it under
334             the same terms as the Perl 5 programming language system itself.
335              
336             =cut