summaryrefslogtreecommitdiff
path: root/test/libtest
diff options
context:
space:
mode:
Diffstat (limited to 'test/libtest')
-rw-r--r--test/libtest/Makefile5
-rw-r--r--test/libtest/README.rst6
-rwxr-xr-xtest/libtest/bin/make-test-scaffolding29
-rw-r--r--test/libtest/driver/Makefile7
-rw-r--r--test/libtest/driver/driver.c216
-rw-r--r--test/libtest/driver/driver.h206
-rw-r--r--test/libtest/driver/driver_main.c726
-rw-r--r--test/libtest/driver/test_driver.1308
-rw-r--r--test/libtest/examples/minimal_example.c8
-rw-r--r--test/libtest/examples/simple_example.c48
-rw-r--r--test/libtest/lib/Makefile5
-rw-r--r--test/libtest/lib/test.320
-rw-r--r--test/libtest/lib/test.h54
-rw-r--r--test/libtest/lib/test_case.h (renamed from test/libtest/driver/test_main.c)43
-rw-r--r--test/libtest/lib/test_runner.c31
-rw-r--r--test/libtest/lib/test_runner.h118
16 files changed, 1594 insertions, 236 deletions
diff --git a/test/libtest/Makefile b/test/libtest/Makefile
index 1dc489b9db8a..3cf6de08f316 100644
--- a/test/libtest/Makefile
+++ b/test/libtest/Makefile
@@ -9,9 +9,8 @@ SUBDIR+= lib
SUBDIR+= driver
SUBDIR+= examples
-.if !make(install)
+.if !make(install) && !make(test)
.include "$(TOP)/mk/elftoolchain.subdir.mk"
.else
-install: .SILENT .PHONY
- echo Nothing to install.
+install test: .SILENT .PHONY
.endif
diff --git a/test/libtest/README.rst b/test/libtest/README.rst
index 3f29c85f8a93..b93dca7a69ee 100644
--- a/test/libtest/README.rst
+++ b/test/libtest/README.rst
@@ -43,7 +43,7 @@ functions contained in a test case named "``helloworld``":
/* File: test.c */
#include "test.h"
- TESTCASE_DESCRIPTION(helloworld) =
+ TEST_CASE_DESCRIPTION(helloworld) =
"A description of the helloworld test case.";
enum test_result
@@ -69,14 +69,14 @@ Test cases can define their own set up and tear down functions:
tc_setup_helloworld(testcase_state *tcs)
{
*tcs = ..allocate a struct helloworld_test.. ;
- return (TESTCASE_OK);
+ return (TEST_CASE_OK);
}
enum testcase_status
tc_teardown_helloworld(testcase_state tcs)
{
.. deallocate test case state..
- return (TESTCASE_OK);
+ return (TEST_CASE_OK);
}
The set up function for a test case will be invoked prior to any of
diff --git a/test/libtest/bin/make-test-scaffolding b/test/libtest/bin/make-test-scaffolding
index c0966d34527b..6ec78e8224e1 100755
--- a/test/libtest/bin/make-test-scaffolding
+++ b/test/libtest/bin/make-test-scaffolding
@@ -79,7 +79,7 @@ cat <<EOF
/* GENERATED FROM: ${@} */
#include <stddef.h>
#include "test.h"
-#include "test_runner.h"
+#include "test_case.h"
EOF
if ! nm ${*} | sort -k 3 | \
@@ -108,9 +108,13 @@ if ! nm ${*} | sort -k 3 | \
function print_test_case_record(tc_name) {
printf("\t{\n")
printf("\t\t.tc_name = \"%s\",\n", tc_name)
- printf("\t\t.tc_description = %s,\n", test_case_descriptions[tc_name])
+ printf("\t\t.tc_description = %s,\n",
+ test_case_descriptions[tc_name])
printf("\t\t.tc_tags = %s,\n", test_case_tags[tc_name])
- printf("\t\t.tc_tests = test_functions_%s\n", tc_name)
+ tf_name = "test_functions_" tc_name
+ printf("\t\t.tc_tests = %s,\n", tf_name)
+ printf("\t\t.tc_count = sizeof (%s) / sizeof (%s[0]),\n",
+ tf_name, tf_name)
printf("\t},\n")
}
function delete_test_functions(tc_name) {
@@ -120,16 +124,19 @@ if ! nm ${*} | sort -k 3 | \
}
}
function print_test_functions_record(tc_name) {
- printf("struct test_descriptor test_functions_%s[] = {\n", tc_name)
+ printf("struct test_function_descriptor test_functions_%s[]",
+ tc_name)
+ printf(" = {\n")
for (tf_name in test_functions) {
if (tc_name != matched_test_case(tf_name))
continue
printf("\t{\n")
- printf("\t\t.t_name = \"%s\",\n", tf_name)
- printf("\t\t.t_description = %s,\n",
+ printf("\t\t.tf_name = \"%s\",\n", tf_name)
+ printf("\t\t.tf_description = %s,\n",
test_function_descriptions[tf_name])
- printf("\t\t.t_func = %s,\n", prefix_tf tf_name)
- printf("\t\t.t_tags = %s\n", test_function_tags[tf_name])
+ printf("\t\t.tf_func = %s,\n", prefix_tf tf_name)
+ printf("\t\t.tf_tags = %s\n",
+ test_function_tags[tf_name])
printf("\t},\n")
}
printf("};\n")
@@ -144,7 +151,7 @@ if ! nm ${*} | sort -k 3 | \
test_case_tags[DEFAULT] = "NULL"
}
($2 == "R" || $2 == "D") && $3 ~ "^" prefix_tc_descr {
- printf("extern testcase_description %s;\n", $3)
+ printf("extern test_case_description %s;\n", $3)
tc_name = suffix($3, prefix_tc_descr)
test_cases[tc_name] = 1
test_case_descriptions[tc_name] = $3
@@ -155,7 +162,7 @@ if ! nm ${*} | sort -k 3 | \
test_case_setup[tc_name] = $3
}
($2 == "R" || $2 == "D") && $3 ~ "^" prefix_tc_tags {
- printf("extern testcase_tags %s;\n", $3)
+ printf("extern test_case_tags %s;\n", $3)
tc_name = suffix($3, prefix_tc_tags)
test_cases[tc_name] = 1
test_case_tags[tc_name] = $3
@@ -206,6 +213,8 @@ if ! nm ${*} | sort -k 3 | \
if (needs_default)
print_test_case_record(DEFAULT)
printf("};\n")
+ printf("const int test_case_count = sizeof(test_cases) / ")
+ printf("sizeof(test_cases[0]);\n")
}'; then
# Cleanup in case of an error.
rm ${output_file}
diff --git a/test/libtest/driver/Makefile b/test/libtest/driver/Makefile
index d149fee159d4..87788d3fb13c 100644
--- a/test/libtest/driver/Makefile
+++ b/test/libtest/driver/Makefile
@@ -6,9 +6,12 @@ TOP= ../../..
CFLAGS+= -I${TOP}/test/libtest/lib
-LIB= test_main
-SRCS= test_main.c
+LIB= driver
+SRCS= driver.c \
+ driver_main.c
WARNS?= 6
+MAN= test_driver.1
+
.include "$(TOP)/mk/elftoolchain.lib.mk"
diff --git a/test/libtest/driver/driver.c b/test/libtest/driver/driver.c
new file mode 100644
index 000000000000..aa85c7cb4ddf
--- /dev/null
+++ b/test/libtest/driver/driver.c
@@ -0,0 +1,216 @@
+/*-
+ * Copyright (c) 2018, Joseph Koshy
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer
+ * in this position and unchanged.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/*
+ * The implementation of the test driver.
+ */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+
+#include <err.h>
+#include <libgen.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sysexits.h>
+
+#include "driver.h"
+
+#if defined(ELFTC_VCSID)
+ELFTC_VCSID("$Id$");
+#endif
+
+#define SYSTEM_TMPDIR_ENV_VAR "TMPDIR"
+
+bool
+test_driver_add_search_path(struct test_run *tr, const char *directory_name)
+{
+ char *canonical_path;
+ struct test_search_path_entry *entry;
+
+ if (!test_driver_is_directory(directory_name))
+ return (false);
+
+ if ((canonical_path = realpath(directory_name, NULL)) == NULL)
+ err(1, "Cannot determine the canonical path for \"%s\"",
+ directory_name);
+
+ /* Look for, and ignore duplicates. */
+ STAILQ_FOREACH(entry, &tr->tr_search_path, tsp_next) {
+ if (strcmp(canonical_path, entry->tsp_directory) == 0)
+ return (true);
+ }
+
+ entry = calloc(1, sizeof(*entry));
+ entry->tsp_directory = canonical_path;
+
+ STAILQ_INSERT_TAIL(&tr->tr_search_path, entry, tsp_next);
+
+ return (true);
+}
+
+/*
+ * Return an initialized test run descriptor.
+ *
+ * The caller should use test_driver_free_run() to release the returned
+ * descriptor.
+ */
+struct test_run *
+test_driver_allocate_run(void)
+{
+ struct test_run *tr;
+
+ tr = calloc(sizeof(struct test_run), 1);
+ tr->tr_action = TEST_RUN_EXECUTE;
+ tr->tr_style = TR_STYLE_LIBTEST;
+ STAILQ_INIT(&tr->tr_test_cases);
+ STAILQ_INIT(&tr->tr_search_path);
+
+ return (tr);
+}
+
+/*
+ * Destroy an allocated test run descriptor.
+ *
+ * The passed in pointer should not be used after this function returns.
+ */
+void
+test_driver_free_run(struct test_run *tr)
+{
+ struct test_search_path_entry *path_entry;
+ struct test_case_selector *test_case_entry;
+ struct test_function_selector *function_entry;
+
+ free(tr->tr_runtime_base_directory);
+ free(tr->tr_name);
+ if (tr->tr_artefact_archive)
+ free(tr->tr_artefact_archive);
+
+ /* Free the search path list. */
+ while (!STAILQ_EMPTY(&tr->tr_search_path)) {
+ path_entry = STAILQ_FIRST(&tr->tr_search_path);
+ STAILQ_REMOVE_HEAD(&tr->tr_search_path, tsp_next);
+ free(path_entry);
+ }
+
+ /* Free the test selector list. */
+ while (!STAILQ_EMPTY(&tr->tr_test_cases)) {
+ test_case_entry = STAILQ_FIRST(&tr->tr_test_cases);
+ STAILQ_REMOVE_HEAD(&tr->tr_test_cases, tcs_next);
+
+ /* Free the linked test functions. */
+ while (!STAILQ_EMPTY(&test_case_entry->tcs_functions)) {
+ function_entry =
+ STAILQ_FIRST(&test_case_entry->tcs_functions);
+ STAILQ_REMOVE_HEAD(&test_case_entry->tcs_functions,
+ tfs_next);
+
+ free(function_entry);
+ }
+
+ free(test_case_entry);
+ }
+
+ free(tr);
+}
+
+/*
+ * Populate unset fields of a struct test_run with defaults.
+ */
+bool
+test_driver_finish_run_initialization(struct test_run *tr, const char *argv0)
+{
+ struct timeval tv;
+ const char *basedir;
+ const char *search_path;
+ const char *last_component;
+ char *argv0_copy, *path_copy, *path_element;
+ char test_name[NAME_MAX];
+
+ if (tr->tr_name == NULL) {
+ /* Per POSIX, basename(3) can modify its argument. */
+ argv0_copy = strdup(argv0);
+ last_component = basename(argv0_copy);
+
+ if (gettimeofday(&tv, NULL))
+ return (false);
+
+ (void) snprintf(test_name, sizeof(test_name), "%s+%ld%ld",
+ last_component, (long) tv.tv_sec, (long) tv.tv_usec);
+
+ tr->tr_name = strdup(test_name);
+
+ free(argv0_copy);
+ }
+
+ /*
+ * Select a base directory, if one was not specified.
+ */
+ if (tr->tr_runtime_base_directory == NULL) {
+ basedir = getenv(TEST_TMPDIR_ENV_VAR);
+ if (basedir == NULL)
+ basedir = getenv(SYSTEM_TMPDIR_ENV_VAR);
+ if (basedir == NULL)
+ basedir = "/tmp";
+ tr->tr_runtime_base_directory = realpath(basedir, NULL);
+ if (tr->tr_runtime_base_directory == NULL)
+ err(1, "realpath(%s) failed", basedir);
+ }
+
+ /*
+ * Add the search paths specified by the environment variable
+ * 'TEST_PATH' to the end of the search list.
+ */
+ if ((search_path = getenv(TEST_SEARCH_PATH_ENV_VAR)) != NULL &&
+ *search_path != '\0') {
+ path_copy = strdup(search_path);
+ path_element = strtok(path_copy, ":");
+ do {
+ if (!test_driver_add_search_path(tr, path_element))
+ warnx("in environment variable \"%s\": path "
+ "\"%s\" does not name a directory.",
+ TEST_SEARCH_PATH_ENV_VAR, path_element);
+ } while ((path_element = strtok(NULL, ":")) != NULL);
+ }
+
+ return (true);
+}
+
+/*
+ * Helper: return true if the passed in path names a directory, or false
+ * otherwise.
+ */
+bool
+test_driver_is_directory(const char *path)
+{
+ struct stat sb;
+ if (stat(path, &sb) != 0)
+ return false;
+ return S_ISDIR(sb.st_mode);
+}
diff --git a/test/libtest/driver/driver.h b/test/libtest/driver/driver.h
new file mode 100644
index 000000000000..5ae5cfd81246
--- /dev/null
+++ b/test/libtest/driver/driver.h
@@ -0,0 +1,206 @@
+/*-
+ * Copyright (c) 2018,2019 Joseph Koshy
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer
+ * in this position and unchanged.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _LIBTEST_DRIVER_H_
+#define _LIBTEST_DRIVER_H_
+
+#include <sys/queue.h>
+
+#include <limits.h>
+#include <stdbool.h>
+
+#include "_elftc.h"
+
+#include "test.h"
+
+#define TEST_SEARCH_PATH_ENV_VAR "TEST_PATH"
+#define TEST_TMPDIR_ENV_VAR "TEST_TMPDIR"
+
+/*
+ * Run time data strucrures.
+ */
+
+/* The completion status for a test run */
+enum test_run_status {
+ /*
+ * All test cases were successfully invoked, and all their contained
+ * test purposes passed.
+ */
+ TR_STATUS_PASS = 0,
+
+ /*
+ * All test cases were successfully invoked but at least one test
+ * function reported a failure.
+ */
+ TR_STATUS_FAIL = 1,
+
+ /*
+ * At least one test case reported an error during its set up or tear
+ * down phase.
+ */
+ TR_STATUS_ERROR = 2
+};
+
+/*
+ * The 'style' of the run determines the manner in which the test
+ * executable reports test status.
+ */
+enum test_run_style {
+ /* Libtest semantics. */
+ TR_STYLE_LIBTEST,
+
+ /*
+ * Be compatible with the Test Anything Protocol
+ * (http://testanything.org/).
+ */
+ TR_STYLE_TAP,
+
+ /* Be compatible with NetBSD ATF(9). */
+ TR_STYLE_ATF
+};
+
+/*
+ * Structures used for selecting tests.
+ */
+struct test_function_selector {
+ const struct test_function_descriptor *tfs_descriptor;
+
+ STAILQ_ENTRY(test_function_selector) tfs_next;
+ int tfs_is_selected;
+};
+
+STAILQ_HEAD(test_function_selector_list, test_function_selector);
+
+struct test_case_selector {
+ const struct test_case_descriptor *tcs_descriptor;
+ STAILQ_ENTRY(test_case_selector) tcs_next;
+ struct test_function_selector_list tcs_functions;
+ int tcs_selected_count;
+};
+
+/*
+ * The action being requested of the test driver.
+ */
+enum test_run_action {
+ TEST_RUN_EXECUTE, /* Execute the selected tests. */
+ TEST_RUN_LIST, /* Only list tests. */
+};
+
+STAILQ_HEAD(test_case_selector_list, test_case_selector);
+
+/*
+ * Runtime directories to look up data files.
+ */
+struct test_search_path_entry {
+ char *tsp_directory;
+ STAILQ_ENTRY(test_search_path_entry) tsp_next;
+};
+
+STAILQ_HEAD(test_search_path_list, test_search_path_entry);
+
+/*
+ * Used to track flags that were explicity set on the command line.
+ */
+enum test_run_flags {
+ TRF_BASE_DIRECTORY = 1U << 0,
+ TRF_EXECUTION_TIME = 1U << 1,
+ TRF_ARTEFACT_ARCHIVE = 1U << 2,
+ TRF_NAME = 1U << 3,
+ TRF_SEARCH_PATH = 1U << 4,
+ TRF_EXECUTION_STYLE = 1U << 5,
+};
+
+/*
+ * Parameters for the run.
+ */
+struct test_run {
+ /*
+ * Flags tracking the options which were explicitly set.
+ *
+ * This field is a bitmask formed of 'enum test_run_flags' values.
+ */
+ unsigned int tr_commandline_flags;
+
+ /* What the test run should do. */
+ enum test_run_action tr_action;
+
+ /* The desired behavior of the test harness. */
+ enum test_run_style tr_style;
+
+ /* The desired verbosity level. */
+ int tr_verbosity;
+
+ /* An optional name assigned by the user for this test run. */
+ char *tr_name;
+
+ /*
+ * The absolute path to the directory under which the test is
+ * to be run.
+ *
+ * Each test case will be invoked in some subdirectory of this
+ * directory.
+ */
+ char *tr_runtime_base_directory;
+
+ /*
+ * The test timeout in seconds.
+ *
+ * A value of zero indicates that the test driver should wait
+ * indefinitely for tests.
+ */
+ long tr_max_seconds_per_test;
+
+ /*
+ * If not NULL, An absolute pathname to an archive that will hold
+ * the artefacts created by a test run.
+ */
+ char *tr_artefact_archive;
+
+ /*
+ * Directories to use when resolving non-absolute data file
+ * names.
+ */
+ struct test_search_path_list tr_search_path;
+
+ /* All tests selected for this run. */
+ struct test_case_selector_list tr_test_cases;
+};
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+struct test_run *test_driver_allocate_run(void);
+bool test_driver_add_search_path(struct test_run *,
+ const char *search_path);
+void test_driver_free_run(struct test_run *);
+bool test_driver_is_directory(const char *);
+bool test_driver_finish_run_initialization(struct test_run *,
+ const char *argv0);
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _LIBTEST_DRIVER_H_ */
diff --git a/test/libtest/driver/driver_main.c b/test/libtest/driver/driver_main.c
new file mode 100644
index 000000000000..c43ccf52ca15
--- /dev/null
+++ b/test/libtest/driver/driver_main.c
@@ -0,0 +1,726 @@
+/*-
+ * Copyright (c) 2018, Joseph Koshy
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer
+ * in this position and unchanged.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/*
+ * This file defines a "main()" that invokes (or lists) the tests that were
+ * linked into the current executable.
+ */
+
+#include <sys/param.h>
+#include <sys/queue.h>
+#include <sys/stat.h>
+
+#include <assert.h>
+#include <err.h>
+#include <errno.h>
+#include <fnmatch.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+#include <sysexits.h>
+#include <time.h>
+#include <unistd.h>
+
+#include "_elftc.h"
+
+#include "test.h"
+#include "test_case.h"
+
+#include "driver.h"
+
+#if defined(ELFTC_VCSID)
+ELFTC_VCSID("$Id$");
+#endif
+
+enum selection_scope {
+ SCOPE_TEST_CASE = 0, /* c:STRING */
+ SCOPE_TEST_FUNCTION, /* f:STRING */
+ SCOPE_TAG, /* t:STRING */
+};
+
+/* Selection list entry. */
+struct selection_option {
+ STAILQ_ENTRY(selection_option) so_next;
+
+ /* The text to use for matching. */
+ const char *so_pattern;
+
+ /*
+ * Whether matched test and test cases should be selected
+ * (if false) or deselected (if true).
+ */
+ bool so_select_tests;
+
+ /* The kind of information to match. */
+ enum selection_scope so_selection_scope;
+};
+
+/* All selection options specified. */
+STAILQ_HEAD(selection_option_list, selection_option);
+
+static struct selection_option *
+parse_selection_option(const char *option)
+{
+ int scope_char;
+ bool select_tests;
+ enum selection_scope scope;
+ struct selection_option *so;
+
+ scope_char = '\0';
+ select_tests = true;
+ scope = SCOPE_TEST_CASE;
+
+ /* Deselection patterns start with a '-'. */
+ if (*option == '-') {
+ select_tests = false;
+ option++;
+ }
+
+ /*
+ * If a scope was not specified, the selection scope defaults
+ * to SCOPE_TEST_CASE.
+ */
+ if (strchr(option, ':') == NULL)
+ scope_char = 'c';
+ else {
+ scope_char = *option++;
+ if (*option != ':')
+ return (NULL);
+ option++; /* Skip over the ':'. */
+ }
+
+ if (*option == '\0')
+ return (NULL);
+
+ switch (scope_char) {
+ case 'c':
+ scope = SCOPE_TEST_CASE;
+ break;
+ case 'f':
+ scope = SCOPE_TEST_FUNCTION;
+ break;
+ case 't':
+ scope = SCOPE_TAG;
+ break;
+ default:
+ return (NULL);
+ }
+
+ so = calloc(1, sizeof(*so));
+ so->so_pattern = option;
+ so->so_selection_scope = scope;
+ so->so_select_tests = select_tests;
+
+ return (so);
+}
+
+/* Test execution styles. */
+struct style_entry {
+ enum test_run_style se_style;
+ const char *se_name;
+};
+
+static const struct style_entry known_styles[] = {
+ { TR_STYLE_LIBTEST, "libtest" },
+ { TR_STYLE_TAP, "tap" },
+ { TR_STYLE_ATF, "atf" }
+};
+
+/*
+ * Parse a test run style.
+ *
+ * This function returns true if the run style was recognized, or
+ * false otherwise.
+ */
+static bool
+parse_run_style(const char *option, enum test_run_style *run_style)
+{
+ size_t n;
+
+ for (n = 0; n < sizeof(known_styles) / sizeof(known_styles[0]); n++) {
+ if (strcasecmp(option, known_styles[n].se_name) == 0) {
+ *run_style = known_styles[n].se_style;
+ return (true);
+ }
+ }
+
+ return (false);
+}
+
+/*
+ * Return the canonical spelling of a test execution style.
+ */
+static const char *
+to_execution_style_name(enum test_run_style run_style)
+{
+ size_t n;
+
+ for (n = 0; n < sizeof(known_styles) / sizeof(known_styles[0]); n++) {
+ if (known_styles[n].se_style == run_style)
+ return (known_styles[n].se_name);
+ }
+
+ return (NULL);
+}
+
+/*
+ * Parse a string value containing a positive integral number.
+ */
+static bool
+parse_execution_time(const char *option, long *execution_time) {
+ char *end;
+ long value;
+
+ if (option == NULL || *option == '\0')
+ return (false);
+
+ value = strtol(option, &end, 10);
+
+ /* Check for parse errors. */
+ if (*end != '\0')
+ return (false);
+
+ /* Reject negative numbers. */
+ if (value < 0)
+ return (false);
+
+ /* Check for overflows during parsing. */
+ if (value == LONG_MAX && errno == ERANGE)
+ return (false);
+
+ *execution_time = value;
+
+ return (true);
+}
+
+/*
+ * Match the names of test cases.
+ *
+ * In the event of a match, then the selection state specifed in
+ * 'option' is applied to all the test functions in the test case.
+ */
+static void
+match_test_cases(struct selection_option *option,
+ struct test_case_selector *tcs)
+{
+ const struct test_case_descriptor *tcd;
+ struct test_function_selector *tfs;
+
+ tcd = tcs->tcs_descriptor;
+
+ if (fnmatch(option->so_pattern, tcd->tc_name, 0))
+ return;
+
+ STAILQ_FOREACH(tfs, &tcs->tcs_functions, tfs_next)
+ tfs->tfs_is_selected = option->so_select_tests;
+}
+
+/*
+ * Match the names of test functions.
+ */
+static void
+match_test_functions(struct selection_option *option,
+ struct test_case_selector *tcs)
+{
+ struct test_function_selector *tfs;
+ const struct test_function_descriptor *tfd;
+
+ STAILQ_FOREACH(tfs, &tcs->tcs_functions, tfs_next) {
+ tfd = tfs->tfs_descriptor;
+
+ if (fnmatch(option->so_pattern, tfd->tf_name, 0))
+ continue;
+
+ tfs->tfs_is_selected = option->so_select_tests;
+ }
+}
+
+/*
+ * Helper: returns true if the specified text matches any of the
+ * entries in the array 'tags'.
+ */
+static bool
+match_tags_helper(const char *pattern, const char *tags[])
+{
+ const char **tag;
+
+ if (!tags)
+ return (false);
+
+ for (tag = tags; *tag && **tag != '\0'; tag++) {
+ if (!fnmatch(pattern, *tag, 0))
+ return (true);
+ }
+
+ return (false);
+}
+
+/*
+ * Match tags.
+ *
+ * Matches against test case tags apply to all the test
+ * functions in the test case.
+ *
+ * Matches against test function tags apply to the matched
+ * test function only.
+ */
+static void
+match_tags(struct selection_option *option,
+ struct test_case_selector *tcs)
+{
+ const struct test_case_descriptor *tcd;
+ const struct test_function_descriptor *tfd;
+ struct test_function_selector *tfs;
+
+ tcd = tcs->tcs_descriptor;
+
+ /*
+ * If the tag in the option matches a tag associated with
+ * a test case, then we set all of the test case's functions
+ * to the specified selection state.
+ */
+ if (match_tags_helper(option->so_pattern, tcd->tc_tags)) {
+ STAILQ_FOREACH(tfs, &tcs->tcs_functions, tfs_next)
+ tfs->tfs_is_selected = option->so_select_tests;
+ return;
+ }
+
+ /*
+ * Otherwise, check the tag against the tags for each function
+ * in the test case and set the selection state of each matched
+ * function.
+ */
+ STAILQ_FOREACH(tfs, &tcs->tcs_functions, tfs_next) {
+ tfd = tfs->tfs_descriptor;
+ if (match_tags_helper(option->so_pattern, tfd->tf_tags))
+ tfs->tfs_is_selected = option->so_select_tests;
+ }
+}
+
+/*
+ * Add the selected tests to the test run.
+ *
+ * The memory used by the options list is returned to the system when this
+ * function completes.
+ */
+static void
+select_tests(struct test_run *tr,
+ struct selection_option_list *selections)
+{
+ int i, j;
+ struct selection_option *selection;
+ const struct test_case_descriptor *tcd;
+ struct test_case_selector *tcs;
+ struct test_function_selector *tfs;
+ bool default_selection_state;
+ int selected_count;
+
+ default_selection_state = STAILQ_EMPTY(selections);
+
+ /*
+ * Set up runtime descriptors.
+ */
+ for (i = 0; i < test_case_count; i++) {
+ if ((tcs = calloc(1, sizeof(*tcs))) == NULL)
+ err(EX_OSERR, "cannot allocate a test-case selector");
+ STAILQ_INSERT_TAIL(&tr->tr_test_cases, tcs, tcs_next);
+ STAILQ_INIT(&tcs->tcs_functions);
+
+ tcd = &test_cases[i];
+
+ tcs->tcs_descriptor = tcd;
+
+ for (j = 0; j < tcd->tc_count; j++) {
+ if ((tfs = calloc(1, sizeof(*tfs))) == NULL)
+ err(EX_OSERR, "cannot allocate a test "
+ "function selector");
+ STAILQ_INSERT_TAIL(&tcs->tcs_functions, tfs, tfs_next);
+
+ tfs->tfs_descriptor = tcd->tc_tests + j;
+ tfs->tfs_is_selected = default_selection_state;
+ }
+ }
+
+ /*
+ * Set or reset the selection state based on the options.
+ */
+ STAILQ_FOREACH(selection, selections, so_next) {
+ STAILQ_FOREACH(tcs, &tr->tr_test_cases, tcs_next) {
+ switch (selection->so_selection_scope) {
+ case SCOPE_TEST_CASE:
+ match_test_cases(selection, tcs);
+ break;
+ case SCOPE_TEST_FUNCTION:
+ match_test_functions(selection, tcs);
+ break;
+ case SCOPE_TAG:
+ match_tags(selection, tcs);
+ break;
+ }
+ }
+ }
+
+ /*
+ * Determine the count of tests selected, for each test case.
+ */
+ STAILQ_FOREACH(tcs, &tr->tr_test_cases, tcs_next) {
+ selected_count = 0;
+ STAILQ_FOREACH(tfs, &tcs->tcs_functions, tfs_next)
+ selected_count += tfs->tfs_is_selected;
+ tcs->tcs_selected_count = selected_count;
+ }
+
+ /* Free up the selection list. */
+ while (!STAILQ_EMPTY(selections)) {
+ selection = STAILQ_FIRST(selections);
+ STAILQ_REMOVE_HEAD(selections, so_next);
+ free(selection);
+ }
+}
+
+/*
+ * Translate a file name to absolute form.
+ *
+ * The caller needs to free the returned pointer.
+ */
+static char *
+to_absolute_path(const char *filename)
+{
+ size_t space_needed;
+ char *absolute_path;
+ char current_directory[PATH_MAX];
+
+ if (filename == NULL || *filename == '\0')
+ return (NULL);
+ if (*filename == '/')
+ return strdup(filename);
+
+ if (getcwd(current_directory, sizeof(current_directory)) == NULL)
+ err(1, "getcwd failed");
+
+ /* Reserve space for the slash separator and the trailing NUL. */
+ space_needed = strlen(current_directory) + strlen(filename) + 2;
+ if ((absolute_path = malloc(space_needed)) == NULL)
+ err(1, "malloc failed");
+ if (snprintf(absolute_path, space_needed, "%s/%s", current_directory,
+ filename) != (int) (space_needed - 1))
+ err(1, "snprintf failed");
+ return (absolute_path);
+}
+
+
+/*
+ * Display run parameters.
+ */
+
+#define FIELD_NAME_WIDTH 24
+#define INFOLINE(NAME, FLAG, FORMAT, ...) do { \
+ printf("I %c %-*s " FORMAT, \
+ (FLAG) ? '!' : '.', \
+ FIELD_NAME_WIDTH, NAME, __VA_ARGS__); \
+ } while (0)
+
+static void
+show_run_header(const struct test_run *tr)
+{
+ time_t start_time;
+ struct test_search_path_entry *path_entry;
+
+ if (tr->tr_verbosity == 0)
+ return;
+
+ INFOLINE("test-run-name", tr->tr_commandline_flags & TRF_NAME,
+ "%s\n", tr->tr_name);
+
+ INFOLINE("test-execution-style",
+ tr->tr_commandline_flags & TRF_EXECUTION_STYLE,
+ "%s\n", to_execution_style_name(tr->tr_style));
+
+ if (!STAILQ_EMPTY(&tr->tr_search_path)) {
+ INFOLINE("test-search-path",
+ tr->tr_commandline_flags & TRF_SEARCH_PATH,
+ "%c", '[');
+ STAILQ_FOREACH(path_entry, &tr->tr_search_path, tsp_next) {
+ printf(" %s", path_entry->tsp_directory);
+ }
+ printf(" ]\n");
+ }
+
+ INFOLINE("test-run-base-directory",
+ tr->tr_commandline_flags & TRF_BASE_DIRECTORY,
+ "%s\n", tr->tr_runtime_base_directory);
+
+ if (tr->tr_artefact_archive) {
+ INFOLINE("test-artefact-archive",
+ tr->tr_commandline_flags & TRF_ARTEFACT_ARCHIVE,
+ "%s\n", tr->tr_artefact_archive);
+ }
+
+ printf("I %c %-*s ",
+ tr->tr_commandline_flags & TRF_EXECUTION_TIME ? '=' : '.',
+ FIELD_NAME_WIDTH, "test-execution-time");
+ if (tr->tr_max_seconds_per_test == 0)
+ printf("unlimited\n");
+ else
+ printf("%lu\n", tr->tr_max_seconds_per_test);
+
+ printf("I %% %-*s %d\n", FIELD_NAME_WIDTH, "test-case-count",
+ test_case_count);
+
+ if (tr->tr_action == TEST_RUN_EXECUTE) {
+ start_time = time(NULL);
+ printf("I %% %-*s %s", FIELD_NAME_WIDTH,
+ "test-run-start-time", ctime(&start_time));
+ }
+}
+
+static void
+show_run_trailer(const struct test_run *tr)
+{
+ time_t end_time;
+
+ if (tr->tr_verbosity == 0)
+ return;
+
+ if (tr->tr_action == TEST_RUN_EXECUTE) {
+ end_time = time(NULL);
+ printf("I %% %-*s %s", FIELD_NAME_WIDTH, "test-run-end-time",
+ asctime(localtime(&end_time)));
+ }
+}
+
+#undef INFOLINE
+#undef FIELD_HEADER_WIDTH
+
+/*
+ * Helper: returns a character indicating the selection status for
+ * a test case. This character is as follows:
+ *
+ * - "*" all test functions in the test case were selected.
+ * - "+" some test functions in the test case were selected.
+ * - "-" no test functions from the test case were selected.
+ */
+static int
+get_test_case_status(const struct test_case_selector *tcs)
+{
+ if (tcs->tcs_selected_count == 0)
+ return '-';
+ if (tcs->tcs_selected_count == tcs->tcs_descriptor->tc_count)
+ return '*';
+ return '?';
+}
+
+/*
+ * Helper: print out a comma-separated list of tags.
+ */
+static void
+show_tags(int indent, const char *tags[])
+{
+ const char **tag;
+
+ printf("%*c: ", indent, ' ');
+ for (tag = tags; *tag && **tag != '\0';) {
+ printf("%s", *tag++);
+ if (*tag && **tag != '\0')
+ printf(",");
+ }
+ printf("\n");
+}
+
+/*
+ * Display a test case descriptor.
+ */
+static void
+show_test_case(struct test_run *tr, const struct test_case_selector *tcs)
+{
+ const struct test_case_descriptor *tcd;
+ int prefix_char;
+
+ prefix_char = get_test_case_status(tcs);
+ tcd = tcs->tcs_descriptor;
+
+ printf("C %c %s\n", prefix_char, tcd->tc_name);
+
+ if (tr->tr_verbosity > 0 && tcd->tc_tags != NULL)
+ show_tags(2, tcd->tc_tags);
+
+ if (tr->tr_verbosity > 1 && tcd->tc_description)
+ printf(" & %s\n", tcd->tc_description);
+}
+
+static void
+show_test_function(struct test_run *tr,
+ const struct test_function_selector *tfs)
+{
+ const struct test_function_descriptor *tfd;
+ int selection_char;
+
+ selection_char = tfs->tfs_is_selected ? '*' : '-';
+ tfd = tfs->tfs_descriptor;
+
+ printf(" F %c %s\n", selection_char, tfd->tf_name);
+
+ if (tr->tr_verbosity > 0 && tfd->tf_tags != NULL)
+ show_tags(4, tfd->tf_tags);
+
+ if (tr->tr_verbosity > 1 && tfd->tf_description)
+ printf(" & %s\n", tfd->tf_description);
+}
+
+static int
+show_listing(struct test_run *tr)
+{
+ const struct test_case_selector *tcs;
+ const struct test_function_selector *tfs;
+
+ STAILQ_FOREACH(tcs, &tr->tr_test_cases, tcs_next) {
+ show_test_case(tr, tcs);
+ STAILQ_FOREACH(tfs, &tcs->tcs_functions, tfs_next)
+ show_test_function(tr, tfs);
+ }
+
+ return (EXIT_SUCCESS);
+}
+
+int
+main(int argc, char **argv)
+{
+ struct test_run *tr;
+ int exit_code, option;
+ enum test_run_style run_style;
+ struct selection_option *selector;
+ struct selection_option_list selections =
+ STAILQ_HEAD_INITIALIZER(selections);
+
+ tr = test_driver_allocate_run();
+
+ /* Parse arguments. */
+ while ((option = getopt(argc, argv, ":R:T:c:ln:p:s:t:v")) != -1) {
+ switch (option) {
+ case 'R': /* Test runtime directory. */
+ if (!test_driver_is_directory(optarg))
+ errx(EX_USAGE, "option -%c: argument \"%s\" "
+ "does not name a directory.", option,
+ optarg);
+ tr->tr_runtime_base_directory = realpath(optarg, NULL);
+ if (tr->tr_runtime_base_directory == NULL)
+ err(1, "realpath failed for \"%s\"", optarg);
+ tr->tr_commandline_flags |= TRF_BASE_DIRECTORY;
+ break;
+ case 'T': /* Max execution time for a test function. */
+ if (!parse_execution_time(
+ optarg, &tr->tr_max_seconds_per_test))
+ errx(EX_USAGE, "option -%c: argument \"%s\" "
+ "is not a valid execution time value.",
+ option, optarg);
+ tr->tr_commandline_flags |= TRF_EXECUTION_TIME;
+ break;
+ case 'c': /* The archive holding artefacts. */
+ tr->tr_artefact_archive = to_absolute_path(optarg);
+ tr->tr_commandline_flags |= TRF_ARTEFACT_ARCHIVE;
+ break;
+ case 'l': /* List matching tests. */
+ tr->tr_action = TEST_RUN_LIST;
+ break;
+ case 'n': /* Test run name. */
+ if (tr->tr_name)
+ free(tr->tr_name);
+ tr->tr_name = strdup(optarg);
+ tr->tr_commandline_flags |= TRF_NAME;
+ break;
+ case 'p': /* Add a search path entry. */
+ if (!test_driver_add_search_path(tr, optarg))
+ errx(EX_USAGE, "option -%c: argument \"%s\" "
+ "does not name a directory.", option,
+ optarg);
+ tr->tr_commandline_flags |= TRF_SEARCH_PATH;
+ break;
+ case 's': /* Test execution style. */
+ if (!parse_run_style(optarg, &run_style))
+ errx(EX_USAGE, "option -%c: argument \"%s\" "
+ "is not a supported test execution style.",
+ option, optarg);
+ tr->tr_style = run_style;
+ tr->tr_commandline_flags |= TRF_EXECUTION_STYLE;
+ break;
+ case 't': /* Test selection option. */
+ if ((selector = parse_selection_option(optarg)) == NULL)
+ errx(EX_USAGE, "option -%c: argument \"%s\" "
+ "is not a valid selection pattern.",
+ option, optarg);
+ STAILQ_INSERT_TAIL(&selections, selector, so_next);
+ break;
+ case 'v':
+ tr->tr_verbosity++;
+ break;
+ case ':':
+ errx(EX_USAGE,
+ "ERROR: option -%c requires an argument.", optopt);
+ break;
+ case '?':
+ errx(EX_USAGE,
+ "ERROR: unrecognized option -%c", optopt);
+ break;
+ default:
+ errx(EX_USAGE, "ERROR: unspecified error.");
+ break;
+ }
+ }
+
+ /*
+ * Set unset fields of the test run descriptor to their
+ * defaults.
+ */
+ if (!test_driver_finish_run_initialization(tr, argv[0]))
+ err(EX_OSERR, "cannot initialize test driver");
+
+ /* Choose tests and test cases to act upon. */
+ select_tests(tr, &selections);
+
+ assert(STAILQ_EMPTY(&selections));
+
+ show_run_header(tr);
+
+ /* Perform the requested action. */
+ switch (tr->tr_action) {
+ case TEST_RUN_LIST:
+ exit_code = show_listing(tr);
+ break;
+
+ case TEST_RUN_EXECUTE:
+ default:
+ /* Not yet implemented. */
+ exit_code = EX_UNAVAILABLE;
+ }
+
+ show_run_trailer(tr);
+
+ test_driver_free_run(tr);
+
+ exit(exit_code);
+}
diff --git a/test/libtest/driver/test_driver.1 b/test/libtest/driver/test_driver.1
new file mode 100644
index 000000000000..a987002e8c67
--- /dev/null
+++ b/test/libtest/driver/test_driver.1
@@ -0,0 +1,308 @@
+.\" Copyright (c) 2019 Joseph Koshy.
+.\" All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\"
+.\" This software is provided by Joseph Koshy ``as is'' and
+.\" any express or implied warranties, including, but not limited to, the
+.\" implied warranties of merchantability and fitness for a particular purpose
+.\" are disclaimed. in no event shall Joseph Koshy be liable
+.\" for any direct, indirect, incidental, special, exemplary, or consequential
+.\" damages (including, but not limited to, procurement of substitute goods
+.\" or services; loss of use, data, or profits; or business interruption)
+.\" however caused and on any theory of liability, whether in contract, strict
+.\" liability, or tort (including negligence or otherwise) arising in any way
+.\" out of the use of this software, even if advised of the possibility of
+.\" such damage.
+.\"
+.\" $Id$
+.\"
+.Dd April 02, 2019
+.Dt TEST-DRIVER 1
+.Os
+.Sh NAME
+.Nm test-driver
+.Nd scaffolding for executing
+.Xr test 3
+based tests from the command-line
+.Sh SYNOPSIS
+.Nm test-executable
+.Op Fl c Ar artefact-archive-name
+.Op Fl l
+.Op Fl n Ar run-name
+.Op Fl p Ar search-path-directory
+.Op Fl R Ar runtime-base-directory
+.Op Fl s Ar execution-style
+.Op Fl t Ar test-selector
+.Op Fl T Ar seconds
+.Op Fl v
+.Sh DESCRIPTION
+The
+.Nm
+library provides a
+.Fn main
+function that will execute the
+.Xr test 3
+based tests in an executable according to the options specified
+on the command-line.
+The
+.Nm
+library usually used in conjunction with code generated by the
+.Xr make-test-scaffolding 1
+utility.
+.Pp
+Test executables built using
+.Nm
+recognize the following command-line options:
+.Bl -tag -width indent
+.It Fl c Ar archive-name
+If this option is specified, then the
+.Nm
+provided scaffolding will copy test outputs and other artefacts from
+the test run to the archive named by argument
+.Ar archive-name .
+The format of the archive is specified by the path name suffix of the
+artefact name.
+The supported output formats are those supported by
+.Xr libarchive 3 .
+.It Fl l
+If this option is specified, then the
+.Nm
+utility will list the selected tests and exit.
+.It Fl n Ar run-name
+Use the specified test run name in status messages and to name
+any files and directories created during the test run.
+If this option is not specified, then the base name of the test
+executable is used.
+.It Fl p Ar search-path-directory
+Add the argument
+.Ar search-path-directory
+to the list of directories searched for by
+.Xr test 3
+utility functions.
+.It Fl R Ar runtime-base-directory
+Set the runtime base directory to the directory specified by the
+argument
+.Ar runtime-base-directory .
+Tests execute with their current directory set to a subdirectory
+within this directory.
+The path specified by argument
+.Ar runtime-base-directory
+must exist, and must name a directory.
+.Pp
+If this option is not specified, then the
+.Ev TEST_TMPDIR
+environment variable will be examined.
+If set to a non-empty value, then its value will be used.
+Otherwise, the value of the
+.Ev TMPDIR
+environment variable will be used, if non-empty.
+If neither of the environment variables
+.Ev TEST_TMPDIR
+and
+.Ev TMPDIR
+contain a non-empty value, then the path
+.Dq Pa /tmp
+will be used.
+.It Fl s Ar execution-style
+Set the desired execution style to that specified by argument
+.Ar execution-style .
+Legal values for
+.Ar execution-style
+are:
+.Bl -tag -width indent -compact
+.It Li atf
+Be compatible with
+.Nx
+.Xr atf 9 .
+.It Li tap
+Be compatible with TAP
+.Pq Test Anything Protocol .
+.It Li test
+Be compatible with libtest (this test framework).
+.El
+The default is to use libtest semantics.
+.It Fl t Ar test-selector
+Select (or deselect) tests to execute according to the argument
+.Ar test-selector .
+.Pp
+Test selectors are specified using the following syntax:
+.Bl -tag -compact -width indent
+.It Xo
+.Op Li - Ns
+.Li c : Ns Ar pattern
+.Xc
+Select test cases whose names match
+.Ar pattern .
+Selecting a test case will cause all of its contained
+test functions to be selected.
+.It Xo
+.Op Li - Ns
+.Li f : Ns Ar pattern
+.Xc
+Select test functions whose names match
+.Ar pattern .
+.It Xo
+.Op Li - Ns
+.Li t : Ns Ar pattern
+.Xc
+Select the test cases and test functions associated with
+tags matching
+.Ar pattern .
+.It Xo
+.Op Li - Ns
+.Ar pattern
+.Xc
+If the
+.Li c ,
+.Li f
+or
+.Li t
+qualifiers were not specified, then the pattern is matched
+against the names of test cases.
+.El
+The
+.Ar pattern
+fields of test selectors use shell wildcard syntax, as implemented by
+.Xr fnmatch 3 .
+.Pp
+If no test selectors are specified then all the tests present in
+the test executable will be run.
+Otherwise, the test selectors specified are processed in the
+order specified on the command line.
+.Pp
+A test selector that does not start with a
+.Dq Li -
+will add the entries that it matches to the currently selected list
+of tests.
+A test selector that starts with a
+.Dq Li -
+will remove the entries that it matches from the currently selected list
+of tests.
+.Pp
+If at least one test selector was specified, and if the result of
+applying the specified test selectors was an empty list
+of tests, then the
+.Nm
+library will exit with an error message.
+.It Fl T Ar seconds
+Set the timeout for individual tests to
+.Ar seconds .
+If a test function fails to return with the specified number of seconds
+then it is treated as having failed.
+The default is to wait indefinitely for the test function to complete.
+.It Fl v
+Increase verbosity level by 1.
+The default verbosity level is 0.
+.El
+.Ss Link-time Pre-requisites
+The
+.Nm
+library expects the following symbols to be present in the
+test executable it is linked with:
+.Pp
+.Bl -tag -width indent -compact
+.It Xo
+.Vt struct test_case_descriptor
+.Va test_cases Ns []
+.Xc
+An array of test cases descriptors.
+Test case descriptors described by
+.Xr test_case 5 .
+.It Xo
+.Vt int
+.Va test_case_count
+.Xc
+The number of entries in the
+.Va test_cases
+array.
+.El
+.Ss Test Execution
+At start up, the
+.Fn main
+function provided by
+.Nm
+will select tests (and test cases) to execute, based on the test
+selection options specified.
+.Pp
+For each selected test case, test execution proceeds as follows:
+.Bl -enum -compact
+.It
+The runtime directory for the test case is created.
+.It
+The test process forks, with test execution continuing in the
+child.
+.It
+.Pq Child
+The current directory of the process is changed to the runtime
+directory.
+.It
+.Pq Child
+The test case set up function is then executed.
+If this function returns an error then test case execution is
+aborted.
+.It
+.Pq Child
+Each selected test function in the test case is then executed and
+its status is output to stdout (or stderr) according to the test
+execution style selected.
+.It
+.Pq Child
+The test case tear down function is then executed.
+.It
+If test artefacts need to be preserved, then these are
+copied to the specified archive.
+.It
+The test's runtime directory is then deleted.
+.El
+.Pp
+After all test cases have been attempted, the
+.Fn main
+function exits with the exit code appropriate for the
+test execution style selected.
+.Sh EXAMPLES
+To run all tests in the binary named
+.Pa tc_example ,
+copying test artefacts to a
+.Xr cpio 1
+archive named
+.Pa /tmp/tc_example.cpio ,
+use:
+.Bd -literal -offset indent
+tc_example -c /tmp/tc_example.cpio
+.Ed
+.Pp
+To execute tests in the test case
+.Dq tc1
+alone, use:
+.Bd -literal -offset indent
+tc_example -t 'c:tc1'
+.Ed
+.Pp
+To execute tests in the test case
+.Dq tc1
+but not the test functions associated with tag
+.Li tag1 ,
+use:
+.Bd -literal -offset indent
+tc_example -t 'c:tc1' -t '-t:tag1'
+.Ed
+.Sh DIAGNOSTICS
+Test programs built with the
+.Nm
+library will exit with an exit code of 0 if all of the selected tests
+passed when run, and with a non-zero exit code if an error
+occurred during test execution.
+.Sh SEE ALSO
+.Xr make-test-scaffolding 1 ,
+.Xr fnmatch 3 ,
+.Xr libarchive 3 ,
+.Xr test 3 ,
+.Xr test_case 5
diff --git a/test/libtest/examples/minimal_example.c b/test/libtest/examples/minimal_example.c
index 4ad08b4bce06..3bc76fe66ea9 100644
--- a/test/libtest/examples/minimal_example.c
+++ b/test/libtest/examples/minimal_example.c
@@ -41,10 +41,16 @@
#include "test.h"
/*
+ * Function prototypes.
+ */
+enum test_result tf_helloworld(test_case_state);
+
+/*
* Function names prefixed with 'tf_' name test functions.
*/
enum test_result
-tf_helloworld(testcase_state state)
+tf_helloworld(test_case_state state)
{
+ (void) state;
return (TEST_PASS);
}
diff --git a/test/libtest/examples/simple_example.c b/test/libtest/examples/simple_example.c
index 6a4f6697eb51..6d72f65dd451 100644
--- a/test/libtest/examples/simple_example.c
+++ b/test/libtest/examples/simple_example.c
@@ -31,6 +31,14 @@
#include "test.h"
/*
+ * Function prototypes.
+ */
+enum test_case_status tc_setup_helloworld(test_case_state *);
+enum test_case_status tc_teardown_helloworld(test_case_state);
+enum test_result tf_helloworld_sayhello(test_case_state);
+enum test_result tf_helloworld_saygoodbye(test_case_state);
+
+/*
* This source defines a single test case named 'helloworld' containing a
* single test function named 'sayhello' contained in that test case. At
* test execution time the test case would be selectable using the tags
@@ -64,35 +72,37 @@
/*
* A symbol name prefixed with 'tc_description_' contains a
- * test case description. The TESTCASE_DESCRIPTION macro offers
+ * test case description. The TEST_CASE_DESCRIPTION macro offers
* a convenient way to define such symbols. In the case of the
* symbol below, the test case named is 'helloworld'.
*/
-TESTCASE_DESCRIPTION(helloworld) = "A description for a test case.";
+TEST_CASE_DESCRIPTION(helloworld) = "A description for a test case.";
/*
* Function names prefixed with 'tc_setup_' are assumed to be test
* case set up functions.
*/
-enum testcase_status
-tc_setup_helloworld(testcase_state *state)
+enum test_case_status
+tc_setup_helloworld(test_case_state *state)
{
- return (TESTCASE_OK);
+ (void) state;
+ return (TEST_CASE_OK);
}
/*
* Function names prefixed with 'tc_teardown_' are assumed to be test
* case tear down functions.
*/
-enum testcase_status
-tc_teardown_helloworld(testcase_state state)
+enum test_case_status
+tc_teardown_helloworld(test_case_state state)
{
- return (TESTCASE_OK);
+ (void) state;
+ return (TEST_CASE_OK);
}
/*
* Names prefixed with 'tc_tags_' denote the tags associated with test
- * cases. The TESTCASE_TAGS macro offers a convenient way to define such
+ * cases. The TESTC_ASE_TAGS macro offers a convenient way to define such
* symbols.
*
* In the example below, all test functions belonging to the test case
@@ -100,7 +110,7 @@ tc_teardown_helloworld(testcase_state state)
*
* Tags lists are terminated by a NULL entry.
*/
-TESTCASE_TAGS(helloworld) = {
+TEST_CASE_TAGS(helloworld) = {
"tag1",
"tag2",
NULL
@@ -110,8 +120,16 @@ TESTCASE_TAGS(helloworld) = {
* Function names prefixed with 'tf_' name test functions.
*/
enum test_result
-tf_helloworld_sayhello(testcase_state state)
+tf_helloworld_sayhello(test_case_state state)
+{
+ (void) state;
+ return (TEST_PASS);
+}
+
+enum test_result
+tf_helloworld_saygoodbye(test_case_state state)
{
+ (void) state;
return (TEST_PASS);
}
@@ -126,6 +144,9 @@ tf_helloworld_sayhello(testcase_state state)
TEST_DESCRIPTION(helloworld_sayhello) =
"A description for the test function 'tf_helloworld_sayhello'.";
+TEST_DESCRIPTION(helloworld_saygoodbye) =
+ "A description for the test function 'tf_helloworld_saygoodbye'.";
+
/*
* Names prefixed by 'tf_tags_' contain the tags associated with
* test functions.
@@ -143,3 +164,8 @@ test_tags tf_tags_helloworld_sayhello = {
"tag4",
NULL
};
+
+test_tags tf_tags_helloworld_saygoodbye = {
+ "tag5",
+ NULL
+};
diff --git a/test/libtest/lib/Makefile b/test/libtest/lib/Makefile
index 7359ddd76a6c..e36d8fa072ba 100644
--- a/test/libtest/lib/Makefile
+++ b/test/libtest/lib/Makefile
@@ -4,10 +4,9 @@ TOP= ../../..
LIB= test
-SRCS= test.c \
- test_runner.c
+SRCS= test.c
-INCS= test.h
+INCS= test.h test_case.h
WARNS?= 6
diff --git a/test/libtest/lib/test.3 b/test/libtest/lib/test.3
index c7045b4039c4..e170c181595a 100644
--- a/test/libtest/lib/test.3
+++ b/test/libtest/lib/test.3
@@ -1,4 +1,4 @@
-.\" Copyright (c) 2018, Joseph Koshy.
+.\" Copyright (c) 2018,2019 Joseph Koshy.
.\" All rights reserved.
.\"
.\" Redistribution and use in source and binary forms, with or without
@@ -24,7 +24,7 @@
.\"
.\" $Id$
.\"
-.Dd December 25, 2018
+.Dd January 21, 2019
.Dt TEST 3
.Os
.Sh NAME
@@ -34,20 +34,20 @@
.Lb libtest
.Sh SYNOPSIS
.In test.h
-.Ft enum testcase_status
-.Fn testcase_setup "testcase_state *state"
-.Ft enum testcase_status
-.Fn testcase_teardown "testcase_state state"
+.Ft enum test_case_status
+.Fn test_case_setup "test_case_state *state"
+.Ft enum test_case_status
+.Fn test_case_teardown "test_case_state state"
.Ft enum test_result
-.Fn test_function "testcase_state state"
+.Fn test_function "test_case_state state"
.Vt "const char"
.Va test_description [] ;
.Vt "const char *"
.Va test_tags [] ;
.Vt "const char"
-.Va testcase_description [] ;
+.Va test_case_description [] ;
.Vt "const char *"
-.Va testcase_tags [] ;
+.Va test_case_tags [] ;
.Sh DESCRIPTION
The
.Lb libtest
@@ -76,7 +76,7 @@ If specified, this set up function would be invoked prior to any test
function contained in the test case.
The set up function can allocate and initialize test-specific state, to be
passed to test functions.
-If no set up function is specified for the test case, a default no-op
+If no set up function is specified for the test case, a default (no-op)
function will be supplied.
.It
An optional test case tear down function.
diff --git a/test/libtest/lib/test.h b/test/libtest/lib/test.h
index 86f91463993b..6928f867a6f4 100644
--- a/test/libtest/lib/test.h
+++ b/test/libtest/lib/test.h
@@ -44,60 +44,59 @@ enum test_result {
/*
* The return values from test case set up and tear down functions.
*
- * - TESTCASE_OK : The set up or tear down function was successful.
- * - TESTCASE_ERROR : Set up or tear down actions could not be completed.
+ * - TEST_CASE_OK : The set up or tear down function was successful.
+ * - TEST_CASE_ERROR : Set up or tear down actions could not be completed.
*
- * If a test case set up function returns TESTCASE_ERROR then:
+ * If a test case set up function returns TEST_CASE_ERROR then:
* - The test functions in the test case will not be run.
* - The test case's tear down function will not be invoked.
* - The test run as a whole will be treated as being in error.
*
- * If a test case tear down function returns a TESTCASE_ERROR, then
+ * If a test case tear down function returns a TEST_CASE_ERROR, then
* the test run as a whole be treated as being in error.
*/
-enum testcase_status {
- TESTCASE_OK = 0,
- TESTCASE_ERROR = 1
+enum test_case_status {
+ TEST_CASE_OK = 0,
+ TEST_CASE_ERROR = 1
};
/*
- * A testcase_state denotes resources that are shared by the test
- * functions that are part of a test case. A testcase_state is allocated
- * by the set up function for a test case. Conversely the test case's
- * tear down function is responsible for deallocating the resources
- * allocated by the set up function.
+ * A 'test_case_state' is a handle to resources shared by the test functions
+ * that make up a test case. A test_case_state is allocated by the test case
+ * set up function and is deallocated by the test case tear down function.
*
- * The test(3) framework treats a testcase_state as an opaque value.
+ * The test(3) framework treats a 'test_case_state' as an opaque value.
*/
-typedef void *testcase_state;
+typedef void *test_case_state;
/*
* Test case and test function descriptions, and convenience macros
* to define these.
*/
-typedef const char testcase_description[];
+typedef const char test_case_description[];
-#if !defined(TEST_DESCRIPTION)
-#define TEST_DESCRIPTION(NAME) test_description tf_description_##NAME
+#if !defined(TEST_CASE_DESCRIPTION)
+#define TEST_CASE_DESCRIPTION(NAME) test_case_description tc_description_##NAME
#endif
typedef const char test_description[];
-#if !defined(TESTCASE_DESCRIPTION)
-#define TESTCASE_DESCRIPTION(NAME) testcase_description tc_description_##NAME
+#if !defined(TEST_DESCRIPTION)
+#define TEST_DESCRIPTION(NAME) test_description tf_description_##NAME
#endif
/*
* Test case and test function tags, and convenience macros to define
* these.
*/
-typedef const char *testcase_tags[];
+typedef const char *test_case_tags[];
-#if !defined(TESTCASE_TAGS)
-#define TESTCASE_TAGS(NAME) testcase_tags tc_tags_##NAME
+#if !defined(TEST_CASE_TAGS)
+#define TEST_CASE_TAGS(NAME) test_case_tags tc_tags_##NAME
#endif
typedef const char *test_tags[];
+
#if !defined(TEST_TAGS)
#define TEST_TAGS(NAME) test_tags tf_tags_##NAME
#endif
@@ -108,7 +107,7 @@ typedef const char *test_tags[];
* If defined for a test case, this function will be called prior to
* the execution of an of the test functions within the test cae. Test
* case execution will be aborted if the function returns any value other
- * than TESTCASE_OK.
+ * than TEST_CASE_OK.
*
* The function can set '*state' to a memory area holding test state to be
* passed to test functions.
@@ -116,8 +115,8 @@ typedef const char *test_tags[];
* If the test case does not define a set up function, then a default
* no-op set up function will be used.
*/
-typedef enum testcase_status (test_case_setup_function)
- (testcase_state *state);
+typedef enum test_case_status test_case_setup_function(
+ test_case_state *state);
/*
* A test function.
@@ -127,7 +126,7 @@ typedef enum testcase_status (test_case_setup_function)
* its test succeeded or TEST_FAIL otherwise. In the event the test could
* not be executed, it can return TEST_UNRESOLVED.
*/
-typedef enum test_result (test_function)(testcase_state state);
+typedef enum test_result test_function(test_case_state state);
/*
* A test case tear down function.
@@ -138,7 +137,8 @@ typedef enum test_result (test_function)(testcase_state state);
* responsible for deallocating the resources that the set up function
* had allocated.
*/
-typedef enum testcase_status (test_case_teardown_function)(testcase_state state);
+typedef enum test_case_status test_case_teardown_function(
+ test_case_state state);
#ifdef __cplusplus
extern "C" {
diff --git a/test/libtest/driver/test_main.c b/test/libtest/lib/test_case.h
index 90e46d9a5fe2..c40b15fb69a1 100644
--- a/test/libtest/driver/test_main.c
+++ b/test/libtest/lib/test_case.h
@@ -24,26 +24,35 @@
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
+#ifndef _LIBTEST_TEST_CASE_H_
+#define _LIBTEST_TEST_CASE_H_
+
+#include "test.h"
+
/*
- * This file defines a "main" that parses command-line arguments and invokes
- * the selected test cases.
+ * These structures describe the test cases that are linked into a
+ * test executable.
*/
-#include <sys/param.h>
-#include <assert.h>
-#include <stdlib.h>
+/* A single test function, with its associated tags and description. */
+struct test_function_descriptor {
+ const char *tf_name; /* Test name. */
+ const char *tf_description; /* Test description. */
+ const char **tf_tags; /* The tags for the test. */
+ test_function *tf_func; /* The function to invoke. */
+};
-#include "_elftc.h"
-#include "test.h"
-#include "test_runner.h"
+/* A test case, with its associated tests. */
+struct test_case_descriptor {
+ const char *tc_name; /* Test case name. */
+ const char *tc_description; /* Test case description. */
+ const char **tc_tags; /* Any associated tags. */
+ const struct test_function_descriptor *tc_tests; /* Contained tests. */
+ const int tc_count; /* The number of tests. */
+};
-ELFTC_VCSID("$Id$");
+/* All test cases linked into the test binary. */
+extern struct test_case_descriptor test_cases[];
+extern const int test_case_count;
-int
-main(int argc, char **argv)
-{
- (void) test_cases;
- (void) argc;
- (void) argv;
- exit(0);
-}
+#endif /* _LIBTEST_TEST_CASE_H_ */
diff --git a/test/libtest/lib/test_runner.c b/test/libtest/lib/test_runner.c
deleted file mode 100644
index 1366ff4a538e..000000000000
--- a/test/libtest/lib/test_runner.c
+++ /dev/null
@@ -1,31 +0,0 @@
-/*-
- * Copyright (c) 2018, Joseph Koshy
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- * 1. Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer
- * in this position and unchanged.
- * 2. Redistributions in binary form must reproduce the above copyright
- * notice, this list of conditions and the following disclaimer in the
- * documentation and/or other materials provided with the distribution.
- *
- * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
- * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
- * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
- * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
- * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
- * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
- * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-/*
- * An implementation of a test driver for test(3) tests.
- */
-
-/* To be implemented. */
diff --git a/test/libtest/lib/test_runner.h b/test/libtest/lib/test_runner.h
deleted file mode 100644
index cbf00f29b44d..000000000000
--- a/test/libtest/lib/test_runner.h
+++ /dev/null
@@ -1,118 +0,0 @@
-/*-
- * Copyright (c) 2018, Joseph Koshy
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- * 1. Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer
- * in this position and unchanged.
- * 2. Redistributions in binary form must reproduce the above copyright
- * notice, this list of conditions and the following disclaimer in the
- * documentation and/or other materials provided with the distribution.
- *
- * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
- * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
- * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
- * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
- * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
- * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
- * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-#ifndef _LIBTEST_TEST_RUNNER_H_
-#define _LIBTEST_TEST_RUNNER_H_
-
-#include "test.h"
-
-/*
- * These data structures and functions are used by test driver that
- * execute tests.
- */
-
-/*
- * The completion status for a test run:
- *
- * - TESTRUN_PASS : All test cases were successfully invoked and all test
- * purposes in the test cases passed.
- * - TESTRUN_FAIL : All test cases were successfully invoked but at least
- * one test purpose reported a test failure.
- * - TESTRUN_ERROR : At least one test case reported an error during its
- * set up or tear down phase.
- */
-enum testrun_status {
- TESTRUN_PASS = 0,
- TESTRUN_FAIL = 1,
- TESTRUN_ERROR = 2
-};
-
-/*
- * A single test function, with its associated tags and description.
- */
-struct test_descriptor {
- const char *t_name; /* Test name. */
- const char *t_description; /* Test description. */
- const char **t_tags; /* Tags associated with the test. */
- test_function *t_func; /* The function to invoke. */
-};
-
-/*
- * A test case.
- */
-struct test_case_descriptor {
- const char *tc_name; /* Test case name. */
- const char *tc_description; /* Test case description. */
- const char **tc_tags; /* Any associated tags. */
- struct test_descriptor *tc_tests; /* The tests in this test case. */
-};
-
-/*
- * All test cases.
- */
-extern struct test_case_descriptor test_cases[];
-
-enum testrun_style {
- /* Libtest semantics. */
- TESTRUN_STYLE_LIBTEST,
-
- /*
- * Be compatible with the Test Anything Protocol
- * (http://testanything.org/).
- */
- TESTRUN_STYLE_TAP,
-
- /* Be compatible with NetBSD ATF(9). */
- TESTRUN_STYLE_ATF
-};
-
-/*
- * Parameters for the run.
- */
-struct test_run {
- /*
- * An optional name assigned by the user for this test run.
- *
- * This name is reported in test logs and is not interpreted
- * by the test harness.
- */
- char *testrun_name;
-
- /* The source directory for the run. */
- char *testrun_source_directory;
-
- /* The directory in which the test is executing. */
- char *testrun_test_directory;
-};
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-#ifdef __cplusplus
-}
-#endif
-
-#endif /* _LIBTEST_TEST_RUNNER_H_ */