#!/usr/bin/env perl

# 2024-06-25
# J. Wied <j@wied.co>

use warnings;
use strict;

use Cwd 'abs_path';
use File::Basename qw(basename dirname);
use File::Temp ':POSIX';
use Getopt::Std;

# parse options
my $opts = {};
getopts( 'uv', $opts );           # u=update, v=verbose
push @ARGV, '.' if @ARGV == 0;    # test current directory

my $nerrors = 0;
$nerrors += test( abs_path $_ ) for (@ARGV);  # abs_path is needed for `tekel .`
$nerrors = 255 if $nerrors > 255;
exit $nerrors;

# test given file or directory;
# return number of errors
sub test {
    my $path = shift;

    if    ( -d $path ) { return test_dir($path) }
    elsif ( -f $path ) { return test_file($path) }
    else               { die "$path: neither file for directory\n"; }
}

# test given directory recursively;
# return number of errors
sub test_dir {
    my $path    = shift;
    my $nerrors = 0;

    opendir( my $DIR, $path ) or die "$path: $!\n";
    while ( readdir($DIR) ) {
        next if ( $_ eq '.' || $_ eq '..' );
        $nerrors += test("$path/$_");
    }
    closedir $DIR;

    return $nerrors;
}

# test given file;
# return number of errors
sub test_file {
    my $path = shift;                       # absolute path because of cd
    return 0 if $path =~ /\.(out|err)$/;    # ignore .out and .err

    # -v: print relative path to file
    if ( $opts->{v} ) {
        my $cwd = Cwd::getcwd;
        print "# ", $path =~ s/$cwd\///r, "\n";
    }

    # extract data from path
    my $dirname = dirname($path);
    my $cmd     = basename($dirname);
    my $args    = basename($path);

    # determine stdout and stderr files
    my $out      = "$path.out";
    my $err      = "$path.err";
    my $temp_out = $opts->{u} ? $out : tmpnam;
    my $temp_err = $opts->{u} ? $err : tmpnam;

    my $nerrors = 0;

    # execute command
    # `cat` is needed for testing stdin
    # `./` is needed if filename is '-'
    my $cmd_code = system(
        "cd '$dirname'; cat './$args' | $cmd $args > '$temp_out' 2> '$temp_err'"
    );

    # compare stdout
    $nerrors +=
      system("git diff --exit-code --no-index -- '$out' '$temp_out'") ? 1 : 0
      if ( -e $out || !-z $temp_out );    # .out exists or temp not empty

    $nerrors +=
      system("git diff --exit-code --no-index -- '$err' '$temp_err'") ? 1 : 0
      if (
        $cmd_code != 0 ||    # command failed
        -e $err ||           # .err exists
        !-z $temp_err        # temporary .err not empty
      );

    return $nerrors ? 1 : 0;    # 1 file should produce only 1 error
}
