11c235774Smrg#! /bin/sh
274835918Smrg# Copyright (C) 2011-2021 Free Software Foundation, Inc.
31c235774Smrg#
41c235774Smrg# This program is free software; you can redistribute it and/or modify
51c235774Smrg# it under the terms of the GNU General Public License as published by
61c235774Smrg# the Free Software Foundation; either version 2, or (at your option)
71c235774Smrg# any later version.
81c235774Smrg#
91c235774Smrg# This program is distributed in the hope that it will be useful,
101c235774Smrg# but WITHOUT ANY WARRANTY; without even the implied warranty of
111c235774Smrg# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
121c235774Smrg# GNU General Public License for more details.
131c235774Smrg#
141c235774Smrg# You should have received a copy of the GNU General Public License
1574835918Smrg# along with this program.  If not, see <https://www.gnu.org/licenses/>.
161c235774Smrg
171c235774Smrg# As a special exception to the GNU General Public License, if you
181c235774Smrg# distribute this file as part of a program that contains a
191c235774Smrg# configuration script generated by Autoconf, you may include it under
201c235774Smrg# the same distribution terms that you use for the rest of that program.
211c235774Smrg
221c235774Smrg# This file is maintained in Automake, please report
231c235774Smrg# bugs to <bug-automake@gnu.org> or send patches to
241c235774Smrg# <automake-patches@gnu.org>.
251c235774Smrg
261c235774Smrgscriptversion=2013-12-23.17; # UTC
271c235774Smrg
281c235774Smrg# Make unconditional expansion of undefined variables an error.  This
291c235774Smrg# helps a lot in preventing typo-related bugs.
301c235774Smrgset -u
311c235774Smrg
321c235774Smrgme=tap-driver.sh
331c235774Smrg
341c235774Smrgfatal ()
351c235774Smrg{
361c235774Smrg  echo "$me: fatal: $*" >&2
371c235774Smrg  exit 1
381c235774Smrg}
391c235774Smrg
401c235774Smrgusage_error ()
411c235774Smrg{
421c235774Smrg  echo "$me: $*" >&2
431c235774Smrg  print_usage >&2
441c235774Smrg  exit 2
451c235774Smrg}
461c235774Smrg
471c235774Smrgprint_usage ()
481c235774Smrg{
491c235774Smrg  cat <<END
501c235774SmrgUsage:
511c235774Smrg  tap-driver.sh --test-name=NAME --log-file=PATH --trs-file=PATH
521c235774Smrg                [--expect-failure={yes|no}] [--color-tests={yes|no}]
531c235774Smrg                [--enable-hard-errors={yes|no}] [--ignore-exit]
541c235774Smrg                [--diagnostic-string=STRING] [--merge|--no-merge]
551c235774Smrg                [--comments|--no-comments] [--] TEST-COMMAND
561c235774SmrgThe '--test-name', '-log-file' and '--trs-file' options are mandatory.
571c235774SmrgEND
581c235774Smrg}
591c235774Smrg
601c235774Smrg# TODO: better error handling in option parsing (in particular, ensure
611c235774Smrg# TODO: $log_file, $trs_file and $test_name are defined).
621c235774Smrgtest_name= # Used for reporting.
631c235774Smrglog_file=  # Where to save the result and output of the test script.
641c235774Smrgtrs_file=  # Where to save the metadata of the test run.
651c235774Smrgexpect_failure=0
661c235774Smrgcolor_tests=0
671c235774Smrgmerge=0
681c235774Smrgignore_exit=0
691c235774Smrgcomments=0
701c235774Smrgdiag_string='#'
711c235774Smrgwhile test $# -gt 0; do
721c235774Smrg  case $1 in
731c235774Smrg  --help) print_usage; exit $?;;
741c235774Smrg  --version) echo "$me $scriptversion"; exit $?;;
751c235774Smrg  --test-name) test_name=$2; shift;;
761c235774Smrg  --log-file) log_file=$2; shift;;
771c235774Smrg  --trs-file) trs_file=$2; shift;;
781c235774Smrg  --color-tests) color_tests=$2; shift;;
791c235774Smrg  --expect-failure) expect_failure=$2; shift;;
801c235774Smrg  --enable-hard-errors) shift;; # No-op.
811c235774Smrg  --merge) merge=1;;
821c235774Smrg  --no-merge) merge=0;;
831c235774Smrg  --ignore-exit) ignore_exit=1;;
841c235774Smrg  --comments) comments=1;;
851c235774Smrg  --no-comments) comments=0;;
861c235774Smrg  --diagnostic-string) diag_string=$2; shift;;
871c235774Smrg  --) shift; break;;
881c235774Smrg  -*) usage_error "invalid option: '$1'";;
891c235774Smrg  esac
901c235774Smrg  shift
911c235774Smrgdone
921c235774Smrg
931c235774Smrgtest $# -gt 0 || usage_error "missing test command"
941c235774Smrg
951c235774Smrgcase $expect_failure in
961c235774Smrg  yes) expect_failure=1;;
971c235774Smrg    *) expect_failure=0;;
981c235774Smrgesac
991c235774Smrg
1001c235774Smrgif test $color_tests = yes; then
1011c235774Smrg  init_colors='
1021c235774Smrg    color_map["red"]="[0;31m" # Red.
1031c235774Smrg    color_map["grn"]="[0;32m" # Green.
1041c235774Smrg    color_map["lgn"]="[1;32m" # Light green.
1051c235774Smrg    color_map["blu"]="[1;34m" # Blue.
1061c235774Smrg    color_map["mgn"]="[0;35m" # Magenta.
1071c235774Smrg    color_map["std"]="[m"     # No color.
1081c235774Smrg    color_for_result["ERROR"] = "mgn"
1091c235774Smrg    color_for_result["PASS"]  = "grn"
1101c235774Smrg    color_for_result["XPASS"] = "red"
1111c235774Smrg    color_for_result["FAIL"]  = "red"
1121c235774Smrg    color_for_result["XFAIL"] = "lgn"
1131c235774Smrg    color_for_result["SKIP"]  = "blu"'
1141c235774Smrgelse
1151c235774Smrg  init_colors=''
1161c235774Smrgfi
1171c235774Smrg
1181c235774Smrg# :; is there to work around a bug in bash 3.2 (and earlier) which
1191c235774Smrg# does not always set '$?' properly on redirection failure.
1201c235774Smrg# See the Autoconf manual for more details.
1211c235774Smrg:;{
1221c235774Smrg  (
1231c235774Smrg    # Ignore common signals (in this subshell only!), to avoid potential
1241c235774Smrg    # problems with Korn shells.  Some Korn shells are known to propagate
1251c235774Smrg    # to themselves signals that have killed a child process they were
1261c235774Smrg    # waiting for; this is done at least for SIGINT (and usually only for
1271c235774Smrg    # it, in truth).  Without the `trap' below, such a behaviour could
1281c235774Smrg    # cause a premature exit in the current subshell, e.g., in case the
1291c235774Smrg    # test command it runs gets terminated by a SIGINT.  Thus, the awk
1301c235774Smrg    # script we are piping into would never seen the exit status it
1311c235774Smrg    # expects on its last input line (which is displayed below by the
1321c235774Smrg    # last `echo $?' statement), and would thus die reporting an internal
1331c235774Smrg    # error.
1341c235774Smrg    # For more information, see the Autoconf manual and the threads:
13574835918Smrg    # <https://lists.gnu.org/archive/html/bug-autoconf/2011-09/msg00004.html>
1361c235774Smrg    # <http://mail.opensolaris.org/pipermail/ksh93-integration-discuss/2009-February/004121.html>
1371c235774Smrg    trap : 1 3 2 13 15
1381c235774Smrg    if test $merge -gt 0; then
1391c235774Smrg      exec 2>&1
1401c235774Smrg    else
1411c235774Smrg      exec 2>&3
1421c235774Smrg    fi
1431c235774Smrg    "$@"
1441c235774Smrg    echo $?
1451c235774Smrg  ) | LC_ALL=C ${AM_TAP_AWK-awk} \
1461c235774Smrg        -v me="$me" \
1471c235774Smrg        -v test_script_name="$test_name" \
1481c235774Smrg        -v log_file="$log_file" \
1491c235774Smrg        -v trs_file="$trs_file" \
1501c235774Smrg        -v expect_failure="$expect_failure" \
1511c235774Smrg        -v merge="$merge" \
1521c235774Smrg        -v ignore_exit="$ignore_exit" \
1531c235774Smrg        -v comments="$comments" \
1541c235774Smrg        -v diag_string="$diag_string" \
1551c235774Smrg'
1561c235774Smrg# TODO: the usages of "cat >&3" below could be optimized when using
1571c235774Smrg#       GNU awk, and/on on systems that supports /dev/fd/.
1581c235774Smrg
1591c235774Smrg# Implementation note: in what follows, `result_obj` will be an
1601c235774Smrg# associative array that (partly) simulates a TAP result object
1611c235774Smrg# from the `TAP::Parser` perl module.
1621c235774Smrg
1631c235774Smrg## ----------- ##
1641c235774Smrg##  FUNCTIONS  ##
1651c235774Smrg## ----------- ##
1661c235774Smrg
1671c235774Smrgfunction fatal(msg)
1681c235774Smrg{
1691c235774Smrg  print me ": " msg | "cat >&2"
1701c235774Smrg  exit 1
1711c235774Smrg}
1721c235774Smrg
1731c235774Smrgfunction abort(where)
1741c235774Smrg{
1751c235774Smrg  fatal("internal error " where)
1761c235774Smrg}
1771c235774Smrg
1781c235774Smrg# Convert a boolean to a "yes"/"no" string.
1791c235774Smrgfunction yn(bool)
1801c235774Smrg{
1811c235774Smrg  return bool ? "yes" : "no";
1821c235774Smrg}
1831c235774Smrg
1841c235774Smrgfunction add_test_result(result)
1851c235774Smrg{
1861c235774Smrg  if (!test_results_index)
1871c235774Smrg    test_results_index = 0
1881c235774Smrg  test_results_list[test_results_index] = result
1891c235774Smrg  test_results_index += 1
1901c235774Smrg  test_results_seen[result] = 1;
1911c235774Smrg}
1921c235774Smrg
1931c235774Smrg# Whether the test script should be re-run by "make recheck".
1941c235774Smrgfunction must_recheck()
1951c235774Smrg{
1961c235774Smrg  for (k in test_results_seen)
1971c235774Smrg    if (k != "XFAIL" && k != "PASS" && k != "SKIP")
1981c235774Smrg      return 1
1991c235774Smrg  return 0
2001c235774Smrg}
2011c235774Smrg
2021c235774Smrg# Whether the content of the log file associated to this test should
2031c235774Smrg# be copied into the "global" test-suite.log.
2041c235774Smrgfunction copy_in_global_log()
2051c235774Smrg{
2061c235774Smrg  for (k in test_results_seen)
2071c235774Smrg    if (k != "PASS")
2081c235774Smrg      return 1
2091c235774Smrg  return 0
2101c235774Smrg}
2111c235774Smrg
2121c235774Smrgfunction get_global_test_result()
2131c235774Smrg{
2141c235774Smrg    if ("ERROR" in test_results_seen)
2151c235774Smrg      return "ERROR"
2161c235774Smrg    if ("FAIL" in test_results_seen || "XPASS" in test_results_seen)
2171c235774Smrg      return "FAIL"
2181c235774Smrg    all_skipped = 1
2191c235774Smrg    for (k in test_results_seen)
2201c235774Smrg      if (k != "SKIP")
2211c235774Smrg        all_skipped = 0
2221c235774Smrg    if (all_skipped)
2231c235774Smrg      return "SKIP"
2241c235774Smrg    return "PASS";
2251c235774Smrg}
2261c235774Smrg
2271c235774Smrgfunction stringify_result_obj(result_obj)
2281c235774Smrg{
2291c235774Smrg  if (result_obj["is_unplanned"] || result_obj["number"] != testno)
2301c235774Smrg    return "ERROR"
2311c235774Smrg
2321c235774Smrg  if (plan_seen == LATE_PLAN)
2331c235774Smrg    return "ERROR"
2341c235774Smrg
2351c235774Smrg  if (result_obj["directive"] == "TODO")
2361c235774Smrg    return result_obj["is_ok"] ? "XPASS" : "XFAIL"
2371c235774Smrg
2381c235774Smrg  if (result_obj["directive"] == "SKIP")
2391c235774Smrg    return result_obj["is_ok"] ? "SKIP" : COOKED_FAIL;
2401c235774Smrg
2411c235774Smrg  if (length(result_obj["directive"]))
2421c235774Smrg      abort("in function stringify_result_obj()")
2431c235774Smrg
2441c235774Smrg  return result_obj["is_ok"] ? COOKED_PASS : COOKED_FAIL
2451c235774Smrg}
2461c235774Smrg
2471c235774Smrgfunction decorate_result(result)
2481c235774Smrg{
2491c235774Smrg  color_name = color_for_result[result]
2501c235774Smrg  if (color_name)
2511c235774Smrg    return color_map[color_name] "" result "" color_map["std"]
2521c235774Smrg  # If we are not using colorized output, or if we do not know how
2531c235774Smrg  # to colorize the given result, we should return it unchanged.
2541c235774Smrg  return result
2551c235774Smrg}
2561c235774Smrg
2571c235774Smrgfunction report(result, details)
2581c235774Smrg{
2591c235774Smrg  if (result ~ /^(X?(PASS|FAIL)|SKIP|ERROR)/)
2601c235774Smrg    {
2611c235774Smrg      msg = ": " test_script_name
2621c235774Smrg      add_test_result(result)
2631c235774Smrg    }
2641c235774Smrg  else if (result == "#")
2651c235774Smrg    {
2661c235774Smrg      msg = " " test_script_name ":"
2671c235774Smrg    }
2681c235774Smrg  else
2691c235774Smrg    {
2701c235774Smrg      abort("in function report()")
2711c235774Smrg    }
2721c235774Smrg  if (length(details))
2731c235774Smrg    msg = msg " " details
2741c235774Smrg  # Output on console might be colorized.
2751c235774Smrg  print decorate_result(result) msg
2761c235774Smrg  # Log the result in the log file too, to help debugging (this is
2771c235774Smrg  # especially true when said result is a TAP error or "Bail out!").
2781c235774Smrg  print result msg | "cat >&3";
2791c235774Smrg}
2801c235774Smrg
2811c235774Smrgfunction testsuite_error(error_message)
2821c235774Smrg{
2831c235774Smrg  report("ERROR", "- " error_message)
2841c235774Smrg}
2851c235774Smrg
2861c235774Smrgfunction handle_tap_result()
2871c235774Smrg{
2881c235774Smrg  details = result_obj["number"];
2891c235774Smrg  if (length(result_obj["description"]))
2901c235774Smrg    details = details " " result_obj["description"]
2911c235774Smrg
2921c235774Smrg  if (plan_seen == LATE_PLAN)
2931c235774Smrg    {
2941c235774Smrg      details = details " # AFTER LATE PLAN";
2951c235774Smrg    }
2961c235774Smrg  else if (result_obj["is_unplanned"])
2971c235774Smrg    {
2981c235774Smrg       details = details " # UNPLANNED";
2991c235774Smrg    }
3001c235774Smrg  else if (result_obj["number"] != testno)
3011c235774Smrg    {
3021c235774Smrg       details = sprintf("%s # OUT-OF-ORDER (expecting %d)",
3031c235774Smrg                         details, testno);
3041c235774Smrg    }
3051c235774Smrg  else if (result_obj["directive"])
3061c235774Smrg    {
3071c235774Smrg      details = details " # " result_obj["directive"];
3081c235774Smrg      if (length(result_obj["explanation"]))
3091c235774Smrg        details = details " " result_obj["explanation"]
3101c235774Smrg    }
3111c235774Smrg
3121c235774Smrg  report(stringify_result_obj(result_obj), details)
3131c235774Smrg}
3141c235774Smrg
3151c235774Smrg# `skip_reason` should be empty whenever planned > 0.
3161c235774Smrgfunction handle_tap_plan(planned, skip_reason)
3171c235774Smrg{
3181c235774Smrg  planned += 0 # Avoid getting confused if, say, `planned` is "00"
3191c235774Smrg  if (length(skip_reason) && planned > 0)
3201c235774Smrg    abort("in function handle_tap_plan()")
3211c235774Smrg  if (plan_seen)
3221c235774Smrg    {
3231c235774Smrg      # Error, only one plan per stream is acceptable.
3241c235774Smrg      testsuite_error("multiple test plans")
3251c235774Smrg      return;
3261c235774Smrg    }
3271c235774Smrg  planned_tests = planned
3281c235774Smrg  # The TAP plan can come before or after *all* the TAP results; we speak
3291c235774Smrg  # respectively of an "early" or a "late" plan.  If we see the plan line
3301c235774Smrg  # after at least one TAP result has been seen, assume we have a late
3311c235774Smrg  # plan; in this case, any further test result seen after the plan will
3321c235774Smrg  # be flagged as an error.
3331c235774Smrg  plan_seen = (testno >= 1 ? LATE_PLAN : EARLY_PLAN)
3341c235774Smrg  # If testno > 0, we have an error ("too many tests run") that will be
3351c235774Smrg  # automatically dealt with later, so do not worry about it here.  If
3361c235774Smrg  # $plan_seen is true, we have an error due to a repeated plan, and that
3371c235774Smrg  # has already been dealt with above.  Otherwise, we have a valid "plan
3381c235774Smrg  # with SKIP" specification, and should report it as a particular kind
3391c235774Smrg  # of SKIP result.
3401c235774Smrg  if (planned == 0 && testno == 0)
3411c235774Smrg    {
3421c235774Smrg      if (length(skip_reason))
3431c235774Smrg        skip_reason = "- "  skip_reason;
3441c235774Smrg      report("SKIP", skip_reason);
3451c235774Smrg    }
3461c235774Smrg}
3471c235774Smrg
3481c235774Smrgfunction extract_tap_comment(line)
3491c235774Smrg{
3501c235774Smrg  if (index(line, diag_string) == 1)
3511c235774Smrg    {
3521c235774Smrg      # Strip leading `diag_string` from `line`.
3531c235774Smrg      line = substr(line, length(diag_string) + 1)
3541c235774Smrg      # And strip any leading and trailing whitespace left.
3551c235774Smrg      sub("^[ \t]*", "", line)
3561c235774Smrg      sub("[ \t]*$", "", line)
3571c235774Smrg      # Return what is left (if any).
3581c235774Smrg      return line;
3591c235774Smrg    }
3601c235774Smrg  return "";
3611c235774Smrg}
3621c235774Smrg
3631c235774Smrg# When this function is called, we know that line is a TAP result line,
3641c235774Smrg# so that it matches the (perl) RE "^(not )?ok\b".
3651c235774Smrgfunction setup_result_obj(line)
3661c235774Smrg{
3671c235774Smrg  # Get the result, and remove it from the line.
3681c235774Smrg  result_obj["is_ok"] = (substr(line, 1, 2) == "ok" ? 1 : 0)
3691c235774Smrg  sub("^(not )?ok[ \t]*", "", line)
3701c235774Smrg
3711c235774Smrg  # If the result has an explicit number, get it and strip it; otherwise,
37274835918Smrg  # automatically assign the next test number to it.
3731c235774Smrg  if (line ~ /^[0-9]+$/ || line ~ /^[0-9]+[^a-zA-Z0-9_]/)
3741c235774Smrg    {
3751c235774Smrg      match(line, "^[0-9]+")
3761c235774Smrg      # The final `+ 0` is to normalize numbers with leading zeros.
3771c235774Smrg      result_obj["number"] = substr(line, 1, RLENGTH) + 0
3781c235774Smrg      line = substr(line, RLENGTH + 1)
3791c235774Smrg    }
3801c235774Smrg  else
3811c235774Smrg    {
3821c235774Smrg      result_obj["number"] = testno
3831c235774Smrg    }
3841c235774Smrg
3851c235774Smrg  if (plan_seen == LATE_PLAN)
3861c235774Smrg    # No further test results are acceptable after a "late" TAP plan
3871c235774Smrg    # has been seen.
3881c235774Smrg    result_obj["is_unplanned"] = 1
3891c235774Smrg  else if (plan_seen && testno > planned_tests)
3901c235774Smrg    result_obj["is_unplanned"] = 1
3911c235774Smrg  else
3921c235774Smrg    result_obj["is_unplanned"] = 0
3931c235774Smrg
3941c235774Smrg  # Strip trailing and leading whitespace.
3951c235774Smrg  sub("^[ \t]*", "", line)
3961c235774Smrg  sub("[ \t]*$", "", line)
3971c235774Smrg
3981c235774Smrg  # This will have to be corrected if we have a "TODO"/"SKIP" directive.
3991c235774Smrg  result_obj["description"] = line
4001c235774Smrg  result_obj["directive"] = ""
4011c235774Smrg  result_obj["explanation"] = ""
4021c235774Smrg
4031c235774Smrg  if (index(line, "#") == 0)
4041c235774Smrg    return # No possible directive, nothing more to do.
4051c235774Smrg
4061c235774Smrg  # Directives are case-insensitive.
4071c235774Smrg  rx = "[ \t]*#[ \t]*([tT][oO][dD][oO]|[sS][kK][iI][pP])[ \t]*"
4081c235774Smrg
4091c235774Smrg  # See whether we have the directive, and if yes, where.
4101c235774Smrg  pos = match(line, rx "$")
4111c235774Smrg  if (!pos)
4121c235774Smrg    pos = match(line, rx "[^a-zA-Z0-9_]")
4131c235774Smrg
4141c235774Smrg  # If there was no TAP directive, we have nothing more to do.
4151c235774Smrg  if (!pos)
4161c235774Smrg    return
4171c235774Smrg
4181c235774Smrg  # Let`s now see if the TAP directive has been escaped.  For example:
4191c235774Smrg  #  escaped:     ok \# SKIP
4201c235774Smrg  #  not escaped: ok \\# SKIP
4211c235774Smrg  #  escaped:     ok \\\\\# SKIP
4221c235774Smrg  #  not escaped: ok \ # SKIP
4231c235774Smrg  if (substr(line, pos, 1) == "#")
4241c235774Smrg    {
4251c235774Smrg      bslash_count = 0
4261c235774Smrg      for (i = pos; i > 1 && substr(line, i - 1, 1) == "\\"; i--)
4271c235774Smrg        bslash_count += 1
4281c235774Smrg      if (bslash_count % 2)
4291c235774Smrg        return # Directive was escaped.
4301c235774Smrg    }
4311c235774Smrg
4321c235774Smrg  # Strip the directive and its explanation (if any) from the test
4331c235774Smrg  # description.
4341c235774Smrg  result_obj["description"] = substr(line, 1, pos - 1)
4351c235774Smrg  # Now remove the test description from the line, that has been dealt
4361c235774Smrg  # with already.
4371c235774Smrg  line = substr(line, pos)
4381c235774Smrg  # Strip the directive, and save its value (normalized to upper case).
4391c235774Smrg  sub("^[ \t]*#[ \t]*", "", line)
4401c235774Smrg  result_obj["directive"] = toupper(substr(line, 1, 4))
4411c235774Smrg  line = substr(line, 5)
4421c235774Smrg  # Now get the explanation for the directive (if any), with leading
4431c235774Smrg  # and trailing whitespace removed.
4441c235774Smrg  sub("^[ \t]*", "", line)
4451c235774Smrg  sub("[ \t]*$", "", line)
4461c235774Smrg  result_obj["explanation"] = line
4471c235774Smrg}
4481c235774Smrg
4491c235774Smrgfunction get_test_exit_message(status)
4501c235774Smrg{
4511c235774Smrg  if (status == 0)
4521c235774Smrg    return ""
4531c235774Smrg  if (status !~ /^[1-9][0-9]*$/)
4541c235774Smrg    abort("getting exit status")
4551c235774Smrg  if (status < 127)
4561c235774Smrg    exit_details = ""
4571c235774Smrg  else if (status == 127)
4581c235774Smrg    exit_details = " (command not found?)"
4591c235774Smrg  else if (status >= 128 && status <= 255)
4601c235774Smrg    exit_details = sprintf(" (terminated by signal %d?)", status - 128)
4611c235774Smrg  else if (status > 256 && status <= 384)
4621c235774Smrg    # We used to report an "abnormal termination" here, but some Korn
4631c235774Smrg    # shells, when a child process die due to signal number n, can leave
4641c235774Smrg    # in $? an exit status of 256+n instead of the more standard 128+n.
4651c235774Smrg    # Apparently, both behaviours are allowed by POSIX (2008), so be
4661c235774Smrg    # prepared to handle them both.  See also Austing Group report ID
4671c235774Smrg    # 0000051 <http://www.austingroupbugs.net/view.php?id=51>
4681c235774Smrg    exit_details = sprintf(" (terminated by signal %d?)", status - 256)
4691c235774Smrg  else
4701c235774Smrg    # Never seen in practice.
4711c235774Smrg    exit_details = " (abnormal termination)"
4721c235774Smrg  return sprintf("exited with status %d%s", status, exit_details)
4731c235774Smrg}
4741c235774Smrg
4751c235774Smrgfunction write_test_results()
4761c235774Smrg{
4771c235774Smrg  print ":global-test-result: " get_global_test_result() > trs_file
4781c235774Smrg  print ":recheck: "  yn(must_recheck()) > trs_file
4791c235774Smrg  print ":copy-in-global-log: " yn(copy_in_global_log()) > trs_file
4801c235774Smrg  for (i = 0; i < test_results_index; i += 1)
4811c235774Smrg    print ":test-result: " test_results_list[i] > trs_file
4821c235774Smrg  close(trs_file);
4831c235774Smrg}
4841c235774Smrg
4851c235774SmrgBEGIN {
4861c235774Smrg
4871c235774Smrg## ------- ##
4881c235774Smrg##  SETUP  ##
4891c235774Smrg## ------- ##
4901c235774Smrg
4911c235774Smrg'"$init_colors"'
4921c235774Smrg
4931c235774Smrg# Properly initialized once the TAP plan is seen.
4941c235774Smrgplanned_tests = 0
4951c235774Smrg
4961c235774SmrgCOOKED_PASS = expect_failure ? "XPASS": "PASS";
4971c235774SmrgCOOKED_FAIL = expect_failure ? "XFAIL": "FAIL";
4981c235774Smrg
4991c235774Smrg# Enumeration-like constants to remember which kind of plan (if any)
5001c235774Smrg# has been seen.  It is important that NO_PLAN evaluates "false" as
5011c235774Smrg# a boolean.
5021c235774SmrgNO_PLAN = 0
5031c235774SmrgEARLY_PLAN = 1
5041c235774SmrgLATE_PLAN = 2
5051c235774Smrg
5061c235774Smrgtestno = 0     # Number of test results seen so far.
5071c235774Smrgbailed_out = 0 # Whether a "Bail out!" directive has been seen.
5081c235774Smrg
5091c235774Smrg# Whether the TAP plan has been seen or not, and if yes, which kind
5101c235774Smrg# it is ("early" is seen before any test result, "late" otherwise).
5111c235774Smrgplan_seen = NO_PLAN
5121c235774Smrg
5131c235774Smrg## --------- ##
5141c235774Smrg##  PARSING  ##
5151c235774Smrg## --------- ##
5161c235774Smrg
5171c235774Smrgis_first_read = 1
5181c235774Smrg
5191c235774Smrgwhile (1)
5201c235774Smrg  {
5211c235774Smrg    # Involutions required so that we are able to read the exit status
5221c235774Smrg    # from the last input line.
5231c235774Smrg    st = getline
5241c235774Smrg    if (st < 0) # I/O error.
5251c235774Smrg      fatal("I/O error while reading from input stream")
5261c235774Smrg    else if (st == 0) # End-of-input
5271c235774Smrg      {
5281c235774Smrg        if (is_first_read)
5291c235774Smrg          abort("in input loop: only one input line")
5301c235774Smrg        break
5311c235774Smrg      }
5321c235774Smrg    if (is_first_read)
5331c235774Smrg      {
5341c235774Smrg        is_first_read = 0
5351c235774Smrg        nextline = $0
5361c235774Smrg        continue
5371c235774Smrg      }
5381c235774Smrg    else
5391c235774Smrg      {
5401c235774Smrg        curline = nextline
5411c235774Smrg        nextline = $0
5421c235774Smrg        $0 = curline
5431c235774Smrg      }
5441c235774Smrg    # Copy any input line verbatim into the log file.
5451c235774Smrg    print | "cat >&3"
5461c235774Smrg    # Parsing of TAP input should stop after a "Bail out!" directive.
5471c235774Smrg    if (bailed_out)
5481c235774Smrg      continue
5491c235774Smrg
5501c235774Smrg    # TAP test result.
5511c235774Smrg    if ($0 ~ /^(not )?ok$/ || $0 ~ /^(not )?ok[^a-zA-Z0-9_]/)
5521c235774Smrg      {
5531c235774Smrg        testno += 1
5541c235774Smrg        setup_result_obj($0)
5551c235774Smrg        handle_tap_result()
5561c235774Smrg      }
5571c235774Smrg    # TAP plan (normal or "SKIP" without explanation).
5581c235774Smrg    else if ($0 ~ /^1\.\.[0-9]+[ \t]*$/)
5591c235774Smrg      {
5601c235774Smrg        # The next two lines will put the number of planned tests in $0.
5611c235774Smrg        sub("^1\\.\\.", "")
5621c235774Smrg        sub("[^0-9]*$", "")
5631c235774Smrg        handle_tap_plan($0, "")
5641c235774Smrg        continue
5651c235774Smrg      }
5661c235774Smrg    # TAP "SKIP" plan, with an explanation.
5671c235774Smrg    else if ($0 ~ /^1\.\.0+[ \t]*#/)
5681c235774Smrg      {
5691c235774Smrg        # The next lines will put the skip explanation in $0, stripping
5701c235774Smrg        # any leading and trailing whitespace.  This is a little more
5711c235774Smrg        # tricky in truth, since we want to also strip a potential leading
5721c235774Smrg        # "SKIP" string from the message.
5731c235774Smrg        sub("^[^#]*#[ \t]*(SKIP[: \t][ \t]*)?", "")
5741c235774Smrg        sub("[ \t]*$", "");
5751c235774Smrg        handle_tap_plan(0, $0)
5761c235774Smrg      }
5771c235774Smrg    # "Bail out!" magic.
5781c235774Smrg    # Older versions of prove and TAP::Harness (e.g., 3.17) did not
5791c235774Smrg    # recognize a "Bail out!" directive when preceded by leading
5801c235774Smrg    # whitespace, but more modern versions (e.g., 3.23) do.  So we
5811c235774Smrg    # emulate the latter, "more modern" behaviour.
5821c235774Smrg    else if ($0 ~ /^[ \t]*Bail out!/)
5831c235774Smrg      {
5841c235774Smrg        bailed_out = 1
5851c235774Smrg        # Get the bailout message (if any), with leading and trailing
5861c235774Smrg        # whitespace stripped.  The message remains stored in `$0`.
5871c235774Smrg        sub("^[ \t]*Bail out![ \t]*", "");
5881c235774Smrg        sub("[ \t]*$", "");
5891c235774Smrg        # Format the error message for the
5901c235774Smrg        bailout_message = "Bail out!"
5911c235774Smrg        if (length($0))
5921c235774Smrg          bailout_message = bailout_message " " $0
5931c235774Smrg        testsuite_error(bailout_message)
5941c235774Smrg      }
5951c235774Smrg    # Maybe we have too look for dianogtic comments too.
5961c235774Smrg    else if (comments != 0)
5971c235774Smrg      {
5981c235774Smrg        comment = extract_tap_comment($0);
5991c235774Smrg        if (length(comment))
6001c235774Smrg          report("#", comment);
6011c235774Smrg      }
6021c235774Smrg  }
6031c235774Smrg
6041c235774Smrg## -------- ##
6051c235774Smrg##  FINISH  ##
6061c235774Smrg## -------- ##
6071c235774Smrg
6081c235774Smrg# A "Bail out!" directive should cause us to ignore any following TAP
6091c235774Smrg# error, as well as a non-zero exit status from the TAP producer.
6101c235774Smrgif (!bailed_out)
6111c235774Smrg  {
6121c235774Smrg    if (!plan_seen)
6131c235774Smrg      {
6141c235774Smrg        testsuite_error("missing test plan")
6151c235774Smrg      }
6161c235774Smrg    else if (planned_tests != testno)
6171c235774Smrg      {
6181c235774Smrg        bad_amount = testno > planned_tests ? "many" : "few"
6191c235774Smrg        testsuite_error(sprintf("too %s tests run (expected %d, got %d)",
6201c235774Smrg                                bad_amount, planned_tests, testno))
6211c235774Smrg      }
6221c235774Smrg    if (!ignore_exit)
6231c235774Smrg      {
6241c235774Smrg        # Fetch exit status from the last line.
6251c235774Smrg        exit_message = get_test_exit_message(nextline)
6261c235774Smrg        if (exit_message)
6271c235774Smrg          testsuite_error(exit_message)
6281c235774Smrg      }
6291c235774Smrg  }
6301c235774Smrg
6311c235774Smrgwrite_test_results()
6321c235774Smrg
6331c235774Smrgexit 0
6341c235774Smrg
6351c235774Smrg} # End of "BEGIN" block.
6361c235774Smrg'
6371c235774Smrg
6381c235774Smrg# TODO: document that we consume the file descriptor 3 :-(
6391c235774Smrg} 3>"$log_file"
6401c235774Smrg
6411c235774Smrgtest $? -eq 0 || fatal "I/O or internal error"
6421c235774Smrg
6431c235774Smrg# Local Variables:
6441c235774Smrg# mode: shell-script
6451c235774Smrg# sh-indentation: 2
64674835918Smrg# eval: (add-hook 'before-save-hook 'time-stamp)
6471c235774Smrg# time-stamp-start: "scriptversion="
6481c235774Smrg# time-stamp-format: "%:y-%02m-%02d.%02H"
64974835918Smrg# time-stamp-time-zone: "UTC0"
6501c235774Smrg# time-stamp-end: "; # UTC"
6511c235774Smrg# End:
652