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