xref: /openbsd/gnu/usr.bin/perl/ext/XS-APItest/t/hash.t (revision eac174f2)
143003dfeSmillert#!perl -w
243003dfeSmillert
343003dfeSmillertuse strict;
443003dfeSmillertuse utf8;
543003dfeSmillertuse Tie::Hash;
6898184e3Ssthenuse Test::More;
743003dfeSmillert
843003dfeSmillertBEGIN {use_ok('XS::APItest')};
943003dfeSmillert
1043003dfeSmillertsub preform_test;
1143003dfeSmillertsub test_present;
1243003dfeSmillertsub test_absent;
1343003dfeSmillertsub test_delete_present;
1443003dfeSmillertsub test_delete_absent;
1543003dfeSmillertsub brute_force_exists;
1643003dfeSmillertsub test_store;
1743003dfeSmillertsub test_fetch_present;
1843003dfeSmillertsub test_fetch_absent;
1943003dfeSmillert
2043003dfeSmillertmy $utf8_for_258 = chr 258;
2143003dfeSmillertutf8::encode $utf8_for_258;
2243003dfeSmillert
23b8851fccSafresh1my @testkeys = ('N', chr utf8::unicode_to_native(198), chr 256);
2443003dfeSmillertmy @keys = (@testkeys, $utf8_for_258);
2543003dfeSmillert
2643003dfeSmillertforeach (@keys) {
2743003dfeSmillert  utf8::downgrade $_, 1;
2843003dfeSmillert}
2943003dfeSmillertmain_tests (\@keys, \@testkeys, '');
3043003dfeSmillert
3143003dfeSmillertforeach (@keys) {
3243003dfeSmillert  utf8::upgrade $_;
3343003dfeSmillert}
3443003dfeSmillertmain_tests (\@keys, \@testkeys, ' [utf8 hash]');
3543003dfeSmillert
3643003dfeSmillert{
3743003dfeSmillert  my %h = (a=>'cheat');
3843003dfeSmillert  tie %h, 'Tie::StdHash';
3943003dfeSmillert  # is bug 36327 fixed?
4043003dfeSmillert  my $result = ($] > 5.009) ? undef : 1;
4143003dfeSmillert
4243003dfeSmillert  is (XS::APItest::Hash::store(\%h, chr 258,  1), $result);
4343003dfeSmillert
4443003dfeSmillert  ok (!exists $h{$utf8_for_258},
4543003dfeSmillert      "hv_store doesn't insert a key with the raw utf8 on a tied hash");
4643003dfeSmillert}
4743003dfeSmillert
48898184e3Ssthen{
4943003dfeSmillert    my $strtab = strtab();
5043003dfeSmillert    is (ref $strtab, 'HASH', "The shared string table quacks like a hash");
5143003dfeSmillert    my $wibble = "\0";
5243003dfeSmillert    eval {
5343003dfeSmillert	$strtab->{$wibble}++;
5443003dfeSmillert    };
5543003dfeSmillert    my $prefix = "Cannot modify shared string table in hv_";
5643003dfeSmillert    my $what = $prefix . 'fetch';
5743003dfeSmillert    like ($@, qr/^$what/,$what);
5843003dfeSmillert    eval {
5943003dfeSmillert	XS::APItest::Hash::store($strtab, 'Boom!',  1)
6043003dfeSmillert    };
6143003dfeSmillert    $what = $prefix . 'store';
6243003dfeSmillert    like ($@, qr/^$what/, $what);
6343003dfeSmillert    if (0) {
6443003dfeSmillert	A::B->method();
6543003dfeSmillert    }
6643003dfeSmillert    # DESTROY should be in there.
6743003dfeSmillert    eval {
6843003dfeSmillert	delete $strtab->{DESTROY};
6943003dfeSmillert    };
7043003dfeSmillert    $what = $prefix . 'delete';
7143003dfeSmillert    like ($@, qr/^$what/, $what);
7243003dfeSmillert    # I can't work out how to get to the code that flips the wasutf8 flag on
7343003dfeSmillert    # the hash key without some ikcy XS
7443003dfeSmillert}
7543003dfeSmillert
7643003dfeSmillert{
7743003dfeSmillert    is_deeply([&XS::APItest::Hash::test_hv_free_ent], [2,2,1,1],
7843003dfeSmillert	      "hv_free_ent frees the value immediately");
7943003dfeSmillert    is_deeply([&XS::APItest::Hash::test_hv_delayfree_ent], [2,2,2,1],
8043003dfeSmillert	      "hv_delayfree_ent keeps the value around until FREETMPS");
8143003dfeSmillert}
8243003dfeSmillert
8343003dfeSmillertforeach my $in ("", "N", "a\0b") {
8443003dfeSmillert    my $got = XS::APItest::Hash::test_share_unshare_pvn($in);
8543003dfeSmillert    is ($got, $in, "test_share_unshare_pvn");
8643003dfeSmillert}
8743003dfeSmillert
88898184e3Ssthen{
8943003dfeSmillert    foreach ([\&XS::APItest::Hash::rot13_hash, \&rot13, "rot 13"],
9043003dfeSmillert	     [\&XS::APItest::Hash::bitflip_hash, \&bitflip, "bitflip"],
9143003dfeSmillert	    ) {
9243003dfeSmillert	my ($setup, $mapping, $name) = @$_;
9343003dfeSmillert	my %hash;
9443003dfeSmillert	my %placebo = (a => 1, p => 2, i => 4, e => 8);
9543003dfeSmillert	$setup->(\%hash);
9643003dfeSmillert	$hash{a}++; @hash{qw(p i e)} = (2, 4, 8);
9743003dfeSmillert
9843003dfeSmillert	test_U_hash(\%hash, \%placebo, [f => 9, g => 10, h => 11], $mapping,
9943003dfeSmillert		    $name);
10043003dfeSmillert    }
10143003dfeSmillert    foreach my $upgrade_o (0, 1) {
10243003dfeSmillert	foreach my $upgrade_n (0, 1) {
10343003dfeSmillert	    my (%hash, %placebo);
10443003dfeSmillert	    XS::APItest::Hash::bitflip_hash(\%hash);
105b8851fccSafresh1	    foreach my $new (["7", utf8::unicode_to_native(65),
106b8851fccSafresh1                                   utf8::unicode_to_native(67),
107b8851fccSafresh1                                   utf8::unicode_to_native(80)
108b8851fccSafresh1                             ],
109b8851fccSafresh1			     ["8", utf8::unicode_to_native(163),
110b8851fccSafresh1                                   utf8::unicode_to_native(171),
111b8851fccSafresh1                                   utf8::unicode_to_native(215)
112b8851fccSafresh1                             ],
11343003dfeSmillert			     ["U", 2603, 2604, 2604],
11443003dfeSmillert                ) {
115b8851fccSafresh1		foreach my $code (utf8::unicode_to_native(78),
116b8851fccSafresh1                                  utf8::unicode_to_native(240),
117b8851fccSafresh1                                  256,
118b8851fccSafresh1                                  1336
119b8851fccSafresh1                ) {
12043003dfeSmillert		    my $key = chr $code;
12143003dfeSmillert		    # This is the UTF-8 byte sequence for the key.
12243003dfeSmillert		    my $key_utf8 = $key;
12343003dfeSmillert		    utf8::encode($key_utf8);
12443003dfeSmillert		    if ($upgrade_o) {
12543003dfeSmillert			$key .= chr 256;
12643003dfeSmillert			chop $key;
12743003dfeSmillert		    }
12843003dfeSmillert		    $hash{$key} = $placebo{$key} = $code;
12943003dfeSmillert		    $hash{$key_utf8} = $placebo{$key_utf8} = "$code as UTF-8";
13043003dfeSmillert		}
13143003dfeSmillert		my $name = 'bitflip ' . shift @$new;
13243003dfeSmillert		my @new_kv;
13343003dfeSmillert		foreach my $code (@$new) {
13443003dfeSmillert		    my $key = chr $code;
13543003dfeSmillert		    if ($upgrade_n) {
13643003dfeSmillert			$key .= chr 256;
13743003dfeSmillert			chop $key;
13843003dfeSmillert		    }
13943003dfeSmillert		    push @new_kv, $key, $_;
14043003dfeSmillert		}
14143003dfeSmillert
14243003dfeSmillert		$name .= ' upgraded(orig) ' if $upgrade_o;
14343003dfeSmillert		$name .= ' upgraded(new) ' if $upgrade_n;
14443003dfeSmillert		test_U_hash(\%hash, \%placebo, \@new_kv, \&bitflip, $name);
14543003dfeSmillert	    }
14643003dfeSmillert	}
14743003dfeSmillert    }
14843003dfeSmillert}
14943003dfeSmillert
15043003dfeSmillertsub test_precomputed_hashes {
15143003dfeSmillert    my $what = shift;
15243003dfeSmillert    my $hash_it = shift;
15343003dfeSmillert    my $ord = shift;
15443003dfeSmillert    my $key_copy = $_[0];
15543003dfeSmillert    $key_copy .= '';
15643003dfeSmillert
15743003dfeSmillert    my %hash;
15843003dfeSmillert    is (XS::APItest::Hash::common({hv => \%hash,
15943003dfeSmillert				   "key$what" => $_[0],
16043003dfeSmillert				   val => $ord,
16143003dfeSmillert				   "hash_$what" => $hash_it,
16243003dfeSmillert				   action => XS::APItest::HV_FETCH_ISSTORE}),
16343003dfeSmillert	$ord, "store $ord with $what \$hash_it = $hash_it");
16443003dfeSmillert    is_deeply ([each %hash], [$_[0], $ord], "First key read is good");
16543003dfeSmillert    is_deeply ([each %hash], [], "No second key good");
16643003dfeSmillert
16743003dfeSmillert    is ($hash{$_[0]}, $ord, "Direct hash read finds $ord");
16843003dfeSmillert
16943003dfeSmillert    is_deeply ([each %hash], [$key_copy, $ord],
17043003dfeSmillert	       "First key read is good with a copy");
17143003dfeSmillert    is_deeply ([each %hash], [], "No second key good");
17243003dfeSmillert
17343003dfeSmillert    is ($hash{$key_copy}, $ord, "Direct hash read finds $ord");
17443003dfeSmillert}
17543003dfeSmillert
17643003dfeSmillert{
17743003dfeSmillert    my $as_utf8 = "\241" . chr 256;
17843003dfeSmillert    chop $as_utf8;
17943003dfeSmillert    my $as_bytes = "\243";
18043003dfeSmillert    foreach my $key ('N', $as_bytes, $as_utf8, "\x{2623}") {
18143003dfeSmillert	my $ord = ord $key;
18243003dfeSmillert	foreach my $hash_it (0, 1) {
18343003dfeSmillert	    foreach my $what (qw(pv sv)) {
18443003dfeSmillert		test_precomputed_hashes($what, $hash_it, $ord, $key);
18543003dfeSmillert	    }
18643003dfeSmillert	    # Generate a shared hash key scalar
18743003dfeSmillert	    my %h = ($key => 1);
18843003dfeSmillert	    test_precomputed_hashes('sv', $hash_it, $ord, (keys %h)[0]);
18943003dfeSmillert	}
19043003dfeSmillert    }
19143003dfeSmillert}
19243003dfeSmillert
193898184e3Ssthen{
194*eac174f2Safresh1    no warnings 'experimental::builtin';
195*eac174f2Safresh1    use builtin 'weaken';
196898184e3Ssthen    my %h;
197898184e3Ssthen    fill_hash_with_nulls(\%h);
198898184e3Ssthen    my @objs;
199898184e3Ssthen    for("a".."z","A".."Z") {
200898184e3Ssthen	weaken($objs[@objs] = $h{$_} = []);
201898184e3Ssthen    }
202898184e3Ssthen    undef %h;
203898184e3Ssthen    no warnings 'uninitialized';
204898184e3Ssthen    local $" = "";
205898184e3Ssthen    is "@objs", "",
206898184e3Ssthen      'explicitly undeffing a hash with nulls frees all entries';
207898184e3Ssthen
208898184e3Ssthen    my $h = {};
209898184e3Ssthen    fill_hash_with_nulls($h);
210898184e3Ssthen    @objs = ();
211898184e3Ssthen    for("a".."z","A".."Z") {
212898184e3Ssthen	weaken($objs[@objs] = $$h{$_} = []);
213898184e3Ssthen    }
214898184e3Ssthen    undef $h;
215898184e3Ssthen    is "@objs", "", 'freeing a hash with nulls frees all entries';
216898184e3Ssthen}
217898184e3Ssthen
218898184e3Ssthen# Tests for HvENAME and UTF8
219898184e3Ssthen{
220898184e3Ssthen    no strict;
221898184e3Ssthen    no warnings 'void';
222898184e3Ssthen    my $hvref;
223898184e3Ssthen
224898184e3Ssthen    *{"\xff::bar"}; # autovivify %ÿ:: without UTF8
225898184e3Ssthen    *{"\xff::bαr::"} = $hvref = \%foo::;
226898184e3Ssthen    undef *foo::;
227898184e3Ssthen    is HvENAME($hvref), "\xff::bαr",
228898184e3Ssthen	'stash alias (utf8 inside bytes) does not create malformed UTF8';
229898184e3Ssthen
230898184e3Ssthen    *{"é::foo"}; # autovivify %é:: with UTF8
231898184e3Ssthen    *{"\xe9::\xe9::"} = $hvref = \%bar::;
232898184e3Ssthen    undef *bar::;
233898184e3Ssthen    is HvENAME($hvref), "\xe9::\xe9",
234898184e3Ssthen	'stash alias (bytes inside utf8) does not create malformed UTF8';
235898184e3Ssthen
236898184e3Ssthen    *{"\xfe::bar"}; *{"\xfd::bar"};
237898184e3Ssthen    *{"\xfe::bαr::"} = \%goo::;
238898184e3Ssthen    *{"\xfd::bαr::"} = $hvref = \%goo::;
239898184e3Ssthen    undef *goo::;
240898184e3Ssthen    like HvENAME($hvref), qr/^[\xfe\xfd]::bαr\z/,
241898184e3Ssthen	'multiple stash aliases (utf8 inside bytes) do not cause bad UTF8';
242898184e3Ssthen
243898184e3Ssthen    *{"è::foo"}; *{"ë::foo"};
244898184e3Ssthen    *{"\xe8::\xe9::"} = $hvref = \%bear::;
245898184e3Ssthen    *{"\xeb::\xe9::"} = \%bear::;
246898184e3Ssthen    undef *bear::;
247898184e3Ssthen    like HvENAME($hvref), qr"^[\xe8\xeb]::\xe9\z",
248898184e3Ssthen	'multiple stash aliases (bytes inside utf8) do not cause bad UTF8';
249898184e3Ssthen}
250898184e3Ssthen
251898184e3Ssthen{ # newHVhv
252898184e3Ssthen    use Tie::Hash;
253898184e3Ssthen    tie my %h, 'Tie::StdHash';
254898184e3Ssthen    %h = 1..10;
255898184e3Ssthen    is join(' ', sort %{newHVhv \%h}), '1 10 2 3 4 5 6 7 8 9',
256898184e3Ssthen      'newHVhv on tied hash';
257898184e3Ssthen}
258898184e3Ssthen
259898184e3Ssthen# helem and hslice on entry with null value
260898184e3Ssthen# This is actually a test for a Perl operator, not an XS API test.  But it
261898184e3Ssthen# requires a hash that can only be produced by XS (although recently it
262898184e3Ssthen# could be encountered when tying hint hashes).
263898184e3Ssthen{
264898184e3Ssthen    my %h;
265898184e3Ssthen    fill_hash_with_nulls(\%h);
266898184e3Ssthen    eval{ $h{84} = 1 };
267898184e3Ssthen    pass 'no crash when writing to hash elem with null value';
268898184e3Ssthen    eval{ no # silly
269898184e3Ssthen	  warnings; # thank you!
270898184e3Ssthen	  @h{85} = 1 };
271898184e3Ssthen    pass 'no crash when writing to hash elem with null value via slice';
27291f110e0Safresh1    eval { delete local $h{86} };
27391f110e0Safresh1    pass 'no crash during local deletion of hash elem with null value';
27491f110e0Safresh1    eval { delete local @h{87,88} };
27591f110e0Safresh1    pass 'no crash during local deletion of hash slice with null values';
276898184e3Ssthen}
277898184e3Ssthen
27891f110e0Safresh1# [perl #111000] Bug number eleventy-one thousand:
27991f110e0Safresh1#                hv_store should work on hint hashes
28091f110e0Safresh1eval q{
28191f110e0Safresh1    BEGIN {
28291f110e0Safresh1	XS::APItest::Hash::store \%^H, "XS::APItest/hash.t", undef;
28391f110e0Safresh1	delete $^H{"XS::APItest/hash.t"};
28491f110e0Safresh1    }
28591f110e0Safresh1};
28691f110e0Safresh1pass("hv_store works on the hint hash");
28791f110e0Safresh1
2886fb12b70Safresh1{
2896fb12b70Safresh1    # [perl #79074] HeSVKEY_force loses UTF8ness
2906fb12b70Safresh1    my %hash = ( "\xff" => 1, "\x{100}" => 1 );
2916fb12b70Safresh1    my @keys = sort ( XS::APItest::Hash::test_force_keys(\%hash) );
2926fb12b70Safresh1    is_deeply(\@keys, [ sort keys %hash ], "check HeSVKEY_force()");
2936fb12b70Safresh1}
2946fb12b70Safresh1
295*eac174f2Safresh1# Test that mg_copy is called when expected (and not called when not)
296*eac174f2Safresh1# No (other) tests in core will fail if the implementation of `keys %tied_hash`
297*eac174f2Safresh1# is (accidentally) changed to also call hv_iterval() and trigger mg_copy.
298*eac174f2Safresh1# However, this behaviour is visible, and tested by Variable::Magic on CPAN.
299*eac174f2Safresh1
300*eac174f2Safresh1{
301*eac174f2Safresh1    my %h;
302*eac174f2Safresh1    my $obj = tie %h, 'Tie::StdHash';
303*eac174f2Safresh1    sv_magic_mycopy(\%h);
304*eac174f2Safresh1
305*eac174f2Safresh1    is(sv_magic_mycopy_count(\%h), 0);
306*eac174f2Safresh1
307*eac174f2Safresh1    $h{perl} = "rules";
308*eac174f2Safresh1
309*eac174f2Safresh1    is(sv_magic_mycopy_count(\%h), 1);
310*eac174f2Safresh1
311*eac174f2Safresh1    is($h{perl}, "rules", "found key");
312*eac174f2Safresh1
313*eac174f2Safresh1    is(sv_magic_mycopy_count(\%h), 2);
314*eac174f2Safresh1
315*eac174f2Safresh1    # keys *doesn't* trigger copy magic, so the count is still 2
316*eac174f2Safresh1    my @flat = keys %h;
317*eac174f2Safresh1
318*eac174f2Safresh1    is(sv_magic_mycopy_count(\%h), 2);
319*eac174f2Safresh1
320*eac174f2Safresh1    @flat = values %h;
321*eac174f2Safresh1
322*eac174f2Safresh1    is(sv_magic_mycopy_count(\%h), 3);
323*eac174f2Safresh1
324*eac174f2Safresh1    @flat = each %h;
325*eac174f2Safresh1
326*eac174f2Safresh1    is(sv_magic_mycopy_count(\%h), 4);
327*eac174f2Safresh1}
328*eac174f2Safresh1
329*eac174f2Safresh1{
330*eac174f2Safresh1    # There are two API variants - hv_delete and hv_delete_ent. The Perl
331*eac174f2Safresh1    # interpreter exclusively uses hv_delete_ent. Only XS code uses hv_delete.
332*eac174f2Safresh1    # Hence the problem case could only be triggered by XS code called on
333*eac174f2Safresh1    # symbol tables, and with particular non-ASCII keys:
334*eac174f2Safresh1
335*eac174f2Safresh1    # Deleting a key with WASUTF from a stash used to trigger a use-after free:
336*eac174f2Safresh1    my $key = "\xFF\x{100}";
337*eac174f2Safresh1    chop $key;
338*eac174f2Safresh1    ++$main::{$key};
339*eac174f2Safresh1    is(XS::APItest::Hash::delete(\%main::, $key), 1,
340*eac174f2Safresh1       "hv_delete doesn't trigger a use-after free");
341*eac174f2Safresh1
342*eac174f2Safresh1    # Perl code has always used this API, which never had the problem:
343*eac174f2Safresh1    ++$main::{$key};
344*eac174f2Safresh1    is(XS::APItest::Hash::delete_ent(\%main::, $key), 1,
345*eac174f2Safresh1       "hv_delete_ent never triggered a use-after free, but test it anyway");
346*eac174f2Safresh1}
347*eac174f2Safresh1
348898184e3Ssthendone_testing;
34943003dfeSmillertexit;
35043003dfeSmillert
35143003dfeSmillert################################   The End   ################################
35243003dfeSmillert
35343003dfeSmillertsub test_U_hash {
35443003dfeSmillert    my ($hash, $placebo, $new, $mapping, $message) = @_;
35543003dfeSmillert    my @hitlist = keys %$placebo;
35643003dfeSmillert    print "# $message\n";
35743003dfeSmillert
35843003dfeSmillert    my @keys = sort keys %$hash;
35943003dfeSmillert    is ("@keys", join(' ', sort($mapping->(keys %$placebo))),
36043003dfeSmillert	"uvar magic called exactly once on store");
36143003dfeSmillert
36243003dfeSmillert    is (keys %$hash, keys %$placebo);
36343003dfeSmillert
36443003dfeSmillert    my $victim = shift @hitlist;
36543003dfeSmillert    is (delete $hash->{$victim}, delete $placebo->{$victim});
36643003dfeSmillert
36743003dfeSmillert    is (keys %$hash, keys %$placebo);
36843003dfeSmillert    @keys = sort keys %$hash;
36943003dfeSmillert    is ("@keys", join(' ', sort($mapping->(keys %$placebo))));
37043003dfeSmillert
37143003dfeSmillert    $victim = shift @hitlist;
37243003dfeSmillert    is (XS::APItest::Hash::delete_ent ($hash, $victim,
37343003dfeSmillert				       XS::APItest::HV_DISABLE_UVAR_XKEY),
37443003dfeSmillert	undef, "Deleting a known key with conversion disabled fails (ent)");
37543003dfeSmillert    is (keys %$hash, keys %$placebo);
37643003dfeSmillert
37743003dfeSmillert    is (XS::APItest::Hash::delete_ent ($hash, $victim, 0),
37843003dfeSmillert	delete $placebo->{$victim},
37943003dfeSmillert	"Deleting a known key with conversion enabled works (ent)");
38043003dfeSmillert    is (keys %$hash, keys %$placebo);
38143003dfeSmillert    @keys = sort keys %$hash;
38243003dfeSmillert    is ("@keys", join(' ', sort($mapping->(keys %$placebo))));
38343003dfeSmillert
38443003dfeSmillert    $victim = shift @hitlist;
38543003dfeSmillert    is (XS::APItest::Hash::delete ($hash, $victim,
38643003dfeSmillert				   XS::APItest::HV_DISABLE_UVAR_XKEY),
38743003dfeSmillert	undef, "Deleting a known key with conversion disabled fails");
38843003dfeSmillert    is (keys %$hash, keys %$placebo);
38943003dfeSmillert
39043003dfeSmillert    is (XS::APItest::Hash::delete ($hash, $victim, 0),
39143003dfeSmillert	delete $placebo->{$victim},
39243003dfeSmillert	"Deleting a known key with conversion enabled works");
39343003dfeSmillert    is (keys %$hash, keys %$placebo);
39443003dfeSmillert    @keys = sort keys %$hash;
39543003dfeSmillert    is ("@keys", join(' ', sort($mapping->(keys %$placebo))));
39643003dfeSmillert
39743003dfeSmillert    my ($k, $v) = splice @$new, 0, 2;
39843003dfeSmillert    $hash->{$k} = $v;
39943003dfeSmillert    $placebo->{$k} = $v;
40043003dfeSmillert    is (keys %$hash, keys %$placebo);
40143003dfeSmillert    @keys = sort keys %$hash;
40243003dfeSmillert    is ("@keys", join(' ', sort($mapping->(keys %$placebo))));
40343003dfeSmillert
40443003dfeSmillert    ($k, $v) = splice @$new, 0, 2;
40543003dfeSmillert    is (XS::APItest::Hash::store_ent($hash, $k, $v), $v, "store_ent");
40643003dfeSmillert    $placebo->{$k} = $v;
40743003dfeSmillert    is (keys %$hash, keys %$placebo);
40843003dfeSmillert    @keys = sort keys %$hash;
40943003dfeSmillert    is ("@keys", join(' ', sort($mapping->(keys %$placebo))));
41043003dfeSmillert
41143003dfeSmillert    ($k, $v) = splice @$new, 0, 2;
41243003dfeSmillert    is (XS::APItest::Hash::store($hash, $k, $v), $v, "store");
41343003dfeSmillert    $placebo->{$k} = $v;
41443003dfeSmillert    is (keys %$hash, keys %$placebo);
41543003dfeSmillert    @keys = sort keys %$hash;
41643003dfeSmillert    is ("@keys", join(' ', sort($mapping->(keys %$placebo))));
41743003dfeSmillert
41843003dfeSmillert    @hitlist = keys %$placebo;
41943003dfeSmillert    $victim = shift @hitlist;
42043003dfeSmillert    is (XS::APItest::Hash::fetch_ent($hash, $victim), $placebo->{$victim},
42143003dfeSmillert	"fetch_ent");
42243003dfeSmillert    is (XS::APItest::Hash::fetch_ent($hash, $mapping->($victim)), undef,
42343003dfeSmillert	"fetch_ent (missing)");
42443003dfeSmillert
42543003dfeSmillert    $victim = shift @hitlist;
42643003dfeSmillert    is (XS::APItest::Hash::fetch($hash, $victim), $placebo->{$victim},
42743003dfeSmillert	"fetch");
42843003dfeSmillert    is (XS::APItest::Hash::fetch($hash, $mapping->($victim)), undef,
42943003dfeSmillert	"fetch (missing)");
43043003dfeSmillert
43143003dfeSmillert    $victim = shift @hitlist;
43243003dfeSmillert    ok (XS::APItest::Hash::exists_ent($hash, $victim), "exists_ent");
43343003dfeSmillert    ok (!XS::APItest::Hash::exists_ent($hash, $mapping->($victim)),
43443003dfeSmillert	"exists_ent (missing)");
43543003dfeSmillert
43643003dfeSmillert    $victim = shift @hitlist;
43743003dfeSmillert    die "Need a victim" unless defined $victim;
43843003dfeSmillert    ok (XS::APItest::Hash::exists($hash, $victim), "exists");
43943003dfeSmillert    ok (!XS::APItest::Hash::exists($hash, $mapping->($victim)),
44043003dfeSmillert	"exists (missing)");
44143003dfeSmillert
44243003dfeSmillert    is (XS::APItest::Hash::common({hv => $hash, keysv => $victim}),
44343003dfeSmillert	$placebo->{$victim}, "common (fetch)");
44443003dfeSmillert    is (XS::APItest::Hash::common({hv => $hash, keypv => $victim}),
44543003dfeSmillert	$placebo->{$victim}, "common (fetch pv)");
44643003dfeSmillert    is (XS::APItest::Hash::common({hv => $hash, keysv => $victim,
44743003dfeSmillert				   action => XS::APItest::HV_DISABLE_UVAR_XKEY}),
44843003dfeSmillert	undef, "common (fetch) missing");
44943003dfeSmillert    is (XS::APItest::Hash::common({hv => $hash, keypv => $victim,
45043003dfeSmillert				   action => XS::APItest::HV_DISABLE_UVAR_XKEY}),
45143003dfeSmillert	undef, "common (fetch pv) missing");
45243003dfeSmillert    is (XS::APItest::Hash::common({hv => $hash, keysv => $mapping->($victim),
45343003dfeSmillert				   action => XS::APItest::HV_DISABLE_UVAR_XKEY}),
45443003dfeSmillert	$placebo->{$victim}, "common (fetch) missing mapped");
45543003dfeSmillert    is (XS::APItest::Hash::common({hv => $hash, keypv => $mapping->($victim),
45643003dfeSmillert				   action => XS::APItest::HV_DISABLE_UVAR_XKEY}),
45743003dfeSmillert	$placebo->{$victim}, "common (fetch pv) missing mapped");
45843003dfeSmillert}
45943003dfeSmillert
46043003dfeSmillertsub main_tests {
46143003dfeSmillert  my ($keys, $testkeys, $description) = @_;
46243003dfeSmillert  foreach my $key (@$testkeys) {
463b8851fccSafresh1    my $lckey = ($key eq chr utf8::unicode_to_native(198)) ? chr utf8::unicode_to_native(230) : lc $key;
46443003dfeSmillert    my $unikey = $key;
46543003dfeSmillert    utf8::encode $unikey;
46643003dfeSmillert
46743003dfeSmillert    utf8::downgrade $key, 1;
46843003dfeSmillert    utf8::downgrade $lckey, 1;
46943003dfeSmillert    utf8::downgrade $unikey, 1;
47043003dfeSmillert    main_test_inner ($key, $lckey, $unikey, $keys, $description);
47143003dfeSmillert
47243003dfeSmillert    utf8::upgrade $key;
47343003dfeSmillert    utf8::upgrade $lckey;
47443003dfeSmillert    utf8::upgrade $unikey;
47543003dfeSmillert    main_test_inner ($key, $lckey, $unikey, $keys,
47643003dfeSmillert		     $description . ' [key utf8 on]');
47743003dfeSmillert  }
47843003dfeSmillert
47943003dfeSmillert  # hv_exists was buggy for tied hashes, in that the raw utf8 key was being
48043003dfeSmillert  # used - the utf8 flag was being lost.
48143003dfeSmillert  perform_test (\&test_absent, (chr 258), $keys, '');
48243003dfeSmillert
48343003dfeSmillert  perform_test (\&test_fetch_absent, (chr 258), $keys, '');
48443003dfeSmillert  perform_test (\&test_delete_absent, (chr 258), $keys, '');
48543003dfeSmillert}
48643003dfeSmillert
48743003dfeSmillertsub main_test_inner {
48843003dfeSmillert  my ($key, $lckey, $unikey, $keys, $description) = @_;
48943003dfeSmillert  perform_test (\&test_present, $key, $keys, $description);
49043003dfeSmillert  perform_test (\&test_fetch_present, $key, $keys, $description);
49143003dfeSmillert  perform_test (\&test_delete_present, $key, $keys, $description);
49243003dfeSmillert
49343003dfeSmillert  perform_test (\&test_store, $key, $keys, $description, [a=>'cheat']);
49443003dfeSmillert  perform_test (\&test_store, $key, $keys, $description, []);
49543003dfeSmillert
49643003dfeSmillert  perform_test (\&test_absent, $lckey, $keys, $description);
49743003dfeSmillert  perform_test (\&test_fetch_absent, $lckey, $keys, $description);
49843003dfeSmillert  perform_test (\&test_delete_absent, $lckey, $keys, $description);
49943003dfeSmillert
50043003dfeSmillert  return if $unikey eq $key;
50143003dfeSmillert
50243003dfeSmillert  perform_test (\&test_absent, $unikey, $keys, $description);
50343003dfeSmillert  perform_test (\&test_fetch_absent, $unikey, $keys, $description);
50443003dfeSmillert  perform_test (\&test_delete_absent, $unikey, $keys, $description);
50543003dfeSmillert}
50643003dfeSmillert
50743003dfeSmillertsub perform_test {
50843003dfeSmillert  my ($test_sub, $key, $keys, $message, @other) = @_;
50943003dfeSmillert  my $printable = join ',', map {ord} split //, $key;
51043003dfeSmillert
51143003dfeSmillert  my (%hash, %tiehash);
51243003dfeSmillert  tie %tiehash, 'Tie::StdHash';
51343003dfeSmillert
51443003dfeSmillert  @hash{@$keys} = @$keys;
51543003dfeSmillert  @tiehash{@$keys} = @$keys;
51643003dfeSmillert
51743003dfeSmillert  &$test_sub (\%hash, $key, $printable, $message, @other);
51843003dfeSmillert  &$test_sub (\%tiehash, $key, $printable, "$message tie", @other);
51943003dfeSmillert}
52043003dfeSmillert
52143003dfeSmillertsub test_present {
52243003dfeSmillert  my ($hash, $key, $printable, $message) = @_;
52343003dfeSmillert
52443003dfeSmillert  ok (exists $hash->{$key}, "hv_exists_ent present$message $printable");
52543003dfeSmillert  ok (XS::APItest::Hash::exists ($hash, $key),
52643003dfeSmillert      "hv_exists present$message $printable");
52743003dfeSmillert}
52843003dfeSmillert
52943003dfeSmillertsub test_absent {
53043003dfeSmillert  my ($hash, $key, $printable, $message) = @_;
53143003dfeSmillert
53243003dfeSmillert  ok (!exists $hash->{$key}, "hv_exists_ent absent$message $printable");
53343003dfeSmillert  ok (!XS::APItest::Hash::exists ($hash, $key),
53443003dfeSmillert      "hv_exists absent$message $printable");
53543003dfeSmillert}
53643003dfeSmillert
53743003dfeSmillertsub test_delete_present {
53843003dfeSmillert  my ($hash, $key, $printable, $message) = @_;
53943003dfeSmillert
54043003dfeSmillert  my $copy = {};
54143003dfeSmillert  my $class = tied %$hash;
54243003dfeSmillert  if (defined $class) {
54343003dfeSmillert    tie %$copy, ref $class;
54443003dfeSmillert  }
54543003dfeSmillert  $copy = {%$hash};
54643003dfeSmillert  ok (brute_force_exists ($copy, $key),
54743003dfeSmillert      "hv_delete_ent present$message $printable");
54843003dfeSmillert  is (delete $copy->{$key}, $key, "hv_delete_ent present$message $printable");
54943003dfeSmillert  ok (!brute_force_exists ($copy, $key),
55043003dfeSmillert      "hv_delete_ent present$message $printable");
55143003dfeSmillert  $copy = {%$hash};
55243003dfeSmillert  ok (brute_force_exists ($copy, $key),
55343003dfeSmillert      "hv_delete present$message $printable");
55443003dfeSmillert  is (XS::APItest::Hash::delete ($copy, $key), $key,
55543003dfeSmillert      "hv_delete present$message $printable");
55643003dfeSmillert  ok (!brute_force_exists ($copy, $key),
55743003dfeSmillert      "hv_delete present$message $printable");
55843003dfeSmillert}
55943003dfeSmillert
56043003dfeSmillertsub test_delete_absent {
56143003dfeSmillert  my ($hash, $key, $printable, $message) = @_;
56243003dfeSmillert
56343003dfeSmillert  my $copy = {};
56443003dfeSmillert  my $class = tied %$hash;
56543003dfeSmillert  if (defined $class) {
56643003dfeSmillert    tie %$copy, ref $class;
56743003dfeSmillert  }
56843003dfeSmillert  $copy = {%$hash};
56943003dfeSmillert  is (delete $copy->{$key}, undef, "hv_delete_ent absent$message $printable");
57043003dfeSmillert  $copy = {%$hash};
57143003dfeSmillert  is (XS::APItest::Hash::delete ($copy, $key), undef,
57243003dfeSmillert      "hv_delete absent$message $printable");
57343003dfeSmillert}
57443003dfeSmillert
57543003dfeSmillertsub test_store {
57643003dfeSmillert  my ($hash, $key, $printable, $message, $defaults) = @_;
57743003dfeSmillert  my $HV_STORE_IS_CRAZY = 1;
57843003dfeSmillert
57943003dfeSmillert  # We are cheating - hv_store returns NULL for a store into an empty
58043003dfeSmillert  # tied hash. This isn't helpful here.
58143003dfeSmillert
58243003dfeSmillert  my $class = tied %$hash;
58343003dfeSmillert
58443003dfeSmillert  # It's important to do this with nice new hashes created each time round
58543003dfeSmillert  # the loop, rather than hashes in the pad, which get recycled, and may have
58643003dfeSmillert  # xhv_array non-NULL
58743003dfeSmillert  my $h1 = {@$defaults};
58843003dfeSmillert  my $h2 = {@$defaults};
58943003dfeSmillert  if (defined $class) {
59043003dfeSmillert    tie %$h1, ref $class;
59143003dfeSmillert    tie %$h2, ref $class;
59243003dfeSmillert    if ($] > 5.009) {
59343003dfeSmillert      # bug 36327 is fixed
59443003dfeSmillert      $HV_STORE_IS_CRAZY = undef;
59543003dfeSmillert    } else {
59643003dfeSmillert      # HV store_ent returns 1 if there was already underlying hash storage
59743003dfeSmillert      $HV_STORE_IS_CRAZY = undef unless @$defaults;
59843003dfeSmillert    }
59943003dfeSmillert  }
60043003dfeSmillert  is (XS::APItest::Hash::store_ent($h1, $key, 1), $HV_STORE_IS_CRAZY,
60143003dfeSmillert      "hv_store_ent$message $printable");
60243003dfeSmillert  ok (brute_force_exists ($h1, $key), "hv_store_ent$message $printable");
60343003dfeSmillert  is (XS::APItest::Hash::store($h2, $key,  1), $HV_STORE_IS_CRAZY,
60443003dfeSmillert      "hv_store$message $printable");
60543003dfeSmillert  ok (brute_force_exists ($h2, $key), "hv_store$message $printable");
60643003dfeSmillert}
60743003dfeSmillert
60843003dfeSmillertsub test_fetch_present {
60943003dfeSmillert  my ($hash, $key, $printable, $message) = @_;
61043003dfeSmillert
61143003dfeSmillert  is ($hash->{$key}, $key, "hv_fetch_ent present$message $printable");
61243003dfeSmillert  is (XS::APItest::Hash::fetch ($hash, $key), $key,
61343003dfeSmillert      "hv_fetch present$message $printable");
61443003dfeSmillert}
61543003dfeSmillert
61643003dfeSmillertsub test_fetch_absent {
61743003dfeSmillert  my ($hash, $key, $printable, $message) = @_;
61843003dfeSmillert
61943003dfeSmillert  is ($hash->{$key}, undef, "hv_fetch_ent absent$message $printable");
62043003dfeSmillert  is (XS::APItest::Hash::fetch ($hash, $key), undef,
62143003dfeSmillert      "hv_fetch absent$message $printable");
62243003dfeSmillert}
62343003dfeSmillert
62443003dfeSmillertsub brute_force_exists {
62543003dfeSmillert  my ($hash, $key) = @_;
62643003dfeSmillert  foreach (keys %$hash) {
62743003dfeSmillert    return 1 if $key eq $_;
62843003dfeSmillert  }
62943003dfeSmillert  return 0;
63043003dfeSmillert}
63143003dfeSmillert
63243003dfeSmillertsub rot13 {
63343003dfeSmillert    my @results = map {my $a = $_; $a =~ tr/A-Za-z/N-ZA-Mn-za-m/; $a} @_;
63443003dfeSmillert    wantarray ? @results : $results[0];
63543003dfeSmillert}
63643003dfeSmillert
63743003dfeSmillertsub bitflip {
638b8851fccSafresh1    my $flip_bit = ord("A") ^ ord("a");
639b8851fccSafresh1    my @results = map {join '', map {chr($flip_bit ^ ord $_)} split '', $_} @_;
64043003dfeSmillert    wantarray ? @results : $results[0];
64143003dfeSmillert}
642