8 BEGIN {use_ok('XS::APItest')};
13 sub test_delete_present;
14 sub test_delete_absent;
15 sub brute_force_exists;
17 sub test_fetch_present;
18 sub test_fetch_absent;
20 my $utf8_for_258 = chr 258;
21 utf8::encode $utf8_for_258;
23 my @testkeys = ('N', chr 198, chr 256);
24 my @keys = (@testkeys, $utf8_for_258);
27 utf8::downgrade $_, 1;
29 main_tests (\@keys, \@testkeys, '');
34 main_tests (\@keys, \@testkeys, ' [utf8 hash]');
38 tie %h, 'Tie::StdHash';
40 my $result = ($] > 5.009) ? undef : 1;
42 is (XS::APItest::Hash::store(\%h, chr 258, 1), $result);
44 ok (!exists $h{$utf8_for_258},
45 "hv_store doesn't insert a key with the raw utf8 on a tied hash");
49 my $strtab = strtab();
50 is (ref $strtab, 'HASH', "The shared string table quacks like a hash");
55 my $prefix = "Cannot modify shared string table in hv_";
56 my $what = $prefix . 'fetch';
57 like ($@, qr/^$what/,$what);
59 XS::APItest::Hash::store($strtab, 'Boom!', 1)
61 $what = $prefix . 'store';
62 like ($@, qr/^$what/, $what);
66 # DESTROY should be in there.
68 delete $strtab->{DESTROY};
70 $what = $prefix . 'delete';
71 like ($@, qr/^$what/, $what);
72 # I can't work out how to get to the code that flips the wasutf8 flag on
73 # the hash key without some ikcy XS
77 is_deeply([&XS::APItest::Hash::test_hv_free_ent], [2,2,1,1],
78 "hv_free_ent frees the value immediately");
79 is_deeply([&XS::APItest::Hash::test_hv_delayfree_ent], [2,2,2,1],
80 "hv_delayfree_ent keeps the value around until FREETMPS");
83 foreach my $in ("", "N", "a\0b") {
84 my $got = XS::APItest::Hash::test_share_unshare_pvn($in);
85 is ($got, $in, "test_share_unshare_pvn");
89 foreach ([\&XS::APItest::Hash::rot13_hash, \&rot13, "rot 13"],
90 [\&XS::APItest::Hash::bitflip_hash, \&bitflip, "bitflip"],
92 my ($setup, $mapping, $name) = @$_;
94 my %placebo = (a => 1, p => 2, i => 4, e => 8);
96 $hash{a}++; @hash{qw(p i e)} = (2, 4, 8);
98 test_U_hash(\%hash, \%placebo, [f => 9, g => 10, h => 11], $mapping,
101 foreach my $upgrade_o (0, 1) {
102 foreach my $upgrade_n (0, 1) {
103 my (%hash, %placebo);
104 XS::APItest::Hash::bitflip_hash(\%hash);
105 foreach my $new (["7", 65, 67, 80],
106 ["8", 163, 171, 215],
107 ["U", 2603, 2604, 2604],
109 foreach my $code (78, 240, 256, 1336) {
111 # This is the UTF-8 byte sequence for the key.
113 utf8::encode($key_utf8);
118 $hash{$key} = $placebo{$key} = $code;
119 $hash{$key_utf8} = $placebo{$key_utf8} = "$code as UTF-8";
121 my $name = 'bitflip ' . shift @$new;
123 foreach my $code (@$new) {
129 push @new_kv, $key, $_;
132 $name .= ' upgraded(orig) ' if $upgrade_o;
133 $name .= ' upgraded(new) ' if $upgrade_n;
134 test_U_hash(\%hash, \%placebo, \@new_kv, \&bitflip, $name);
140 sub test_precomputed_hashes {
144 my $key_copy = $_[0];
148 is (XS::APItest::Hash::common({hv => \%hash,
151 "hash_$what" => $hash_it,
152 action => XS::APItest::HV_FETCH_ISSTORE}),
153 $ord, "store $ord with $what \$hash_it = $hash_it");
154 is_deeply ([each %hash], [$_[0], $ord], "First key read is good");
155 is_deeply ([each %hash], [], "No second key good");
157 is ($hash{$_[0]}, $ord, "Direct hash read finds $ord");
159 is_deeply ([each %hash], [$key_copy, $ord],
160 "First key read is good with a copy");
161 is_deeply ([each %hash], [], "No second key good");
163 is ($hash{$key_copy}, $ord, "Direct hash read finds $ord");
167 my $as_utf8 = "\241" . chr 256;
169 my $as_bytes = "\243";
170 foreach my $key ('N', $as_bytes, $as_utf8, "\x{2623}") {
172 foreach my $hash_it (0, 1) {
173 foreach my $what (qw(pv sv)) {
174 test_precomputed_hashes($what, $hash_it, $ord, $key);
176 # Generate a shared hash key scalar
178 test_precomputed_hashes('sv', $hash_it, $ord, (keys %h)[0]);
184 use Scalar::Util 'weaken';
186 fill_hash_with_nulls(\%h);
188 for("a".."z","A".."Z") {
189 weaken($objs[@objs] = $h{$_} = []);
192 no warnings 'uninitialized';
195 'explicitly undeffing a hash with nulls frees all entries';
198 fill_hash_with_nulls($h);
200 for("a".."z","A".."Z") {
201 weaken($objs[@objs] = $$h{$_} = []);
204 is "@objs", "", 'freeing a hash with nulls frees all entries';
207 # Tests for HvENAME and UTF8
213 *{"\xff::bar"}; # autovivify %ÿ:: without UTF8
214 *{"\xff::bαr::"} = $hvref = \%foo::;
216 is HvENAME($hvref), "\xff::bαr",
217 'stash alias (utf8 inside bytes) does not create malformed UTF8';
219 *{"é::foo"}; # autovivify %é:: with UTF8
220 *{"\xe9::\xe9::"} = $hvref = \%bar::;
222 is HvENAME($hvref), "\xe9::\xe9",
223 'stash alias (bytes inside utf8) does not create malformed UTF8';
225 *{"\xfe::bar"}; *{"\xfd::bar"};
226 *{"\xfe::bαr::"} = \%goo::;
227 *{"\xfd::bαr::"} = $hvref = \%goo::;
229 like HvENAME($hvref), qr/^[\xfe\xfd]::bαr\z/,
230 'multiple stash aliases (utf8 inside bytes) do not cause bad UTF8';
232 *{"è::foo"}; *{"ë::foo"};
233 *{"\xe8::\xe9::"} = $hvref = \%bear::;
234 *{"\xeb::\xe9::"} = \%bear::;
236 like HvENAME($hvref), qr"^[\xe8\xeb]::\xe9\z",
237 'multiple stash aliases (bytes inside utf8) do not cause bad UTF8';
242 tie my %h, 'Tie::StdHash';
244 is join(' ', sort %{newHVhv \%h}), '1 10 2 3 4 5 6 7 8 9',
245 'newHVhv on tied hash';
248 # helem and hslice on entry with null value
249 # This is actually a test for a Perl operator, not an XS API test. But it
250 # requires a hash that can only be produced by XS (although recently it
251 # could be encountered when tying hint hashes).
254 fill_hash_with_nulls(\%h);
256 pass 'no crash when writing to hash elem with null value';
258 warnings; # thank you!
260 pass 'no crash when writing to hash elem with null value via slice';
263 # [perl #111000] Bug number eleventy-one thousand:
264 # hv_store should work on hint hashes
267 XS::APItest::Hash::store \%^H, "XS::APItest/hash.t", undef;
268 delete $^H{"XS::APItest/hash.t"};
271 pass("hv_store works on the hint hash");
276 ################################ The End ################################
279 my ($hash, $placebo, $new, $mapping, $message) = @_;
280 my @hitlist = keys %$placebo;
281 print "# $message\n";
283 my @keys = sort keys %$hash;
284 is ("@keys", join(' ', sort($mapping->(keys %$placebo))),
285 "uvar magic called exactly once on store");
287 is (keys %$hash, keys %$placebo);
289 my $victim = shift @hitlist;
290 is (delete $hash->{$victim}, delete $placebo->{$victim});
292 is (keys %$hash, keys %$placebo);
293 @keys = sort keys %$hash;
294 is ("@keys", join(' ', sort($mapping->(keys %$placebo))));
296 $victim = shift @hitlist;
297 is (XS::APItest::Hash::delete_ent ($hash, $victim,
298 XS::APItest::HV_DISABLE_UVAR_XKEY),
299 undef, "Deleting a known key with conversion disabled fails (ent)");
300 is (keys %$hash, keys %$placebo);
302 is (XS::APItest::Hash::delete_ent ($hash, $victim, 0),
303 delete $placebo->{$victim},
304 "Deleting a known key with conversion enabled works (ent)");
305 is (keys %$hash, keys %$placebo);
306 @keys = sort keys %$hash;
307 is ("@keys", join(' ', sort($mapping->(keys %$placebo))));
309 $victim = shift @hitlist;
310 is (XS::APItest::Hash::delete ($hash, $victim,
311 XS::APItest::HV_DISABLE_UVAR_XKEY),
312 undef, "Deleting a known key with conversion disabled fails");
313 is (keys %$hash, keys %$placebo);
315 is (XS::APItest::Hash::delete ($hash, $victim, 0),
316 delete $placebo->{$victim},
317 "Deleting a known key with conversion enabled works");
318 is (keys %$hash, keys %$placebo);
319 @keys = sort keys %$hash;
320 is ("@keys", join(' ', sort($mapping->(keys %$placebo))));
322 my ($k, $v) = splice @$new, 0, 2;
325 is (keys %$hash, keys %$placebo);
326 @keys = sort keys %$hash;
327 is ("@keys", join(' ', sort($mapping->(keys %$placebo))));
329 ($k, $v) = splice @$new, 0, 2;
330 is (XS::APItest::Hash::store_ent($hash, $k, $v), $v, "store_ent");
332 is (keys %$hash, keys %$placebo);
333 @keys = sort keys %$hash;
334 is ("@keys", join(' ', sort($mapping->(keys %$placebo))));
336 ($k, $v) = splice @$new, 0, 2;
337 is (XS::APItest::Hash::store($hash, $k, $v), $v, "store");
339 is (keys %$hash, keys %$placebo);
340 @keys = sort keys %$hash;
341 is ("@keys", join(' ', sort($mapping->(keys %$placebo))));
343 @hitlist = keys %$placebo;
344 $victim = shift @hitlist;
345 is (XS::APItest::Hash::fetch_ent($hash, $victim), $placebo->{$victim},
347 is (XS::APItest::Hash::fetch_ent($hash, $mapping->($victim)), undef,
348 "fetch_ent (missing)");
350 $victim = shift @hitlist;
351 is (XS::APItest::Hash::fetch($hash, $victim), $placebo->{$victim},
353 is (XS::APItest::Hash::fetch($hash, $mapping->($victim)), undef,
356 $victim = shift @hitlist;
357 ok (XS::APItest::Hash::exists_ent($hash, $victim), "exists_ent");
358 ok (!XS::APItest::Hash::exists_ent($hash, $mapping->($victim)),
359 "exists_ent (missing)");
361 $victim = shift @hitlist;
362 die "Need a victim" unless defined $victim;
363 ok (XS::APItest::Hash::exists($hash, $victim), "exists");
364 ok (!XS::APItest::Hash::exists($hash, $mapping->($victim)),
367 is (XS::APItest::Hash::common({hv => $hash, keysv => $victim}),
368 $placebo->{$victim}, "common (fetch)");
369 is (XS::APItest::Hash::common({hv => $hash, keypv => $victim}),
370 $placebo->{$victim}, "common (fetch pv)");
371 is (XS::APItest::Hash::common({hv => $hash, keysv => $victim,
372 action => XS::APItest::HV_DISABLE_UVAR_XKEY}),
373 undef, "common (fetch) missing");
374 is (XS::APItest::Hash::common({hv => $hash, keypv => $victim,
375 action => XS::APItest::HV_DISABLE_UVAR_XKEY}),
376 undef, "common (fetch pv) missing");
377 is (XS::APItest::Hash::common({hv => $hash, keysv => $mapping->($victim),
378 action => XS::APItest::HV_DISABLE_UVAR_XKEY}),
379 $placebo->{$victim}, "common (fetch) missing mapped");
380 is (XS::APItest::Hash::common({hv => $hash, keypv => $mapping->($victim),
381 action => XS::APItest::HV_DISABLE_UVAR_XKEY}),
382 $placebo->{$victim}, "common (fetch pv) missing mapped");
386 my ($keys, $testkeys, $description) = @_;
387 foreach my $key (@$testkeys) {
388 my $lckey = ($key eq chr 198) ? chr 230 : lc $key;
390 utf8::encode $unikey;
392 utf8::downgrade $key, 1;
393 utf8::downgrade $lckey, 1;
394 utf8::downgrade $unikey, 1;
395 main_test_inner ($key, $lckey, $unikey, $keys, $description);
398 utf8::upgrade $lckey;
399 utf8::upgrade $unikey;
400 main_test_inner ($key, $lckey, $unikey, $keys,
401 $description . ' [key utf8 on]');
404 # hv_exists was buggy for tied hashes, in that the raw utf8 key was being
405 # used - the utf8 flag was being lost.
406 perform_test (\&test_absent, (chr 258), $keys, '');
408 perform_test (\&test_fetch_absent, (chr 258), $keys, '');
409 perform_test (\&test_delete_absent, (chr 258), $keys, '');
412 sub main_test_inner {
413 my ($key, $lckey, $unikey, $keys, $description) = @_;
414 perform_test (\&test_present, $key, $keys, $description);
415 perform_test (\&test_fetch_present, $key, $keys, $description);
416 perform_test (\&test_delete_present, $key, $keys, $description);
418 perform_test (\&test_store, $key, $keys, $description, [a=>'cheat']);
419 perform_test (\&test_store, $key, $keys, $description, []);
421 perform_test (\&test_absent, $lckey, $keys, $description);
422 perform_test (\&test_fetch_absent, $lckey, $keys, $description);
423 perform_test (\&test_delete_absent, $lckey, $keys, $description);
425 return if $unikey eq $key;
427 perform_test (\&test_absent, $unikey, $keys, $description);
428 perform_test (\&test_fetch_absent, $unikey, $keys, $description);
429 perform_test (\&test_delete_absent, $unikey, $keys, $description);
433 my ($test_sub, $key, $keys, $message, @other) = @_;
434 my $printable = join ',', map {ord} split //, $key;
436 my (%hash, %tiehash);
437 tie %tiehash, 'Tie::StdHash';
439 @hash{@$keys} = @$keys;
440 @tiehash{@$keys} = @$keys;
442 &$test_sub (\%hash, $key, $printable, $message, @other);
443 &$test_sub (\%tiehash, $key, $printable, "$message tie", @other);
447 my ($hash, $key, $printable, $message) = @_;
449 ok (exists $hash->{$key}, "hv_exists_ent present$message $printable");
450 ok (XS::APItest::Hash::exists ($hash, $key),
451 "hv_exists present$message $printable");
455 my ($hash, $key, $printable, $message) = @_;
457 ok (!exists $hash->{$key}, "hv_exists_ent absent$message $printable");
458 ok (!XS::APItest::Hash::exists ($hash, $key),
459 "hv_exists absent$message $printable");
462 sub test_delete_present {
463 my ($hash, $key, $printable, $message) = @_;
466 my $class = tied %$hash;
467 if (defined $class) {
468 tie %$copy, ref $class;
471 ok (brute_force_exists ($copy, $key),
472 "hv_delete_ent present$message $printable");
473 is (delete $copy->{$key}, $key, "hv_delete_ent present$message $printable");
474 ok (!brute_force_exists ($copy, $key),
475 "hv_delete_ent present$message $printable");
477 ok (brute_force_exists ($copy, $key),
478 "hv_delete present$message $printable");
479 is (XS::APItest::Hash::delete ($copy, $key), $key,
480 "hv_delete present$message $printable");
481 ok (!brute_force_exists ($copy, $key),
482 "hv_delete present$message $printable");
485 sub test_delete_absent {
486 my ($hash, $key, $printable, $message) = @_;
489 my $class = tied %$hash;
490 if (defined $class) {
491 tie %$copy, ref $class;
494 is (delete $copy->{$key}, undef, "hv_delete_ent absent$message $printable");
496 is (XS::APItest::Hash::delete ($copy, $key), undef,
497 "hv_delete absent$message $printable");
501 my ($hash, $key, $printable, $message, $defaults) = @_;
502 my $HV_STORE_IS_CRAZY = 1;
504 # We are cheating - hv_store returns NULL for a store into an empty
505 # tied hash. This isn't helpful here.
507 my $class = tied %$hash;
509 # It's important to do this with nice new hashes created each time round
510 # the loop, rather than hashes in the pad, which get recycled, and may have
512 my $h1 = {@$defaults};
513 my $h2 = {@$defaults};
514 if (defined $class) {
515 tie %$h1, ref $class;
516 tie %$h2, ref $class;
519 $HV_STORE_IS_CRAZY = undef;
521 # HV store_ent returns 1 if there was already underlying hash storage
522 $HV_STORE_IS_CRAZY = undef unless @$defaults;
525 is (XS::APItest::Hash::store_ent($h1, $key, 1), $HV_STORE_IS_CRAZY,
526 "hv_store_ent$message $printable");
527 ok (brute_force_exists ($h1, $key), "hv_store_ent$message $printable");
528 is (XS::APItest::Hash::store($h2, $key, 1), $HV_STORE_IS_CRAZY,
529 "hv_store$message $printable");
530 ok (brute_force_exists ($h2, $key), "hv_store$message $printable");
533 sub test_fetch_present {
534 my ($hash, $key, $printable, $message) = @_;
536 is ($hash->{$key}, $key, "hv_fetch_ent present$message $printable");
537 is (XS::APItest::Hash::fetch ($hash, $key), $key,
538 "hv_fetch present$message $printable");
541 sub test_fetch_absent {
542 my ($hash, $key, $printable, $message) = @_;
544 is ($hash->{$key}, undef, "hv_fetch_ent absent$message $printable");
545 is (XS::APItest::Hash::fetch ($hash, $key), undef,
546 "hv_fetch absent$message $printable");
549 sub brute_force_exists {
550 my ($hash, $key) = @_;
551 foreach (keys %$hash) {
552 return 1 if $key eq $_;
558 my @results = map {my $a = $_; $a =~ tr/A-Za-z/N-ZA-Mn-za-m/; $a} @_;
559 wantarray ? @results : $results[0];
563 my @results = map {join '', map {chr(32 ^ ord $_)} split '', $_} @_;
564 wantarray ? @results : $results[0];