The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
package autoclassUtil;
use lib qw(t);
use strict;
use Carp;
use Test::More;
# NG 09-11-19: Test::Deep doesn't export cmp_details, deep_diag until recent version (0.104)
#              so we import them "by hand" instead. import of bag, methods was never needed.
# use Test::Deep qw(bag methods cmp_details deep_diag);
use Test::Deep;
*cmp_details=\&Test::Deep::cmp_details;
*deep_diag=\&Test::Deep::deep_diag;
# use Scalar::Util qw(reftype); # don't do it! Test::Deep exports reftype, too
use Scalar::Util;
use Exporter();
use Class::AutoClass;

our @ISA=qw(Exporter);
our @EXPORT=qw(as_hash cmp_attrs cmp_can cmp_hashes cmp_keys cmp_layers cmp_lists 
	       report report_fail report_pass);

sub as_hash {
  my $obj=shift;
  my %hash=%$obj;
  \%hash;
}

# $actual is object. $correct is HASH of attr=>value pairs wih Test::Deep decorations
sub cmp_attrs {
  my($actual,$correct,$label,$file,$line)=@_;
  report_fail(defined $actual,"$label: defined",$file,$line) or return 0;
  my($ok,$details)=cmp_details($actual,methods(%$correct));
  report_fail($ok,"$label: attributes",$file,$line,$details) or return 0;
}

# $actual is object. $correct can be HASH or ARRAY of methods
#   if HASH, keys are attributes (really can be any methods)
sub cmp_can {
  my($actual,$correct,$label,$file,$line)=@_;
  report_fail(defined $actual,"$label: object defined",$file,$line) or return 0;
  my @correct_attrs='HASH' eq Scalar::Util::reftype $correct? keys %$correct: @$correct;
  my(@ok,@bad);
  map {UNIVERSAL::can($actual,$_)? push(@ok,$_): push(@bad,$_);} @correct_attrs;
  report_fail(!@bad,"$label: attributes defined",$file,$line,"attributes '@bad' not defined");
}

# $actual,$correct can be HASHs or ARRAYs. 
# if HASHs, keys are extracted and compared
sub cmp_keys {
  my($actual,$correct,$label,$file,$line)=@_;
  report_fail(defined $actual,"$label: keys defined",$file,$line) or return 0;
  my $actual_keys='HASH' eq Scalar::Util::reftype $actual? [keys %$actual]: $actual;
  my @correct_keys='HASH' eq Scalar::Util::reftype $correct? keys %$correct: @$correct;
  my($ok,$details)=cmp_details($actual_keys,bag(@correct_keys));
  report_fail($ok,"$label: keys",$file,$line,$details);
}

# $actual,$correct are ARRAYs. 
# this is a real crock.
#   nodes have names like xxx10,xxx11,xxx2. first digit defines 'layer'. 
#   all nodes of layer i must appear before any nodes of layer i+1. 
#   nodes of the same layer may appear in any order
# $correct only used to make sure $actual has the right nodes. its order doesn't matter
sub cmp_layers {
  my($actual,$correct,$label,$file,$line)=@_;
  my($ok,$details)=cmp_details($actual,bag(@$correct));
  report_fail($ok,"$label: nodes",$file,$line,$details) or return 0;
  my @layers=map {/^\D+(\d)/} @$actual;
  my @correct=sort {$a<=>$b} @layers; # the correct order is 1,2,3,...
  my($ok,$details)=cmp_details(\@layers,\@correct);
  report_fail($ok,"$label: order",$file,$line,$details);
}

# $actual,$correct are HASHES. 
# like cmp_deeply but reports errors the way we want
sub cmp_hashes {
  my($actual,$correct,$label,$file,$line)=@_;
  return 1 if !defined($actual) && !(defined $correct);
  report_fail(defined $actual,"$label: defined",$file,$line) or return 0;
  my($ok,$details)=cmp_details($actual,$correct);
  report_fail($ok,"$label",$file,$line,$details);
}
# $actual,$correct are ARRAYs. 
# like cmp_deeply but reports errors the way we want
sub cmp_lists {
  my($actual,$correct,$label,$file,$line)=@_;
  return 1 if !defined($actual) && !(defined $correct);
  report_fail(defined $actual,"$label: defined",$file,$line) or return 0;
  my($ok,$details)=cmp_details($actual,$correct);
  report_fail($ok,"$label",$file,$line,$details);
}

sub report {
  my($ok,$label,$file,$line,$details)=@_;
  pass($label), return if $ok;
  fail($label);
  diag("from $file line $line") if defined $file;
  if (defined $details) {
    diag(deep_diag($details)) if ref $details;
    diag($details) unless ref $details;
  }
  return 0;
}

sub report_pass {
  my($ok,$label)=@_;
  pass($label) if $ok;
  $ok;
}
sub report_fail {
  my($ok,$label,$file,$line,$details)=@_;
  return 1 if $ok;
  fail($label);
  diag("from $file line $line") if defined $file;
  if (defined $details) {
    diag(deep_diag($details)) if ref $details;
    diag($details) unless ref $details;
  }
  return 0;
}
  


1;