# -*- mode: sh; sh-basic-offset: 2; indent-tabs-mode: nil; -*-
# vim:set ft=sh et sw=2 ts=2:
#
# xtest_setup v1.2.1 - shtest suite in a box
# Author: Scott Shambarger <devel@shambarger.net>
#
# Copyright (C) 2021 Scott Shambarger
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
#
# Instructions to use:
#
#   Place this file in same directory as shtest_setup, and source
#   it from your test script.
#
#   Define your tests in functions named: 'xtest::group<#>::<name>'
#   where <name> is available for running a group of tests,
#   and group<#> orders the tests when run.  All xtest::group* functions
#   are whitelisted so functions can fail in them in strict mode.
#
#   As handling traps is "tricky", functions named 'xtest::onexit*'
#   which will be called if xtest::run_tests does not complete
#   (eg. premature exit in a test)
#
#   Register files, setup global whitelists etc, and then call
#   'xtest::run_tests' to do all the work
#
# Provides:
#
#   xtest::err <msg>
#
#     Echo to stderr
#
#   xtest::fail <msg>
#
#     Echo err and exit 1
#
#   xtest::no_env_tests
#
#     Call to disable default environment tests
#
#   xtest::run_tests <title> [ <args> ]
#
#     Parses command-line <args>, runs requested tests and creates
#     summary report with <title>, and resets.
#
#   xtest::parse_options [ <args> ]
#
#     Parses command-line <args> for logging modes/strict flags
#
# Shortcuts:
#
#   xtest::reg_test <id> <expect1> <desc> =>
#                  shtest::check_reg_files <id> <desc> <expect1>
#
#     Checks <expect1> against 1st registered file, others expected
#     to be empty
#
#   xtest::reg_test2 <id> <log> <stderr/out> <desc> =>
#                  shtest::check_reg_files <id> <desc> <log> <stderr/out>
#
#     Checks <expect1> <expect2> against 1st/2nd registered files,
#     others expected to be empty.
#
# Aliases
#
#   xtest => shtest::check_result
#   vtest => shtest::check_var
#   atest => shtest::check_array
#   ltest => shtest::check_value
#   ftest => xtest::reg_test
#   ftest2 => xtest::reg_test2
#
# shellcheck shell=bash disable=SC1091,SC2034

_XTEST_ENV_TESTS=1

xtest::err() {
  echo >&2 "$*"
}

xtest::fail() {
  xtest::err "$*"
  exit 1
}

. "${BASH_SOURCE[0]%/*}/shtest_setup" ||
  xtest::fail "Unable to source shtest_setup"

xtest::reg_test() { # <id> <ref> <desc>
  # LOG matches <ref>, ERR should be empty...
  shtest::check_reg_files "${1-}" "${3-}" "${2-}"
}

xtest::reg_test2() { # <id> <ref> <stderr/out> <desc>
  # LOG matches <ref>, ERR matches <err>
  shtest::check_reg_files "${1-}" "${4-}" "${2-}" "${3-}"
}

# make tests easier to add
shopt -s expand_aliases
alias xtest='shtest::check_result'
alias vtest='shtest::check_var'
alias atest='shtest::check_array'
alias ltest='shtest::check_value'
alias ftest='xtest::reg_test'
alias ftest2='xtest::reg_test2'

xtest::_usage() { # <modes>
  local mode modes=()
  xtest::err "Usage: ${0##*/} <options>"
  xtest::err "  <options> include:"
  xtest::err "    help - show help"
  xtest::err "    verbose - always show test descriptions"
  xtest::err "    quiet - show summary only"
  xtest::err "    strict - enable -eEu bash option"
  xtest::err "    trace - enable backtrace on error"
  xtest::err "    all - run all tests (default)"
  xtest::err "    +<id/pattern> - show test <id/pattern(*|?)> (may be repeated)"
  for mode in "$@"; do modes+=("${mode#xtest::group*::}"); done
  xtest::err "    ${modes[*]} - include these groups"
  shtest::cleanup
  exit 1
}

xtest::parse_options() { # <args>...
  local arg

  for arg in "$@"; do
    case ${arg} in
      strict)
        shtest::strict
        shtest::global_whitelist "xtest::group*"
        ;;
      trace) shtest::trace ;;
      verbose) shtest::verbose ;;
      quiet) shtest::quiet ;;
      help) xtest::_parse_args _help ;;
    esac
  done
  return 0
}

# sets _reqs, _all
xtest::_parse_args() { # <args>...
  local _arg IFS; unset IFS

  { read -r -a _all -d '' || :; } \
    <<< "$(compgen -A function xtest::group | command -p sort)"

  xtest::parse_options "$@"

  for _arg in "$@"; do
    if [[ "${_all[*]}" =~ (^| )(xtest::group.::"$_arg")($| ) ]]; then
      _reqs+=("${BASH_REMATCH[2]}")
    else
      case ${_arg} in
        strict|trace|verbose|quiet|all) : ;;
        +*)
          _arg=${_arg#+}; [[ ${_arg} ]] || xtest::_usage "${_all[@]}"
          shtest::add_focus "${_arg}"
          ;;
        _help)
          xtest::_usage "${_all[@]}"
          ;;
        *)
          xtest::err "Unknown option: ${_arg}"
          xtest::_usage "${_all[@]}"
          ;;
      esac
    fi
  done

  return 0
}

xtest::no_env_tests() {
  _XTEST_ENV_TESTS=''
}

xtest::_prerun() {
  local cmd clist=() IFS; unset IFS
  { read -r -a clist -d '' || :; } <<< "$(compgen -A function xtest::onexit || :)"
  for cmd in ${clist[*]+"${clist[@]}"}; do
    shtest::add_onexit "${cmd}"
  done
  return 0
}

xtest::_postrun() {
  local cmd clist=() IFS; unset IFS
  { read -r -a clist -d '' || :; } <<< "$(compgen -A function xtest::onexit || :)"
  for cmd in ${clist[*]+"${clist[@]}"}; do
    shtest::remove_onexit "${cmd}"
  done
  return 0
}

xtest::run_tests() { # <title> <args>...
  local _xtest_title=${1-} _xtest_env _xtest_cmd _all _reqs=()
  shift || set --

  xtest::_parse_args "$@"

  xtest::_prerun

  # minimal environment
  set -- "${_reqs[@]:-${_all[@]}}"
  unset _reqs _all

  for _xtest_cmd in "$@"; do
    [[ ${_XTEST_ENV_TESTS} ]] && shtest::save_env _xtest_env
    "${_xtest_cmd}"
    shtest::prefix
    [[ ${_XTEST_ENV_TESTS} ]] && {
      shtest::check_env "env-${_xtest_cmd##*:}" \
                        _xtest_env "check config environment"
    }
  done

  xtest::_postrun

  local rc=0
  shtest::summary_report "${_xtest_title}" || {
    rc=$?
    shtest::log "To display one test, use \"+<id/pattern>\" (repeatable)"
  }

  shtest::reset

  return ${rc}
}
