#! /bin/bash

# set $WITH_DASH to 1 to run tests also with dash
with_dash=${WITH_DASH:-0}

test_dir="tests"
test_root="$test_dir/root"
test_data="$test_dir/data"

programs="\
  /usr/bin/bash\
  /usr/bin/ksh\
  /usr/bin/busybox\
"

[ "$with_dash" = 1 ] && programs="$programs /usr/bin/dash"

busybox_programs="\
  /usr/bin/basename\
  /usr/bin/cat\
  /usr/bin/chmod\
  /usr/bin/cut\
  /usr/bin/dirname\
  /usr/bin/grep\
  /usr/bin/ln\
  /usr/bin/ls\
  /usr/bin/mkdir\
  /usr/bin/mktemp\
  /usr/bin/readlink\
  /usr/bin/rm\
  /usr/bin/sed\
  /usr/bin/sleep\
  /usr/bin/tr\
"
dummy_programs="\
  /usr/bin/kernel-install\
  /usr/bin/sdbootutil\
  /usr/sbin/grub2-install\
  /usr/sbin/grub2-mkconfig\
  /usr/sbin/grub2-set-default\
  /usr/sbin/kexec\
  /usr/sbin/shim-install\
"

# Notes
#
# /usr/bin/bootctl, /usr/bin/date, /usr/bin/df, /usr/sbin/efibootmgr, /usr/sbin/fdisk, and /usr/bin/uname are wrapper scripts
#
# - 'bootctl status' returns a sample status listing; other bootctl calls do nothing
# - 'date "+%F %T"' returns the fixed date "2023-11-01 23:59:58"
# - 'df --local --sync --output=source /foo' returns $TEST_DF_foo
# - 'efibootmgr' returns sample output.
# - 'fdisk -l -o 'Device,Type' /dev/sda returns a sample lsiting; other fdisk calls return an error
# - 'uname -m' returns $TEST_ARCH
#
# programs listed in dummy_programs are replaced by wrapper scripts that
# return 0 or (if set) the value of the environment variable $TEST_<NAME>_EXIT
# where <NAME> is the upper case program name, with any '-' replaced by '_'.
# For example, grub2-install exits with $TEST_GRUB2_INSTALL_EXIT.

opt_refs=

while true ; do
  case $1 in
    -r|--make-reference) opt_refs=".ref" ; shift ; continue ;;
  esac

  break
done

init_tests ()
{
  rm -rf "$test_root"

  mkdir -p "$test_root"/{tmp,boot/grub2,etc/sysconfig,dev,var/log,usr/bin/system,usr/lib/bootloader,usr/lib64,usr/sbin}

  for i in bin sbin lib lib64 ; do
    ln -s "usr/$i" "$test_root"
  done

  cp -r $test_data/* "$test_root"

  touch "$test_root/var/log/pbl.log"

  for p in $programs $busybox_programs ; do
    cp --parents "$p" $(ldd "$p" | sed -e '/\//! d; s/^.*[ \t]\(\/[^ ]\+\) .*$/\1/') "$test_root"
  done

  for p in $busybox_programs ; do
    mv "$test_root/$p" "$test_root/usr/bin/system"
  done

  for p in $dummy_programs ; do
    cat >"$test_root/$p" <<'EOF'
#! /usr/bin/bash

p=${0##*/}
p=${p//-/_}
p=TEST_${p@U}_EXIT

eval "p_exit=\$$p"

[ -z "$p_exit" ] && p_exit=0

if [ "$p_exit" != 0 ] ; then
  echo "$0 $*: test error $p_exit :-(" >&2
fi

exit $p_exit
EOF
   chmod 755 "$test_root/$p"
  done

  make install DESTDIR="$test_root" INTERPRETER=sh
  sed -i -e '1s/[^/]*sh/sh/' "$test_root/usr/sbin/pbl"
}

done_tests ()
{
  rm -r "$test_root"
}

set_bash ()
{
  ln -sf "bash" "$test_root/usr/bin/sh"

  for p in $busybox_programs ; do
    ln -sf "system/${p##*/}" "$test_root/$p"
  done

  shell=bash
}

set_dash ()
{
  ln -sf "dash" "$test_root/usr/bin/sh"

  for p in $busybox_programs ; do
    ln -sf "system/${p##*/}" "$test_root/$p"
  done

  shell=dash
}

set_ksh ()
{
  ln -sf "ksh" "$test_root/usr/bin/sh"

  for p in $busybox_programs ; do
    ln -sf "system/${p##*/}" "$test_root/$p"
  done

  shell=ksh
}

set_busybox ()
{
  ln -sf "busybox" "$test_root/usr/bin/sh"

  for p in $busybox_programs ; do
    ln -sf "busybox" "$test_root/$p"
  done

  shell=busybox
}

get_testdata ()
{
  t="$1"
  root_dir="$2"

  sed -E -e "s/ pbl-[0-9]+: / pbl-0: /" < "$root_dir/var/log/pbl.log" > "$test_dir/$t/pbl.log$suffix"
  cp "$root_dir/etc/sysconfig/bootloader" "$test_dir/$t/bootloader$suffix"
}

do_test ()
{
  t="$1"

  real_root="$test_dir/real_root"

  mkdir "$real_root" || exit 1

  cp -r $test_root/* "$test_dir/real_root"

  for data_dir in "$test_dir/$t"/data* ; do
    if [ -d "$data_dir" ] ; then
      cp -r --remove-destination "$data_dir"/* "$real_root"
    fi
  done

  echo -n >"$real_root/var/log/pbl.log"

  if [ -n "$opt_refs" ] ; then
    suffix=".$shell$opt_refs"
  else
    suffix=".$shell"
  fi

  script=$(mktemp)

  # add 'run' function for logging command exit codes
  cat >"$script" <<'EOF'
    run () {
      "$@"
      if [ "$?" = 0 ] ; then
        echo "[✔] $*"
      else
        echo "[✘] $* = $?"
      fi
    }

EOF

  cat "$test_dir/$t/script" >>"$script"

  # mounting /proc or /dev needed?
  unshare --pid --fork --user --map-root-user --mount-proc --root="$real_root" >"$test_dir/$t/output$suffix" 2>&1 <"$script"

  get_testdata "$t" "$real_root"

  rm -r "$real_root"
}

do_tests ()
{
  for t in "$test_dir"/[0-9]???_* ; do
    do_test "${t##*/}"
  done
}

show_results ()
{
  echo "== $shell =="

  rm -f "$test_dir/testresults_$shell.diff"

  for t in "$test_dir"/[0-9]???_* ; do
    n=${t##*/}
    printf "%-48s" "  $n"
    first=
    for i in "$t"/*."$shell".ref ; do
      [ -e "$i" ] || continue
      i="${i%.ref}"
      f=${i##*/}
      echo -n "$first$f "
      if diff -u "$i.ref" "$i" >> "$test_dir/testresults_$shell.diff" ; then
        echo -en "\u2714"
      else
        test_error=1
        echo -en "\u2718"
      fi
      first=", "
    done
    echo
  done

}

init_tests

test_error=0

if [ -n "$opt_refs" ] ; then
  set_bash
  do_tests

  set_ksh
  do_tests

  set_busybox
  do_tests

  if [ "$with_dash" = 1 ] ; then
    set_dash
    do_tests
  fi
else
  set_bash
  do_tests
  show_results

  set_ksh
  do_tests
  show_results

  set_busybox
  do_tests
  show_results

  if [ "$with_dash" = 1 ] ; then
    set_dash
    do_tests
    show_results
  fi
fi

done_tests

exit "$test_error"
