From ce3340d2fa85e3c290f50f585a7876e6d5c22987 Mon Sep 17 00:00:00 2001 From: hyung-hwan Date: Sat, 17 Dec 2022 16:04:13 +0000 Subject: [PATCH] touched up test scripts. switched to the tap style testing --- ac/tap-driver.sh | 651 +++++++++++++++++++++++++++++++++++++++++++++++ t/s-001.sh | 37 +++ t/t.h | 21 -- t/tap.h | 291 +++++++++++++++++++++ t/tap.inc | 31 +++ 5 files changed, 1010 insertions(+), 21 deletions(-) create mode 100755 ac/tap-driver.sh create mode 100644 t/s-001.sh delete mode 100644 t/t.h create mode 100644 t/tap.h create mode 100644 t/tap.inc diff --git a/ac/tap-driver.sh b/ac/tap-driver.sh new file mode 100755 index 0000000..0ca4903 --- /dev/null +++ b/ac/tap-driver.sh @@ -0,0 +1,651 @@ +#! /bin/sh +# Copyright (C) 2011-2020 Free Software Foundation, Inc. +# +# 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 2, 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 . + +# As a special exception to the GNU General Public License, if you +# distribute this file as part of a program that contains a +# configuration script generated by Autoconf, you may include it under +# the same distribution terms that you use for the rest of that program. + +# This file is maintained in Automake, please report +# bugs to or send patches to +# . + +scriptversion=2013-12-23.17; # UTC + +# Make unconditional expansion of undefined variables an error. This +# helps a lot in preventing typo-related bugs. +set -u + +me=tap-driver.sh + +fatal () +{ + echo "$me: fatal: $*" >&2 + exit 1 +} + +usage_error () +{ + echo "$me: $*" >&2 + print_usage >&2 + exit 2 +} + +print_usage () +{ + cat < + # + trap : 1 3 2 13 15 + if test $merge -gt 0; then + exec 2>&1 + else + exec 2>&3 + fi + "$@" + echo $? + ) | LC_ALL=C ${AM_TAP_AWK-awk} \ + -v me="$me" \ + -v test_script_name="$test_name" \ + -v log_file="$log_file" \ + -v trs_file="$trs_file" \ + -v expect_failure="$expect_failure" \ + -v merge="$merge" \ + -v ignore_exit="$ignore_exit" \ + -v comments="$comments" \ + -v diag_string="$diag_string" \ +' +# TODO: the usages of "cat >&3" below could be optimized when using +# GNU awk, and/on on systems that supports /dev/fd/. + +# Implementation note: in what follows, `result_obj` will be an +# associative array that (partly) simulates a TAP result object +# from the `TAP::Parser` perl module. + +## ----------- ## +## FUNCTIONS ## +## ----------- ## + +function fatal(msg) +{ + print me ": " msg | "cat >&2" + exit 1 +} + +function abort(where) +{ + fatal("internal error " where) +} + +# Convert a boolean to a "yes"/"no" string. +function yn(bool) +{ + return bool ? "yes" : "no"; +} + +function add_test_result(result) +{ + if (!test_results_index) + test_results_index = 0 + test_results_list[test_results_index] = result + test_results_index += 1 + test_results_seen[result] = 1; +} + +# Whether the test script should be re-run by "make recheck". +function must_recheck() +{ + for (k in test_results_seen) + if (k != "XFAIL" && k != "PASS" && k != "SKIP") + return 1 + return 0 +} + +# Whether the content of the log file associated to this test should +# be copied into the "global" test-suite.log. +function copy_in_global_log() +{ + for (k in test_results_seen) + if (k != "PASS") + return 1 + return 0 +} + +function get_global_test_result() +{ + if ("ERROR" in test_results_seen) + return "ERROR" + if ("FAIL" in test_results_seen || "XPASS" in test_results_seen) + return "FAIL" + all_skipped = 1 + for (k in test_results_seen) + if (k != "SKIP") + all_skipped = 0 + if (all_skipped) + return "SKIP" + return "PASS"; +} + +function stringify_result_obj(result_obj) +{ + if (result_obj["is_unplanned"] || result_obj["number"] != testno) + return "ERROR" + + if (plan_seen == LATE_PLAN) + return "ERROR" + + if (result_obj["directive"] == "TODO") + return result_obj["is_ok"] ? "XPASS" : "XFAIL" + + if (result_obj["directive"] == "SKIP") + return result_obj["is_ok"] ? "SKIP" : COOKED_FAIL; + + if (length(result_obj["directive"])) + abort("in function stringify_result_obj()") + + return result_obj["is_ok"] ? COOKED_PASS : COOKED_FAIL +} + +function decorate_result(result) +{ + color_name = color_for_result[result] + if (color_name) + return color_map[color_name] "" result "" color_map["std"] + # If we are not using colorized output, or if we do not know how + # to colorize the given result, we should return it unchanged. + return result +} + +function report(result, details) +{ + if (result ~ /^(X?(PASS|FAIL)|SKIP|ERROR)/) + { + msg = ": " test_script_name + add_test_result(result) + } + else if (result == "#") + { + msg = " " test_script_name ":" + } + else + { + abort("in function report()") + } + if (length(details)) + msg = msg " " details + # Output on console might be colorized. + print decorate_result(result) msg + # Log the result in the log file too, to help debugging (this is + # especially true when said result is a TAP error or "Bail out!"). + print result msg | "cat >&3"; +} + +function testsuite_error(error_message) +{ + report("ERROR", "- " error_message) +} + +function handle_tap_result() +{ + details = result_obj["number"]; + if (length(result_obj["description"])) + details = details " " result_obj["description"] + + if (plan_seen == LATE_PLAN) + { + details = details " # AFTER LATE PLAN"; + } + else if (result_obj["is_unplanned"]) + { + details = details " # UNPLANNED"; + } + else if (result_obj["number"] != testno) + { + details = sprintf("%s # OUT-OF-ORDER (expecting %d)", + details, testno); + } + else if (result_obj["directive"]) + { + details = details " # " result_obj["directive"]; + if (length(result_obj["explanation"])) + details = details " " result_obj["explanation"] + } + + report(stringify_result_obj(result_obj), details) +} + +# `skip_reason` should be empty whenever planned > 0. +function handle_tap_plan(planned, skip_reason) +{ + planned += 0 # Avoid getting confused if, say, `planned` is "00" + if (length(skip_reason) && planned > 0) + abort("in function handle_tap_plan()") + if (plan_seen) + { + # Error, only one plan per stream is acceptable. + testsuite_error("multiple test plans") + return; + } + planned_tests = planned + # The TAP plan can come before or after *all* the TAP results; we speak + # respectively of an "early" or a "late" plan. If we see the plan line + # after at least one TAP result has been seen, assume we have a late + # plan; in this case, any further test result seen after the plan will + # be flagged as an error. + plan_seen = (testno >= 1 ? LATE_PLAN : EARLY_PLAN) + # If testno > 0, we have an error ("too many tests run") that will be + # automatically dealt with later, so do not worry about it here. If + # $plan_seen is true, we have an error due to a repeated plan, and that + # has already been dealt with above. Otherwise, we have a valid "plan + # with SKIP" specification, and should report it as a particular kind + # of SKIP result. + if (planned == 0 && testno == 0) + { + if (length(skip_reason)) + skip_reason = "- " skip_reason; + report("SKIP", skip_reason); + } +} + +function extract_tap_comment(line) +{ + if (index(line, diag_string) == 1) + { + # Strip leading `diag_string` from `line`. + line = substr(line, length(diag_string) + 1) + # And strip any leading and trailing whitespace left. + sub("^[ \t]*", "", line) + sub("[ \t]*$", "", line) + # Return what is left (if any). + return line; + } + return ""; +} + +# When this function is called, we know that line is a TAP result line, +# so that it matches the (perl) RE "^(not )?ok\b". +function setup_result_obj(line) +{ + # Get the result, and remove it from the line. + result_obj["is_ok"] = (substr(line, 1, 2) == "ok" ? 1 : 0) + sub("^(not )?ok[ \t]*", "", line) + + # If the result has an explicit number, get it and strip it; otherwise, + # automatically assign the next test number to it. + if (line ~ /^[0-9]+$/ || line ~ /^[0-9]+[^a-zA-Z0-9_]/) + { + match(line, "^[0-9]+") + # The final `+ 0` is to normalize numbers with leading zeros. + result_obj["number"] = substr(line, 1, RLENGTH) + 0 + line = substr(line, RLENGTH + 1) + } + else + { + result_obj["number"] = testno + } + + if (plan_seen == LATE_PLAN) + # No further test results are acceptable after a "late" TAP plan + # has been seen. + result_obj["is_unplanned"] = 1 + else if (plan_seen && testno > planned_tests) + result_obj["is_unplanned"] = 1 + else + result_obj["is_unplanned"] = 0 + + # Strip trailing and leading whitespace. + sub("^[ \t]*", "", line) + sub("[ \t]*$", "", line) + + # This will have to be corrected if we have a "TODO"/"SKIP" directive. + result_obj["description"] = line + result_obj["directive"] = "" + result_obj["explanation"] = "" + + if (index(line, "#") == 0) + return # No possible directive, nothing more to do. + + # Directives are case-insensitive. + rx = "[ \t]*#[ \t]*([tT][oO][dD][oO]|[sS][kK][iI][pP])[ \t]*" + + # See whether we have the directive, and if yes, where. + pos = match(line, rx "$") + if (!pos) + pos = match(line, rx "[^a-zA-Z0-9_]") + + # If there was no TAP directive, we have nothing more to do. + if (!pos) + return + + # Let`s now see if the TAP directive has been escaped. For example: + # escaped: ok \# SKIP + # not escaped: ok \\# SKIP + # escaped: ok \\\\\# SKIP + # not escaped: ok \ # SKIP + if (substr(line, pos, 1) == "#") + { + bslash_count = 0 + for (i = pos; i > 1 && substr(line, i - 1, 1) == "\\"; i--) + bslash_count += 1 + if (bslash_count % 2) + return # Directive was escaped. + } + + # Strip the directive and its explanation (if any) from the test + # description. + result_obj["description"] = substr(line, 1, pos - 1) + # Now remove the test description from the line, that has been dealt + # with already. + line = substr(line, pos) + # Strip the directive, and save its value (normalized to upper case). + sub("^[ \t]*#[ \t]*", "", line) + result_obj["directive"] = toupper(substr(line, 1, 4)) + line = substr(line, 5) + # Now get the explanation for the directive (if any), with leading + # and trailing whitespace removed. + sub("^[ \t]*", "", line) + sub("[ \t]*$", "", line) + result_obj["explanation"] = line +} + +function get_test_exit_message(status) +{ + if (status == 0) + return "" + if (status !~ /^[1-9][0-9]*$/) + abort("getting exit status") + if (status < 127) + exit_details = "" + else if (status == 127) + exit_details = " (command not found?)" + else if (status >= 128 && status <= 255) + exit_details = sprintf(" (terminated by signal %d?)", status - 128) + else if (status > 256 && status <= 384) + # We used to report an "abnormal termination" here, but some Korn + # shells, when a child process die due to signal number n, can leave + # in $? an exit status of 256+n instead of the more standard 128+n. + # Apparently, both behaviours are allowed by POSIX (2008), so be + # prepared to handle them both. See also Austing Group report ID + # 0000051 + exit_details = sprintf(" (terminated by signal %d?)", status - 256) + else + # Never seen in practice. + exit_details = " (abnormal termination)" + return sprintf("exited with status %d%s", status, exit_details) +} + +function write_test_results() +{ + print ":global-test-result: " get_global_test_result() > trs_file + print ":recheck: " yn(must_recheck()) > trs_file + print ":copy-in-global-log: " yn(copy_in_global_log()) > trs_file + for (i = 0; i < test_results_index; i += 1) + print ":test-result: " test_results_list[i] > trs_file + close(trs_file); +} + +BEGIN { + +## ------- ## +## SETUP ## +## ------- ## + +'"$init_colors"' + +# Properly initialized once the TAP plan is seen. +planned_tests = 0 + +COOKED_PASS = expect_failure ? "XPASS": "PASS"; +COOKED_FAIL = expect_failure ? "XFAIL": "FAIL"; + +# Enumeration-like constants to remember which kind of plan (if any) +# has been seen. It is important that NO_PLAN evaluates "false" as +# a boolean. +NO_PLAN = 0 +EARLY_PLAN = 1 +LATE_PLAN = 2 + +testno = 0 # Number of test results seen so far. +bailed_out = 0 # Whether a "Bail out!" directive has been seen. + +# Whether the TAP plan has been seen or not, and if yes, which kind +# it is ("early" is seen before any test result, "late" otherwise). +plan_seen = NO_PLAN + +## --------- ## +## PARSING ## +## --------- ## + +is_first_read = 1 + +while (1) + { + # Involutions required so that we are able to read the exit status + # from the last input line. + st = getline + if (st < 0) # I/O error. + fatal("I/O error while reading from input stream") + else if (st == 0) # End-of-input + { + if (is_first_read) + abort("in input loop: only one input line") + break + } + if (is_first_read) + { + is_first_read = 0 + nextline = $0 + continue + } + else + { + curline = nextline + nextline = $0 + $0 = curline + } + # Copy any input line verbatim into the log file. + print | "cat >&3" + # Parsing of TAP input should stop after a "Bail out!" directive. + if (bailed_out) + continue + + # TAP test result. + if ($0 ~ /^(not )?ok$/ || $0 ~ /^(not )?ok[^a-zA-Z0-9_]/) + { + testno += 1 + setup_result_obj($0) + handle_tap_result() + } + # TAP plan (normal or "SKIP" without explanation). + else if ($0 ~ /^1\.\.[0-9]+[ \t]*$/) + { + # The next two lines will put the number of planned tests in $0. + sub("^1\\.\\.", "") + sub("[^0-9]*$", "") + handle_tap_plan($0, "") + continue + } + # TAP "SKIP" plan, with an explanation. + else if ($0 ~ /^1\.\.0+[ \t]*#/) + { + # The next lines will put the skip explanation in $0, stripping + # any leading and trailing whitespace. This is a little more + # tricky in truth, since we want to also strip a potential leading + # "SKIP" string from the message. + sub("^[^#]*#[ \t]*(SKIP[: \t][ \t]*)?", "") + sub("[ \t]*$", ""); + handle_tap_plan(0, $0) + } + # "Bail out!" magic. + # Older versions of prove and TAP::Harness (e.g., 3.17) did not + # recognize a "Bail out!" directive when preceded by leading + # whitespace, but more modern versions (e.g., 3.23) do. So we + # emulate the latter, "more modern" behaviour. + else if ($0 ~ /^[ \t]*Bail out!/) + { + bailed_out = 1 + # Get the bailout message (if any), with leading and trailing + # whitespace stripped. The message remains stored in `$0`. + sub("^[ \t]*Bail out![ \t]*", ""); + sub("[ \t]*$", ""); + # Format the error message for the + bailout_message = "Bail out!" + if (length($0)) + bailout_message = bailout_message " " $0 + testsuite_error(bailout_message) + } + # Maybe we have too look for dianogtic comments too. + else if (comments != 0) + { + comment = extract_tap_comment($0); + if (length(comment)) + report("#", comment); + } + } + +## -------- ## +## FINISH ## +## -------- ## + +# A "Bail out!" directive should cause us to ignore any following TAP +# error, as well as a non-zero exit status from the TAP producer. +if (!bailed_out) + { + if (!plan_seen) + { + testsuite_error("missing test plan") + } + else if (planned_tests != testno) + { + bad_amount = testno > planned_tests ? "many" : "few" + testsuite_error(sprintf("too %s tests run (expected %d, got %d)", + bad_amount, planned_tests, testno)) + } + if (!ignore_exit) + { + # Fetch exit status from the last line. + exit_message = get_test_exit_message(nextline) + if (exit_message) + testsuite_error(exit_message) + } + } + +write_test_results() + +exit 0 + +} # End of "BEGIN" block. +' + +# TODO: document that we consume the file descriptor 3 :-( +} 3>"$log_file" + +test $? -eq 0 || fatal "I/O or internal error" + +# Local Variables: +# mode: shell-script +# sh-indentation: 2 +# eval: (add-hook 'before-save-hook 'time-stamp) +# time-stamp-start: "scriptversion=" +# time-stamp-format: "%:y-%02m-%02d.%02H" +# time-stamp-time-zone: "UTC0" +# time-stamp-end: "; # UTC" +# End: diff --git a/t/s-001.sh b/t/s-001.sh new file mode 100644 index 0000000..9613230 --- /dev/null +++ b/t/s-001.sh @@ -0,0 +1,37 @@ +#!/bin/sh + +##srcdir=$(dirname "$0") +##srcdir="${0%/*}" +[ -z "$srcdir" ] +. "${srcdir}/tap.inc" + +test_default_index() +{ + local msg="default index.html" + local srvaddr=127.0.0.1:54321 + local tmpdir="/tmp/s-001.$$" + ../bin/hio-webs "${srvaddr}" "${tmpdir}" 2>/dev/null & + local jid=$! + + mkdir -p "${tmpdir}" + cat >"${tmpdir}/index.html" < + +hello + + +EOF + sleep 0.5 + local hc=$(curl -s -w '%{http_code}\n' -o /dev/null "http://${srvaddr}") + tap_ensure "$hc" "200" "$msg - got $hc" + + rm -rf "${tmpdir}" + local hc=$(curl -s -w '%{http_code}\n' -o /dev/null "http://${srvaddr}") + tap_ensure "$hc" "404" "$msg - got $hc" + + kill -TERM ${jid} + wait ${jid} +} + +test_default_index +tap_end \ No newline at end of file diff --git a/t/t.h b/t/t.h deleted file mode 100644 index 9fb72d2..0000000 --- a/t/t.h +++ /dev/null @@ -1,21 +0,0 @@ -#ifndef _HAWK_T_T_H_ -#define _HAWK_T_T_H_ - -#include - -#if defined(__STDC_VERSION__) && (__STDC_VERSION__>=199901L) -# define T_ASSERT_FAIL0() printf("FAILURE in %s:%s[%d]\n", __FILE__, __func__, (int)__LINE__) -# define T_ASSERT_FAIL1(msg1) printf("FAILURE in %s:%s[%d] - %s\n", __FILE__, __func__, (int)__LINE__, msg1) -# define T_ASSERT_FAIL2(msg1,msg2) printf("FAILURE in %s:%s[%d] - %s - %s\n", __FILE__, __func__, (int)__LINE__, msg1, msg2) -#else -# define T_ASSERT_FAIL0() printf("FAILURE in %s[%d]\n", __FILE__, (int)__LINE__) -# define T_ASSERT_FAIL1(msg1) printf("FAILURE in %s[%d] - %s\n", __FILE__, (int)__LINE__, msg1) -# define T_ASSERT_FAIL2(msg1,msg2) printf("FAILURE in %s[%d] - %s - %s\n", __FILE__, (int)__LINE__, msg1, msg2) -#endif - -#define T_ASSERT0(test) do { if (!(test)) { T_ASSERT_FAIL0(); goto oops; } } while(0) -#define T_ASSERT1(test,msg1) do { if (!(test)) { T_ASSERT_FAIL1(msg1); goto oops; } } while(0) -#define T_ASSERT2(test,msg1,msg2) do { if (!(test)) { T_ASSERT_FAIL2(msg1,msg2); goto oops; } } while(0) - - -#endif diff --git a/t/tap.h b/t/tap.h new file mode 100644 index 0000000..7f968b0 --- /dev/null +++ b/t/tap.h @@ -0,0 +1,291 @@ +/* +** tap4embedded : http://github.com/fperrad/tap4embedded +** +** Copyright (C) 2016-2017 Francois Perrad. +** +** tap4embedded is free software; you can redistribute it and/or modify it +** under the terms of the Artistic License 2.0 +*/ + +#ifndef TAP_H +#define TAP_H + +#ifndef TAP_PUTCHAR +#include +#define TAP_PUTCHAR(c) fputc((c), stdout) +#define TAP_FLUSH() fflush(stdout) +#endif + +#ifndef TAP_FLUSH +#define TAP_FLUSH() +#endif + +#if defined(__unix__) || defined(_WIN32) || defined(_WIN64) +#include +#define TAP_EXIT(n) exit(n) +#else +#define TAP_EXIT(n) +#endif + +#if defined(__bool_true_false_are_defined) || defined(__cplusplus) +#define TAP_BOOL bool +#define TAP_TRUE true +#define TAP_FALSE false +#else +#define TAP_BOOL char +#define TAP_TRUE 1 +#define TAP_FALSE 0 +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +extern void plan(unsigned int nb); +extern void no_plan(void); +extern void skip_all(const char *reason); +extern void done_testing(unsigned int nb); +extern void bail_out(const char *reason); +extern void ok(const char *file, unsigned int line, TAP_BOOL test, const char *name); +extern void todo(const char *reason, unsigned int count); +extern void skip(const char *reason, unsigned int count); +extern void todo_skip(const char *reason); +extern void skip_rest(const char *reason); +extern void diag (const char *msg); +extern int exit_status (void); + +#define OK(test, name) ok(__FILE__, __LINE__, (test), (name)) +#define NOK(test, name) ok(__FILE__, __LINE__, !(test), (name)) +#define PASS(name) ok(__FILE__, __LINE__, TAP_TRUE, (name)) +#define FAIL(name) ok(__FILE__, __LINE__, TAP_FALSE, (name)) + +#ifndef TAP_NO_IMPL + +typedef struct { + unsigned int curr_test; + unsigned int expected_tests; + unsigned int todo_upto; + const char *todo_reason; + TAP_BOOL have_plan; + TAP_BOOL no_plan; + TAP_BOOL have_output_plan; + TAP_BOOL done_testing; + TAP_BOOL is_passing; +} tap_t; + +tap_t tap; + +void putstr(const char *msg) { + TAP_BOOL need_begin = TAP_FALSE; + for (; *msg != '\0'; msg++) { + if (need_begin) { + TAP_PUTCHAR('#'); + TAP_PUTCHAR(' '); + need_begin = TAP_FALSE; + } + TAP_PUTCHAR(*msg); + if (*msg == '\n') { + need_begin = TAP_TRUE; + } + } +} + +void putuint (unsigned int n) { + char buf[8 * sizeof(unsigned int) + 1]; + char *ptr = &buf[sizeof(buf) - 1]; + *ptr = '\0'; + do { + char c = '0' + (n % 10u); + --ptr; + *ptr = c; + n /= 10u; + } while (n != 0u); + putstr(ptr); +} + +void not_yet_plan(void) { + if (tap.have_plan) { + putstr("You tried to plan twice\n"); + TAP_FLUSH(); + TAP_EXIT(-1); + } +} + +void plan(unsigned int nb) { + not_yet_plan(); + putstr("1.."); + putuint(nb); + TAP_PUTCHAR('\n'); + tap.expected_tests = nb; + tap.have_plan = TAP_TRUE; + tap.have_output_plan = TAP_TRUE; + tap.is_passing = TAP_TRUE; +} + +void no_plan(void) { + not_yet_plan(); + tap.have_plan = TAP_TRUE; + tap.no_plan = TAP_TRUE; + tap.is_passing = TAP_TRUE; +} + +void skip_all(const char *reason) { + not_yet_plan(); + putstr("1..0 # SKIP "); + putstr(reason); + TAP_PUTCHAR('\n'); + tap.curr_test = 1u; + tap.expected_tests = 1u; + tap.have_plan = TAP_TRUE; + tap.have_output_plan = TAP_TRUE; + tap.is_passing = TAP_TRUE; + TAP_FLUSH(); +} + +void done_testing(unsigned int nb) { + if (tap.done_testing) { + putstr("done_testing() was already called\n"); + } + else { + tap.done_testing = TAP_TRUE; + if ((tap.expected_tests != nb) && (tap.expected_tests != 0u)) { + putstr("# Looks like you planned "); + putuint(tap.expected_tests); + putstr(" tests but ran "); + putuint(nb); + TAP_PUTCHAR('.'); + TAP_PUTCHAR('\n'); + } + else { + tap.expected_tests = nb; + } + if (! tap.have_output_plan) { + putstr("1.."); + putuint(nb); + TAP_PUTCHAR('\n'); + tap.have_output_plan = TAP_TRUE; + } + if ((tap.expected_tests != tap.curr_test) || (tap.curr_test == 0u)) { + tap.is_passing = TAP_FALSE; + } + putstr("# Done with tap4embedded.\n"); + } + TAP_FLUSH(); +} + +void bail_out(const char *reason) { + putstr("Bail out!"); + if (reason != NULL) { + TAP_PUTCHAR(' '); + TAP_PUTCHAR(' '); + putstr(reason); + } + TAP_PUTCHAR('\n'); + TAP_FLUSH(); + TAP_EXIT(-1); +} + +void need_plan(void) { + if (! tap.have_plan) { + putstr("You tried to run a test without a plan\n"); + TAP_FLUSH(); + TAP_EXIT(-1); + } +} + +void ok(const char *file, unsigned int line, TAP_BOOL test, const char *name) { + need_plan(); + ++tap.curr_test; + if (! test) { + putstr("not "); + } + putstr("ok "); + putuint(tap.curr_test); + if (name != NULL) { + putstr(" - "); + putstr(name); + } + if ((tap.todo_reason != NULL) && (tap.todo_upto >= tap.curr_test)) { + putstr(" # TODO # "); + putstr(tap.todo_reason); + } + TAP_PUTCHAR('\n'); + if (! test) { + putstr("# Failed"); + if (tap.todo_upto >= tap.curr_test) { + putstr(" (TODO)"); + } + else { + tap.is_passing = TAP_FALSE; + } + putstr(" test ("); + putstr(file); + putstr(" at line "); + putuint(line); + putstr(")\n"); + } +} + +void todo(const char *reason, unsigned int count) { + tap.todo_upto = tap.curr_test + count; + tap.todo_reason = reason; +} + +void skip(const char *reason, unsigned int count) { + unsigned int i; + need_plan(); + for (i = 0u; i < count; i++) { + ++tap.curr_test; + putstr("ok "); + putuint(tap.curr_test); + putstr(" - # skip"); + if (reason != NULL) { + TAP_PUTCHAR(' '); + putstr(reason); + } + TAP_PUTCHAR('\n'); + } +} + +void todo_skip(const char *reason) { + need_plan(); + ++tap.curr_test; + putstr("not ok "); + putuint(tap.curr_test); + putstr(" - # TODO & SKIP"); + if (reason != NULL) { + TAP_PUTCHAR(' '); + putstr(reason); + } + TAP_PUTCHAR('\n'); +} + +void skip_rest(const char *reason) { + skip(reason, tap.expected_tests - tap.curr_test); +} + +void diag (const char *msg) { + TAP_PUTCHAR('#'); + TAP_PUTCHAR(' '); + putstr(msg); + TAP_PUTCHAR('\n'); +} + +int exit_status (void) { + if (! tap.done_testing) { + done_testing(tap.curr_test); + } +#if defined(EXIT_SUCCESS) && defined(EXIT_FAILURE) + return tap.is_passing ? EXIT_SUCCESS : EXIT_FAILURE; +#else + return tap.is_passing ? 0 : -1; +#endif +} + +#endif + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/t/tap.inc b/t/tap.inc new file mode 100644 index 0000000..b5b5872 --- /dev/null +++ b/t/tap.inc @@ -0,0 +1,31 @@ +tap_total=0 + +function tap_end() +{ + printf "1..%d\n" tap_total +} + +function tap_ok(msg) +{ + tap_total++; + printf "ok %d - %s\n" tap_total msg +} + +function tap_fail(msg) +{ + tap_total++; + printf "not ok %d - %s\n" tap_total msg +} + +function tap_skip(msg) +{ + tap_total++; + printf "ok %d - # skip%s%s\n" tap_total (length(msg) > 0? " ": "") msg +} + +function tap_ensure(a, b, desc, line) +{ + id = sprintf("%s[%d]", desc, line); + if (a != b) tap_fail (id); + else tap_ok (id); +}