This is a live mirror of the Perl 5 development currently hosted at https://github.com/perl/perl5
perlhacktips: add some notes on TRUE and FALSE
[perl5.git] / ext / XS-APItest / t / handy.t
1 #!perl -w
2
3 BEGIN {
4     require 'loc_tools.pl';   # Contains locales_enabled() and
5                               # find_utf8_ctype_locale()
6 }
7
8 use strict;
9 use Test::More;
10 use Config;
11
12 use XS::APItest;
13
14 my $tab = " " x 4;  # Indent subsidiary tests this much
15
16 use Unicode::UCD qw(search_invlist prop_invmap prop_invlist);
17 my ($charname_list, $charname_map, $format, $default) = prop_invmap("Name Alias");
18
19 sub get_charname($) {
20     my $cp = shift;
21
22     # If there is a an abbreviation for the code point name, use it
23     my $name_index = search_invlist(\@{$charname_list}, $cp);
24     if (defined $name_index) {
25         my $synonyms = $charname_map->[$name_index];
26         if (ref $synonyms) {
27             my $pat = qr/: abbreviation/;
28             my @abbreviations = grep { $_ =~ $pat } @$synonyms;
29             if (@abbreviations) {
30                 return $abbreviations[0] =~ s/$pat//r;
31             }
32         }
33     }
34
35     # Otherwise, use the full name
36     use charnames ();
37     return charnames::viacode($cp) // "No name";
38 }
39
40 sub truth($) {  # Converts values so is() works
41     return (shift) ? 1 : 0;
42 }
43
44 my $base_locale;
45 my $utf8_locale;
46 if(locales_enabled('LC_ALL')) {
47     require POSIX;
48     $base_locale = POSIX::setlocale( &POSIX::LC_ALL, "C");
49     if (defined $base_locale && $base_locale eq 'C') {
50         use locale; # make \w work right in non-ASCII lands
51
52         # Some locale implementations don't have the 128-255 characters all
53         # mean nothing.  Skip the locale tests in that situation
54         for my $i (128 .. 255) {
55             if (chr(utf8::unicode_to_native($i)) =~ /[[:print:]]/) {
56                 undef $base_locale;
57                 last;
58             }
59         }
60
61         $utf8_locale = find_utf8_ctype_locale() if $base_locale;
62     }
63 }
64
65 sub get_display_locale_or_skip($$) {
66
67     # Helper function intimately tied to its callers.  It knows the loop
68     # iterates with a locale of "", meaning don't use locale; $base_locale
69     # meaning to use a non-UTF-8 locale; and $utf8_locale.
70     #
71     # It checks to see if the current test should be skipped or executed,
72     # returning an empty list for the former, and for the latter:
73     #   ( 'locale display name',
74     #     bool of is this a UTF-8 locale )
75     #
76     # The display name is the empty string if not using locale.  Functions
77     # with _LC in their name are skipped unless in locale, and functions
78     # without _LC are executed only outside locale.  However, if no locales at
79     # all are on the system, the _LC functions are executed outside locale.
80
81     my ($locale, $suffix) = @_;
82
83     # The test should be skipped if the input is for a non-existent locale
84     return unless defined $locale;
85
86     # Here the input is defined, either a locale name or "".  If the test is
87     # for not using locales, we want to do the test for non-LC functions,
88     # and skip it for LC ones (except if there are no locales on the system,
89     # we do it for LC ones as if they weren't LC).
90     if ($locale eq "") {
91         return ("", 0) if $suffix !~ /LC/ || ! defined $base_locale;
92         return;
93     }
94
95     # Here the input is for a real locale.  We don't test the non-LC functions
96     # for locales.
97     return if $suffix !~ /LC/;
98
99     # Here is for a LC function and a real locale.  The base locale is not
100     # UTF-8.
101     return (" ($locale locale)", 0) if $locale eq $base_locale;
102
103     # The only other possibility is that we have a UTF-8 locale
104     return (" ($locale)", 1);
105 }
106
107 sub try_malforming($$$)
108 {
109     # Determines if the tests for malformed UTF-8 should be done.  When done,
110     # the .xs code creates malformations by pretending the length is shorter
111     # than it actually is.  Some things can't be malformed, and sometimes this
112     # test knows that the current code doesn't look for a malformation under
113     # various circumstances.
114
115     my ($i, $function, $using_locale) = @_;
116
117     # Single bytes can't be malformed
118     return 0 if $i < ((ord "A" == 65) ? 128 : 160);
119
120     # ASCII doesn't need to ever look beyond the first byte.
121     return 0 if $function eq "ASCII";
122
123     # No controls above 255, so the code doesn't look at those
124     return 0 if $i > 255 && $function eq "CNTRL";
125
126     # No non-ASCII digits below 256, except if using locales.
127     return 0 if $i < 256 && ! $using_locale && $function =~ /X?DIGIT/;
128
129     return 1;
130 }
131
132 my %properties = (
133                    # name => Lookup-property name
134                    alnum => 'Word',
135                    wordchar => 'Word',
136                    alphanumeric => 'Alnum',
137                    alpha => 'XPosixAlpha',
138                    ascii => 'ASCII',
139                    blank => 'Blank',
140                    cntrl => 'Control',
141                    digit => 'Digit',
142                    graph => 'Graph',
143                    idfirst => '_Perl_IDStart',
144                    idcont => '_Perl_IDCont',
145                    lower => 'XPosixLower',
146                    print => 'Print',
147                    psxspc => 'XPosixSpace',
148                    punct => 'XPosixPunct',
149                    quotemeta => '_Perl_Quotemeta',
150                    space => 'XPerlSpace',
151                    vertws => 'VertSpace',
152                    upper => 'XPosixUpper',
153                    xdigit => 'XDigit',
154                 );
155
156 my %seen;
157 my @warnings;
158 local $SIG{__WARN__} = sub { push @warnings, @_ };
159
160 my %utf8_param_code = (
161                         "_safe"                 =>  0,
162                         "_safe, malformed"      =>  1,
163                         "deprecated unsafe"     => -1,
164                         "deprecated mathoms"    => -2,
165                       );
166
167 foreach my $name (sort keys %properties, 'octal') {
168     my @invlist;
169     if ($name eq 'octal') {
170         # Hand-roll an inversion list with 0-7 in it and nothing else.
171         push @invlist, ord "0", ord "8";
172     }
173     else {
174         my $property = $properties{$name};
175         @invlist = prop_invlist($property, '_perl_core_internal_ok');
176         if (! @invlist) {
177
178             # An empty return could mean an unknown property, or merely that
179             # it is empty.  Call in scalar context to differentiate
180             if (! prop_invlist($property, '_perl_core_internal_ok')) {
181                 fail("No inversion list found for $property");
182                 next;
183             }
184         }
185     }
186
187     # Include all the Latin1 code points, plus 0x100.
188     my @code_points = (0 .. 256);
189
190     # Then include the next few boundaries above those from this property
191     my $above_latins = 0;
192     foreach my $range_start (@invlist) {
193         next if $range_start < 257;
194         push @code_points, $range_start - 1, $range_start;
195         $above_latins++;
196         last if $above_latins > 5;
197     }
198
199     # This makes sure we are using the Perl definition of idfirst and idcont,
200     # and not the Unicode.  There are a few differences.
201     push @code_points, ord "\N{ESTIMATED SYMBOL}" if $name =~ /^id(first|cont)/;
202     if ($name eq "idcont") {    # And some that are continuation but not start
203         push @code_points, ord("\N{GREEK ANO TELEIA}"),
204                            ord("\N{COMBINING GRAVE ACCENT}");
205     }
206
207     # And finally one non-Unicode code point.
208     push @code_points, 0x110000;    # Above Unicode, no prop should match
209     no warnings 'non_unicode';
210
211     for my $j (@code_points) {
212         my $i = utf8::native_to_unicode($j);
213         my $function = uc($name);
214
215         is (@warnings, 0, "Got no unexpected warnings in previous iteration")
216            or diag("@warnings");
217         undef @warnings;
218
219         my $matches = search_invlist(\@invlist, $i);
220         if (! defined $matches) {
221             $matches = 0;
222         }
223         else {
224             $matches = truth(! ($matches % 2));
225         }
226
227         my $ret;
228         my $char_name = get_charname($j);
229         my $display_name = sprintf "\\x{%02X, %s}", $i, $char_name;
230         my $display_call = "is${function}( $display_name )";
231
232         foreach my $suffix ("", "_A", "_L1", "_LC", "_uni", "_uvchr",
233                             "_LC_uvchr", "_utf8", "_LC_utf8")
234         {
235
236             # Not all possible macros have been defined
237             if ($name eq 'vertws') {
238
239                 # vertws is always all of Unicode
240                 next if $suffix !~ / ^ _ ( uni | uvchr | utf8 ) $ /x;
241             }
242             elsif ($name eq 'alnum') {
243
244                 # ALNUM_A, ALNUM_L1, and ALNUM_uvchr are not defined as these
245                 # suffixes were added later, after WORDCHAR was created to be
246                 # a clearer synonym for ALNUM
247                 next if    $suffix eq '_A'
248                         || $suffix eq '_L1'
249                         || $suffix eq '_uvchr';
250             }
251             elsif ($name eq 'octal') {
252                 next if $suffix ne ""  && $suffix ne '_A' && $suffix ne '_L1';
253             }
254             elsif ($name eq 'quotemeta') {
255                 # There is only one macro for this, and is defined only for
256                 # Latin1 range
257                 next if $suffix ne ""
258             }
259
260             foreach my $locale ("", $base_locale, $utf8_locale) {
261
262                 my ($display_locale, $locale_is_utf8)
263                                 = get_display_locale_or_skip($locale, $suffix);
264                 next unless defined $display_locale;
265
266                 use if $locale, "locale";
267                 POSIX::setlocale( &POSIX::LC_ALL, $locale) if $locale;
268
269                 if ($suffix !~ /utf8/) {    # _utf8 has to handled specially
270                     my $display_call
271                        = "is${function}$suffix( $display_name )$display_locale";
272                     $ret = truth eval "test_is${function}$suffix($i)";
273                     if (is ($@, "", "$display_call didn't give error")) {
274                         my $truth = $matches;
275                         if ($truth) {
276
277                             # The single byte functions are false for
278                             # above-Latin1
279                             if ($i >= 256) {
280                                 $truth = 0
281                                         if $suffix=~ / ^ ( _A | _L [1C] )? $ /x;
282                             }
283                             elsif (   utf8::native_to_unicode($i) >= 128
284                                    && $name ne 'quotemeta')
285                             {
286
287                                 # The no-suffix and _A functions are false
288                                 # for non-ASCII.  So are  _LC  functions on a
289                                 # non-UTF-8 locale
290                                 $truth = 0 if    $suffix eq "_A"
291                                               || $suffix eq ""
292                                               || (     $suffix =~ /LC/
293                                                   && ! $locale_is_utf8);
294                             }
295                         }
296
297                         is ($ret, $truth, "${tab}And correctly returns $truth");
298                     }
299                 }
300                 else {  # _utf8 suffix
301                     my $char = chr($i);
302                     utf8::upgrade($char);
303                     $char = quotemeta $char if $char eq '\\' || $char eq "'";
304                     my $truth;
305                     if (   $suffix =~ /LC/
306                         && ! $locale_is_utf8
307                         && $i < 256
308                         && utf8::native_to_unicode($i) >= 128)
309                     {   # The C-locale _LC function returns FALSE for Latin1
310                         # above ASCII
311                         $truth = 0;
312                     }
313                     else {
314                         $truth = $matches;
315                     }
316
317                     foreach my $utf8_param("_safe",
318                                            "_safe, malformed",
319                                            "deprecated unsafe"
320                                           )
321                     {
322                         my $utf8_param_code = $utf8_param_code{$utf8_param};
323                         my $expect_error = $utf8_param_code > 0;
324                         next if      $expect_error
325                                 && ! try_malforming($i, $function,
326                                                     $suffix =~ /LC/);
327
328                         my $display_call = "is${function}$suffix( $display_name"
329                                          . ", $utf8_param )$display_locale";
330                         $ret = truth eval "test_is${function}$suffix('$char',"
331                                         . " $utf8_param_code)";
332                         if ($expect_error) {
333                             isnt ($@, "",
334                                     "expected and got error in $display_call");
335                             like($@, qr/Malformed UTF-8 character/,
336                                 "${tab}And got expected message");
337                             if (is (@warnings, 1,
338                                            "${tab}Got a single warning besides"))
339                             {
340                                 like($warnings[0],
341                                      qr/Malformed UTF-8 character.*short/,
342                                      "${tab}Got expected warning");
343                             }
344                             else {
345                                 diag("@warnings");
346                             }
347                             undef @warnings;
348                         }
349                         elsif (is ($@, "", "$display_call didn't give error")) {
350                             is ($ret, $truth,
351                                 "${tab}And correctly returned $truth");
352                             if ($utf8_param_code < 0) {
353                                 my $warnings_ok;
354                                 my $unique_function = "is" . $function . $suffix;
355                                 if (! $seen{$unique_function}++) {
356                                     $warnings_ok = is(@warnings, 1,
357                                         "${tab}This is first call to"
358                                       . " $unique_function; Got a single"
359                                       . " warning");
360                                     if ($warnings_ok) {
361                                         $warnings_ok = like($warnings[0],
362                 qr/starting in Perl .* will require an additional parameter/,
363                                             "${tab}The warning was the expected"
364                                           . " deprecation one");
365                                     }
366                                 }
367                                 else {
368                                     $warnings_ok = is(@warnings, 0,
369                                         "${tab}This subsequent call to"
370                                       . " $unique_function did not warn");
371                                 }
372                                 $warnings_ok or diag("@warnings");
373                                 undef @warnings;
374                             }
375                         }
376                     }
377                 }
378             }
379         }
380     }
381 }
382
383 my %to_properties = (
384                 FOLD  => 'Case_Folding',
385                 LOWER => 'Lowercase_Mapping',
386                 TITLE => 'Titlecase_Mapping',
387                 UPPER => 'Uppercase_Mapping',
388             );
389
390
391 foreach my $name (sort keys %to_properties) {
392     my $property = $to_properties{$name};
393     my ($list_ref, $map_ref, $format, $missing)
394                                       = prop_invmap($property, );
395     if (! $list_ref || ! $map_ref) {
396         fail("No inversion map found for $property");
397         next;
398     }
399     if ($format !~ / ^ a l? $ /x) {
400         fail("Unexpected inversion map format ('$format') found for $property");
401         next;
402     }
403
404     # Include all the Latin1 code points, plus 0x100.
405     my @code_points = (0 .. 256);
406
407     # Then include the next few multi-char folds above those from this
408     # property, and include the next few single folds as well
409     my $above_latins = 0;
410     my $multi_char = 0;
411     for my $i (0 .. @{$list_ref} - 1) {
412         my $range_start = $list_ref->[$i];
413         next if $range_start < 257;
414         if (ref $map_ref->[$i] && $multi_char < 5)  {
415             push @code_points, $range_start - 1
416                                         if $code_points[-1] != $range_start - 1;
417             push @code_points, $range_start;
418             $multi_char++;
419         }
420         elsif ($above_latins < 5) {
421             push @code_points, $range_start - 1
422                                         if $code_points[-1] != $range_start - 1;
423             push @code_points, $range_start;
424             $above_latins++;
425         }
426         last if $above_latins >= 5 && $multi_char >= 5;
427     }
428
429     # And finally one non-Unicode code point.
430     push @code_points, 0x110000;    # Above Unicode, no prop should match
431     no warnings 'non_unicode';
432
433     # $j is native; $i unicode.
434     for my $j (@code_points) {
435         my $i = utf8::native_to_unicode($j);
436         my $function = $name;
437
438         my $index = search_invlist(\@{$list_ref}, $j);
439
440         my $ret;
441         my $char_name = get_charname($j);
442         my $display_name = sprintf "\\N{U+%02X, %s}", $i, $char_name;
443
444         foreach my $suffix ("", "_L1", "_LC") {
445
446             # This is the only macro defined for L1
447             next if $suffix eq "_L1" && $function ne "LOWER";
448
449           SKIP:
450             foreach my $locale ("", $base_locale, $utf8_locale) {
451
452                 # titlecase is not defined in locales.
453                 next if $name eq 'TITLE' && $suffix eq "_LC";
454
455                 my ($display_locale, $locale_is_utf8)
456                                 = get_display_locale_or_skip($locale, $suffix);
457                 next unless defined $display_locale;
458
459                 skip("to${name}_LC does not work for LATIN SMALL LETTER SHARP S"
460                   . "$display_locale", 1)
461                             if  $i == 0xDF && $name =~ / FOLD | UPPER /x
462                              && $suffix eq "_LC" && $locale_is_utf8;
463
464                 use if $locale, "locale";
465                 POSIX::setlocale( &POSIX::LC_ALL, $locale) if $locale;
466
467                 my $display_call = "to${function}$suffix("
468                                  . " $display_name )$display_locale";
469                 $ret = eval "test_to${function}$suffix($j)";
470                 if (is ($@, "", "$display_call didn't give error")) {
471                     my $should_be;
472                     if ($i > 255) {
473                         $should_be = $j;
474                     }
475                     elsif (    $i > 127
476                             && (   $suffix eq ""
477                                 || ($suffix eq "_LC" && ! $locale_is_utf8)))
478                     {
479                         $should_be = $j;
480                     }
481                     elsif ($map_ref->[$index] != $missing) {
482                         $should_be = $map_ref->[$index] + $j - $list_ref->[$index]
483                     }
484                     else {
485                         $should_be = $j;
486                     }
487
488                     is ($ret, $should_be,
489                         sprintf("${tab}And correctly returned 0x%02X",
490                                                               $should_be));
491                 }
492             }
493         }
494
495         # The _uni, uvchr, and _utf8 functions return both the ordinal of the
496         # first code point of the result, and the result in utf8.  The .xs
497         # tests return these in an array, in [0] and [1] respectively, with
498         # [2] the length of the utf8 in bytes.
499         my $utf8_should_be = "";
500         my $first_ord_should_be;
501         if (ref $map_ref->[$index]) {   # A multi-char result
502             for my $j (0 .. @{$map_ref->[$index]} - 1) {
503                 $utf8_should_be .= chr $map_ref->[$index][$j];
504             }
505
506             $first_ord_should_be = $map_ref->[$index][0];
507         }
508         else {  # A single-char result
509             $first_ord_should_be = ($map_ref->[$index] != $missing)
510                                     ? $map_ref->[$index] + $j
511                                                          - $list_ref->[$index]
512                                     : $j;
513             $utf8_should_be = chr $first_ord_should_be;
514         }
515         utf8::upgrade($utf8_should_be);
516
517         # Test _uni, uvchr
518         foreach my $suffix ('_uni', '_uvchr') {
519             my $s;
520             my $len;
521             my $display_call = "to${function}$suffix( $display_name )";
522             $ret = eval "test_to${function}$suffix($j)";
523             if (is ($@, "", "$display_call didn't give error")) {
524                 is ($ret->[0], $first_ord_should_be,
525                     sprintf("${tab}And correctly returned 0x%02X",
526                                                     $first_ord_should_be));
527                 is ($ret->[1], $utf8_should_be, "${tab}Got correct utf8");
528                 use bytes;
529                 is ($ret->[2], length $utf8_should_be,
530                     "${tab}Got correct number of bytes for utf8 length");
531             }
532         }
533
534         # Test _utf8
535         my $char = chr($j);
536         utf8::upgrade($char);
537         $char = quotemeta $char if $char eq '\\' || $char eq "'";
538         foreach my $utf8_param("_safe",
539                                 "_safe, malformed",
540                                 "deprecated unsafe",
541                                 "deprecated mathoms",
542                                 )
543         {
544             use Config;
545             next if    $utf8_param eq 'deprecated mathoms'
546                     && $Config{'ccflags'} =~ /-DNO_MATHOMS/;
547
548             my $utf8_param_code = $utf8_param_code{$utf8_param};
549             my $expect_error = $utf8_param_code > 0;
550
551             # Skip if can't malform (because is a UTF-8 invariant)
552             next if $expect_error && $i < ((ord "A" == 65) ? 128 : 160);
553
554             my $display_call = "to${function}_utf8($display_name, $utf8_param )";
555             $ret = eval   "test_to${function}_utf8('$char', $utf8_param_code)";
556             if ($expect_error) {
557                 isnt ($@, "", "expected and got error in $display_call");
558                 like($@, qr/Malformed UTF-8 character/,
559                      "${tab}And got expected message");
560                 undef @warnings;
561             }
562             elsif (is ($@, "", "$display_call didn't give error")) {
563                 is ($ret->[0], $first_ord_should_be,
564                     sprintf("${tab}And correctly returned 0x%02X",
565                                                     $first_ord_should_be));
566                 is ($ret->[1], $utf8_should_be, "${tab}Got correct utf8");
567                 use bytes;
568                 is ($ret->[2], length $utf8_should_be,
569                     "${tab}Got correct number of bytes for utf8 length");
570                 if ($utf8_param_code < 0) {
571                     my $warnings_ok;
572                     if (! $seen{"${function}_utf8$utf8_param"}++) {
573                         $warnings_ok = is(@warnings, 1,
574                                                    "${tab}Got a single warning");
575                         if ($warnings_ok) {
576                             my $expected;
577                             if ($utf8_param_code == -2) {
578                                 my $lc_func = lc $function;
579                                 $expected
580                 = qr/starting in Perl .* to_utf8_$lc_func\(\) will be removed/;
581                             }
582                             else {
583                                 $expected
584                 = qr/starting in Perl .* will require an additional parameter/;
585                             }
586                             $warnings_ok = like($warnings[0], $expected,
587                                       "${tab}Got expected deprecation warning");
588                         }
589                     }
590                     else {
591                         $warnings_ok = is(@warnings, 0,
592                                   "${tab}Deprecation warned only the one time");
593                     }
594                     $warnings_ok or diag("@warnings");
595                     undef @warnings;
596                 }
597             }
598         }
599     }
600 }
601
602 # This is primarily to make sure that no non-Unicode warnings get generated
603 is(scalar @warnings, 0, "No unexpected warnings were generated in the tests")
604   or diag @warnings;
605
606 done_testing;