The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
package # Hide from PAUSE indexer
  Math::Histogram::Test;
use strict;
use warnings;

use Config qw(%Config);
use File::Spec;
use Capture::Tiny qw(capture);
use Exporter ();
use Test::More;

our @ISA = qw(Exporter);
our @EXPORT = qw(run_ctest is_approx axis_eq histogram_eq);

our ($USE_VALGRIND, $USE_GDB);

my $in_testdir = not(-d 't');
my $base_dir;
my $ctest_dir;

if ($in_testdir) {
  $base_dir = File::Spec->updir;
  $USE_VALGRIND = -e File::Spec->catfile(File::Spec->updir, 'USE_VALGRIND');
  $USE_GDB = -e File::Spec->catfile(File::Spec->updir, 'USE_GDB');
}
else {
  $base_dir = File::Spec->curdir;
  $USE_VALGRIND = -e 'USE_VALGRIND';
  $USE_GDB = -e 'USE_GDB';
}
$ctest_dir = File::Spec->catdir($base_dir, 'ctest');

my @ctests = glob( "$ctest_dir/*.c" );
my @exe = grep -f $_, map {s/\.c$/$Config{exe_ext}/; $_} @ctests;

sub locate_exe {
  my $exe = shift;
  if (-f $exe) {
    return $exe;
  }
  my $inctest = File::Spec->catfile($ctest_dir, $exe);
  if (-f $inctest) {
    return $inctest;
  }
  return;
}

sub run_ctest {
  my ($executable, $options) = @_;
  my $to_run = locate_exe($executable);

  return if not defined $to_run;

  #my ($stdout, $stderr) = capture {
    my @cmd;
    if ($USE_VALGRIND) {
      push @cmd, "valgrind", "--suppressions=" .  File::Spec->catfile($base_dir, 'perl.supp');
    }
    elsif ($USE_GDB) {
      push @cmd, "gdb";
    }
    push @cmd, $to_run, ref($options)?@$options:();
    note("@cmd");
    system(@cmd)
      and fail("C test did not exit with 0");
  #};
  #print $stdout;
  #warn $stderr if defined $stderr and $stderr ne '';
  return 1;
}

sub is_approx {
  my ($l, $r, $m) = @_;
  my $is_undef = !defined($l) || !defined($r);
  $l = "<undef>" if not defined $l;
  $r = "<undef>" if not defined $r;
  my $ok = ok(
    !$is_undef
    && $l+1e-9 > $r
    && $l-1e-9 < $r,
    $m
  );
  note("'$m' failed: $l != $r") if not $ok;
  return $ok;
}

sub axis_eq {
  my ($t, $ref, $name) = @_;
  isa_ok($t, 'Math::Histogram::Axis');

  is_approx($t->min, $ref->min, "$name: min");
  is_approx($t->max, $ref->max, "$name: max");
  is_approx($t->width, $ref->width, "$name: width");

  is($t->nbins, $ref->nbins, "$name: nbins")
    or return; # short circuit if nbins differs

  my $n = $ref->nbins;
  for my $ibin (1..$n) {
    is_approx($t->binsize($ibin), $ref->binsize($ibin), "$name, $ibin: binsize");
    is_approx($t->lower_boundary($ibin), $ref->lower_boundary($ibin), "$name, $ibin: lower_boundary");
    is_approx($t->upper_boundary($ibin), $ref->upper_boundary($ibin), "$name, $ibin: upper_boundary");
    is_approx($t->bin_center($ibin), $ref->bin_center($ibin), "$name, $ibin: bin_center");
    my ($lower, $center, $upper) = map $ref->$_($ibin), qw(lower_boundary bin_center upper_boundary);

    is($t->find_bin($lower), $ibin, "$name, $ibin: found lower bin boundary");
    is($t->find_bin($center), $ibin, "$name, $ibin: found bin center");
    is($t->find_bin($upper), $ibin+1, "$name, $ibin: found upper bin boundary");
  }

}

sub histogram_eq {
  my ($t, $ref, $name) = @_;

  is_approx($t->total, $ref->total, "$name: total");
  is($t->nfills, $ref->nfills, "$name: nfills");
  is($t->ndim, $ref->ndim, "$name: ndim")
    or return;

  my $ndim = $t->ndim;
  foreach my $i (0..$ndim-1) {
    axis_eq($t->get_axis($i), $ref->get_axis($i), "$name (axis $i)");
  }

  ok($ref->data_equal_to($t), "$name: data equal to");
}

1;