1*5486feefSafresh1package Test2::Compare::Hash; 2*5486feefSafresh1use strict; 3*5486feefSafresh1use warnings; 4*5486feefSafresh1 5*5486feefSafresh1use base 'Test2::Compare::Base'; 6*5486feefSafresh1 7*5486feefSafresh1our $VERSION = '0.000162'; 8*5486feefSafresh1 9*5486feefSafresh1use Test2::Util::HashBase qw/inref meta ending items order for_each_key for_each_val/; 10*5486feefSafresh1 11*5486feefSafresh1use Carp qw/croak confess/; 12*5486feefSafresh1use Scalar::Util qw/reftype/; 13*5486feefSafresh1 14*5486feefSafresh1sub init { 15*5486feefSafresh1 my $self = shift; 16*5486feefSafresh1 17*5486feefSafresh1 if( defined( my $ref = $self->{+INREF} ) ) { 18*5486feefSafresh1 croak "Cannot specify both 'inref' and 'items'" if $self->{+ITEMS}; 19*5486feefSafresh1 croak "Cannot specify both 'inref' and 'order'" if $self->{+ORDER}; 20*5486feefSafresh1 $self->{+ITEMS} = {%$ref}; 21*5486feefSafresh1 $self->{+ORDER} = [sort keys %$ref]; 22*5486feefSafresh1 } 23*5486feefSafresh1 else { 24*5486feefSafresh1 # Clone the ref to be safe 25*5486feefSafresh1 $self->{+ITEMS} = $self->{+ITEMS} ? {%{$self->{+ITEMS}}} : {}; 26*5486feefSafresh1 if ($self->{+ORDER}) { 27*5486feefSafresh1 my @all = keys %{$self->{+ITEMS}}; 28*5486feefSafresh1 my %have = map { $_ => 1 } @{$self->{+ORDER}}; 29*5486feefSafresh1 my @missing = grep { !$have{$_} } @all; 30*5486feefSafresh1 croak "Keys are missing from the 'order' array: " . join(', ', sort @missing) 31*5486feefSafresh1 if @missing; 32*5486feefSafresh1 } 33*5486feefSafresh1 else { 34*5486feefSafresh1 $self->{+ORDER} = [sort keys %{$self->{+ITEMS}}]; 35*5486feefSafresh1 } 36*5486feefSafresh1 } 37*5486feefSafresh1 38*5486feefSafresh1 $self->{+FOR_EACH_KEY} ||= []; 39*5486feefSafresh1 $self->{+FOR_EACH_VAL} ||= []; 40*5486feefSafresh1 41*5486feefSafresh1 $self->SUPER::init(); 42*5486feefSafresh1} 43*5486feefSafresh1 44*5486feefSafresh1sub name { '<HASH>' } 45*5486feefSafresh1 46*5486feefSafresh1sub meta_class { 'Test2::Compare::Meta' } 47*5486feefSafresh1 48*5486feefSafresh1sub verify { 49*5486feefSafresh1 my $self = shift; 50*5486feefSafresh1 my %params = @_; 51*5486feefSafresh1 my ($got, $exists) = @params{qw/got exists/}; 52*5486feefSafresh1 53*5486feefSafresh1 return 0 unless $exists; 54*5486feefSafresh1 return 0 unless defined $got; 55*5486feefSafresh1 return 0 unless ref($got); 56*5486feefSafresh1 return 0 unless reftype($got) eq 'HASH'; 57*5486feefSafresh1 return 1; 58*5486feefSafresh1} 59*5486feefSafresh1 60*5486feefSafresh1sub add_prop { 61*5486feefSafresh1 my $self = shift; 62*5486feefSafresh1 $self->{+META} = $self->meta_class->new unless defined $self->{+META}; 63*5486feefSafresh1 $self->{+META}->add_prop(@_); 64*5486feefSafresh1} 65*5486feefSafresh1 66*5486feefSafresh1sub add_field { 67*5486feefSafresh1 my $self = shift; 68*5486feefSafresh1 my ($name, $check) = @_; 69*5486feefSafresh1 70*5486feefSafresh1 croak "field name is required" 71*5486feefSafresh1 unless defined $name; 72*5486feefSafresh1 73*5486feefSafresh1 croak "field '$name' has already been specified" 74*5486feefSafresh1 if exists $self->{+ITEMS}->{$name}; 75*5486feefSafresh1 76*5486feefSafresh1 push @{$self->{+ORDER}} => $name; 77*5486feefSafresh1 $self->{+ITEMS}->{$name} = $check; 78*5486feefSafresh1} 79*5486feefSafresh1 80*5486feefSafresh1sub add_for_each_key { 81*5486feefSafresh1 my $self = shift; 82*5486feefSafresh1 push @{$self->{+FOR_EACH_KEY}} => @_; 83*5486feefSafresh1} 84*5486feefSafresh1 85*5486feefSafresh1sub add_for_each_val { 86*5486feefSafresh1 my $self = shift; 87*5486feefSafresh1 push @{$self->{+FOR_EACH_VAL}} => @_; 88*5486feefSafresh1} 89*5486feefSafresh1 90*5486feefSafresh1sub deltas { 91*5486feefSafresh1 my $self = shift; 92*5486feefSafresh1 my %params = @_; 93*5486feefSafresh1 my ($got, $convert, $seen) = @params{qw/got convert seen/}; 94*5486feefSafresh1 95*5486feefSafresh1 my @deltas; 96*5486feefSafresh1 my $items = $self->{+ITEMS}; 97*5486feefSafresh1 my $each_key = $self->{+FOR_EACH_KEY}; 98*5486feefSafresh1 my $each_val = $self->{+FOR_EACH_VAL}; 99*5486feefSafresh1 100*5486feefSafresh1 # Make a copy that we can munge as needed. 101*5486feefSafresh1 my %fields = %$got; 102*5486feefSafresh1 103*5486feefSafresh1 my $meta = $self->{+META}; 104*5486feefSafresh1 push @deltas => $meta->deltas(%params) if defined $meta; 105*5486feefSafresh1 106*5486feefSafresh1 for my $key (@{$self->{+ORDER}}) { 107*5486feefSafresh1 my $check = $convert->($items->{$key}); 108*5486feefSafresh1 my $exists = exists $fields{$key}; 109*5486feefSafresh1 my $val = delete $fields{$key}; 110*5486feefSafresh1 111*5486feefSafresh1 if ($exists) { 112*5486feefSafresh1 for my $kcheck (@$each_key) { 113*5486feefSafresh1 $kcheck = $convert->($kcheck); 114*5486feefSafresh1 115*5486feefSafresh1 push @deltas => $kcheck->run( 116*5486feefSafresh1 id => [HASHKEY => $key], 117*5486feefSafresh1 convert => $convert, 118*5486feefSafresh1 seen => $seen, 119*5486feefSafresh1 exists => $exists, 120*5486feefSafresh1 got => $key, 121*5486feefSafresh1 ); 122*5486feefSafresh1 } 123*5486feefSafresh1 124*5486feefSafresh1 for my $vcheck (@$each_val) { 125*5486feefSafresh1 $vcheck = $convert->($vcheck); 126*5486feefSafresh1 127*5486feefSafresh1 push @deltas => $vcheck->run( 128*5486feefSafresh1 id => [HASH => $key], 129*5486feefSafresh1 convert => $convert, 130*5486feefSafresh1 seen => $seen, 131*5486feefSafresh1 exists => $exists, 132*5486feefSafresh1 got => $val, 133*5486feefSafresh1 ); 134*5486feefSafresh1 } 135*5486feefSafresh1 } 136*5486feefSafresh1 137*5486feefSafresh1 push @deltas => $check->run( 138*5486feefSafresh1 id => [HASH => $key], 139*5486feefSafresh1 convert => $convert, 140*5486feefSafresh1 seen => $seen, 141*5486feefSafresh1 exists => $exists, 142*5486feefSafresh1 $exists ? (got => $val) : (), 143*5486feefSafresh1 ); 144*5486feefSafresh1 } 145*5486feefSafresh1 146*5486feefSafresh1 if (keys %fields) { 147*5486feefSafresh1 for my $key (sort keys %fields) { 148*5486feefSafresh1 my $val = $fields{$key}; 149*5486feefSafresh1 150*5486feefSafresh1 for my $kcheck (@$each_key) { 151*5486feefSafresh1 $kcheck = $convert->($kcheck); 152*5486feefSafresh1 153*5486feefSafresh1 push @deltas => $kcheck->run( 154*5486feefSafresh1 id => [HASHKEY => $key], 155*5486feefSafresh1 convert => $convert, 156*5486feefSafresh1 seen => $seen, 157*5486feefSafresh1 got => $key, 158*5486feefSafresh1 exists => 1, 159*5486feefSafresh1 ); 160*5486feefSafresh1 } 161*5486feefSafresh1 162*5486feefSafresh1 for my $vcheck (@$each_val) { 163*5486feefSafresh1 $vcheck = $convert->($vcheck); 164*5486feefSafresh1 165*5486feefSafresh1 push @deltas => $vcheck->run( 166*5486feefSafresh1 id => [HASH => $key], 167*5486feefSafresh1 convert => $convert, 168*5486feefSafresh1 seen => $seen, 169*5486feefSafresh1 got => $val, 170*5486feefSafresh1 exists => 1, 171*5486feefSafresh1 ); 172*5486feefSafresh1 } 173*5486feefSafresh1 174*5486feefSafresh1 # if items are left over, and ending is true, we have a problem! 175*5486feefSafresh1 if ($self->{+ENDING}) { 176*5486feefSafresh1 push @deltas => $self->delta_class->new( 177*5486feefSafresh1 dne => 'check', 178*5486feefSafresh1 verified => undef, 179*5486feefSafresh1 id => [HASH => $key], 180*5486feefSafresh1 got => $val, 181*5486feefSafresh1 check => undef, 182*5486feefSafresh1 183*5486feefSafresh1 $self->{+ENDING} eq 'implicit' ? (note => 'implicit end') : (), 184*5486feefSafresh1 ); 185*5486feefSafresh1 } 186*5486feefSafresh1 } 187*5486feefSafresh1 } 188*5486feefSafresh1 189*5486feefSafresh1 return @deltas; 190*5486feefSafresh1} 191*5486feefSafresh1 192*5486feefSafresh11; 193*5486feefSafresh1 194*5486feefSafresh1__END__ 195*5486feefSafresh1 196*5486feefSafresh1=pod 197*5486feefSafresh1 198*5486feefSafresh1=encoding UTF-8 199*5486feefSafresh1 200*5486feefSafresh1=head1 NAME 201*5486feefSafresh1 202*5486feefSafresh1Test2::Compare::Hash - Representation of a hash in a deep comparison. 203*5486feefSafresh1 204*5486feefSafresh1=head1 DESCRIPTION 205*5486feefSafresh1 206*5486feefSafresh1In deep comparisons this class is used to represent a hash. 207*5486feefSafresh1 208*5486feefSafresh1=head1 SOURCE 209*5486feefSafresh1 210*5486feefSafresh1The source code repository for Test2-Suite can be found at 211*5486feefSafresh1F<https://github.com/Test-More/Test2-Suite/>. 212*5486feefSafresh1 213*5486feefSafresh1=head1 MAINTAINERS 214*5486feefSafresh1 215*5486feefSafresh1=over 4 216*5486feefSafresh1 217*5486feefSafresh1=item Chad Granum E<lt>exodist@cpan.orgE<gt> 218*5486feefSafresh1 219*5486feefSafresh1=back 220*5486feefSafresh1 221*5486feefSafresh1=head1 AUTHORS 222*5486feefSafresh1 223*5486feefSafresh1=over 4 224*5486feefSafresh1 225*5486feefSafresh1=item Chad Granum E<lt>exodist@cpan.orgE<gt> 226*5486feefSafresh1 227*5486feefSafresh1=back 228*5486feefSafresh1 229*5486feefSafresh1=head1 COPYRIGHT 230*5486feefSafresh1 231*5486feefSafresh1Copyright 2018 Chad Granum E<lt>exodist@cpan.orgE<gt>. 232*5486feefSafresh1 233*5486feefSafresh1This program is free software; you can redistribute it and/or 234*5486feefSafresh1modify it under the same terms as Perl itself. 235*5486feefSafresh1 236*5486feefSafresh1See F<http://dev.perl.org/licenses/> 237*5486feefSafresh1 238*5486feefSafresh1=cut 239