#!/usr/bin/perl
# Filename:	tb
# Author:	David Ljung Madison <DaveSource.com>
# See License:	http://MarginalHacks.com/License
# Description:	testbench for comparing two commands
use strict;
use IO::File;
use IPC::Open3;

##################################################
# Setup the variables
##################################################
my $PROGNAME = $0;
$PROGNAME =~ s|.*/||;

my $REDO = 0;
$SIG{PIPE} = sub { $REDO = 1; };
sub run {
  my ($cmd,$in) = @_;

  my ($iH,$oH,$eH) = (new IO::File,new IO::File,new IO::File);

  my $pid = open3($iH,$oH,$eH, $cmd);
  die("Couldn't run [$cmd]\n") if $pid==-1;

  print $iH $in;
  close $iH;
  my @out = <$oH>;
  close $oH;
  my @err = <$eH>;
  close $eH;

  waitpid($pid,0);
  my $ret = $?;

  return (\@out,\@err,$ret) unless $REDO;
  $REDO = 0;
  run($cmd,$in);
}

sub diff {
  my ($a,$b) = @_;

  return 1 if ref($a) ne ref($b);
#if (!ref($a) && $a ne $b) { chomp($a); chomp($b); print "compare difference:\n[$a]\n[$b]\n"; }
  return ($a ne $b ? 1 : 0) unless ref($a);
  return ($$a ne $$b) if ref($a) eq "SCALAR";
  if (ref($a) eq "ARRAY") {
    return 1 unless $#$a == $#$b;
    for(my $i=0; $i<=$#$a; $i++) {
      return 1 if diff($a->[$i],$b->[$i]);
    }
    return 0;
  }
  if (ref($a) eq "HASH") {
    my @ak = keys %$a;
    my @bk = keys %$b;
    return 1 if diff(\@ak,\@bk);
    foreach my $k ( @ak ) {
      return 1 if diff($a->{$k},$b->{$k});
    }
    return 0;
  }

  die("diff() doesn't do ".ref($a)." today..");
}

my $IGNORE;	# Kludge
sub cmp_run {
  my ($a,$b,@opt) = @_;

  my $in = pop @opt;

  foreach my $opt ( @opt ) {
print "$a,$b [$opt]\n";
    my $errors = 0;

    my ($oa,$ea,$ra) = run("$a $opt",$in);
    my ($ob,$eb,$rb) = run("$b $opt",$in);

    # KLUDGE: Replace occurences of $b in out to $a (for print of PROGNAME,...)
    @$ob = map {s/$b/$a/g; $_} @$ob;
    @$eb = map {s/$b/$a/g; $_} @$eb;
    # KLUDGE: Pull out $IGNORE lines
    @$ob = grep(!/$IGNORE/, @$ob) if $IGNORE;
    @$eb = grep(!/$IGNORE/, @$eb) if $IGNORE;

    if (diff($oa,$ob)) {
      print "OUTPUT DIFF:  [$a,$b] [$opt]:\n";
      print "- $a ","-"x30,"\n";
      print @$oa;
      print "- $b ","-"x30,"\n";
      print @$ob;
      print "-"x70,"\n";
      $errors++;
    }
    if (diff($ea,$eb)) {
      print "ERROR DIFF:   [$a,$b] [$opt]:\n";
      print "- $a ","-"x30,"\n";
      print @$ea;
      print "- $b ","-"x30,"\n";
      print @$eb;
      print "-"x70,"\n";
      $errors++;
    }
    if ($ra != $rb) {
      print "EXITCODE DIFF:   [$a,$b] [$opt]:\n";
      print "error:\n@$ea\n" unless $errors;
      print "$a -> [$ra]\n";
      print "$b -> [$rb]\n";
      $errors++;
    }

    die("\n") if $errors;
  }
}

# All permutations of arrays
sub combine {
  my $a1 = shift @_;
  $a1 = [$a1] unless ref $a1;
  return @$a1 unless @_;
  my $a2 = shift @_;
  $a2 = [$a2] unless ref $a2;

  my @n;
  foreach my $e1 (@$a1) {
    foreach my $e2 (@$a2) {
      push(@n,"$e1 $e2");
    }
  }
  return @n unless @_;
  combine(\@n,@_);
}

##################################################

$IGNORE = "--nosplit-lines";	# New feature shows up in usage..

# Can't test:
# --interactive
# --max-procs

my @usage = ("--help", "-bad_arg", "--bad_arg");
my @eof = ("-e5","-e6","-e8","-ezog","-e","--eof=5","--eof=8","--eof=zog","--eof");
my @maxargs = ("-n 1","-n 2","-n 3","-n 99","-n 0","--max-args=1","--max-args=2");
my @maxlines = ("-l1","-l2","-l3","-l99","-l0","--max-lines=1","--max-lines=2");

my $in_count = "1\n2\n__\n4 4\n 5\n66\n777\n8\n_\n9\na\n"; 
my $Z = chr(0x0);
my $in_zero = "1${Z}2 '3 4${Z}5 6${Z}7${Z}${Z}";
my $in_blank = "\n  \n\n     \n\n  ";

## Basic args and eof tests
#cmp_run("xargs","xags", @usage, $in_count);
cmp_run("xargs","xags", combine(\@eof,\@maxargs), $in_count);
#cmp_run("xargs","xags", combine("-t",\@maxargs), $in_count);
cmp_run("xargs","xags", combine(\@eof,\@maxlines), $in_count);

### Basic maxchars test
### For some reason, xargs doesn't print the last possible argument line
##cmp_run("xargs","xags", combine("-s",[1,2,10..18]), $in_count);

# Doesn't seem to be passing the -0 properly??
## Basic \0 divider test
cmp_run("xargs","xags", combine("-0",\@maxargs,"e"), $in_zero);
cmp_run("xargs","xags", combine("-0",\@maxlines,"e"), $in_zero);

# Basic --no-run-if-empty test
#cmp_run("xargs","xags", combine(["--no-run-if-empty",""],\@maxlines), $in_blank);
#cmp_run("xargs","xags", combine(["--no-run-if-empty",""],\@maxlines), $in_blank);

print STDERR "PASS\n";
