Also test Perl_hv_common() and pre-computed hashes for SV keys.
[p5sagit/p5-mst-13.2.git] / ext / XS / APItest / t / hash.t
CommitLineData
0314122a 1#!perl -w
2
3BEGIN {
4 chdir 't' if -d 't';
5 @INC = '../lib';
6 push @INC, "::lib:$MacPerl::Architecture:" if $^O eq 'MacOS';
7 require Config; import Config;
8 if ($Config{'extensions'} !~ /\bXS\/APItest\b/) {
9 # Look, I'm using this fully-qualified variable more than once!
10 my $arch = $MacPerl::Architecture;
11 print "1..0 # Skip: XS::APItest was not built\n";
12 exit 0;
13 }
14}
15
3128e575 16use strict;
17use utf8;
0314122a 18use Tie::Hash;
3128e575 19use Test::More 'no_plan';
20
55289a74 21BEGIN {use_ok('XS::APItest')};
0314122a 22
3128e575 23sub preform_test;
24sub test_present;
25sub test_absent;
26sub test_delete_present;
27sub test_delete_absent;
28sub brute_force_exists;
29sub test_store;
30sub test_fetch_present;
31sub test_fetch_absent;
0314122a 32
b60cf05a 33my $utf8_for_258 = chr 258;
34utf8::encode $utf8_for_258;
0314122a 35
3128e575 36my @testkeys = ('N', chr 198, chr 256);
b60cf05a 37my @keys = (@testkeys, $utf8_for_258);
0314122a 38
3128e575 39foreach (@keys) {
40 utf8::downgrade $_, 1;
41}
42main_tests (\@keys, \@testkeys, '');
0314122a 43
3128e575 44foreach (@keys) {
45 utf8::upgrade $_;
46}
47main_tests (\@keys, \@testkeys, ' [utf8 hash]');
0314122a 48
3128e575 49{
50 my %h = (a=>'cheat');
51 tie %h, 'Tie::StdHash';
9568a123 52 # is bug 36327 fixed?
53 my $result = ($] > 5.009) ? undef : 1;
54
55 is (XS::APItest::Hash::store(\%h, chr 258, 1), $result);
3128e575 56
57 ok (!exists $h{$utf8_for_258},
58 "hv_store doesn't insert a key with the raw utf8 on a tied hash");
59}
0314122a 60
9568a123 61if ($] > 5.009) {
5d2b1485 62 my $strtab = strtab();
63 is (ref $strtab, 'HASH', "The shared string table quacks like a hash");
8ca60cef 64 my $wibble = "\0";
5d2b1485 65 eval {
8ca60cef 66 $strtab->{$wibble}++;
5d2b1485 67 };
68 my $prefix = "Cannot modify shared string table in hv_";
69 my $what = $prefix . 'fetch';
70 like ($@, qr/^$what/,$what);
71 eval {
72 XS::APItest::Hash::store($strtab, 'Boom!', 1)
73 };
74 $what = $prefix . 'store';
75 like ($@, qr/^$what/, $what);
76 if (0) {
77 A::B->method();
78 }
79 # DESTROY should be in there.
80 eval {
81 delete $strtab->{DESTROY};
82 };
83 $what = $prefix . 'delete';
84 like ($@, qr/^$what/, $what);
85 # I can't work out how to get to the code that flips the wasutf8 flag on
86 # the hash key without some ikcy XS
87}
2dc92170 88
89{
90 is_deeply([&XS::APItest::Hash::test_hv_free_ent], [2,2,1,1],
91 "hv_free_ent frees the value immediately");
92 is_deeply([&XS::APItest::Hash::test_hv_delayfree_ent], [2,2,2,1],
93 "hv_delayfree_ent keeps the value around until FREETMPS");
94}
35ab5632 95
96foreach my $in ("", "N", "a\0b") {
97 my $got = XS::APItest::Hash::test_share_unshare_pvn($in);
98 is ($got, $in, "test_share_unshare_pvn");
99}
100
55289a74 101if ($] > 5.009) {
53c40a8f 102 foreach ([\&XS::APItest::Hash::rot13_hash, \&rot13, "rot 13"],
103 [\&XS::APItest::Hash::bitflip_hash, \&bitflip, "bitflip"],
104 ) {
105 my ($setup, $mapping, $name) = @$_;
106 my %hash;
107 my %placebo = (a => 1, p => 2, i => 4, e => 8);
108 $setup->(\%hash);
109 $hash{a}++; @hash{qw(p i e)} = (2, 4, 8);
110
111 test_U_hash(\%hash, \%placebo, [f => 9, g => 10, h => 11], $mapping,
112 $name);
113 }
850f5f16 114 foreach my $upgrade_o (0, 1) {
115 foreach my $upgrade_n (0, 1) {
116 my (%hash, %placebo);
117 XS::APItest::Hash::bitflip_hash(\%hash);
118 foreach my $new (["7", 65, 67, 80],
119 ["8", 163, 171, 215],
120 ["U", 2603, 2604, 2604],
121 ) {
122 foreach my $code (78, 240, 256, 1336) {
123 my $key = chr $code;
124 # This is the UTF-8 byte sequence for the key.
125 my $key_utf8 = $key;
126 utf8::encode($key_utf8);
127 if ($upgrade_o) {
128 $key .= chr 256;
129 chop $key;
130 }
131 $hash{$key} = $placebo{$key} = $code;
132 $hash{$key_utf8} = $placebo{$key_utf8} = "$code as UTF-8";
133 }
134 my $name = 'bitflip ' . shift @$new;
135 my @new_kv;
136 foreach my $code (@$new) {
137 my $key = chr $code;
138 if ($upgrade_n) {
139 $key .= chr 256;
140 chop $key;
141 }
142 push @new_kv, $key, $_;
143 }
144
145 $name .= ' upgraded(orig) ' if $upgrade_o;
146 $name .= ' upgraded(new) ' if $upgrade_n;
147 test_U_hash(\%hash, \%placebo, \@new_kv, \&bitflip, $name);
148 }
149 }
150 }
53c40a8f 151}
152
527df579 153{
154 my $as_utf8 = "\241" . chr 256;
155 chop $as_utf8;
156 my $as_bytes = "\243";
157 foreach my $key ('N', $as_bytes, $as_utf8, "\x{2623}") {
158 my $ord = ord $key;
58ca560a 159 foreach my $hash_it (0, 1) {
160 foreach my $what (qw(pv sv)) {
161 my %hash;
162 is (XS::APItest::Hash::common({hv => \%hash,
163 "key$what" => $key,
164 val => $ord,
165 "hash_$what" => $hash_it,
166 action =>
167 XS::APItest::HV_FETCH_ISSTORE}),
168 $ord, "store $ord with $what \$hash_it = $hash_it");
169 is_deeply ([each %hash], [$key, $ord],
170 "First key read is good");
171 is_deeply ([each %hash], [], "No second key good");
172
173 is ($hash{$key}, $ord, "Direct hash read finds $ord");
174 }
527df579 175 }
176 }
177}
178
53c40a8f 179exit;
180
181################################ The End ################################
182
183sub test_U_hash {
184 my ($hash, $placebo, $new, $mapping, $message) = @_;
185 my @hitlist = keys %$placebo;
186 print "# $message\n";
b54b4831 187
53c40a8f 188 my @keys = sort keys %$hash;
189 is ("@keys", join(' ', sort($mapping->(keys %$placebo))),
190 "uvar magic called exactly once on store");
b54b4831 191
850f5f16 192 is (keys %$hash, keys %$placebo);
55289a74 193
53c40a8f 194 my $victim = shift @hitlist;
195 is (delete $hash->{$victim}, delete $placebo->{$victim});
55289a74 196
850f5f16 197 is (keys %$hash, keys %$placebo);
53c40a8f 198 @keys = sort keys %$hash;
199 is ("@keys", join(' ', sort($mapping->(keys %$placebo))));
55289a74 200
53c40a8f 201 $victim = shift @hitlist;
202 is (XS::APItest::Hash::delete_ent ($hash, $victim,
55289a74 203 XS::APItest::HV_DISABLE_UVAR_XKEY),
204 undef, "Deleting a known key with conversion disabled fails (ent)");
850f5f16 205 is (keys %$hash, keys %$placebo);
55289a74 206
53c40a8f 207 is (XS::APItest::Hash::delete_ent ($hash, $victim, 0),
208 delete $placebo->{$victim},
209 "Deleting a known key with conversion enabled works (ent)");
850f5f16 210 is (keys %$hash, keys %$placebo);
53c40a8f 211 @keys = sort keys %$hash;
212 is ("@keys", join(' ', sort($mapping->(keys %$placebo))));
55289a74 213
53c40a8f 214 $victim = shift @hitlist;
215 is (XS::APItest::Hash::delete ($hash, $victim,
55289a74 216 XS::APItest::HV_DISABLE_UVAR_XKEY),
217 undef, "Deleting a known key with conversion disabled fails");
850f5f16 218 is (keys %$hash, keys %$placebo);
53c40a8f 219
220 is (XS::APItest::Hash::delete ($hash, $victim, 0),
221 delete $placebo->{$victim},
222 "Deleting a known key with conversion enabled works");
850f5f16 223 is (keys %$hash, keys %$placebo);
53c40a8f 224 @keys = sort keys %$hash;
225 is ("@keys", join(' ', sort($mapping->(keys %$placebo))));
226
227 my ($k, $v) = splice @$new, 0, 2;
228 $hash->{$k} = $v;
229 $placebo->{$k} = $v;
850f5f16 230 is (keys %$hash, keys %$placebo);
53c40a8f 231 @keys = sort keys %$hash;
232 is ("@keys", join(' ', sort($mapping->(keys %$placebo))));
233
234 ($k, $v) = splice @$new, 0, 2;
235 is (XS::APItest::Hash::store_ent($hash, $k, $v), $v, "store_ent");
236 $placebo->{$k} = $v;
850f5f16 237 is (keys %$hash, keys %$placebo);
53c40a8f 238 @keys = sort keys %$hash;
239 is ("@keys", join(' ', sort($mapping->(keys %$placebo))));
240
241 ($k, $v) = splice @$new, 0, 2;
242 is (XS::APItest::Hash::store($hash, $k, $v), $v, "store");
53c40a8f 243 $placebo->{$k} = $v;
850f5f16 244 is (keys %$hash, keys %$placebo);
53c40a8f 245 @keys = sort keys %$hash;
246 is ("@keys", join(' ', sort($mapping->(keys %$placebo))));
247
248 @hitlist = keys %$placebo;
249 $victim = shift @hitlist;
250 is (XS::APItest::Hash::fetch_ent($hash, $victim), $placebo->{$victim},
251 "fetch_ent");
252 is (XS::APItest::Hash::fetch_ent($hash, $mapping->($victim)), undef,
bdee33e4 253 "fetch_ent (missing)");
254
53c40a8f 255 $victim = shift @hitlist;
256 is (XS::APItest::Hash::fetch($hash, $victim), $placebo->{$victim},
257 "fetch");
258 is (XS::APItest::Hash::fetch($hash, $mapping->($victim)), undef,
bdee33e4 259 "fetch (missing)");
260
53c40a8f 261 $victim = shift @hitlist;
262 ok (XS::APItest::Hash::exists_ent($hash, $victim), "exists_ent");
263 ok (!XS::APItest::Hash::exists_ent($hash, $mapping->($victim)),
bdee33e4 264 "exists_ent (missing)");
265
53c40a8f 266 $victim = shift @hitlist;
6b4de907 267 die "Need a victim" unless defined $victim;
53c40a8f 268 ok (XS::APItest::Hash::exists($hash, $victim), "exists");
269 ok (!XS::APItest::Hash::exists($hash, $mapping->($victim)),
270 "exists (missing)");
6b4de907 271
272 is (XS::APItest::Hash::common({hv => $hash, keysv => $victim}),
273 $placebo->{$victim}, "common (fetch)");
274 is (XS::APItest::Hash::common({hv => $hash, keypv => $victim}),
275 $placebo->{$victim}, "common (fetch pv)");
276 is (XS::APItest::Hash::common({hv => $hash, keysv => $victim,
277 action => XS::APItest::HV_DISABLE_UVAR_XKEY}),
278 undef, "common (fetch) missing");
279 is (XS::APItest::Hash::common({hv => $hash, keypv => $victim,
280 action => XS::APItest::HV_DISABLE_UVAR_XKEY}),
281 undef, "common (fetch pv) missing");
282 is (XS::APItest::Hash::common({hv => $hash, keysv => $mapping->($victim),
283 action => XS::APItest::HV_DISABLE_UVAR_XKEY}),
284 $placebo->{$victim}, "common (fetch) missing mapped");
285 is (XS::APItest::Hash::common({hv => $hash, keypv => $mapping->($victim),
286 action => XS::APItest::HV_DISABLE_UVAR_XKEY}),
287 $placebo->{$victim}, "common (fetch pv) missing mapped");
b54b4831 288}
289
3128e575 290sub main_tests {
291 my ($keys, $testkeys, $description) = @_;
292 foreach my $key (@$testkeys) {
293 my $lckey = ($key eq chr 198) ? chr 230 : lc $key;
294 my $unikey = $key;
295 utf8::encode $unikey;
0314122a 296
3128e575 297 utf8::downgrade $key, 1;
298 utf8::downgrade $lckey, 1;
299 utf8::downgrade $unikey, 1;
300 main_test_inner ($key, $lckey, $unikey, $keys, $description);
0314122a 301
3128e575 302 utf8::upgrade $key;
303 utf8::upgrade $lckey;
304 utf8::upgrade $unikey;
305 main_test_inner ($key, $lckey, $unikey, $keys,
306 $description . ' [key utf8 on]');
307 }
0314122a 308
3128e575 309 # hv_exists was buggy for tied hashes, in that the raw utf8 key was being
310 # used - the utf8 flag was being lost.
311 perform_test (\&test_absent, (chr 258), $keys, '');
0314122a 312
3128e575 313 perform_test (\&test_fetch_absent, (chr 258), $keys, '');
314 perform_test (\&test_delete_absent, (chr 258), $keys, '');
0314122a 315}
316
3128e575 317sub main_test_inner {
318 my ($key, $lckey, $unikey, $keys, $description) = @_;
319 perform_test (\&test_present, $key, $keys, $description);
320 perform_test (\&test_fetch_present, $key, $keys, $description);
321 perform_test (\&test_delete_present, $key, $keys, $description);
b60cf05a 322
3128e575 323 perform_test (\&test_store, $key, $keys, $description, [a=>'cheat']);
324 perform_test (\&test_store, $key, $keys, $description, []);
b60cf05a 325
3128e575 326 perform_test (\&test_absent, $lckey, $keys, $description);
327 perform_test (\&test_fetch_absent, $lckey, $keys, $description);
328 perform_test (\&test_delete_absent, $lckey, $keys, $description);
b60cf05a 329
3128e575 330 return if $unikey eq $key;
331
332 perform_test (\&test_absent, $unikey, $keys, $description);
333 perform_test (\&test_fetch_absent, $unikey, $keys, $description);
334 perform_test (\&test_delete_absent, $unikey, $keys, $description);
b60cf05a 335}
336
3128e575 337sub perform_test {
338 my ($test_sub, $key, $keys, $message, @other) = @_;
b60cf05a 339 my $printable = join ',', map {ord} split //, $key;
340
3128e575 341 my (%hash, %tiehash);
342 tie %tiehash, 'Tie::StdHash';
b60cf05a 343
3128e575 344 @hash{@$keys} = @$keys;
345 @tiehash{@$keys} = @$keys;
b60cf05a 346
3128e575 347 &$test_sub (\%hash, $key, $printable, $message, @other);
348 &$test_sub (\%tiehash, $key, $printable, "$message tie", @other);
b60cf05a 349}
350
3128e575 351sub test_present {
352 my ($hash, $key, $printable, $message) = @_;
353
354 ok (exists $hash->{$key}, "hv_exists_ent present$message $printable");
355 ok (XS::APItest::Hash::exists ($hash, $key),
356 "hv_exists present$message $printable");
b60cf05a 357}
358
3128e575 359sub test_absent {
360 my ($hash, $key, $printable, $message) = @_;
858117f8 361
3128e575 362 ok (!exists $hash->{$key}, "hv_exists_ent absent$message $printable");
363 ok (!XS::APItest::Hash::exists ($hash, $key),
364 "hv_exists absent$message $printable");
b60cf05a 365}
366
3128e575 367sub test_delete_present {
368 my ($hash, $key, $printable, $message) = @_;
b60cf05a 369
3128e575 370 my $copy = {};
371 my $class = tied %$hash;
372 if (defined $class) {
373 tie %$copy, ref $class;
374 }
375 $copy = {%$hash};
8829b5e2 376 ok (brute_force_exists ($copy, $key),
377 "hv_delete_ent present$message $printable");
3128e575 378 is (delete $copy->{$key}, $key, "hv_delete_ent present$message $printable");
8829b5e2 379 ok (!brute_force_exists ($copy, $key),
380 "hv_delete_ent present$message $printable");
3128e575 381 $copy = {%$hash};
8829b5e2 382 ok (brute_force_exists ($copy, $key),
383 "hv_delete present$message $printable");
3128e575 384 is (XS::APItest::Hash::delete ($copy, $key), $key,
385 "hv_delete present$message $printable");
8829b5e2 386 ok (!brute_force_exists ($copy, $key),
387 "hv_delete present$message $printable");
b60cf05a 388}
389
3128e575 390sub test_delete_absent {
391 my ($hash, $key, $printable, $message) = @_;
b60cf05a 392
3128e575 393 my $copy = {};
394 my $class = tied %$hash;
395 if (defined $class) {
396 tie %$copy, ref $class;
397 }
398 $copy = {%$hash};
399 is (delete $copy->{$key}, undef, "hv_delete_ent absent$message $printable");
400 $copy = {%$hash};
401 is (XS::APItest::Hash::delete ($copy, $key), undef,
402 "hv_delete absent$message $printable");
b60cf05a 403}
404
3128e575 405sub test_store {
406 my ($hash, $key, $printable, $message, $defaults) = @_;
407 my $HV_STORE_IS_CRAZY = 1;
b60cf05a 408
3128e575 409 # We are cheating - hv_store returns NULL for a store into an empty
410 # tied hash. This isn't helpful here.
0314122a 411
3128e575 412 my $class = tied %$hash;
0314122a 413
9568a123 414 # It's important to do this with nice new hashes created each time round
415 # the loop, rather than hashes in the pad, which get recycled, and may have
416 # xhv_array non-NULL
417 my $h1 = {@$defaults};
418 my $h2 = {@$defaults};
3128e575 419 if (defined $class) {
9568a123 420 tie %$h1, ref $class;
421 tie %$h2, ref $class;
422 if ($] > 5.009) {
423 # bug 36327 is fixed
424 $HV_STORE_IS_CRAZY = undef;
425 } else {
426 # HV store_ent returns 1 if there was already underlying hash storage
427 $HV_STORE_IS_CRAZY = undef unless @$defaults;
428 }
3128e575 429 }
9568a123 430 is (XS::APItest::Hash::store_ent($h1, $key, 1), $HV_STORE_IS_CRAZY,
431 "hv_store_ent$message $printable");
432 ok (brute_force_exists ($h1, $key), "hv_store_ent$message $printable");
433 is (XS::APItest::Hash::store($h2, $key, 1), $HV_STORE_IS_CRAZY,
3128e575 434 "hv_store$message $printable");
9568a123 435 ok (brute_force_exists ($h2, $key), "hv_store$message $printable");
3128e575 436}
0314122a 437
3128e575 438sub test_fetch_present {
439 my ($hash, $key, $printable, $message) = @_;
b60cf05a 440
3128e575 441 is ($hash->{$key}, $key, "hv_fetch_ent present$message $printable");
442 is (XS::APItest::Hash::fetch ($hash, $key), $key,
443 "hv_fetch present$message $printable");
0314122a 444}
445
3128e575 446sub test_fetch_absent {
447 my ($hash, $key, $printable, $message) = @_;
b60cf05a 448
3128e575 449 is ($hash->{$key}, undef, "hv_fetch_ent absent$message $printable");
450 is (XS::APItest::Hash::fetch ($hash, $key), undef,
451 "hv_fetch absent$message $printable");
452}
b60cf05a 453
3128e575 454sub brute_force_exists {
455 my ($hash, $key) = @_;
456 foreach (keys %$hash) {
457 return 1 if $key eq $_;
458 }
459 return 0;
b60cf05a 460}
b54b4831 461
462sub rot13 {
463 my @results = map {my $a = $_; $a =~ tr/A-Za-z/N-ZA-Mn-za-m/; $a} @_;
464 wantarray ? @results : $results[0];
465}
53c40a8f 466
467sub bitflip {
468 my @results = map {join '', map {chr(32 ^ ord $_)} split '', $_} @_;
469 wantarray ? @results : $results[0];
470}