File Coverage

blib/lib/Test2/Tools/TypeTiny.pm
Criterion Covered Total %
statement 263 278 94.6
branch 37 54 68.5
condition 15 21 71.4
subroutine 44 45 97.7
pod 11 11 100.0
total 370 409 90.4


line stmt bran cond sub pod time code
1             package Test2::Tools::TypeTiny;
2              
3             # ABSTRACT: Test2 tools for checking Type::Tiny types
4 3     3   209556 use version;
  3         3813  
  3         24  
5             our $VERSION = 'v0.93.1'; # VERSION
6              
7 3     3   360 use v5.18;
  3         22  
8 3     3   22 use strict;
  3         19  
  3         92  
9 3     3   15 use warnings;
  3         24  
  3         230  
10              
11 3     3   969 use parent 'Exporter';
  3         609  
  3         17  
12              
13 3     3   242 use List::Util v1.29 qw< uniq shuffle pairmap pairs >;
  3         61  
  3         468  
14 3     3   18 use Scalar::Util qw< blessed refaddr >;
  3         6  
  3         231  
15              
16 3     3   1859 use Test2::API qw< context run_subtest >;
  3         272113  
  3         445  
17 3     3   1847 use Test2::Tools::Basic;
  3         4101  
  3         341  
18 3     3   2287 use Test2::Tools::Compare qw< is like >;
  3         243180  
  3         452  
19 3     3   1777 use Test2::Tools::Exception qw< lives dies >;
  3         2461  
  3         217  
20 3     3   24 use Test2::Compare qw< compare strict_convert >;
  3         7  
  3         144  
21              
22 3     3   1975 use Data::Dumper;
  3         24699  
  3         306  
23              
24 3     3   1798 use namespace::clean;
  3         52224  
  3         35  
25              
26             our $DEBUG_INDENT = 4;
27              
28             #pod =encoding utf8
29             #pod
30             #pod =head1 SYNOPSIS
31             #pod
32             #pod use Test2::V0;
33             #pod use Test2::Tools::TypeTiny;
34             #pod
35             #pod use MyTypes qw< FullyQualifiedDomainName >;
36             #pod
37             #pod type_subtest FullyQualifiedDomainName, sub {
38             #pod my $type = shift;
39             #pod
40             #pod should_pass_initially(
41             #pod $type,
42             #pod qw<
43             #pod www.example.com
44             #pod example.com
45             #pod www123.prod.some.domain.example.com
46             #pod llanfairpwllgwyngllgogerychwyrndrobwllllantysiliogogogoch.co.uk
47             #pod >,
48             #pod );
49             #pod should_fail(
50             #pod $type,
51             #pod qw< www ftp001 .com domains.t x.c prod|ask|me -prod3.example.com >,
52             #pod );
53             #pod should_coerce_into(
54             #pod $type,
55             #pod qw<
56             #pod ftp001-prod3 ftp001-prod3.ourdomain.com
57             #pod prod-ask-me prod-ask-me.ourdomain.com
58             #pod nonprod3-foobar-me nonprod3-foobar-me.ourdomain.com
59             #pod >,
60             #pod );
61             #pod should_sort_into(
62             #pod $type,
63             #pod [qw< ftp001-prod3 ftp001-prod3.ourdomain.com prod-ask-me.ourdomain.com >],
64             #pod );
65             #pod
66             #pod parameters_should_create_type(
67             #pod $type,
68             #pod [], [3], [0, 0], [1, 2],
69             #pod );
70             #pod parameters_should_die_as(
71             #pod $type,
72             #pod [], qr,
73             #pod [-3], qr,
74             #pod [0.2], qr,
75             #pod );
76             #pod
77             #pod message_should_report_as(
78             #pod $type,
79             #pod undef, qr
80             #pod );
81             #pod explanation_should_report_as(
82             #pod $type,
83             #pod undef, [
84             #pod qr,
85             #pod ],
86             #pod );
87             #pod };
88             #pod
89             #pod done_testing;
90             #pod
91             #pod =head1 DESCRIPTION
92             #pod
93             #pod This module provides a set of tools for checking L types. This is similar to
94             #pod L, but works against the L and has more functionality for testing
95             #pod and troubleshooting coercions, error messages, and other aspects of the type.
96             #pod
97             #pod =head1 FUNCTIONS
98             #pod
99             #pod All functions are exported by default. These functions create L
100             #pod to contain different classes of tests.
101             #pod
102             #pod Besides the wrapper itself, these functions are most useful wrapped inside of a L
103             #pod coderef.
104             #pod
105             #pod =cut
106              
107             our @EXPORT_OK = (qw<
108             type_subtest
109             should_pass_initially should_fail_initially should_pass should_fail should_coerce_into
110             parameters_should_create_type parameters_should_die_as
111             message_should_report_as explanation_should_report_as
112             should_sort_into
113             >);
114             our @EXPORT = @EXPORT_OK;
115              
116             #pod =head2 Wrappers
117             #pod
118             #pod =head3 type_subtest
119             #pod
120             #pod type_subtest Type, sub {
121             #pod my $type = shift;
122             #pod
123             #pod ...
124             #pod };
125             #pod
126             #pod Creates a subtest with the given type as the test name, and passed as the only parameter. Using a
127             #pod generic C<$type> variable makes it much easier to copy and paste test code from other type tests
128             #pod without accidentally forgetting to change your custom type within the code.
129             #pod
130             #pod If the type can be inlined, this will also run two separate subtests (within the main type subtest)
131             #pod to check both the inlined constraint and the slower coderef constraint. The second subtest will
132             #pod have a inline-less type, cloned from the original type. This is done by stripping out the inlined
133             #pod constraint (or generator) in the clone.
134             #pod
135             #pod The tester sub will be used in both subtests. If you need the inlined constraint for certain
136             #pod tests, you can use the C<< $type->can_be_inlined >> method to check which version of the test its
137             #pod running. However, inlined checks should do the exact same thing as coderef checks, so keep these
138             #pod kind of exceptions to a minimum.
139             #pod
140             #pod Note that it doesn't do anything to the parent types. If your type check is solely relying on
141             #pod parent checks, this will only run the one subtest. If the parent checks are part of your package,
142             #pod you should check those separately.
143             #pod
144             #pod =cut
145              
146             sub type_subtest ($&) {
147 8     8 1 706050 my ($type, $tester_coderef) = @_;
148              
149 8         47 my $ctx = context();
150 8         6242 my $pass;
151              
152             # XXX: Private method abuse
153 8 100 66     50 if (!$type->_is_null_constraint && $type->has_inlined) {
154 4         84 $pass = run_subtest(
155             "Type Test: ".$type->display_name,
156             \&_multi_type_split_subtest,
157             { buffered => 1, inherit_trace => 1 },
158             $type, $tester_coderef,
159             );
160             }
161             else {
162 4         95 $pass = run_subtest(
163             "Type Test: ".$type->display_name,
164             $tester_coderef,
165             { buffered => 1 },
166             $type,
167             );
168             }
169              
170 8         44779 $ctx->release;
171              
172 8         353 return $pass;
173             }
174              
175             sub _multi_type_split_subtest {
176 4     4   1503 my ($type, $tester_coderef) = @_;
177 4         22 my $ctx = context();
178              
179 4         274 plan 2;
180              
181 4         1359 my $orig_result = run_subtest(
182             'original type',
183             $tester_coderef,
184             { buffered => 1 },
185             $type,
186             );
187              
188             ### XXX: There is some internal mechanics abuse to try to get this type, because Type::Tiny
189             ### doesn't really have a $type->create_inlineless_type method, and methods like _clone and
190             ### create_child_type don't cleanly do what we want. (We don't want a child type that
191             ### would be impacted by parental inlined constraints.)
192              
193             # Create the inline-less type
194 4         17890 my %inlineless_opts = %$type;
195 4         42 delete $inlineless_opts{$_} for qw<
196             compiled_type_constraint uniq tmp
197             inlined inline_generator
198             _overload_coderef _overload_coderef_no_rebuild
199             >;
200 4         12 $inlineless_opts{display_name} .= ' (inline-less)';
201              
202 4         58 my $inlineless_type = blessed($type)->new(%inlineless_opts);
203              
204 4         780 my $inlineless_result = run_subtest(
205             'inline-less type',
206             $tester_coderef,
207             { buffered => 1 },
208             $inlineless_type,
209             );
210              
211 4         23390 $ctx->release;
212 4   66     73 return $orig_result && $inlineless_result;
213             }
214              
215             #pod =head2 Value Testers
216             #pod
217             #pod Most of these checks will run through C and C calls to confirm the
218             #pod coderefs don't die. If you need to validate the error messages themselves, consider using some of
219             #pod the L.
220             #pod
221             #pod =head3 should_pass_initially
222             #pod
223             #pod should_pass_initially($type, @values);
224             #pod
225             #pod Creates a subtest that confirms the type will pass with all of the given C<@values>, without any
226             #pod need for coercions.
227             #pod
228             #pod =cut
229              
230             sub should_pass_initially {
231 6     6 1 1558 my $ctx = context();
232 6         608 my $pass = run_subtest(
233             'should pass (without coercions)',
234             \&_should_pass_initially_subtest,
235             { buffered => 1, inherit_trace => 1 },
236             @_,
237             );
238 6         7287 $ctx->release;
239              
240 6         211 return $pass;
241             }
242              
243             sub _should_pass_initially_subtest {
244 6     6   2102 my ($type, @values) = @_;
245              
246 6         37 plan scalar @values;
247              
248 6         2032 foreach my $value (@values) {
249 18         3788 my $val_dd = _dd($value);
250 18         52 my @val_explain = _constraint_type_check_debug_map($type, $value);
251 18         76 _check_error_message_methods($type, $value);
252              
253 18         687 ok $type->check($value), "$val_dd should pass", @val_explain;
254             }
255             }
256              
257             #pod =head3 should_fail_initially
258             #pod
259             #pod should_fail_initially($type, @values);
260             #pod
261             #pod Creates a subtest that confirms the type will fail with all of the given C<@values>, without using
262             #pod any coercions.
263             #pod
264             #pod This function is included for completeness. However, items in C should
265             #pod realistically end up in either a L block (if it always fails, even with coercions) or
266             #pod a L block (if it would pass after coercions).
267             #pod
268             #pod =cut
269              
270             sub should_fail_initially {
271 6     6 1 83 my $ctx = context();
272 6         688 my $pass = run_subtest(
273             'should fail (without coercions)',
274             \&_should_fail_initially_subtest,
275             { buffered => 1, inherit_trace => 1 },
276             @_,
277             );
278 6         8558 $ctx->release;
279              
280 6         192 return $pass;
281             }
282              
283             sub _should_fail_initially_subtest {
284 6     6   1978 my ($type, @values) = @_;
285              
286 6         29 plan scalar @values;
287              
288 6         1780 foreach my $value (@values) {
289 32         7363 my $val_dd = _dd($value);
290 32         86 my @val_explain = _constraint_type_check_debug_map($type, $value);
291 32         124 _check_error_message_methods($type, $value);
292              
293 32         38155 ok !$type->check($value), "$val_dd should fail", @val_explain;
294             }
295             }
296              
297             #pod =head3 should_pass
298             #pod
299             #pod should_pass($type, @values);
300             #pod
301             #pod Creates a subtest that confirms the type will pass with all of the given C<@values>, including
302             #pod values that might need coercions. If it initially passes, that's okay, too. If the type does not
303             #pod have a coercion and it fails the initial check, it will stop there and fail the test.
304             #pod
305             #pod This function is included for completeness. However, L is the better function
306             #pod for types with known coercions, as it checks the resulting coerced values as well.
307             #pod
308             #pod =cut
309              
310             sub should_pass {
311 8     8 1 133 my $ctx = context();
312 8         938 my $pass = run_subtest(
313             'should pass',
314             \&_should_pass_subtest,
315             { buffered => 1, inherit_trace => 1 },
316             @_,
317             );
318 8         16268 $ctx->release;
319              
320 8         239 return $pass;
321             }
322              
323             sub _should_pass_subtest {
324 8     8   3103 my ($type, @values) = @_;
325              
326 8         52 plan scalar @values;
327              
328 8         2726 foreach my $value (@values) {
329 40         3856 my $val_dd = _dd($value);
330 40         103 my @val_explain = _constraint_type_check_debug_map($type, $value);
331 40         145 _check_error_message_methods($type, $value);
332              
333 40 100       11938 if ($type->check($value)) {
    100          
334 18         362 pass "$val_dd should pass (initial check)", @val_explain;
335 18         4229 next;
336             }
337             elsif (!$type->has_coercion) {
338 6         355 fail "$val_dd should pass (no coercion)", @val_explain;
339 6         20853 next;
340             }
341              
342             # try to coerce then
343 16         418 my @coercion_debug = _coercion_type_check_debug_map($type, $value);
344 16         54 my $new_value = $type->coerce($value);
345 16         5154 my $new_dd = _dd($new_value);
346 16 50       45 unless (_check_coercion($value, $new_value)) {
347 0         0 fail "$val_dd should pass (failed coercion)", @val_explain, @coercion_debug;
348 0         0 next;
349             }
350 16         41 _check_error_message_methods($type, $new_value);
351              
352             # final check
353 16         196 @val_explain = _constraint_type_check_debug_map($type, $new_value);
354 16         45 ok $type->check($new_value), "$val_dd should pass (coerced into $new_dd)", @val_explain, @coercion_debug;
355             }
356             }
357              
358             #pod =head3 should_fail
359             #pod
360             #pod should_fail($type, @values);
361             #pod
362             #pod Creates a subtest that confirms the type will fail with all of the given C<@values>, even when
363             #pod those values are ran through its coercions.
364             #pod
365             #pod =cut
366              
367             sub should_fail {
368 8     8 1 108 my $ctx = context();
369 8         906 my $pass = run_subtest(
370             'should fail',
371             \&_should_fail_subtest,
372             { buffered => 1, inherit_trace => 1 },
373             @_,
374             );
375 8         15480 $ctx->release;
376              
377 8         245 return $pass;
378             }
379              
380             sub _should_fail_subtest {
381 8     8   2828 my ($type, @values) = @_;
382              
383 8         74 plan scalar @values;
384              
385 8         2550 foreach my $value (@values) {
386 36         1706 my $val_dd = _dd($value);
387 36         101 my @val_explain = _constraint_type_check_debug_map($type, $value);
388 36         99 _check_error_message_methods($type, $value);
389              
390 36 100       17680 if ($type->check($value)) {
    100          
391 6         192 fail "$val_dd should fail (initial check)", @val_explain;
392 6         19769 next;
393             }
394             elsif (!$type->has_coercion) {
395 12         714 pass "$val_dd should fail (no coercion)", @val_explain;
396 12         3367 next;
397             }
398              
399             # try to coerce then
400 18         334 my @coercion_debug = _coercion_type_check_debug_map($type, $value);
401 18         42 my $new_value = $type->coerce($value);
402 18         1385 my $new_dd = _dd($new_value);
403 18 100       37 unless (_check_coercion($value, $new_value)) {
404 8         40 pass "$val_dd should fail (failed coercion)", @val_explain, @coercion_debug;
405 8         1583 next;
406             }
407 10         27 _check_error_message_methods($type, $new_value);
408              
409             # final check
410 10         190 @val_explain = _constraint_type_check_debug_map($type, $new_value);
411 10         23 ok !$type->check($new_value), "$val_dd should fail (coerced into $new_dd)", @val_explain, @coercion_debug;
412             }
413             }
414              
415             #pod =head3 should_coerce_into
416             #pod
417             #pod should_coerce_into($type, @orig_coerced_kv_pairs);
418             #pod should_coerce_into($type,
419             #pod # orig # coerced
420             #pod undef, 0,
421             #pod [], 0,
422             #pod );
423             #pod
424             #pod Creates a subtest that confirms the type will take the "key" in C<@orig_coerced_kv_pairs> and
425             #pod coerce it into the "value" in C<@orig_coerced_kv_pairs>. (The C<@orig_coerced_kv_pairs> parameter
426             #pod is essentially an ordered hash here, with support for ref values as the "key".)
427             #pod
428             #pod The original value should not pass initial checks, as it would not be coerced in most use cases.
429             #pod These would be considered test failures.
430             #pod
431             #pod =cut
432              
433             sub should_coerce_into {
434 4     4 1 626 my $ctx = context();
435 4         411 my $pass = run_subtest(
436             'should coerce into',
437             \&_should_coerce_into_subtest,
438             { buffered => 1, inherit_trace => 1 },
439             @_,
440             );
441 4         11962 $ctx->release;
442              
443 4         127 return $pass;
444             }
445              
446             sub _should_coerce_into_subtest {
447 4     4   1304 my ($type, @kv_pairs) = @_;
448              
449 4         31 plan int( scalar(@kv_pairs) / 2 );
450              
451 4         1303 foreach my $kv (pairs @kv_pairs) {
452 25         16450 my ($value, $expected) = @$kv;
453              
454 25         75 my $val_dd = _dd($value);
455 25         68 my @val_explain = _constraint_type_check_debug_map($type, $value);
456 25         73 _check_error_message_methods($type, $value);
457              
458 25 50       1318 if ($type->check($value)) {
    50          
459 0         0 fail "$val_dd should fail (initial check)";
460 0         0 next;
461             }
462             elsif (!$type->has_coercion) {
463 0         0 fail "$val_dd should coerce (no coercion)";
464 0         0 next;
465             }
466              
467             # try to coerce then
468 25         621 my @coercion_debug = _coercion_type_check_debug_map($type, $value);
469 25         106 my $new_value = $type->coerce($value);
470 25         2890 my $new_dd = _dd($new_value);
471 25 100       64 unless (_check_coercion($value, $new_value)) {
472 3         27 fail "$val_dd should coerce", @val_explain, @coercion_debug;
473 3         15349 next;
474             }
475 22         49 _check_error_message_methods($type, $new_value);
476              
477             # make sure it matches the expected value
478 22         278 @val_explain = _constraint_type_check_debug_map($type, $new_value);
479 22         122 is $new_value, $expected, "$val_dd (coerced)", @val_explain, @coercion_debug;
480             }
481             }
482              
483             #pod =head2 Parameter Testers
484             #pod
485             #pod These tests should only be used for parameter validation. None of the resulting types are checked
486             #pod in other ways, so you should include other L with different kinds of
487             #pod parameterized types.
488             #pod
489             #pod Note that L don't require any sort of validation
490             #pod because the L is always called first, and
491             #pod should die on parameter validation failure, prior to the C call. The same applies
492             #pod for coercion generators as well.
493             #pod
494             #pod =head3 parameters_should_create_type
495             #pod
496             #pod parameters_should_create_type($type, @parameter_sets);
497             #pod parameters_should_create_type($type,
498             #pod [],
499             #pod [3],
500             #pod [0, 0],
501             #pod [1, 2],
502             #pod );
503             #pod
504             #pod Creates a subtest that confirms the type will successfully create a parameterized type with each of
505             #pod the set of parameters in C<@parameter_sets> (a list of arrayrefs).
506             #pod
507             #pod =cut
508              
509             sub parameters_should_create_type {
510 4     4 1 726 my $type = shift;
511 4 50       17 die $type->display_name." is not a parameterized type" unless $type->is_parameterizable;
512              
513 4         37 my $ctx = context();
514 4         298 my $pass = run_subtest(
515             'parameters should create type',
516             \&_parameters_should_create_type_subtest,
517             { buffered => 1, inherit_trace => 1 },
518             $type, @_,
519             );
520 4         7335 $ctx->release;
521              
522 4         120 return $pass;
523             }
524              
525             sub _parameters_should_create_type_subtest {
526 4     4   911 my ($type, @parameter_sets) = @_;
527              
528 4         19 plan scalar(@parameter_sets);
529              
530 4         987 foreach my $parameter_set (@parameter_sets) {
531 48         7270 my $val_dd = _dd($parameter_set);
532              
533             # NOTE: lives is a separate statement, so that $@ is populated after failure
534 48         73 my $new_type;
535 48     48   311 my $ok = lives { $new_type = $type->of(@$parameter_set) };
  48         857  
536 48         30169 ok($ok, $val_dd, "Reported exception: $@");
537              
538             # XXX: no idea what it takes in, so just pass in a few values
539 48 50       11373 next unless $new_type;
540 48         379 _check_error_message_methods($new_type, $_) for (1, 0, -1, undef, \"", {}, []);
541             }
542             }
543              
544             #pod =head3 parameters_should_die_as
545             #pod
546             #pod parameters_should_die_as($type, @parameter_sets_exception_regex_pairs);
547             #pod parameters_should_die_as($type,
548             #pod # params # exceptions
549             #pod [], qr,
550             #pod [-3], qr,
551             #pod [0.2], qr,
552             #pod );
553             #pod
554             #pod Creates a subtest that confirms the type will fail validation (fatally) with the given parameters
555             #pod and exceptions. The RHS should be an regular expression, but can be anything that
556             #pod L accepts.
557             #pod
558             #pod =cut
559              
560             sub parameters_should_die_as {
561 4     4 1 102 my $type = shift;
562 4 50       19 die $type->display_name." is not a parameterized type" unless $type->is_parameterizable;
563              
564 4         43 my $ctx = context();
565 4         470 my $pass = run_subtest(
566             'parameters should die as',
567             \&_parameters_should_die_as_subtest,
568             { buffered => 1, inherit_trace => 1 },
569             $type, @_,
570             );
571 4         5475 $ctx->release;
572              
573 4         118 return $pass;
574             }
575              
576             sub _parameters_should_die_as_subtest {
577 4     4   1445 my ($type, @pairs) = @_;
578              
579 4         35 plan int( scalar(@pairs) / 2 );
580              
581 4         1302 foreach my $pair (pairs @pairs) {
582 8         3241 my ($parameter_set, $expected) = @$pair;
583 8         23 my $val_dd = _dd($parameter_set);
584              
585             like(
586 8     8   120 dies { $type->of(@$parameter_set) },
587 8         56 $expected,
588             $val_dd,
589             );
590             }
591             }
592              
593             #pod =head2 Error Message Testers
594             #pod
595             #pod =head3 message_should_report_as
596             #pod
597             #pod message_should_report_as($type, @value_message_regex_pairs);
598             #pod message_should_report_as($type,
599             #pod # values # messages
600             #pod 1, qr,
601             #pod undef, qr!Must be a fully-qualified domain name, not !,
602             #pod # valid value; checking message, anyway
603             #pod 'example.com', qr,
604             #pod );
605             #pod
606             #pod Creates a subtest that confirms error message output against the value. Technically,
607             #pod L works for valid values, too, so this isn't actually trapping assertion
608             #pod failures, just checking the output of that method.
609             #pod
610             #pod The RHS should be an regular expression, but it can be anything that L
611             #pod accepts.
612             #pod
613             #pod =cut
614              
615             sub message_should_report_as {
616 4     4 1 70 my $ctx = context();
617 4         396 my $pass = run_subtest(
618             'message should report as',
619             \&_message_should_report_as_subtest,
620             { buffered => 1, inherit_trace => 1 },
621             @_,
622             );
623 4         3862 $ctx->release;
624              
625 4         110 return $pass;
626             }
627              
628             sub _message_should_report_as_subtest {
629 4     4   1269 my ($type, @pairs) = @_;
630              
631 4         26 plan int( scalar(@pairs) / 2 );
632              
633 4         1351 foreach my $pair (pairs @pairs) {
634 4         12 my ($value, $message_check) = @$pair;
635 4         13 my $val_dd = _dd($value);
636              
637 4         21 my $message_got = $type->get_message($value);
638              
639 4         122 like $message_got, $message_check, $val_dd;
640             }
641             }
642              
643             #pod =head3 explanation_should_report_as
644             #pod
645             #pod explanation_should_report_as($type, @value_explanation_check_pairs);
646             #pod explanation_should_report_as($type,
647             #pod # values # explanation check
648             #pod 'example.com', [
649             #pod qr< did not pass type constraint >,
650             #pod qr< expects domain label count \(\?LD\) to be between 3 and 5>,
651             #pod qr<\$_ appears to be a 2LD>,
652             #pod ],
653             #pod undef, [
654             #pod qr< did not pass type constraint >,
655             #pod qr<\$_ is not a legal FQDN>,
656             #pod ],
657             #pod );
658             #pod
659             #pod Creates a subtest that confirms deeper explanation message output from L
660             #pod against the value. Unlike C, C actually needs failed values to
661             #pod report back a string message. The second parameter to C is not passed, so expect
662             #pod error messages that inspect C<$_>.
663             #pod
664             #pod The RHS should be an arrayref of regular expressions, since C reports back an
665             #pod arrayref of strings. Although, it can be anything that L accepts, and
666             #pod since it's a looser check, gaps in the arrayref are allowed.
667             #pod
668             #pod =cut
669              
670             sub explanation_should_report_as {
671 4     4 1 68 my $ctx = context();
672 4         371 my $pass = run_subtest(
673             'explanation should report as',
674             \&_explanation_should_report_as_subtest,
675             { buffered => 1, inherit_trace => 1 },
676             @_,
677             );
678 4         5655 $ctx->release;
679              
680 4         112 return $pass;
681             }
682              
683             sub _explanation_should_report_as_subtest {
684 4     4   1065 my ($type, @pairs) = @_;
685              
686 4         36 plan int( scalar(@pairs) / 2 );
687              
688 4         1166 foreach my $pair (pairs @pairs) {
689 4         13 my ($value, $explanation_check) = @$pair;
690 4         14 my $val_dd = _dd($value);
691              
692 4         21 my $explanation_got = $type->validate_explain($value);
693              
694             my @explanation_explain =
695 4 50       801 defined $explanation_got ? ( "Resulting Explanation:", map { " $_" } @$explanation_got ) :
  12         32  
696             ()
697             ;
698 4         19 like $explanation_got, $explanation_check, $val_dd, @explanation_explain;
699             }
700             }
701              
702             #pod =head2 Other Testers
703             #pod
704             #pod =head3 should_sort_into
705             #pod
706             #pod should_sort_into($type, @sorted_arrayrefs);
707             #pod
708             #pod Creates a subtest that confirms the type will sort into the expected lists given. The input list
709             #pod is a shuffled version of the sorted list.
710             #pod
711             #pod Because this introduces some non-deterministic behavior to the test, it will run through 100 cycles
712             #pod of shuffling and sorting to confirm the results. A good sorter should always return a
713             #pod deterministic result for a given list, with enough fallbacks to account for every unique case.
714             #pod Any failure will immediate stop the loop and return both the shuffled input and output list in the
715             #pod failure output, so that you can temporarily test in a more deterministic manner, as you debug the
716             #pod fault.
717             #pod
718             #pod =cut
719              
720             sub should_sort_into {
721 4     4 1 72 my $ctx = context();
722 4         439 my $pass = run_subtest(
723             'should sort into',
724             \&_should_sort_into_subtest,
725             { buffered => 1, inherit_trace => 1 },
726             @_,
727             );
728 4         8027 $ctx->release;
729              
730 4         118 return $pass;
731             }
732              
733             sub _should_sort_into_subtest {
734 4     4   1465 my ($type, @sorted_lists) = @_;
735              
736 4         68 plan scalar(@sorted_lists);
737              
738 4         1279 foreach my $sorted_list (@sorted_lists) {
739 4         48 my @expected_sort = @$sorted_list;
740              
741 4         15 my $val_dd = _dd(\@expected_sort);
742              
743 4         12 my (@shuffled, @sorted);
744 4         16 foreach my $i (1..100) {
745 400         2643 @shuffled = shuffle @expected_sort;
746 400         2448 @sorted = $type->sort(@shuffled);
747              
748             # To hide all of these iterations, we'll compare with 'compare' first, and if it's a failure,
749             # we'll use 'is' to advertise the failure.
750 400         69953 my $delta = compare(\@sorted, \@expected_sort, \&strict_convert);
751 400 50       316860 last if $delta; # let 'is' fail
752             }
753              
754             # pass or fail
755 4         24 my @io_explain = (
756             "Shuffled Input: "._dd(\@shuffled),
757             "Resulting Output: "._dd(\@sorted),
758             );
759 4         37 is \@sorted, \@expected_sort, $val_dd, @io_explain;
760             }
761             }
762              
763             # Helpers
764             sub _dd {
765 596     596   41685 my $dd = Data::Dumper->new([ shift ])->Terse(1)->Indent(0)->Useqq(1)->Deparse(1)->Quotekeys(0)->Sortkeys(1)->Maxdepth(2);
766 596         30400 my $val = $dd->Dump;
767 596         12342 $val =~ s/\s+/ /gs;
768 596         3471 return $val;
769             };
770              
771             sub _constraint_type_check_debug_map {
772 199     199   442 my ($type, $value) = @_;
773              
774 199         353 my $dd = _dd($value);
775              
776 199         738 my @diag_map = ($type->display_name." constraint map:");
777 199 50       1339 if (length $dd > 30) {
778 0         0 push @diag_map, " Full value: $dd";
779 0         0 $dd = '...';
780             }
781              
782 199         295 my $current_check = $type;
783 199         674 while ($current_check) {
784 1254         11653 my $type_name = $current_check->display_name;
785 1254         5064 my $check = $current_check->check($value);
786              
787 1254 100       10590 my $check_label = $check ? 'PASSED' : 'FAILED';
788 1254         6704 push @diag_map, sprintf('%*s%s->check(%s) ==> %s', $DEBUG_INDENT, '', $type_name, $dd, $check_label);
789              
790             # Advertize failure message and deeper explanations
791 1254 100       2219 unless ($check) {
792 170         464 push @diag_map, sprintf('%*s%s: %s', $DEBUG_INDENT * 2, '', 'message', $current_check->get_message($value));
793              
794 170 50 66     4652 if ($current_check->is_parameterized && $current_check->parent->has_deep_explanation) {
795 0         0 push @diag_map, sprintf('%*s%s:', $DEBUG_INDENT * 2, '', 'parameterized deep explanation (from parent)');
796 0         0 my $deep = eval { $current_check->parent->deep_explanation->( $current_check, $value, '$_' ) };
  0         0  
797              
798             # Account for bugs in parent->deep_explanation
799             push @diag_map, (
800             $@ ? sprintf('%*s%s: %s', $DEBUG_INDENT * 3, '', 'EVAL ERROR', $@) :
801             !defined $deep ? sprintf('%*s%s', $DEBUG_INDENT * 3, '', 'NO RESULTS') :
802             ref $deep ne 'ARRAY' ? sprintf('%*s%s: %s', $DEBUG_INDENT * 3, '', 'ILLEGAL RETURN TYPE', ref $deep) :
803 0 0       0 (map { sprintf('%*s%s', $DEBUG_INDENT * 3, '', $_) } @$deep)
  0 0       0  
    0          
804             );
805             }
806             }
807              
808 1254     0   6110 local $SIG{__WARN__} = sub {};
809 1254         2798 push @diag_map, sprintf('%*s%s: %s', $DEBUG_INDENT * 2, '', 'is defined as', $current_check->_perlcode);
810              
811 1254         154326 $current_check = $current_check->parent;
812             };
813              
814 199         1795 return @diag_map;
815             }
816              
817             sub _coercion_type_check_debug_map {
818 59     59   121 my ($type, $value) = @_;
819              
820 59         152 my $dd = _dd($value);
821              
822 59         163 my @diag_map = ($type->display_name." coercion map:");
823 59 50       321 if (length $dd > 30) {
824 0         0 push @diag_map, sprintf('%*s%s: %s', $DEBUG_INDENT, '', 'Full value', $dd);
825 0         0 $dd = '...';
826             }
827              
828 59     59   199 foreach my $coercion_type ($type, (pairmap { $a } @{$type->coercion->type_coercion_map}) ) {
  59         731  
  59         118  
829 118         240 my $type_name = $coercion_type->display_name;
830 118         425 my $check = $coercion_type->check($value);
831              
832 118 100       612 my $check_label = $check ? 'PASSED' : 'FAILED';
833 118 100 66     340 $check_label .= sprintf ' (coerced into %s)', _dd($type->coerce($value)) if $check && $coercion_type != $type;
834              
835 118         483 push @diag_map, sprintf('%*s%s->check(%s) ==> %s', $DEBUG_INDENT, '', $type_name, $dd, $check_label);
836 118 100       304 last if $check;
837             }
838              
839 59         357 return @diag_map;
840             }
841              
842             sub _check_coercion {
843 59     59   122 my ($old_value, $new_value) = @_;
844 59   100     149 $old_value //= '';
845 59   100     149 $new_value //= '';
846              
847             # compare memory addresses for refs instead
848 59 100 50     245 ($old_value, $new_value) = map { refaddr($_) // '' } ($old_value, $new_value)
  8   66     23  
849             if ref $old_value || ref $new_value
850             ;
851              
852             # returns true if it was coerced
853 59         153 return $old_value ne $new_value;
854             }
855              
856             sub _check_error_message_methods {
857 535     535   133352 my ($type, $value) = @_;
858              
859             # If it dies, we just let it naturally die
860 535         1504 $type->get_message($value);
861 535         19857 $type->validate_explain($value); # will return undef on good values
862             }
863              
864             #pod =head1 TROUBLESHOOTING
865             #pod
866             #pod =head2 Test name output
867             #pod
868             #pod The test names within each C function are somewhat dynamic, depending on which stage of
869             #pod the test it failed at. Most of the time, this is self-explanatory, but double negatives may make
870             #pod the output a tad logic-twisting:
871             #pod
872             #pod not ok 1 - ...
873             #pod
874             #pod # should_*_initially
875             #pod "val" should pass # simple should_pass_initially failure
876             #pod "val" should fail # simple should_fail_initially failure
877             #pod
878             #pod # should_*
879             #pod "val" should fail (initial check) # should_fail didn't initially fail
880             #pod "val" should pass (no coercion) # should_pass initally failed, and didn't have a coercion to use
881             #pod "val" should pass (failed coercion) # should_pass failed both the check and coercion
882             #pod "val" should fail (coerced into "val2") # should_fail still successfully coerced into a good value
883             #pod "val" should pass (coerced into "val2") # should_pass coerced into a bad value
884             #pod
885             #pod # should_coerce_into has similar errors as above
886             #pod
887             #pod =head3 Type Map Diagnostics
888             #pod
889             #pod Because types can be twisty mazes of inherited parents or multiple coercion maps, any failures will
890             #pod produce a verbose set of diagnostics. These come in two flavors: constraint maps and coercion maps,
891             #pod depending on where in the process the test failed.
892             #pod
893             #pod For example, a constraint map could look like:
894             #pod
895             #pod # (some definition output truncated)
896             #pod
897             #pod MyStringType constraint map:
898             #pod MyStringType->check("value") ==> FAILED
899             #pod message: Must be a good value
900             #pod is defined as: do { package Type::Tiny; ... ) }
901             #pod StrMatch["(?^ux:...)"]->check("value") ==> FAILED
902             #pod message: StrMatch did not pass type constraint: ...
903             #pod is defined as: do { package Type::Tiny; !ref($_) and !!( $_ =~ $Types::Standard::StrMatch::expressions{"..."} ) }
904             #pod StrMatch->check("value") ==> PASSED
905             #pod is defined as: do { package Type::Tiny; defined($_) and do { ref(\$_) eq 'SCALAR' or ref(\(my $val = $_)) eq 'SCALAR' } }
906             #pod Str->check("value") ==> PASSED
907             #pod is defined as: do { package Type::Tiny; defined($_) and do { ref(\$_) eq 'SCALAR' or ref(\(my $val = $_)) eq 'SCALAR' } }
908             #pod Value->check("value") ==> PASSED
909             #pod is defined as: (defined($_) and not ref($_))
910             #pod Defined->check("value") ==> PASSED
911             #pod is defined as: (defined($_))
912             #pod Item->check("value") ==> PASSED
913             #pod is defined as: (!!1)
914             #pod Any->check("value") ==> PASSED
915             #pod is defined as: (!!1)
916             #pod
917             #pod The diagnostics checked the final value with each individual parent check (including itself).
918             #pod Based on this output, the value passed all of the lower-level C checks, because it is a string.
919             #pod But, it failed the more-specific C regular expression. This will give you an idea of
920             #pod which type to adjust, if necessary.
921             #pod
922             #pod A coercion map would look like this:
923             #pod
924             #pod MyStringType coercion map:
925             #pod MyStringType->check("value") ==> FAILED
926             #pod FQDN->check("value") ==> FAILED
927             #pod Username->check("value") ==> FAILED
928             #pod Hostname->check("value") ==> PASSED (coerced into "value2")
929             #pod
930             #pod The diagnostics looked at L's C (and the type itself), figured
931             #pod out which types were acceptable for coercion, and returned the coercion result that passed. In
932             #pod this case, none of the types passed except C, which was coerced into C.
933             #pod
934             #pod Based on this, either C converted it to the wrong value (one that did not pass
935             #pod C), or one of the higher-level checks should have passed and didn't.
936             #pod
937             #pod =cut
938              
939             1;
940              
941             __END__