1#!perl 2 3BEGIN { 4 if ($ENV{PERL_CORE}) { 5 chdir 't' if -d 't'; 6 @INC = '../lib'; 7 } 8} 9 10use strict; use warnings; 11use Test::More; 12my $n_tests = 0; 13 14use Hash::Util::FieldHash qw( :all); 15my $ob_reg = Hash::Util::FieldHash::_ob_reg; 16 17######################### 18 19my $fieldhash_mode = 2; 20 21# define ref types to use with some tests 22my @test_types; 23BEGIN { 24 # skipping CODE refs, they are differently scoped 25 @test_types = qw( SCALAR ARRAY HASH GLOB); 26} 27 28### The id() function 29{ 30 BEGIN { $n_tests += 4 } 31 my $ref = []; 32 is id( $ref), refaddr( $ref), "id is refaddr"; 33 my %h; 34 Hash::Util::FieldHash::_fieldhash \ %h, $fieldhash_mode; 35 $h{ $ref} = (); 36 my ( $key) = keys %h; 37 is id( $ref), $key, "id is FieldHash key"; 38 my $scalar = 'string'; 39 is id( $scalar), $scalar, "string passes unchanged"; 40 $scalar = 1234; 41 is id( $scalar), $scalar, "number passes unchanged"; 42} 43 44### idhash functionality 45{ 46 BEGIN { $n_tests += 3 } 47 Hash::Util::FieldHash::idhash my %h; 48 my $ref = sub {}; 49 my $val = 123; 50 $h{ $ref} = $val; 51 my ( $key) = keys %h; 52 is $key, id( $ref), "idhash key correct"; 53 is $h{ $ref}, $val, "value retrieved through ref"; 54 is scalar keys %$ob_reg, 0, "no auto-registry in idhash"; 55} 56 57### the register() and id_2obj functions 58{ 59 BEGIN { $n_tests += 9 } 60 my $obj = {}; 61 my $id = id( $obj); 62 is id_2obj( $id), undef, "unregistered object not retrieved"; 63 is scalar keys %$ob_reg, 0, "object registry empty"; 64 is register( $obj), $obj, "object returned by register"; 65 is scalar keys %$ob_reg, 1, "object registry nonempty"; 66 is id_2obj( $id), $obj, "registered object retrieved"; 67 my %hash; 68 register( $obj, \ %hash); 69 $hash{ $id} = 123; 70 is scalar keys %hash, 1, "key present in registered hash"; 71 undef $obj; 72 is scalar keys %hash, 0, "key collected from registered hash"; 73 is scalar keys %$ob_reg, 0, "object registry empty again"; 74 eval { register( 1234) }; 75 like $@, qr/^Attempt to register/, "registering non-ref is fatal"; 76 77} 78 79### Object auto-registry 80 81BEGIN { $n_tests += 3 } 82{ 83 { 84 my $obj = {}; 85 { 86 my $h = {}; 87 Hash::Util::FieldHash::_fieldhash $h, $fieldhash_mode; 88 $h->{ $obj} = 123; 89 is( keys %$ob_reg, 1, "one object registered"); 90 } 91 # field hash stays alive until $obj dies 92 is( keys %$ob_reg, 1, "object still registered"); 93 } 94 is( keys %$ob_reg, 0, "object unregistered"); 95} 96 97### existence/retrieval/deletion 98BEGIN { $n_tests += 6 } 99{ 100 no warnings 'misc'; 101 my $val = 123; 102 Hash::Util::FieldHash::_fieldhash \ my( %h), $fieldhash_mode; 103 for ( [ str => 'abc'], [ ref => {}] ) { 104 my ( $keytype, $key) = @$_; 105 $h{ $key} = $val; 106 ok( exists $h{ $key}, "existence ($keytype)"); 107 is( $h{ $key}, $val, "retrieval ($keytype)"); 108 delete $h{ $key}; 109 is( keys %h, 0, "deletion ($keytype)"); 110 } 111} 112 113### id-action (stringification independent of bless) 114BEGIN { $n_tests += 5 } 115# use Scalar::Util qw( refaddr); 116{ 117 my( %f, %g, %h, %i); 118 Hash::Util::FieldHash::_fieldhash \ %f, $fieldhash_mode; 119 Hash::Util::FieldHash::_fieldhash \ %g, $fieldhash_mode; 120 my $val = 123; 121 my $key = []; 122 $f{ $key} = $val; 123 is( $f{ $key}, $val, "plain key set in field"); 124 my ( $id) = keys %f; 125 my $refaddr = refaddr($key); 126 is $id, $refaddr, "key is refaddr"; 127 bless $key; 128 is( $f{ $key}, $val, "access through blessed"); 129 $key = []; 130 $h{ $key} = $val; 131 is( $h{ $key}, $val, "plain key set in hash"); 132 bless $key; 133 isnt( $h{ $key}, $val, "no access through blessed"); 134} 135 136# Garbage collection 137BEGIN { $n_tests += 1 + 2*( 3*@test_types + 5) + 1 + 2 } 138 139{ 140 my %h; 141 Hash::Util::FieldHash::_fieldhash \ %h, $fieldhash_mode; 142 $h{ []} = 123; 143 is( keys %h, 0, "blip"); 144} 145 146for my $preload ( [], [ map {}, 1 .. 3] ) { 147 my $pre = @$preload ? ' (preloaded)' : ''; 148 my %f; 149 Hash::Util::FieldHash::_fieldhash \ %f, $fieldhash_mode; 150 my @preval = map "$_", @$preload; 151 @f{ @$preload} = @preval; 152 # Garbage collection separately 153 for my $type ( @test_types) { 154 { 155 my $ref = gen_ref( $type); 156 $f{ $ref} = $type; 157 my ( $val) = grep $_ eq $type, values %f; 158 is( $val, $type, "$type visible$pre"); 159 is( 160 keys %$ob_reg, 161 1 + @$preload, 162 "$type obj registered$pre" 163 ); 164 } 165 is( keys %f, @$preload, "$type gone$pre"); 166 } 167 168 # Garbage collection collectively 169 is( keys %$ob_reg, @$preload, "no objs remaining$pre"); 170 { 171 my @refs = map gen_ref( $_), @test_types; 172 @f{ @refs} = @test_types; 173 ok( 174 eq_set( [ values %f], [ @test_types, @preval]), 175 "all types present$pre", 176 ); 177 is( 178 keys %$ob_reg, 179 @test_types + @$preload, 180 "all types registered$pre", 181 ); 182 } 183 die "preload gone" unless defined $preload; 184 ok( eq_set( [ values %f], \ @preval), "all types gone$pre"); 185 is( keys %$ob_reg, @$preload, "all types unregistered$pre"); 186} 187is( keys %$ob_reg, 0, "preload gone after loop"); 188 189# autovivified key 190{ 191 my %h; 192 Hash::Util::FieldHash::_fieldhash \ %h, $fieldhash_mode; 193 my $ref = {}; 194 my $x = $h{ $ref}->[ 0]; 195 is keys %h, 1, "autovivified key present"; 196 undef $ref; 197 is keys %h, 0, "autovivified key collected"; 198} 199 200# big key sets 201BEGIN { $n_tests += 8 } 202{ 203 my $size = 10_000; 204 my %f; 205 Hash::Util::FieldHash::_fieldhash \ %f, $fieldhash_mode; 206 { 207 my @refs = map [], 1 .. $size; 208 $f{ $_} = 1 for @refs; 209 is( keys %f, $size, "many keys singly"); 210 is( 211 keys %$ob_reg, 212 $size, 213 "many objects singly", 214 ); 215 } 216 is( keys %f, 0, "many keys singly gone"); 217 is( 218 keys %$ob_reg, 219 0, 220 "many objects singly unregistered", 221 ); 222 223 { 224 my @refs = map [], 1 .. $size; 225 @f{ @refs } = ( 1) x @refs; 226 is( keys %f, $size, "many keys at once"); 227 is( 228 keys %$ob_reg, 229 $size, 230 "many objects at once", 231 ); 232 } 233 is( keys %f, 0, "many keys at once gone"); 234 is( 235 keys %$ob_reg, 236 0, 237 "many objects at once unregistered", 238 ); 239} 240 241# many field hashes 242BEGIN { $n_tests += 6 } 243{ 244 my $n_fields = 1000; 245 my @fields = map {}, $n_fields; 246 Hash::Util::FieldHash::_fieldhash( $_, $fieldhash_mode) for @fields; 247 my @obs = map gen_ref( $_), @test_types; 248 my $n_obs = @obs; 249 for my $field ( @fields ) { 250 @{ $field }{ @obs} = map ref, @obs; 251 } 252 my $err = grep keys %$_ != @obs, @fields; 253 is( $err, 0, "$n_obs entries in $n_fields fields"); 254 is( keys %$ob_reg, @obs, "$n_obs obs registered"); 255 pop @obs; 256 $err = grep keys %$_ != @obs, @fields; 257 is( $err, 0, "one entry gone from $n_fields fields"); 258 is( keys %$ob_reg, @obs, "one ob unregistered"); 259 @obs = (); 260 $err = grep keys %$_ != @obs, @fields; 261 is( $err, 0, "all entries gone from $n_fields fields"); 262 is( keys %$ob_reg, @obs, "all obs unregistered"); 263} 264 265 266# direct hash assignment 267BEGIN { $n_tests += 4 } 268{ 269 Hash::Util::FieldHash::_fieldhash( $_, $fieldhash_mode) for \ my( %f, %g, %h); 270 my $size = 6; 271 my @obs = map [], 1 .. $size; 272 @f{ @obs} = ( 1) x $size; 273 $g{ $_} = $f{ $_} for keys %f; # single assignment 274 %h = %f; # wholesale assignment 275 @obs = (); 276 is keys %$ob_reg, 0, "all keys collected"; 277 is keys %f, 0, "orig garbage-collected"; 278 is keys %g, 0, "single-copy garbage-collected"; 279 is keys %h, 0, "wholesale-copy garbage-collected"; 280} 281 282{ 283 # prototypes in place? 284 my %proto_tab = ( 285 fieldhash => '\\%', 286 fieldhashes => '', 287 idhash => '\\%', 288 idhashes => '', 289 id => '$', 290 id_2obj => '$', 291 register => '$@', 292 ); 293 294 295 my @notfound = grep !exists $proto_tab{ $_} => 296 @Hash::Util::FieldHash::EXPORT_OK; 297 ok @notfound == 0, "All exports in table"; 298 is prototype( "Hash::Util::FieldHash::$_") || '', $proto_tab{ $_}, 299 "$_ has prototype ($proto_tab{ $_})" for 300 @Hash::Util::FieldHash::EXPORT_OK; 301 302 BEGIN { $n_tests += 1 + @Hash::Util::FieldHash::EXPORT_OK } 303} 304 305{ 306 BEGIN { $n_tests += 1 } 307 Hash::Util::FieldHash::_fieldhash \ my( %h), $fieldhash_mode; 308 bless \ %h, 'abc'; # this bus-errors with a certain bug 309 ok( 1, "no bus error on bless") 310} 311 312BEGIN { plan tests => $n_tests } 313 314####################################################################### 315 316sub refaddr { 317 # silence possible warnings from hex() on 64bit systems 318 no warnings 'portable'; 319 320 my $ref = shift; 321 hex +($ref =~ /\(0x([[:xdigit:]]+)\)$/)[ 0]; 322} 323 324use Symbol qw( gensym); 325 326BEGIN { 327 my %gen = ( 328 SCALAR => sub { \ my $o }, 329 ARRAY => sub { [] }, 330 HASH => sub { {} }, 331 GLOB => sub { gensym }, 332 CODE => sub { sub {} }, 333 ); 334 335 sub gen_ref { $gen{ shift()}->() } 336} 337