5 require Config; import Config;
7 if ($Config{extensions} !~ /\bHash\/Util\b/) {
8 print "1..0 # Skip: Hash::Util was not built\n";
23 lock_value unlock_value
26 hash_locked hash_unlocked
27 hashref_locked hashref_unlocked
28 hidden_keys legal_keys
30 lock_ref_keys unlock_ref_keys
31 lock_ref_value unlock_ref_value
32 lock_hashref unlock_hashref
34 hidden_ref_keys legal_ref_keys
36 hash_seed hash_value bucket_stats bucket_info bucket_array
38 lock_hash_recurse unlock_hash_recurse
40 plan tests => 236 + @Exported_Funcs;
41 use_ok 'Hash::Util', @Exported_Funcs;
43 foreach my $func (@Exported_Funcs) {
44 can_ok __PACKAGE__, $func;
47 my %hash = (foo => 42, bar => 23, locked => 'yep');
49 eval { $hash{baz} = 99; };
50 like( $@, qr/^Attempt to access disallowed key 'baz' in a restricted hash/,
52 is( $hash{bar}, 23, '$hash{bar} == 23' );
53 ok( !exists $hash{baz},'!exists $hash{baz}' );
56 ok( !exists $hash{bar},'!exists $hash{bar}' );
58 is( $hash{bar}, 69 ,'$hash{bar} == 69');
60 eval { () = $hash{i_dont_exist} };
61 like( $@, qr/^Attempt to access disallowed key 'i_dont_exist' in a restricted hash/,
64 lock_value(%hash, 'locked');
65 eval { print "# oops" if $hash{four} };
66 like( $@, qr/^Attempt to access disallowed key 'four' in a restricted hash/,
69 eval { $hash{"\x{2323}"} = 3 };
70 like( $@, qr/^Attempt to access disallowed key '(.*)' in a restricted hash/,
73 eval { delete $hash{locked} };
74 like( $@, qr/^Attempt to delete readonly key 'locked' from a restricted hash/,
75 'trying to delete a locked key' );
76 eval { $hash{locked} = 42; };
77 like( $@, qr/^Modification of a read-only value attempted/,
78 'trying to change a locked key' );
79 is( $hash{locked}, 'yep', '$hash{locked} is yep' );
81 eval { delete $hash{I_dont_exist} };
82 like( $@, qr/^Attempt to delete disallowed key 'I_dont_exist' from a restricted hash/,
83 'trying to delete a key that doesnt exist' );
85 ok( !exists $hash{I_dont_exist},'!exists $hash{I_dont_exist}' );
88 $hash{I_dont_exist} = 42;
89 is( $hash{I_dont_exist}, 42, 'unlock_keys' );
91 eval { $hash{locked} = 42; };
92 like( $@, qr/^Modification of a read-only value attempted/,
93 ' individual key still readonly' );
94 eval { delete $hash{locked} },
95 is( $@, '', ' but can be deleted :(' );
97 unlock_value(%hash, 'locked');
99 is( $hash{locked}, 42, 'unlock_value' );
103 my %hash = ( foo => 42, locked => 23 );
106 eval { %hash = ( wubble => 42 ) }; # we know this will bomb
107 like( $@, qr/^Attempt to access disallowed key 'wubble'/,'Disallowed 3' );
112 my %hash = (KEY => 'val', RO => 'val');
114 lock_value(%hash, 'RO');
116 eval { %hash = (KEY => 1) };
117 like( $@, qr/^Attempt to delete readonly key 'RO' from a restricted hash/,
118 'attempt to delete readonly key from restricted hash' );
122 my %hash = (KEY => 1, RO => 2);
124 eval { %hash = (KEY => 1, RO => 2) };
125 is( $@, '', 'No error message, as expected');
130 lock_keys(%hash, qw(foo bar));
131 is( keys %hash, 0, 'lock_keys() w/keyset shouldnt add new keys' );
133 is( keys %hash, 1, '1 element in hash' );
134 eval { $hash{wibble} = 42 };
135 like( $@, qr/^Attempt to access disallowed key 'wibble' in a restricted hash/,
136 'write threw error (locked)');
139 eval { $hash{wibble} = 23; };
140 is( $@, '', 'unlock_keys' );
144 my %hash = (foo => 42, bar => undef, baz => 0);
145 lock_keys(%hash, qw(foo bar baz up down));
146 is( keys %hash, 3, 'lock_keys() w/keyset didnt add new keys' );
147 is_deeply( \%hash, { foo => 42, bar => undef, baz => 0 },'is_deeply' );
149 eval { $hash{up} = 42; };
150 is( $@, '','No error 1' );
152 eval { $hash{wibble} = 23 };
153 like( $@, qr/^Attempt to access disallowed key 'wibble' in a restricted hash/,
158 my %hash = (foo => 42, bar => undef);
159 eval { lock_keys(%hash, qw(foo baz)); };
160 like( $@, qr/^Hash has key 'bar' which is not in the new key set/,
165 my %hash = (foo => 42, bar => 23);
167 ok( hashref_locked( \%hash ), 'hashref_locked' );
168 ok( hash_locked( %hash ), 'hash_locked' );
170 ok( Internals::SvREADONLY(%hash),'Was locked %hash' );
171 ok( Internals::SvREADONLY($hash{foo}),'Was locked $hash{foo}' );
172 ok( Internals::SvREADONLY($hash{bar}),'Was locked $hash{bar}' );
174 unlock_hash ( %hash );
175 ok( hashref_unlocked( { %hash } ), 'hashref_unlocked' );
176 ok( hash_unlocked( %hash ), 'hash_unlocked' );
178 ok( !Internals::SvREADONLY(%hash),'Was unlocked %hash' );
179 ok( !Internals::SvREADONLY($hash{foo}),'Was unlocked $hash{foo}' );
180 ok( !Internals::SvREADONLY($hash{bar}),'Was unlocked $hash{bar}' );
184 my %hash = (foo => 42, bar => 23);
185 ok( ! hashref_locked( { %hash } ), 'hashref_locked negated' );
186 ok( ! hash_locked( %hash ), 'hash_locked negated' );
189 ok( ! hashref_unlocked( \%hash ), 'hashref_unlocked negated' );
190 ok( ! hash_unlocked( %hash ), 'hash_unlocked negated' );
194 eval { () = $ENV{I_DONT_EXIST} };
197 qr/^Attempt to access disallowed key 'I_DONT_EXIST' in a restricted hash/,
200 unlock_keys(%ENV); # Test::Builder cannot print test failures otherwise
205 lock_keys(%hash, 'first');
207 is (scalar keys %hash, 0, "place holder isn't a key");
209 is (scalar keys %hash, 1, "we now have a key");
211 is (scalar keys %hash, 0, "now no key");
215 $hash{interregnum} = 1.5;
216 is (scalar keys %hash, 1, "key again");
217 delete $hash{interregnum};
218 is (scalar keys %hash, 0, "no key again");
220 lock_keys(%hash, 'second');
222 is (scalar keys %hash, 0, "place holder isn't a key");
224 eval {$hash{zeroeth} = 0};
226 qr/^Attempt to access disallowed key 'zeroeth' in a restricted hash/,
227 'locked key never mentioned before should fail');
228 eval {$hash{first} = -1};
230 qr/^Attempt to access disallowed key 'first' in a restricted hash/,
231 'previously locked place holders should also fail');
232 is (scalar keys %hash, 0, "and therefore there are no keys");
234 is (scalar keys %hash, 1, "we now have just one key");
235 delete $hash{second};
236 is (scalar keys %hash, 0, "back to zero");
238 unlock_keys(%hash); # We have deliberately left a placeholder.
243 is (scalar keys %hash, 2, "two keys, values both undef");
247 is (scalar keys %hash, 2, "still two keys after locking");
249 eval {$hash{second} = -1};
251 qr/^Attempt to access disallowed key 'second' in a restricted hash/,
252 'previously locked place holders should fail');
254 is ($hash{void}, undef,
255 "undef values should not be misunderstood as placeholders");
256 is ($hash{nowt}, undef,
257 "undef values should not be misunderstood as placeholders (again)");
261 # perl #18651 - tim@consultix-inc.com found a rather nasty data dependant
262 # bug whereby hash iterators could lose hash keys (and values, as the code
263 # is common) for restricted hashes.
265 my @keys = qw(small medium large);
267 # There should be no difference whether it is restricted or not
268 foreach my $lock (0, 1) {
269 # Try setting all combinations of the 3 keys
270 foreach my $usekeys (0..7) {
272 for my $bits (0,1,2) {
273 push @usekeys, $keys[$bits] if $usekeys & (1 << $bits);
275 my %clean = map {$_ => length $_} @usekeys;
277 lock_keys ( %target, @keys ) if $lock;
279 while (my ($k, $v) = each %clean) {
284 = ($lock ? 'locked' : 'not locked') . ' keys ' . join ',', @usekeys;
286 is (scalar keys %target, scalar keys %clean, "scalar keys for $message");
287 is (scalar values %target, scalar values %clean,
288 "scalar values for $message");
289 # Yes. All these sorts are necessary. Even for "identical hashes"
290 # Because the data dependency of the test involves two of the strings
291 # colliding on the same bucket, so the iterator order (output of keys,
292 # values, each) depends on the addition order in the hash. And locking
293 # the keys of the hash involves behind the scenes key additions.
294 is_deeply( [sort keys %target] , [sort keys %clean],
295 "list keys for $message");
296 is_deeply( [sort values %target] , [sort values %clean],
297 "list values for $message");
299 is_deeply( [sort %target] , [sort %clean],
300 "hash in list context for $message");
302 my (@clean, @target);
303 while (my ($k, $v) = each %clean) {
306 while (my ($k, $v) = each %target) {
307 push @target, $k, $v;
310 is_deeply( [sort @target] , [sort @clean],
311 "iterating with each for $message");
316 # Check clear works on locked empty hashes - SEGVs on 5.8.2.
321 ok(keys(%hash) == 0, 'clear empty lock_hash() hash');
327 ok(keys(%hash) == 0, 'clear empty lock_keys() hash');
330 # Copy-on-write scalars should not be deletable after lock_hash;
332 my %hash = (key=>__PACKAGE__);
334 eval { delete $hash{key} };
335 like $@, qr/^Attempt to delete readonly key /,
336 'COW scalars are not exempt from lock_hash (delete)';
338 like $@, qr/^Attempt to delete readonly key /,
339 'COW scalars are not exempt from lock_hash (clear)';
342 my $hash_seed = hash_seed();
343 ok(defined($hash_seed) && $hash_seed ne '', "hash_seed $hash_seed");
353 bless [], __PACKAGE__;
357 for my $state ('', 'locked') {
358 my $a = Minder->new();
359 is ($counter, 1, "There is 1 object $state");
362 is ($counter, 1, "There is still 1 object $state");
364 lock_keys(%hash) if $state;
366 is ($counter, 1, "There is still 1 object $state");
368 is ($counter, 1, "Still 1 object $state");
370 is ($counter, 0, "0 objects when hash key is deleted $state");
372 is ($counter, 0, "Still 0 objects $state");
374 is ($counter, 0, "0 objects after clear $state");
378 my %hash = map {$_,$_} qw(fwiffffff foosht teeoo);
380 delete $hash{fwiffffff};
381 is (scalar keys %hash, 2,"Count of keys after delete on locked hash");
383 is (scalar keys %hash, 2,"Count of keys after unlock");
385 my ($first, $value) = each %hash;
386 is ($hash{$first}, $value, "Key has the expected value before the lock");
388 is ($hash{$first}, $value, "Key has the expected value after the lock");
390 my ($second, $v2) = each %hash;
392 is ($hash{$first}, $value, "Still correct after iterator advances");
393 is ($hash{$second}, $v2, "Other key has the expected value");
398 hv_store(%test,'x',$x);
399 is($test{x},'foo','hv_store() stored');
401 is($x,'bar','hv_store() aliased');
402 is($test{x},'bar','hv_store() aliased and stored');
406 my %hash=map { $_ => 1 } qw( a b c d e f);
409 ok(Internals::SvREADONLY(%hash),'lock_keys DDS/t 1');
410 delete @hash{qw(b e)};
411 my @hidden=sort(hidden_keys(%hash));
412 my @legal=sort(legal_keys(%hash));
413 my @keys=sort(keys(%hash));
414 #warn "@legal\n@keys\n";
415 is("@hidden","b e",'lock_keys @hidden DDS/t');
416 is("@legal","a b d e f",'lock_keys @legal DDS/t');
417 is("@keys","a d f",'lock_keys @keys DDS/t');
422 ok(Internals::SvREADONLY(%hash),'lock_keys DDS/t 2');
423 Hash::Util::unlock_keys(%hash);
424 ok(!Internals::SvREADONLY(%hash),'unlock_keys DDS/t 2');
428 lock_keys(%hash,keys(%hash),'a'..'f');
429 ok(Internals::SvREADONLY(%hash),'lock_keys args DDS/t');
430 my @hidden=sort(hidden_keys(%hash));
431 my @legal=sort(legal_keys(%hash));
432 my @keys=sort(keys(%hash));
433 is("@hidden","a b c d e f",'lock_keys() @hidden DDS/t 3');
434 is("@legal","0 2 4 6 8 a b c d e f",'lock_keys() @legal DDS/t 3');
435 is("@keys","0 2 4 6 8",'lock_keys() @keys');
438 my %hash=map { $_ => 1 } qw( a b c d e f);
440 lock_ref_keys(\%hash);
441 ok(Internals::SvREADONLY(%hash),'lock_ref_keys DDS/t');
442 delete @hash{qw(b e)};
443 my @hidden=sort(hidden_keys(%hash));
444 my @legal=sort(legal_keys(%hash));
445 my @keys=sort(keys(%hash));
446 #warn "@legal\n@keys\n";
447 is("@hidden","b e",'lock_ref_keys @hidden DDS/t 1');
448 is("@legal","a b d e f",'lock_ref_keys @legal DDS/t 1');
449 is("@keys","a d f",'lock_ref_keys @keys DDS/t 1');
453 lock_ref_keys(\%hash,keys %hash,'a'..'f');
454 ok(Internals::SvREADONLY(%hash),'lock_ref_keys args DDS/t');
455 my @hidden=sort(hidden_keys(%hash));
456 my @legal=sort(legal_keys(%hash));
457 my @keys=sort(keys(%hash));
458 is("@hidden","a b c d e f",'lock_ref_keys() @hidden DDS/t 2');
459 is("@legal","0 2 4 6 8 a b c d e f",'lock_ref_keys() @legal DDS/t 2');
460 is("@keys","0 2 4 6 8",'lock_ref_keys() @keys DDS/t 2');
464 lock_ref_keys_plus(\%hash,'a'..'f');
465 ok(Internals::SvREADONLY(%hash),'lock_ref_keys_plus args DDS/t');
466 my @hidden=sort(hidden_keys(%hash));
467 my @legal=sort(legal_keys(%hash));
468 my @keys=sort(keys(%hash));
469 is("@hidden","a b c d e f",'lock_ref_keys_plus() @hidden DDS/t');
470 is("@legal","0 2 4 6 8 a b c d e f",'lock_ref_keys_plus() @legal DDS/t');
471 is("@keys","0 2 4 6 8",'lock_ref_keys_plus() @keys DDS/t');
474 my %hash=(0..9, 'a' => 'alpha');
475 lock_ref_keys_plus(\%hash,'a'..'f');
476 ok(Internals::SvREADONLY(%hash),'lock_ref_keys_plus args overlap');
477 my @hidden=sort(hidden_keys(%hash));
478 my @legal=sort(legal_keys(%hash));
479 my @keys=sort(keys(%hash));
480 is("@hidden","b c d e f",'lock_ref_keys_plus() @hidden overlap');
481 is("@legal","0 2 4 6 8 a b c d e f",'lock_ref_keys_plus() @legal overlap');
482 is("@keys","0 2 4 6 8 a",'lock_ref_keys_plus() @keys overlap');
486 lock_keys_plus(%hash,'a'..'f');
487 ok(Internals::SvREADONLY(%hash),'lock_keys_plus args DDS/t');
488 my @hidden=sort(hidden_keys(%hash));
489 my @legal=sort(legal_keys(%hash));
490 my @keys=sort(keys(%hash));
491 is("@hidden","a b c d e f",'lock_keys_plus() @hidden DDS/t 3');
492 is("@legal","0 2 4 6 8 a b c d e f",'lock_keys_plus() @legal DDS/t 3');
493 is("@keys","0 2 4 6 8",'lock_keys_plus() @keys DDS/t 3');
496 my %hash=(0..9, 'a' => 'alpha');
497 lock_keys_plus(%hash,'a'..'f');
498 ok(Internals::SvREADONLY(%hash),'lock_keys_plus args overlap non-ref');
499 my @hidden=sort(hidden_keys(%hash));
500 my @legal=sort(legal_keys(%hash));
501 my @keys=sort(keys(%hash));
502 is("@hidden","b c d e f",'lock_keys_plus() @hidden overlap non-ref');
503 is("@legal","0 2 4 6 8 a b c d e f",'lock_keys_plus() @legal overlap non-ref');
504 is("@keys","0 2 4 6 8 a",'lock_keys_plus() @keys overlap non-ref');
508 my %hash = ('a'..'f');
511 my @lock = ('a', 'c', 'e', 'g');
512 lock_keys(%hash, @lock);
513 my $ref = all_keys(%hash, @keys, @ph);
514 my @crrack = sort(@keys);
515 my @ooooff = qw(a c e);
518 ok(ref $ref eq ref \%hash && $ref == \%hash,
519 "all_keys() - \$ref is a reference to \%hash");
520 is_deeply(\@crrack, \@ooooff, "Keys are what they should be");
521 is_deeply(\@ph, \@bam, "Placeholders in place");
527 b => [ qw( beta gamma delta ) ],
528 c => [ 'epsilon', { zeta => 'eta' }, ],
529 d => { theta => 'iota' },
531 lock_hash_recurse(%hash);
532 ok( hash_locked(%hash),
533 "lock_hash_recurse(): top-level hash locked" );
534 ok( hash_locked(%{$hash{d}}),
535 "lock_hash_recurse(): element which is hashref locked" );
536 ok( ! hash_locked(%{$hash{c}[1]}),
537 "lock_hash_recurse(): element which is hashref in array ref not locked" );
539 unlock_hash_recurse(%hash);
540 ok( hash_unlocked(%hash),
541 "unlock_hash_recurse(): top-level hash unlocked" );
542 ok( hash_unlocked(%{$hash{d}}),
543 "unlock_hash_recurse(): element which is hashref unlocked" );
544 ok( hash_unlocked(%{$hash{c}[1]}),
545 "unlock_hash_recurse(): element which is hashref in array ref not locked" );
549 my $h1= hash_value("foo");
550 my $h2= hash_value("bar");
551 is( $h1, hash_value("foo") );
552 is( $h2, hash_value("bar") );
555 my @info1= bucket_info({});
556 my @info2= bucket_info({1..10});
557 my @stats1= bucket_stats({});
558 my @stats2= bucket_stats({1..10});
559 my $array1= bucket_array({});
560 my $array2= bucket_array({1..10});
561 is("@info1","0 8 0");
562 is("@info2[0,1]","5 8");
563 is("@stats1","0 8 0");
564 is("@stats2[0,1]","5 8");
565 my @keys1= sort map { ref $_ ? @$_ : () } @$array1;
566 my @keys2= sort map { ref $_ ? @$_ : () } @$array2;
568 is("@keys2","1 3 5 7 9");