Page MenuHomeFreeBSD

D26928.diff
No OneTemporary

D26928.diff

Index: contrib/atf/Kyuafile
===================================================================
--- contrib/atf/Kyuafile
+++ contrib/atf/Kyuafile
@@ -4,5 +4,6 @@
include("atf-c/Kyuafile")
include("atf-c++/Kyuafile")
+include("atf-lua/Kyuafile")
include("atf-sh/Kyuafile")
include("test-programs/Kyuafile")
Index: contrib/atf/atf-lua/Kyuafile
===================================================================
--- /dev/null
+++ contrib/atf/atf-lua/Kyuafile
@@ -0,0 +1,9 @@
+syntax("kyuafile", 1)
+
+test_suite("atf")
+
+atf_test_program{name="config_test"}
+atf_test_program{name="integration_test"}
+atf_test_program{name="latf_test"}
+atf_test_program{name="tc_test"}
+
Index: contrib/atf/atf-lua/atf-lua.1
===================================================================
--- /dev/null
+++ contrib/atf/atf-lua/atf-lua.1
@@ -0,0 +1,65 @@
+.\"
+.\" SPDX-License-Identifier: BSD-2-Clause-FreeBSD
+.\"
+.\" Copyright (c) 202o Kyle Evans <kevans@FreeBSD.org>
+.\" 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 THE AUTHOR AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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.
+.\"
+.\" $FreeBSD$
+.\"
+.Dd October 20, 2020
+.Dt ATF-LUA 1
+.Os
+.Sh NAME
+.Nm atf-lua
+.Nd interpreter for lua-based test programs
+.Sh SYNOPSIS
+.Nm
+.Ar script
+.Sh DESCRIPTION
+.Nm
+is an interpreter that runs the test program given in
+.Ar script
+after loading the
+.Xr atf-lua 3
+library.
+.Pp
+.Nm
+executes the interpreter, loads the
+.Xr atf-lua 3
+library and then runs the script.
+You must consider
+.Nm atf-lua
+to be a stock Lua interpreter by default and thus should not use any
+non-standard extensions.
+.Sh EXAMPLES
+Scripts using
+.Xr atf-lua 3
+should hardcode the path to
+.Nm
+in the script:
+.Bd -literal -offset indent
+#! /path/to/bin/atf-lua
+.Ed
+.Sh SEE ALSO
+.Xr atf-lua 3
Index: contrib/atf/atf-lua/atf-lua.3
===================================================================
--- /dev/null
+++ contrib/atf/atf-lua/atf-lua.3
@@ -0,0 +1,327 @@
+.\"
+.\" SPDX-License-Identifier: BSD-2-Clause-FreeBSD
+.\"
+.\" Copyright (c) 202o Kyle Evans <kevans@FreeBSD.org>
+.\" 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 THE AUTHOR AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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.
+.\"
+.\" $FreeBSD$
+.\"
+.Dd October 21, 2020
+.Dt ATF-LUA 3
+.Os
+.Sh NAME
+.Nm atf.TestCase ,
+.Nm atf.check_equal ,
+.Nm atf.config_get ,
+.Nm atf.config_has ,
+.Nm atf.expect_death ,
+.Nm atf.expect_exit ,
+.Nm atf.expect_fail ,
+.Nm atf.expect_pass ,
+.Nm atf.expect_signal ,
+.Nm atf.expect_timeout ,
+.Nm atf.fail ,
+.Nm atf.get ,
+.Nm atf.get_srcdir ,
+.Nm atf.pass ,
+.Nm atf.require_prog ,
+.Nm atf.set ,
+.Nm atf.skip
+.Nd Lua API to write ATF-based test programs
+.Sh SYNOPSIS
+.Dv atf.TestCase
+.Pp
+.Fn atf.check_equal "expected_expression" "actual_expression"
+.Fn atf.config_get "varname" "default"
+.Fn atf.config_has "varname"
+.Fn atf.expect_death "reason"
+.Fn atf.expect_exit "reason" "exitcode"
+.Fn atf.expect_fail "reason"
+.Fn atf.expect_pass
+.Fn atf.expect_signal "reason" "signal"
+.Fn atf.expect_timeout "reason"
+.Fn atf.fail "reason"
+.Fn atf.get "varname"
+.Fn atf.get_srcdir
+.Fn atf.pass
+.Fn atf.require_prog "prog_name"
+.Fn atf.set "varname" "value"
+.Fn atf.skip "reason"
+.Sh DESCRIPTION
+ATF
+provides a simple but powerful interface to easily write test programs in
+the Lua scripting language.
+These are extremely helpful given that they are trivial to write due to the
+language simplicity and the great deal of available external tools, so they
+are often ideal to test other applications at the user level.
+.Pp
+Test programs written using this library must be run using the
+.Xr atf-lua 1
+interpreter by putting the following on their very first line:
+.Bd -literal -offset indent
+#! /path/to/bin/atf-lua
+.Ed
+.Pp
+Lua-based test programs are more flexible than their
+.Xr atf-sh 1
+counterpart.
+The basic test structure will resemble this:
+.Bd -literal -offset indent
+local atf = require('atf')
+
+atf.TestCase "tc1" {
+ head = function()
+ ... first test case's header ...
+ end,
+ body = function()
+ ... first test case's body ...
+ end,
+}
+
+atf.TestCase "tc2" {
+ head = function()
+ ... second test case's header ...
+ end,
+ body = function()
+ ... second test case's body ...
+ end,
+ cleanup = function()
+ ... second test case's cleanup ...
+ end,
+}
+
+\&... additional test cases ...
+.Ed
+.Ss Definition of test cases
+Test cases have an identifier and are composed of three different parts:
+the header, the body and an optional cleanup routine, all of which are
+described in
+.Xr atf-test-case 4 .
+To define test cases, one must construct it with
+.Dv atf.TestCase ,
+which takes a first parameter specifying the test case's name and instructs the
+library to accept it as a valid test case.
+The second parameter is an object defining the test case, which should include
+.Fn body ,
+and may include
+.Fn head
+and
+.Fn cleanup .
+.Pp
+It is important to note that an
+.Dv atf.TestCase ,
+unlike in other
+.Xr atf 7
+framework libraries,
+.Em does
+set the test case up for execution when the program is run.
+.Ss Program initialization
+This library does not have an analog for the
+.Nm atf_init_test_cases
+required by
+.Xr atf-sh 3
+to register test cases to run.
+.Ss Configuration variables
+The test case has read-only access to the current configuration variables
+through the
+.Nm atf.config_has
+and
+.Nm atf.config_get
+methods.
+The former takes a single parameter specifying a variable name and returns
+a boolean indicating whether the variable is defined or not.
+The latter can take one or two parameters.
+If it takes only one, it specifies the variable from which to get the
+value, and this variable must be defined.
+If it takes two, the second one specifies a default value to be returned
+if the variable is not available.
+.Ss Access to the source directory
+It is possible to get the path to the test case's source directory from
+anywhere in the test program by using the
+.Nm atf.get_srcdir
+function.
+.Ss Requiring programs
+Aside from the
+.Va require.progs
+meta-data variable available in the header only, one can also check for
+additional programs in the test case's body by using the
+.Nm atf.require_prog
+function, which takes the base name or full path of a single binary.
+Relative paths are forbidden.
+If it is not found, the test case will be automatically skipped.
+.Ss Test case finalization
+The test case finalizes either when the body reaches its end, at which
+point the test is assumed to have
+.Em passed ,
+or at any explicit call to
+.Nm atf.pass ,
+.Nm atf.fail
+or
+.Nm atf.skip .
+These three functions terminate the execution of the test case immediately.
+The cleanup routine will be processed afterwards in a completely automated
+way, regardless of the test case's termination reason.
+.Pp
+.Nm atf.pass
+does not take any parameters.
+.Nm atf.fail
+and
+.Nm atf.skip
+take a single string parameter that describes why the test case failed or
+was skipped, respectively.
+It is very important to provide a clear error message in both cases so that
+the user can quickly know why the test did not pass.
+.Ss Expectations
+Everything explained in the previous section changes when the test case
+expectations are redefined by the programmer.
+.Pp
+Each test case has an internal state called
+.Sq expect
+that describes what the test case expectations are at any point in time.
+The value of this property can change during execution by any of:
+.Bl -tag -width indent
+.It Fn atf.expect_death "reason"
+Expects the test case to exit prematurely regardless of the nature of the
+exit.
+.It Fn atf.expect_exit "reason" "exitcode"
+Expects the test case to exit cleanly.
+.Pp
+.Fa exitcode
+is optional.
+If
+.Fa exitcode
+is provided, the runtime engine will validate that the exit code of the test
+case matches the one provided in this call.
+Otherwise, the exact value will be ignored.
+.It Fn atf.expect_fail "reason"
+Any failure raised in this mode is recorded, but such failures do not report
+the test case as failed; instead, the test case finalizes cleanly and is
+reported as
+.Sq expected failure ;
+this report includes the provided
+.Fa reason
+as part of it.
+If no error is raised while running in this mode, then the test case is
+reported as
+.Sq failed .
+.Pp
+This mode is useful to reproduce actual known bugs in tests.
+Whenever the developer fixes the bug later on, the test case will start
+reporting a failure, signaling the developer that the test case must be
+adjusted to the new conditions.
+In this situation, it is useful, for example, to set
+.Fa reason
+as the bug number for tracking purposes.
+.It Fn atf.expect_pass
+This is the normal mode of execution.
+In this mode, any failure is reported as such to the user and the test case
+is marked as
+.Sq failed .
+.It Fn atf.expect_signal "reason" "signo"
+Expects the test case to terminate due to the reception of a signal.
+.Pp
+.Fa signo
+is optional.
+If
+.Fa signo
+is provided, the runtime engine will validate that the signal that terminated
+the test case matches the one provided in this call.
+Otherwise, the exact value will be ignored.
+.It Fn atf.expect_timeout "reason"
+Expects the test case to execute for longer than its timeout.
+.El
+.Ss Helper functions for common checks
+.Bl -tag -width indent
+.It Nm atf.check_equal "expected_expression" "actual_expression"
+This function takes two expressions, evaluates them and, if their
+results differ, aborts the test case with an appropriate failure message.
+The common style is to put the expected value in the first parameter and the
+actual value in the second parameter.
+.El
+.Ss Test inheritance
+Test programs written with
+.Nm
+can take advantage of test object inheritance.
+By default, all tests are derived from the
+.Dv atf.TestCase
+.Dq class
+and auto-registered, but test cases can also opt out of auto-registration if
+they're primarily intended for being inherited by providing a boolean
+.Va atf_auto
+in the test case definition.
+.Pp
+See
+.Sx EXAMPLES .
+.Sh EXAMPLES
+The following shows a complete test program with a single test case that
+validates the addition operator:
+.Bd -literal -offset indent
+local atf = require('atf')
+
+atf.TestCase "addition" {
+ head = function()
+ atf.set("descr", "Sample tests for the addition operator")
+ end,
+ body = function()
+ atf.check_equal(0, 0 + 0)
+ atf.check_equal(1, 0 + 1)
+ atf.check_equal(1, 1 + 0)
+ atf.check_equal(2, 1 + 1)
+ atf.check_equal(300, 100 + 200)
+ end,
+}
+.Ed
+.Pp
+The following showcases various modes of test inheritance that are allowed:
+.Bd -literal -offset indent
+local atf = require('atf')
+
+-- The ident string of a non-auto TestCase is generally unused, since these are
+-- not auto-registered by default. They're not technically required to be
+-- unique.
+local TestSkel = atf.TestCase "skeleton" {
+ atf_auto = false,
+ -- This head function will be called for any tests that inherit from
+ -- TestSkel. Note that a body is not provided here, so by default a
+ -- test derived from this will fail because they're unimplemented.
+ head = function()
+ atf.set("require.user", "unprivileged")
+ end,
+}
+
+-- The local we assigned to the result of the above atf.TestCase expression
+-- can then be used to derive another test, RequiresUnpriv.
+TestSkel "RequiresUnpriv" {
+ -- The atf_auto property is not inherited, and it's assumed to be true
+ -- if it's not set. Therefore, any test derived from the above skeleton
+ -- will still get autoregistered.
+ body = function()
+ -- Execute some things that require an unprivileged user.
+ end,
+}
+.Ed
+.Sh SEE ALSO
+.Xr atf-lua 1 ,
+.Xr atf-test-program 1 ,
+.Xr atf-test-case 4
Index: contrib/atf/atf-lua/atf-lua.c
===================================================================
--- /dev/null
+++ contrib/atf/atf-lua/atf-lua.c
@@ -0,0 +1,335 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
+ *
+ * Copyright (c) 2020 Kyle Evans <kevans@FreeBSD.org>
+ *
+ * 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 THE AUTHOR AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/stat.h>
+
+#include <errno.h>
+#include <libgen.h>
+#include <unistd.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+
+#define linit_c
+#define LUA_LIB
+
+#include "lprefix.h"
+
+#include "lua.h"
+
+#include "lualib.h"
+#include "lauxlib.h"
+
+#include "latf.h"
+
+#if defined(HAVE_GNU_GETOPT)
+# define GETOPT_POSIX "+"
+#else
+# define GETOPT_POSIX ""
+#endif
+
+#define usage_error(...) usage_error_code(EXIT_FAILURE, __VA_ARGS__)
+
+/*
+** these libs are loaded by lua.c and are readily available to any Lua
+** program
+*/
+static const luaL_Reg loadedlibs[] = {
+ {"_G", luaopen_base},
+ {LUA_LOADLIBNAME, luaopen_package},
+ {LUA_COLIBNAME, luaopen_coroutine},
+ {LUA_TABLIBNAME, luaopen_table},
+ {LUA_IOLIBNAME, luaopen_io},
+ {LUA_OSLIBNAME, luaopen_os},
+ {LUA_STRLIBNAME, luaopen_string},
+ {LUA_MATHLIBNAME, luaopen_math},
+ {LUA_UTF8LIBNAME, luaopen_utf8},
+ {LUA_DBLIBNAME, luaopen_debug},
+#if defined(LUA_COMPAT_BITLIB)
+ {LUA_BITLIBNAME, luaopen_bit32},
+#endif
+ {"atf", luaopen_atf},
+ {NULL, NULL}
+};
+
+static const char *m_prog_name;
+
+static void
+atf_lua_openlibs (lua_State *L) {
+ for (const luaL_Reg *lib = loadedlibs; lib->func != NULL; lib++) {
+ luaL_requiref(L, lib->name, lib->func, 1);
+ lua_pop(L, 1);
+ }
+}
+
+static void
+runtime_error_ap(const char *fmt, va_list ap)
+{
+ fprintf(stderr, "%s: ERROR: ", m_prog_name);
+ vfprintf(stderr, fmt, ap);
+ fprintf(stderr, "\n");
+}
+
+static void __dead2
+runtime_error(int exitcode, const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ runtime_error_ap(fmt, ap);
+ va_end(ap);
+
+ exit(exitcode);
+}
+
+static void __dead2
+usage_error_code(int exitcode, const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ runtime_error_ap(fmt, ap);
+ va_end(ap);
+
+ fprintf(stderr, "%s: See atf-lua(1) for usage details.\n",
+ m_prog_name);
+ exit(exitcode);
+}
+
+static int
+atf_lua_panic(lua_State *L)
+{
+ struct latf_error *error, **errorp;
+
+ /* Not ours, just push it as a string. */
+ if (!lua_isuserdata(L, -1)) {
+ fprintf(stderr, "Lua error: %s\n", lua_tostring(L, -1));
+ exit(1);
+ }
+
+ errorp = (struct latf_error **)luaL_checkudata(L, -1,
+ LATF_ERROR_METATABLE);
+ error = *errorp;
+
+ if (error->err_msg == NULL)
+ exit(error->err_exitcode);
+
+ runtime_error(error->err_exitcode, error->err_msg);
+}
+
+static void
+atf_lua_parse_tcname(char *tcarg, const char **tcname, const char **tcmethod)
+{
+ char *sep;
+
+ *tcname = tcarg;
+ *tcmethod = NULL;
+ sep = strchr(tcarg, ':');
+ if (sep == NULL) {
+ *tcmethod = "body";
+ return;
+ }
+
+ *sep++ = '\0';
+ *tcmethod = sep;
+
+ if (strcmp(sep, "body") != 0 && strcmp(sep, "cleanup") != 0)
+ usage_error("Unknown test case part `%s'", *sep);
+}
+
+static int
+atf_execute(lua_State *L, char *argv[])
+{
+ const char *tcname, *tcmethod;
+ int ret;
+
+ /* Will exit if m_argv[0] is malformed. */
+ atf_lua_parse_tcname(argv[0], &tcname, &tcmethod);
+
+ /*
+ * The latf layer will again bubble any errors straight to the panic
+ * handler that we've setup, with exception to a couple checks that it
+ * makes early on and indicates via the return value. Currently, the
+ * main error we'll observe upon return is that the test case wasn't
+ * registered.
+ *
+ * This is the easy part, as latf_execute() just needs to invoke the
+ * head() method on whichever test we've been instructed to run, then
+ * run the request method. Whatever may be driving us, probably kyua,
+ * will drive invocation of the cleanup method if needed -- that still
+ * goes through this path.
+ */
+ ret = latf_execute(L, tcname, tcmethod);
+ if (ret == 0)
+ return (EXIT_SUCCESS);
+ else if (ret == ENOENT) {
+ usage_error("Unknown test case `%s'", tcname);
+ }
+
+ usage_error("Unhandled return/error d", ret);
+}
+
+int
+main(int argc, char *argv[])
+{
+ const char *atf_magic, *script, *srcdir, *resultfile;
+ char **m_argv;
+ lua_State *L;
+ int ch, m_argc, ret;
+ bool lflag;
+ struct stat sb;
+
+ m_prog_name = basename(argv[0]);
+ /* libtool workaround: skip the "lt-" prefix if present. */
+ if (strncmp(m_prog_name, "lt-", 3) == 0)
+ m_prog_name += 3;
+
+ m_argc = --argc;
+ m_argv = ++argv;
+
+ if (m_argc < 1)
+ usage_error("No test program provided");
+
+ script = m_argv[0];
+ if (stat(script, &sb) != 0)
+ runtime_error(EXIT_FAILURE, "The test program '%s' does not exist", script);
+
+ L = luaL_newstate();
+ if (L == NULL)
+ runtime_error(EXIT_FAILURE, "Failed to create state: not enough memory");
+
+ atf_lua_openlibs(L);
+
+ /*
+ * We set the panic handler here, but realistically it's OK to set it
+ * any time after loading libs above and before loading the file below
+ * since there's no need to catch errors in the early stages of
+ * initialization -- none of those will return our custom userdata
+ * error.
+ */
+ lua_atpanic(L, atf_lua_panic);
+ latf_set_args(L, m_argc, m_argv);
+
+ lflag = 0;
+ srcdir = "";
+ resultfile = "/dev/stdout";
+ while ((ch = getopt(m_argc, m_argv, GETOPT_POSIX ":lr:s:v:")) != -1) {
+ switch (ch) {
+ case 'l':
+ lflag = true;
+ break;
+
+ case 'r':
+ resultfile = optarg;
+ break;
+
+ case 's':
+ srcdir = optarg;
+ break;
+
+ case 'v':
+ if (*optarg == '\0')
+ runtime_error(EXIT_FAILURE,
+ "-v requires a non-empty argument");
+ if (!latf_add_var(L, optarg))
+ runtime_error(EXIT_FAILURE,
+ "-v requires an argument of the form "
+ "var=value");
+ break;
+
+ case ':':
+ usage_error("Option -%c requires an argument.", optopt);
+ break;
+
+ case '?':
+ default:
+ usage_error("Unknown option -%c.", optopt);
+ }
+ }
+ m_argc -= optind;
+ m_argv += optind;
+
+ latf_set_resultfile(L, resultfile);
+
+ /*
+ * srcdir gets plopped into the test config. One can fetch it with
+ * atf.config_get("srcdir"), but we also provide atf.get_srcdir() for
+ * some consistency with atf-sh(3).
+ */
+ latf_set_srcdir(L, srcdir);
+
+ /*
+ * It's important to make sure we're generally ready to start executing
+ * things *before* loading the script here. Primarily, we want to make
+ * sure the srcdir is intact in case that's somehow relevant to test
+ * case registration. All registration will happen at this point.
+ */
+ ret = luaL_dofile(L, script);
+ if (ret != LUA_OK) {
+ const char *errstr = lua_tostring(L, -1);
+
+ errstr = errstr == NULL ? "unknown" : errstr;
+ runtime_error(EXIT_FAILURE, "Error while executing %s: %s",
+ script, errstr);
+ }
+
+ if (lflag) {
+ if (m_argc > 0)
+ usage_error("Cannot provide test case names with -l");
+
+ /*
+ * `-l` is an easy one to deal with. latf_list will iterate
+ * over all the tests that registered when we initially loaded
+ * the script. Each test will have its head method invoked,
+ * then latf_list will pull and output any vars that were set
+ * in the head() function.
+ */
+ latf_list(L);
+
+ /* All errors will hit our panic handler. */
+ return (EXIT_SUCCESS);
+ }
+
+ if (m_argc == 0)
+ usage_error("Must provide a test case name");
+ else if (m_argc > 1)
+ usage_error("Cannot provide more than one test case name");
+
+ atf_magic = getenv("__RUNNING_INSIDE_ATF_RUN");
+ if (atf_magic == NULL || strcmp(atf_magic, "internal-yes-value") != 0) {
+ fprintf(stderr, "%s: WARNING: Running test cases outside "
+ "of kyua(1) is unsupported\n", m_prog_name);
+ fprintf(stderr, "%s: WARNING: No isolation nor timeout "
+ "control is being applied; you may get unexpected failures; see "
+ "atf-test-case(4)\n", m_prog_name);
+ }
+
+ return (atf_execute(L, m_argv));
+}
Index: contrib/atf/atf-lua/config_test.sh
===================================================================
--- /dev/null
+++ contrib/atf/atf-lua/config_test.sh
@@ -0,0 +1,77 @@
+# Copyright (c) 2007 The NetBSD Foundation, Inc.
+# 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 THE NETBSD FOUNDATION, INC. AND
+# CONTRIBUTORS ``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 FOUNDATION OR CONTRIBUTORS 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.
+
+atf_test_case has
+has_head()
+{
+ atf_set "descr" "Verifies that atf.config_has works"
+}
+has_body()
+{
+ h="$(atf_get_srcdir)/misc_helpers -s $(atf_get_srcdir)"
+
+ #atf_check -s eq:0 -o match:'foo not found' -e ignore \
+ # -v "TEST_VARIABLE=foo" env
+
+ atf_check -s eq:0 -o match:'foo not found' -e ignore -x \
+ "TEST_VARIABLE=foo ${h} config_has"
+
+ atf_check -s eq:0 -o match:'foo found' -e ignore -x \
+ "TEST_VARIABLE=foo ${h} -v foo=bar config_has"
+
+ echo "Checking for deprecated variables"
+ atf_check -s eq:0 -o match:'workdir not found' -e ignore -x \
+ "TEST_VARIABLE=workdir ${h} config_has"
+}
+
+atf_test_case get
+get_head()
+{
+ atf_set "descr" "Verifies that atf.config_get works"
+}
+get_body()
+{
+ h="$(atf_get_srcdir)/misc_helpers -s $(atf_get_srcdir)"
+
+ ${h} config_get_undefined >out 2>err && \
+ atf_fail "Getting an undefined variable succeeded"
+
+ grep 'not find' err || \
+ atf_fail "Getting an undefined variable did not report an error"
+
+ atf_check -s eq:0 -o match:'foo = bar' -e ignore -x \
+ "TEST_VARIABLE=foo ${h} -v foo=bar config_get"
+
+ atf_check -s eq:0 -o match:'foo = baz' -e ignore -x \
+ "TEST_VARIABLE=foo ${h} -v foo=baz config_get"
+}
+
+atf_init_test_cases()
+{
+ atf_add_test_case has
+ atf_add_test_case get
+}
+
+# vim: syntax=sh:expandtab:shiftwidth=4:softtabstop=4
Index: contrib/atf/atf-lua/integration_test.sh
===================================================================
--- /dev/null
+++ contrib/atf/atf-lua/integration_test.sh
@@ -0,0 +1,120 @@
+# Copyright (c) 2010 The NetBSD Foundation, Inc.
+# 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 THE NETBSD FOUNDATION, INC. AND
+# CONTRIBUTORS ``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 FOUNDATION OR CONTRIBUTORS 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.
+
+: ${ATF_LUA:="__ATF_LUA__"}
+
+create_test_program() {
+ local output="${1}"; shift
+ echo "#! ${ATF_LUA} ${*}" >"${output}"
+ cat >>"${output}"
+ chmod +x "${output}"
+}
+
+atf_test_case no_args
+no_args_body()
+{
+ cat >experr <<EOF
+atf-lua: ERROR: No test program provided
+atf-lua: See atf-lua(1) for usage details.
+EOF
+ atf_check -s eq:1 -o ignore -e file:experr "${ATF_LUA}"
+}
+
+atf_test_case no_tests
+no_tests_body()
+{
+ create_test_program tp <<EOF
+EOF
+
+ cat >expout <<EOF
+Content-Type: application/X-atf-tp; version="1"
+
+EOF
+
+ atf_check -s eq:0 -o file:expout -e ignore "${ATF_LUA}" tp -l
+}
+atf_test_case missing_script
+missing_script_body()
+{
+ cat >experr <<EOF
+atf-lua: ERROR: The test program 'non-existent' does not exist
+EOF
+ atf_check -s eq:1 -o ignore -e file:experr "${ATF_LUA}" non-existent
+}
+
+atf_test_case missing_test
+missing_test_body()
+{
+ create_test_program tp <<EOF
+atf.TestCase "existing" {
+ body = function()
+ end,
+}
+EOF
+
+ cat >experr <<EOF
+atf-lua: ERROR: Unknown test case \`nonexistent'
+atf-lua: See atf-lua(1) for usage details.
+EOF
+
+ atf_check -s eq:1 -o ignore -e file:experr "${ATF_LUA}" tp nonexistent
+}
+
+atf_test_case arguments
+arguments_body()
+{
+ create_test_program tp <<EOF
+print(">>>" .. arg[0] .. "<<<")
+for i in ipairs(arg) do
+ print(">>>" .. arg[i] .. "<<<")
+end
+
+os.exit(0)
+EOF
+
+ cat >expout <<EOF
+>>>./tp<<<
+>>> a b <<<
+>>>foo<<<
+EOF
+ atf_check -s eq:0 -o file:expout -e empty ./tp ' a b ' foo
+
+ cat >expout <<EOF
+>>>tp<<<
+>>> hello bye <<<
+>>>foo bar<<<
+EOF
+ atf_check -s eq:0 -o file:expout -e empty "${ATF_LUA}" tp \
+ ' hello bye ' 'foo bar'
+}
+
+atf_init_test_cases()
+{
+ atf_add_test_case no_args
+ atf_add_test_case no_tests
+ atf_add_test_case missing_script
+ atf_add_test_case missing_test
+ atf_add_test_case arguments
+}
Index: contrib/atf/atf-lua/latf.h
===================================================================
--- /dev/null
+++ contrib/atf/atf-lua/latf.h
@@ -0,0 +1,85 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
+ *
+ * Copyright (c) 2020 Kyle Evans <kevans@FreeBSD.org>
+ *
+ * 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 THE AUTHOR AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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.
+ *
+ * $FreeBSD$
+ */
+
+#ifndef LATF_H
+#define LATF_H
+
+struct latf_error {
+ const char *err_prefix;
+ const char *err_msg;
+ int err_exitcode;
+};
+
+#define LATF_ERROR_METATABLE "latf error metatable"
+
+#define ATF_PROP__PREFIX "atf_"
+#define ATF_PROP__INTERNAL_PREFIX "_" ATF_PROP__PREFIX
+
+/* Registry entries */
+#define ATF_GLOBAL_PROP_TCS ATF_PROP__INTERNAL_PREFIX "tcs"
+#define ATF_GLOBAL_PROP_VARS ATF_PROP__INTERNAL_PREFIX "vars"
+
+/* Private TestCase properties */
+#define ATF_PROP_TC ATF_PROP__INTERNAL_PREFIX "tc"
+#define ATF_PROP_VARS ATF_PROP__INTERNAL_PREFIX "vars"
+#define ATF_PROP_IDENT ATF_PROP__INTERNAL_PREFIX "ident"
+
+/* Public TestCase properties */
+#define ATF_PROP_AUTO ATF_PROP__PREFIX "auto"
+
+/* Provided by latf_tc */
+extern const char *tc_executing;
+extern const char *tc_method_executing;
+
+extern int tc_resultfile_fd;
+
+/*
+ * execcb is called with a testcase key, value as last two items on the stack.
+ * The callback can return non-zero to indicate "bail out immediately" with
+ * whatever state the callback has left the stack in.
+ */
+typedef int (*latf_tc_foreach_cb)(lua_State *, void *);
+int latf_tc_foreach(lua_State *, latf_tc_foreach_cb, void *);
+bool latf_tc_get(lua_State *, const char *);
+
+void latf_tc_list(lua_State *);
+int latf_tc_execute(lua_State *, const char *, const char *);
+int latf_tc_obj(lua_State *);
+
+/* Provided by latf.c */
+int _latf_fail(lua_State *, const char *, ...);
+int luaopen_atf(lua_State *);
+void latf_set_args(lua_State *, int, char **);
+void latf_set_resultfile(lua_State *, const char *);
+int latf_execute(lua_State *, const char *, const char *);
+void latf_list(lua_State *);
+bool latf_add_var(lua_State *, char *);
+bool latf_set_srcdir(lua_State *, const char *);
+
+#endif /* LATF_H */
Index: contrib/atf/atf-lua/latf.c
===================================================================
--- /dev/null
+++ contrib/atf/atf-lua/latf.c
@@ -0,0 +1,762 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
+ *
+ * Copyright (c) 2020 Kyle Evans <kevans@FreeBSD.org>
+ *
+ * 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 THE AUTHOR AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/stat.h>
+
+#include <fcntl.h>
+#include <unistd.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "atf-c/error.h"
+#include "atf-c/tc.h"
+#include "atf-c/utils.h"
+
+#include "lprefix.h"
+#include "lua.h"
+#include "lualib.h"
+#include "lauxlib.h"
+#include "ldebug.h"
+
+#include "latf.h"
+
+#define _latf_write_result(...) dprintf(tc_resfile_fd, __VA_ARGS__)
+#define _latf_write_result_ap(fmt, va) vdprintf(tc_resfile_fd, (fmt), (va))
+
+/* Courtesy of RhodiumToad's lspawn. */
+LUA_API int (lua_error) (lua_State *L) __dead2;
+
+int tc_resfile_fd = -1;
+
+/* We can only have one anyways, might as well save the runtime allocation. */
+static struct latf_error atferr;
+static enum latf_tc_expect {
+ TCE_PASS,
+ TCE_DEATH,
+ TCE_EXIT,
+ TCE_FAIL,
+ TCE_SIGNAL,
+ TCE_TIMEOUT,
+} latf_tc_expected = TCE_PASS;
+static char *latf_tc_expected_reason;
+
+/*
+ * Here we map the various test expectations to the message that we'll provide
+ * if the expectation is violated.
+ */
+static const char *latf_tc_expected_msg[] = {
+ [TCE_DEATH] = "Test case was expected to terminate abruptly but it "
+ "continued execution",
+ [TCE_EXIT] = "Test case was expected to exit cleanly but it continued "
+ "execution",
+ [TCE_FAIL] = "Test case was expecting a failure but none were raised",
+ [TCE_SIGNAL] = "Test case was expected to receive a termination signal "
+ "but it continued execution",
+ [TCE_TIMEOUT] = "Test case was expected to hang but it continued "
+ "execution",
+};
+
+/* These two are straight out of which(1). */
+static bool
+sane_xaccess(const char *candidate)
+{
+ struct stat st;
+
+ /* work around access(2) false positives for superuser. */
+ return (access(candidate, X_OK) == 0 &&
+ stat(candidate, &st) == 0 &&
+ S_ISREG(st.st_mode) &&
+ (getuid() != 0 ||
+ (st.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH)) != 0));
+}
+
+static bool
+path_search(const char *prog)
+{
+ char candidate[PATH_MAX];
+ const char *d, *envpath;
+ char *path;
+ bool ret;
+
+ ret = false;
+ if ((envpath = getenv("PATH")) == NULL)
+ return (false);
+ if ((path = strdup(envpath)) == NULL)
+ return (false);
+
+ while ((d = strsep(&path, ":")) != NULL) {
+ if (*d == '\0')
+ d = ".";
+ if (snprintf(candidate, sizeof(candidate), "%s/%s", d,
+ prog) >= (int)sizeof(candidate))
+ continue;
+ if (sane_xaccess(candidate)) {
+ ret = true;
+ break;
+ }
+ }
+
+ free(path);
+ return (ret);
+}
+
+/*
+ * _latf_bail is our standard mechanism for passing errors back up the chain.
+ * It is almost always wrong for latf to call luaL_error/lua_error directly from
+ * C that's been called by the loaded lua script. It's safe to send errors from
+ * the outer layer of C that's invoking the lua script, so it's best to just
+ * assume that any luaL_error/lua_error usage is wrong.
+ */
+static void __dead2
+_latf_bail(lua_State *L, int exitcode, const char *msg)
+{
+
+ atferr.err_msg = msg;
+ atferr.err_exitcode = exitcode;
+ *(struct latf_error **)lua_newuserdata(L, sizeof(atferr)) =
+ &atferr;
+ luaL_getmetatable(L, LATF_ERROR_METATABLE);
+ lua_setmetatable(L, -2);
+ lua_error(L);
+}
+
+static int __dead2 __printflike(3, 4)
+_latf_error(lua_State *L, int exitcode, const char *fmt, ...)
+{
+ char *msg;
+ va_list ap;
+
+ va_start(ap, fmt);
+ if (vasprintf(&msg, fmt, ap) == -1)
+ _latf_bail(L, 128, "out of memory while reporting error");
+ va_end(ap);
+
+ _latf_bail(L, exitcode, msg);
+}
+
+/*
+ * _latf_finish is for internal usage by pass/fail/skip below to properly exit
+ * the interpreter and bubble up their results. Basically, _latf_finish must be
+ * called whenever we've written out the last line to our results file to make
+ * sure we close it properly before we bail out.
+ */
+static void __dead2
+_latf_finish(lua_State *L, int exitcode)
+{
+ if (tc_resfile_fd >= 0 && tc_resfile_fd != STDOUT_FILENO &&
+ tc_resfile_fd != STDIN_FILENO) {
+ close(tc_resfile_fd);
+ tc_resfile_fd = -1;
+ }
+
+ _latf_bail(L, exitcode, NULL);
+}
+
+static int __printflike(2, 3)
+_latf_skip(lua_State *L, const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ _latf_write_result("skipped: ");
+ _latf_write_result_ap(fmt, ap);
+ va_end(ap);
+
+ _latf_finish(L, 0);
+}
+
+int __dead2 __printflike(2, 3)
+_latf_fail(lua_State *L, const char *fmt, ...)
+{
+ va_list ap;
+
+ switch (latf_tc_expected) {
+ case TCE_FAIL:
+ va_start(ap, fmt);
+ _latf_write_result("expected_failure: %s: ",
+ latf_tc_expected_reason);
+ _latf_write_result_ap(fmt, ap);
+ _latf_write_result("\n");
+ va_end(ap);
+
+ _latf_finish(L, 0);
+ break;
+ case TCE_PASS:
+ va_start(ap, fmt);
+ _latf_write_result("failed: ");
+ _latf_write_result_ap(fmt, ap);
+ _latf_write_result("\n");
+ va_end(ap);
+
+ _latf_finish(L, 1);
+ break;
+ default:
+ _latf_error(L, 128, "Unreachable");
+ break;
+ }
+}
+
+static int
+_latf_pass(lua_State *L)
+{
+
+ switch (latf_tc_expected) {
+ case TCE_FAIL:
+ latf_tc_expected = TCE_PASS;
+ _latf_fail(L, "Test case was expecting a failure but got a "
+ "pass instead");
+ break;
+ case TCE_PASS:
+ _latf_write_result("passed\n");
+ _latf_finish(L, 0);
+ break;
+ default:
+ _latf_error(L, 128, "Unreachable");
+ break;
+ }
+}
+
+/*
+ * _latf_validate_expect is where we fail out if an expectation that isn't pass
+ * has been set and we haven't met it. It should be called by the outer C
+ * layer after test execution has completed to finally fail the test if the test
+ * was expecting some non-pass scenario. It must also be called when
+ * expectations change for the most part, as a previous non-pass expectation has
+ * clearly been violated if we're still executing far enough to reach this,
+ * perhaps with exception to timeout.
+ */
+static void
+_latf_validate_expect(lua_State *L)
+{
+ const char *msg;
+
+ /* If we expected pass, all is good. */
+ if (latf_tc_expected == TCE_PASS)
+ return;
+
+ /* Otherwise, we've already failed. */
+ msg = latf_tc_expected_msg[latf_tc_expected];
+ latf_tc_expected = TCE_PASS;
+ _latf_fail(L, "%s", msg);
+}
+
+/*
+ * Thus begins the implementation of our atf.* methods. Leading underbar marks
+ * internal implementations of atf.* methods that may be reused.
+ */
+static int
+latf_get(lua_State *L)
+{
+ int nargs;
+
+ nargs = lua_gettop(L);
+ luaL_argcheck(L, nargs > 0, 1, "not enough arguments");
+ luaL_checktype(L, 1, LUA_TSTRING);
+
+ if (!latf_tc_get(L, tc_executing))
+ _latf_error(L, 128, "atf.set called in invalid test");
+
+ lua_pushstring(L, ATF_PROP_VARS);
+ lua_gettable(L, -2);
+ /* Key to fetch */
+ lua_pushvalue(L, 1);
+ lua_gettable(L, -2);
+ return (1);
+}
+
+static int
+latf_set(lua_State *L)
+{
+
+ if (strcmp(tc_method_executing, "head") != 0)
+ _latf_error(L, 128,
+ "atf.set called from the test case's body");
+ if (lua_gettop(L) != 2)
+ _latf_error(L, 128,
+ "atf.set takes two args: key, value strings");
+
+ /* Summon up the test case props. */
+ if (!latf_tc_get(L, tc_executing))
+ _latf_error(L, 128, "atf.set called in invalid test");
+
+ lua_pushstring(L, ATF_PROP_VARS);
+ lua_gettable(L, -2);
+
+ lua_pushvalue(L, 1);
+ lua_pushvalue(L, 2);
+ lua_settable(L, -3);
+
+ return (0);
+}
+
+static int
+latf_check_equal(lua_State *L)
+{
+
+ if (lua_gettop(L) < 2)
+ _latf_error(L, 128,
+ "wrong number of arguments for atf.check_equal (need 2)");
+
+ if (!lua_compare(L, 1, 2, LUA_OPEQ)) {
+ /* Nope, failure */
+ lua_Debug dbg;
+ const char *expected, *actual;
+
+ expected = luaL_tolstring(L, 1, NULL);
+ actual = luaL_tolstring(L, 2, NULL);
+ lua_getstack(L, 1, &dbg);
+ lua_getinfo(L, "Sl", &dbg);
+ _latf_fail(L, "%s != %s [%s:%d]", expected, actual,
+ dbg.short_src, dbg.currentline);
+ }
+ return (0);
+}
+
+static int
+_latf_config_get(lua_State *L)
+{
+ /* Grab registry[ATF_GLOBAL_PROP_VARS] */
+ lua_pushstring(L, ATF_GLOBAL_PROP_VARS);
+ lua_gettable(L, LUA_REGISTRYINDEX);
+
+ /* Push the key (1) */
+ lua_pushvalue(L, 1);
+ lua_gettable(L, -2);
+
+ /* Knock off the vars table */
+ lua_remove(L, -2);
+
+ /* If it's unset, pop off the nil. */
+ if (lua_isnil(L, -1)) {
+ lua_pop(L, 1);
+ return (0);
+ }
+
+ return (1);
+}
+
+static int
+latf_config_get(lua_State *L)
+{
+ int nargs;
+
+ nargs = lua_gettop(L);
+ if (nargs == 0)
+ _latf_error(L, 1,
+ "Incorrect number of parameters for atf.config_get");
+ luaL_checktype(L, 1, LUA_TSTRING);
+
+ if (!_latf_config_get(L) && nargs == 1)
+ return (_latf_error(L, 1,
+ "Could not find configuration variable `%s'",
+ lua_tostring(L, 1)));
+
+ return (1);
+}
+
+static int
+latf_config_has(lua_State *L)
+{
+
+ luaL_argcheck(L, lua_gettop(L) > 0, 1, "not enough arguments");
+ luaL_checktype(L, 1, LUA_TSTRING);
+ lua_settop(L, 1);
+
+ lua_pushboolean(L, _latf_config_get(L));
+ return (1);
+}
+
+static int
+latf_get_srcdir(lua_State *L)
+{
+
+ lua_pushstring(L, "srcdir");
+ return (latf_config_get(L));
+}
+
+static int
+latf_expect_death(lua_State *L)
+{
+ const char *reason;
+
+ luaL_argcheck(L, lua_gettop(L) > 0, 1, "not enough arguments");
+ reason = luaL_checkstring(L, 1);
+
+ _latf_validate_expect(L);
+ latf_tc_expected = TCE_DEATH;
+ _latf_write_result("expected_death: %s\n", reason);
+ return (0);
+}
+
+static int
+latf_expect_exit(lua_State *L)
+{
+ const char *reason;
+ int exitcode, nargs;
+
+ nargs = lua_gettop(L);
+ luaL_argcheck(L, nargs > 0, 1, "not enough arguments");
+ reason = luaL_checkstring(L, 1);
+
+ _latf_validate_expect(L);
+ latf_tc_expected = TCE_EXIT;
+
+ if (nargs >= 2) {
+ exitcode = luaL_checknumber(L, 2);
+ _latf_write_result("expected_exit(%d): %s\n", exitcode,
+ reason);
+ } else {
+ _latf_write_result("expected_exit: %s\n", reason);
+ }
+
+ return (0);
+}
+
+static int
+latf_expect_fail(lua_State *L)
+{
+ const char *reason;
+
+ luaL_argcheck(L, lua_gettop(L) > 0, 1, "not enough arguments");
+ reason = luaL_checkstring(L, 1);
+
+ _latf_validate_expect(L);
+ latf_tc_expected = TCE_FAIL;
+ latf_tc_expected_reason = strdup(reason);
+ if (latf_tc_expected_reason == NULL)
+ _latf_bail(L, 128, "out of memory");
+ return (0);
+}
+
+static int
+latf_expect_pass(lua_State *L)
+{
+
+ _latf_validate_expect(L);
+ latf_tc_expected = TCE_PASS;
+ free(latf_tc_expected_reason);
+ latf_tc_expected_reason = NULL;
+ return (0);
+}
+
+static int
+latf_expect_signal(lua_State *L)
+{
+ const char *reason;
+ int signo, nargs;
+
+ nargs = lua_gettop(L);
+ luaL_argcheck(L, nargs > 0, 1, "not enough arguments");
+ reason = luaL_checkstring(L, 1);
+
+ _latf_validate_expect(L);
+ latf_tc_expected = TCE_SIGNAL;
+
+ if (nargs >= 2) {
+ signo = luaL_checknumber(L, 2);
+ _latf_write_result("expected_signal(%d): %s\n", signo,
+ reason);
+ } else {
+ _latf_write_result("expected_signal: %s\n", reason);
+ }
+
+ return (0);
+}
+
+static int
+latf_expect_timeout(lua_State *L)
+{
+ const char *reason;
+
+ luaL_argcheck(L, lua_gettop(L) > 0, 1, "not enough arguments");
+ reason = luaL_checkstring(L, 1);
+
+ _latf_validate_expect(L);
+ latf_tc_expected = TCE_TIMEOUT;
+ _latf_write_result("expected_timeout: %s\n", reason);
+ return (0);
+}
+
+static int
+latf_fail(lua_State *L)
+{
+ const char *reason;
+
+ luaL_argcheck(L, lua_gettop(L) > 0, 1, "not enough arguments");
+ reason = luaL_checkstring(L, 1);
+
+ _latf_fail(L, "%s", reason);
+ return (0);
+}
+
+static int
+latf_pass(lua_State *L)
+{
+
+ return (_latf_pass(L));
+}
+
+static int
+latf_skip(lua_State *L)
+{
+ const char *reason;
+
+ luaL_argcheck(L, lua_gettop(L) > 0, 1, "not enough arguments");
+ reason = luaL_checkstring(L, 1);
+
+ return (_latf_skip(L, "%s", reason));
+}
+
+static int
+latf_require_prog(lua_State *L)
+{
+ const char *prog;
+
+ luaL_argcheck(L, lua_gettop(L) > 0, 1, "not enough arguments");
+ prog = luaL_checkstring(L, 1);
+
+ if (*prog != '/' && strchr(prog, '/') != NULL)
+ return (_latf_fail(L, "atf_require_prog does not accept "
+ "relative path name `%s'", prog));
+
+ if (*prog == '/') {
+ if (!sane_xaccess(prog))
+ return (_latf_skip(L, "The required program %s could "
+ "not be found", prog));
+ return (0);
+ }
+
+ /* Fallback to a PATH search */
+ if (!path_search(prog))
+ return (_latf_skip(L, "The required program %s could "
+ "not be found in PATH", prog));
+ return (0);
+}
+
+#define NORMAL_FUNC(f) { #f, latf_##f }
+static const struct luaL_Reg latf[] = {
+ /* get(k) */
+ NORMAL_FUNC(get),
+ /* set(k, v) */
+ NORMAL_FUNC(set),
+ /* check_equal(expected, actual) */
+ NORMAL_FUNC(check_equal),
+ /* config_get(varname[, default]) */
+ NORMAL_FUNC(config_get),
+ /* config_has(varname) */
+ NORMAL_FUNC(config_has),
+ /* get_srcdir() */
+ NORMAL_FUNC(get_srcdir),
+ /* expect_death(reason) */
+ NORMAL_FUNC(expect_death),
+ /* expect_exit(reason[, exitcode]) */
+ NORMAL_FUNC(expect_exit),
+ /* expect_fail(reason) */
+ NORMAL_FUNC(expect_fail),
+ /* expect_pass() */
+ NORMAL_FUNC(expect_pass),
+ /* expect_signal(reason[, signal]) */
+ NORMAL_FUNC(expect_signal),
+ /* expect_timeout(reason) */
+ NORMAL_FUNC(expect_timeout),
+ /* fail(reason) */
+ NORMAL_FUNC(fail),
+ /* pass() */
+ NORMAL_FUNC(pass),
+ /* skip(reason) */
+ NORMAL_FUNC(skip),
+ /* require_prog(prog_name) */
+ NORMAL_FUNC(require_prog),
+ {NULL, NULL},
+};
+#undef NORMAL_FUNC
+
+int
+luaopen_atf(lua_State *L)
+{
+
+ luaL_newlib(L, latf);
+ luaL_newmetatable(L, LATF_ERROR_METATABLE);
+
+ /*
+ * luaL_newmetatable creates the named metatable, pushes it into
+ * the registry table, and also leaves a copy on the stack. We can
+ * discard the version from the stack since we won't be using it, and
+ * later setup bits here will assume they're being constructed right
+ * after the lib table.
+ */
+ lua_pop(L, 1);
+
+ /* Creates atf.TestCase. */
+ latf_tc_obj(L);
+
+ /* Setup some of our internal registry entries with a blank table. */
+ lua_newtable(L);
+ lua_setfield(L, LUA_REGISTRYINDEX, ATF_GLOBAL_PROP_TCS);
+ lua_newtable(L);
+ lua_setfield(L, LUA_REGISTRYINDEX, ATF_GLOBAL_PROP_VARS);
+ return (1);
+}
+
+void
+latf_set_resultfile(lua_State *L, const char *resfile)
+{
+
+ if (tc_resfile_fd >= 0 && tc_resfile_fd != STDOUT_FILENO &&
+ tc_resfile_fd != STDERR_FILENO)
+ close(tc_resfile_fd);
+
+ /* Open as needed. */
+ if (strcmp(resfile, "/dev/stdout") == 0) {
+ tc_resfile_fd = STDOUT_FILENO;
+ } else if (strcmp(resfile, "/dev/stderr") == 0) {
+ tc_resfile_fd = STDERR_FILENO;
+ } else {
+ tc_resfile_fd = open(resfile, O_WRONLY | O_CREAT | O_TRUNC,
+ 0644);
+ if (tc_resfile_fd == -1)
+ _latf_error(L, 128,
+ "Cannot create results file '%s'", resfile);
+ }
+}
+
+int
+latf_execute(lua_State *L, const char *test, const char *method)
+{
+ int error;
+
+ /* Most errors will be passed up to our panic handler. */
+ error = latf_tc_execute(L, test, method);
+ if (error != 0)
+ return (error);
+
+ /*
+ * Here is where we potentially grab a failure if an expectation was set
+ * and then not hit. This is usually invoked from within a lua context
+ * by calling one of the atf.expect_* functions, so it typically raises
+ * an error upon violation and will again hit our panic handler.
+ */
+ _latf_validate_expect(L);
+ _latf_write_result("passed\n");
+ return (0);
+}
+
+void
+latf_list(lua_State *L)
+{
+
+ return (latf_tc_list(L));
+}
+
+static void
+latf_config_set(lua_State *L, const char *name, const char *value)
+{
+ /* Grab registry[ATF_GLOBAL_PROP_VARS] */
+ lua_pushstring(L, ATF_GLOBAL_PROP_VARS);
+ lua_gettable(L, LUA_REGISTRYINDEX);
+
+ /* Insert! */
+ lua_pushstring(L, name);
+ lua_pushstring(L, value);
+ lua_settable(L, -3);
+ lua_setfield(L, LUA_REGISTRYINDEX, ATF_GLOBAL_PROP_VARS);
+}
+
+/*
+ * latf_add_var parse the -v argument (in the form of key=value) and stashes
+ * the key into the config table.
+ */
+bool
+latf_add_var(lua_State *L, char *arg)
+{
+ char *split;
+
+ if (*arg == '\0')
+ return (false);
+ split = strchr(arg, '=');
+ if (split == NULL)
+ return (false);
+
+ *split++ = '\0';
+
+ latf_config_set(L, arg, split);
+ return (true);
+}
+
+bool
+latf_set_srcdir(lua_State *L, const char *srcdir)
+{
+ char full_srcdir[MAXPATHLEN];
+ size_t len;
+
+ if (*srcdir != '/') {
+ if (getcwd(full_srcdir, sizeof(full_srcdir)) == NULL)
+ return (false);
+ len = strlen(full_srcdir);
+ full_srcdir[len++] = '/';
+ full_srcdir[len++] = '\0';
+ len = strlcat(full_srcdir, srcdir, sizeof(full_srcdir));
+ } else {
+ len = strlcpy(full_srcdir, srcdir, sizeof(full_srcdir));
+ }
+
+ if (len >= sizeof(full_srcdir))
+ return (false);
+
+ /*
+ * This is inconsistent with atf-sh, but consistent with how atf-c and
+ * atf-c++ operate. We'll provide an atf.get_srcdir() to be consistent
+ * with all predecessors to some extent, but that call really just
+ * fetches it from the config table.
+ */
+ latf_config_set(L, "srcdir", full_srcdir);
+ return (true);
+}
+
+/*
+ * latf_set_args does generally what lua proper does, setting up the arg table
+ * based on the arguments to the test case. These start with the strip name
+ * and include, for instance, the test case name/method or -l.
+ */
+void
+latf_set_args(lua_State *L, int argc, char **argv)
+{
+
+ lua_createtable(L, argc, argc);
+ for (int i = 0; i < argc; ++i) {
+ lua_pushstring(L, argv[i]);
+ lua_rawseti(L, -2, i);
+ }
+ lua_setglobal(L, "arg");
+}
Index: contrib/atf/atf-lua/latf_tc.c
===================================================================
--- /dev/null
+++ contrib/atf/atf-lua/latf_tc.c
@@ -0,0 +1,450 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
+ *
+ * Copyright (c) 2020 Kyle Evans <kevans@FreeBSD.org>
+ *
+ * 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 THE AUTHOR AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+
+#include <errno.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "lprefix.h"
+
+#include "lua.h"
+
+#include "lualib.h"
+#include "lauxlib.h"
+#include "ldebug.h"
+
+#include "latf.h"
+
+const char *tc_executing;
+const char *tc_method_executing;
+
+static int latf_tc_ident(lua_State *L);
+static int latf_tc_stub_body(lua_State *L);
+
+static const char *latf_tc_inherited[] = {
+ "head",
+ "body",
+ "cleanup",
+};
+
+/* Is the value at the top of the stack a test case? */
+static bool
+latf_is_tc(lua_State *L)
+{
+ bool atf_tc;
+
+ /* Trivially false; not a table... */
+ if (!lua_istable(L, -1))
+ return (false);
+
+ lua_pushstring(L, ATF_PROP_TC);
+ lua_gettable(L, -2);
+ atf_tc = lua_toboolean(L, -1);
+ lua_pop(L, 1);
+
+ return (atf_tc);
+}
+
+/*
+ * Is the value at the top of the stack a test case prop? All strings unless
+ * otherwise noted.
+ */
+static bool
+latf_is_tc_prop(lua_State *L)
+{
+
+ return lua_isstring(L, -1);
+}
+
+/*
+ * execcb is called with a testcase key, value as last two items on the stack.
+ * The callback can return non-zero to indicate "bail out immediately" with
+ * whatever state the callback has left the stack in.
+ */
+int
+latf_tc_foreach(lua_State *L, latf_tc_foreach_cb execcb, void *data)
+{
+ int error;
+
+ lua_pushstring(L, ATF_GLOBAL_PROP_TCS);
+ lua_gettable(L, LUA_REGISTRYINDEX);
+ lua_pushnil(L);
+ while (lua_next(L, -2) != 0) {
+ if ((error = execcb(L, data)) != 0)
+ return (error);
+
+ /* Pop the value. */
+ lua_pop(L, 1);
+ }
+ /* Pop the globaltable. */
+ lua_pop(L, 1);
+ return (0);
+}
+
+/* Call head/body/cleanup, tc is at -1. */
+static void
+latf_tc_method(lua_State *L, const char *tc_ident, const char *method)
+{
+ int error;
+
+ tc_executing = tc_ident;
+ tc_method_executing = method;
+ lua_pushstring(L, method);
+ lua_gettable(L, -2);
+ if (lua_isnil(L, -1)) {
+ tc_executing = tc_method_executing = NULL;
+ if (strcmp(method, "cleanup") == 0)
+ /* Just return success; cleanup is optional. */
+ return;
+ else
+ latf_tc_stub_body(L);
+ }
+ error = lua_pcall(L, 0, 0, 0);
+ tc_executing = tc_method_executing = NULL;
+
+ if (error == LUA_OK)
+ return;
+ switch (error) {
+ case LUA_ERRRUN:
+ /* Just print errors that aren't ours and bail. */
+ if (!lua_isuserdata(L, -1)) {
+ const char *msg;
+
+ msg = lua_tostring(L, -1);
+ fprintf(stderr, "%s\n", msg);
+ exit(128);
+ }
+
+ lua_error(L);
+ break;
+ case LUA_ERRMEM:
+ luaL_error(L, "out of memory");
+ break;
+#if LUA_ERRGCMM
+ /* Removed in Lua 5.4 as these are converted to warnings. */
+ case LUA_ERRGCMM:
+ luaL_error(L, "__gc error");
+ break;
+#endif
+ /* LUA_ERRERR not handled since we didn't specify a message handler. */
+ default:
+ luaL_error(L, "Unknown error type %d", error);
+ }
+}
+
+static int
+latf_tc_list_cb(lua_State *L, void *data)
+{
+ int *counter;
+ const char *ident;
+
+ counter = data;
+ lua_pushstring(L, ATF_PROP_IDENT);
+ lua_gettable(L, -2);
+ ident = lua_tostring(L, -1);
+ lua_pushvalue(L, -2);
+ /* If it fails, we'll bail out to the panic handler. */
+ latf_tc_method(L, ident, "head");
+ printf("\n");
+ printf("ident: %s\n", ident);
+ lua_pushstring(L, ATF_PROP_VARS);
+ lua_gettable(L, -2);
+ lua_pushnil(L);
+ while (lua_next(L, -2) != 0) {
+ printf("%s: %s\n", lua_tostring(L, -2),
+ lua_tostring(L, -1));
+ lua_pop(L, 1);
+ }
+ lua_pop(L, 3);
+ *counter++;
+ return (0);
+}
+
+void
+latf_tc_list(lua_State *L)
+{
+ int counter;
+
+ counter = 0;
+ printf("Content-Type: application/X-atf-tp; version=\"1\"\n");
+ /* Bubbles up any errors. */
+ latf_tc_foreach(L, latf_tc_list_cb, &counter);
+ if (counter == 0)
+ printf("\n");
+}
+
+static int
+latf_tc_get_cb(lua_State *L, void *data)
+{
+ const char *check_name, *tc_name;
+ int comp;
+
+ check_name = data;
+ lua_pushstring(L, ATF_PROP_IDENT);
+ lua_gettable(L, -2);
+ tc_name = lua_tostring(L, -1);
+ comp = (strcmp(tc_name, check_name) == 0);
+
+ lua_pop(L, 1);
+ return (comp);
+}
+
+/*
+ * latf_tc_get will enumerate the registered test cases, in search of one with
+ * an ident string matching name. If found, the latf_tc_get callback will
+ * indicate to latf_tc_foreach that it should stop, leaving the expected test at
+ * the top of stack for the latf_tc_get caller to use. If it's not found, the
+ * stack should be in the same state that it was in when we entered latf_tc_get.
+ */
+bool
+latf_tc_get(lua_State *L, const char *name)
+{
+
+ /*
+ * data is opaque to latf_enumerate_testcases_exec and cb won't deref
+ * it.
+ */
+ return (latf_tc_foreach(L, latf_tc_get_cb, __DECONST(char *, name)) != 0);
+}
+
+/*
+ * This is the final piece of constructing an individual test case. We've gone
+ * through latf_tc_ident and now we've invoked the closure that's returned from
+ * that, with ident and a table to inherit from (atf.TestCase by default). Note
+ * that we don't inherit many values from the parent, mainly head/body/cleanup.
+ *
+ * latf_tc_new will also make the __call method of this new table effectively
+ * the closure that we started this chain of events with, only the table to
+ * inherit from will be the very table that we're constructing here! The return
+ * value can then be used to create more tests cases in an identical fashion,
+ * ad infinitum.
+ */
+static int
+latf_tc_new(lua_State *L)
+{
+ int inherit, nargs;
+ bool doreg;
+
+ nargs = lua_gettop(L);
+ luaL_argcheck(L, nargs > 0, 1, "not enough arguments");
+ luaL_checktype(L, 1, LUA_TTABLE);
+
+ lua_settop(L, 1);
+ lua_pushvalue(L, lua_upvalueindex(1));
+ lua_setfield(L, -2, ATF_PROP_IDENT);
+
+ inherit = lua_upvalueindex(2);
+ if (!lua_isnil(L, inherit)) {
+ for (size_t i = 0; i < nitems(latf_tc_inherited); ++i) {
+ const char *field;
+ bool doinherit;
+
+ field = latf_tc_inherited[i];
+ lua_pushstring(L, field);
+ lua_gettable(L, -2);
+
+ /*
+ * We only inherit a field if an override isn't
+ * provided in the new test case definition.
+ */
+ doinherit = lua_isnil(L, -1);
+ lua_pop(L, 1);
+ if (doinherit) {
+ lua_pushstring(L, field);
+ lua_gettable(L, inherit);
+ lua_setfield(L, -2, field);
+ }
+ }
+ }
+
+ /* _atf_tc = true */
+ lua_pushboolean(L, 1);
+ lua_setfield(L, -2, ATF_PROP_TC);
+
+ /*
+ * _atf_vars = table, and we'll set _atf_vars[has.cleanup] if cleanup
+ * is callable so that kyua knows to execute :cleanup.
+ */
+ lua_newtable(L);
+ lua_pushstring(L, "cleanup");
+ lua_gettable(L, -3);
+ if (lua_isfunction(L, -1)) {
+ lua_pushstring(L, "true");
+ lua_setfield(L, -3, "has.cleanup");
+ }
+ lua_pop(L, 1);
+ lua_setfield(L, -2, ATF_PROP_VARS);
+
+ /* Push the __call closure back for inheritance usage. */
+ lua_newtable(L);
+ lua_pushvalue(L, -2);
+ lua_pushcclosure(L, latf_tc_ident, 1);
+ lua_setfield(L, -2, "__call");
+ lua_setmetatable(L, -2);
+
+ /* Make sure atf_auto is in order; if it's not set, it's true. */
+ lua_pushstring(L, ATF_PROP_AUTO);
+ lua_gettable(L, -2);
+ if (lua_isnil(L, -1)) {
+ lua_pop(L, 1);
+ doreg = true;
+ lua_pushboolean(L, 1);
+ lua_setfield(L, -2, ATF_PROP_AUTO);
+ } else {
+ doreg = lua_toboolean(L, -1);
+ lua_pop(L, 1);
+ }
+
+ if (doreg) {
+ const char *ident;
+
+ ident = lua_tostring(L, lua_upvalueindex(1));
+ if (latf_tc_get(L, ident)) {
+ lua_pop(L, 1);
+ return (luaL_error(L, "double registered '%s'", ident));
+ }
+ /* Grab registry[ATF_GLOBAL_PROP_TCS] */
+ lua_pushstring(L, ATF_GLOBAL_PROP_TCS);
+ lua_gettable(L, LUA_REGISTRYINDEX);
+
+ /* Push test */
+ lua_pushinteger(L, lua_rawlen(L, -1) + 1);
+ lua_pushvalue(L, -3);
+ lua_settable(L, -3);
+ lua_setfield(L, LUA_REGISTRYINDEX, ATF_GLOBAL_PROP_TCS);
+ }
+
+ /* Return the table. */
+ return (1);
+}
+
+static int
+latf_tc_ident(lua_State *L)
+{
+
+ luaL_argcheck(L, lua_gettop(L) >= 2, 2, "not enough arguments");
+ lua_settop(L, 2);
+
+ lua_pushvalue(L, lua_upvalueindex(1));
+ /* ident, inherit as upvalues */
+ lua_pushcclosure(L, latf_tc_new, 2);
+ return (1);
+}
+
+/*
+ * We provide these three default functions for atf.TestCase. The provided
+ * head() is harmless, but the default body() will effectively
+ * just invoke atf.fail() due to it not being an implemented test.
+ */
+static int
+latf_tc_stub_head(lua_State *L)
+{
+ return (0);
+}
+
+static int
+latf_tc_stub_body(lua_State *L)
+{
+
+ _latf_fail(L, "Test case not implemented");
+ return (0);
+}
+
+int
+latf_tc_obj(lua_State *L)
+{
+
+ /* Entry at the top of the stack should be the lib table. */
+ if (!lua_istable(L, -1))
+ return (luaL_error(L, "expected a table"));
+
+ /*
+ * Construct a callable TestCase object with:
+ * - head(): test head
+ * - body(): test body
+ * - internal props (ident, atf_auto)
+ */
+ lua_newtable(L);
+
+ /* Test callbacks. */
+ lua_pushcfunction(L, latf_tc_stub_head);
+ lua_setfield(L, -2, "head");
+ lua_pushcfunction(L, latf_tc_stub_body);
+ lua_setfield(L, -2, "body");
+
+ lua_pushstring(L, "TestCase");
+ lua_setfield(L, -2, ATF_PROP_IDENT);
+
+ lua_pushboolean(L, 1);
+ lua_setfield(L, -2, ATF_PROP_AUTO);
+
+ /*
+ * Push the TestCase as upvalue #1 to a closure for __call.
+ * latf_tc_ident will take a string argument and return another closure
+ * from latf_tc_new that's expecting a table to start from. This allows
+ * a fairly pleasant syntax:
+ *
+ * atf.TestCase "Ident String" { [Test Definition] }
+ */
+ lua_newtable(L);
+ lua_pushvalue(L, -2);
+ lua_pushcclosure(L, latf_tc_ident, 1);
+ lua_setfield(L, -2, "__call");
+ lua_setmetatable(L, -2);
+
+ lua_setfield(L, -2, "TestCase");
+
+ return (0);
+}
+
+/*
+ * latf_tc_execute will either return a negative errno or positive number
+ * to indicate failure, or 0 on success. Negative errno is returned for
+ * failures in the immediate area, while positive number is returned for errors
+ * propagated up to us from the interpreter.
+ */
+int
+latf_tc_execute(lua_State *L, const char *test, const char *method)
+{
+
+ if (!latf_tc_get(L, test))
+ return (ENOENT);
+
+ /* Errors bubble up. */
+ latf_tc_method(L, test, "head");
+ latf_tc_method(L, test, method);
+
+ return (0);
+}
+
Index: contrib/atf/atf-lua/latf_test.lua
===================================================================
--- /dev/null
+++ contrib/atf/atf-lua/latf_test.lua
@@ -0,0 +1,164 @@
+--
+-- SPDX-License-Identifier: BSD-2-Clause-FreeBSD
+--
+-- Copyright (c) 2018 Kyle Evans <kevans@FreeBSD.org>
+--
+-- 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 THE AUTHOR AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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.
+--
+-- $FreeBSD$
+--
+
+local atf = require("atf")
+
+atf.TestCase "check_basic" {
+ body = function()
+ atf.check_equal(true, true)
+ atf.check_equal(false, false)
+ atf.check_equal(1, 1)
+ atf.check_equal("string", "string")
+ end,
+}
+
+atf.TestCase "check_fail_bool" {
+ body = function()
+ atf.expect_fail("Intentional failure")
+ atf.check_equal(true, false)
+ end,
+}
+
+atf.TestCase "check_fail_num" {
+ body = function()
+ atf.expect_fail("Intentional failure")
+ atf.check_equal(3, 1)
+ end,
+}
+
+atf.TestCase "check_fail_str" {
+ body = function()
+ atf.expect_fail("Intentional failure")
+ atf.check_equal("test", "tes")
+ end,
+}
+
+atf.TestCase "check_fail_table" {
+ body = function()
+ atf.expect_fail("Intentional failure")
+ atf.check_equal({}, {})
+ end,
+}
+
+atf.TestCase "check_fail_obj" {
+ body = function()
+ atf.expect_fail("Intentional failure")
+
+ local _meta = {
+ __eq = function(lhs, rhs)
+ return lhs["val"] == rhs["val"]
+ end,
+ }
+
+ local a_obj = setmetatable({val = 3}, _meta)
+ local b_obj = setmetatable({val = 4}, _meta)
+ atf.check_equal(a_obj, b_obj)
+ end,
+}
+
+atf.TestCase "check_obj" {
+ body = function()
+ local _meta = {
+ __eq = function(lhs, rhs)
+ return lhs["val"] == rhs["val"]
+ end,
+ }
+
+ local a_obj = setmetatable({val = 3}, _meta)
+ local b_obj = setmetatable({val = 3}, _meta)
+ atf.check_equal(true, a_obj == b_obj)
+ atf.check_equal(a_obj, b_obj)
+ end,
+}
+
+atf.TestCase "expect_exit_any_success" {
+ body = function()
+ atf.expect_exit("Expected exit")
+
+ os.exit(0)
+ end,
+}
+
+
+atf.TestCase "expect_exit_any_fail" {
+ body = function()
+ atf.expect_exit("Expected exit")
+
+ os.exit(1)
+ end,
+}
+
+atf.TestCase "expect_exit_specific_ok" {
+ body = function()
+ atf.expect_exit("Expected exit", 1)
+
+ os.exit(1)
+ end,
+}
+
+atf.TestCase "expect_timeout" {
+ head = function()
+ atf.set("timeout", "2")
+ end,
+ body = function()
+ atf.expect_timeout("Planned timeout")
+
+ local _end = os.clock() + 4
+ -- Spin!
+ while os.clock() < _end do end
+ end,
+}
+
+atf.TestCase "fail" {
+ body = function()
+ atf.expect_fail("Explicit failure")
+ atf.fail("See? I wasn't kidding.")
+ end,
+}
+
+atf.TestCase "pass" {
+ body = function()
+ atf.pass()
+ end,
+}
+
+atf.TestCase "without_cleanup" {
+ body = function()
+ atf.check_equal(nil, atf.get("has.cleanup"))
+ end,
+}
+
+atf.TestCase "with_cleanup" {
+ body = function()
+ atf.check_equal("true", atf.get("has.cleanup"))
+ end,
+ cleanup = function()
+ end,
+}
+
Index: contrib/atf/atf-lua/misc_helpers.lua
===================================================================
--- /dev/null
+++ contrib/atf/atf-lua/misc_helpers.lua
@@ -0,0 +1,127 @@
+--
+-- SPDX-License-Identifier: BSD-2-Clause-FreeBSD
+--
+-- Copyright (c) 2020 Kyle Evans <kevans@FreeBSD.org>
+--
+-- 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 THE AUTHOR AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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.
+--
+-- $FreeBSD$
+--
+
+-- Helper tests for "t_tc".
+local t_tc_helper = atf.TestCase "t_tc_helper" {
+ atf_auto = false,
+ head = function()
+ atf.set("descr", "Helper test case for the t_tc test program")
+ end,
+}
+
+t_tc_helper "tc_pass_true" {
+ body = function()
+ os.exit(0)
+ end,
+}
+
+t_tc_helper "tc_pass_false" {
+ body = function()
+ os.exit(1)
+ end,
+}
+
+t_tc_helper "tc_fail" {
+ body = function()
+ io.stderr:write("An error")
+ os.exit(1)
+ end,
+}
+
+t_tc_helper "tc_expect_exit_fail" {
+ body = function()
+ atf.expect_exit("Expected exit", 1)
+
+ os.exit(os.getenv("EXIT_CODE"))
+ end,
+}
+
+t_tc_helper "tc_expect_signal_any" {
+ body = function()
+ atf.expect_signal("Expected signal")
+ end,
+}
+
+t_tc_helper "tc_expect_signal_sigusr1" {
+ body = function()
+ atf.expect_signal("Expected signal", 30)
+ end,
+}
+
+t_tc_helper "tc_check_srcdir" {
+ body = function()
+ atf.check_equal(os.getenv('ATF_SRCDIR'), atf.get_srcdir())
+ atf.check_equal(atf.get_srcdir(), atf.config_get('srcdir'))
+ end,
+}
+
+t_tc_helper "tc_skip" {
+ body = function()
+ atf.skip("Intentional skip")
+ end,
+}
+
+t_tc_helper "tc_missing_body" {
+}
+
+-- Helper tests for "t_config".
+
+local t_config_helper = atf.TestCase "t_config_helper" {
+ atf_auto = false,
+ head = function()
+ atf.set("descr",
+ "Helper test case for the t_config test program")
+ end,
+}
+
+t_config_helper "config_get" {
+ body = function()
+ local test_var = os.getenv("TEST_VARIABLE")
+ if atf.config_has(test_var) then
+ print(test_var .. " = " .. atf.config_get(test_var))
+ end
+ end,
+}
+
+t_config_helper "config_has" {
+ body = function()
+ local test_var = os.getenv("TEST_VARIABLE")
+ if atf.config_has(test_var) then
+ print(test_var .. " found")
+ else
+ print(test_var .. " not found")
+ end
+ end,
+}
+
+t_config_helper "config_get_undefined" {
+ body = function()
+ atf.config_get("undefined")
+ end,
+}
Index: contrib/atf/atf-lua/tc_test.sh
===================================================================
--- /dev/null
+++ contrib/atf/atf-lua/tc_test.sh
@@ -0,0 +1,124 @@
+#
+# SPDX-License-Identifier: BSD-2-Clause-FreeBSD
+#
+# Copyright (c) 2017 Kyle Evans <kevans@FreeBSD.org>
+#
+# 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 THE AUTHOR AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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.
+#
+# $FreeBSD$
+
+atf_test_case default_status
+default_status_head()
+{
+ atf_set "descr" "Verifies that test cases get the correct default" \
+ "status if they did not provide any"
+}
+default_status_body()
+{
+ h="$(atf_get_srcdir)/misc_helpers -s $(atf_get_srcdir)"
+ atf_check -s eq:0 -o ignore -e ignore ${h} tc_pass_true
+ atf_check -s eq:1 -o ignore -e ignore ${h} tc_pass_false
+ atf_check -s eq:1 -o ignore -e match:'An error' ${h} tc_fail
+}
+
+atf_test_case missing_body
+missing_body_head()
+{
+ atf_set "descr" "Verifies that test cases without a body are reported" \
+ "as failed"
+}
+
+missing_body_body()
+{
+ cat >expout <<EOF
+failed: Test case not implemented
+EOF
+
+ h="$(atf_get_srcdir)/misc_helpers -s $(atf_get_srcdir)"
+ atf_check -s eq:1 -o file:expout -e ignore ${h} tc_missing_body
+}
+
+atf_test_case expect_exit_fail
+expect_exit_fail_head()
+{
+ atf_set "descr" "Verifies that a failed exit expectation reports as" \
+ "failed."
+}
+
+expect_exit_fail_body()
+{
+ cat >expout <<EOF
+expected_exit(1): Expected exit
+EOF
+
+ h="$(atf_get_srcdir)/misc_helpers -s $(atf_get_srcdir)"
+ atf_check -s eq:2 -o file:expout -e ignore -x \
+ "EXIT_CODE=2 ${h} tc_expect_exit_fail"
+}
+atf_test_case expect_signal
+expect_signal_head()
+{
+ atf_set "descr" "Verifies that a signalled exit expectation outputs" \
+ "properly."
+}
+
+expect_signal_body()
+{
+ cat >expout_any <<EOF
+expected_signal: Expected signal
+failed: Test case was expected to receive a termination signal but it continued execution
+EOF
+ cat >expout_sigusr1 <<EOF
+expected_signal(30): Expected signal
+failed: Test case was expected to receive a termination signal but it continued execution
+EOF
+
+ h="$(atf_get_srcdir)/misc_helpers -s $(atf_get_srcdir)"
+ atf_check -s eq:1 -o file:expout_any -e ignore -x ${h} \
+ tc_expect_signal_any
+ atf_check -s eq:1 -o file:expout_sigusr1 -e ignore -x ${h} \
+ tc_expect_signal_sigusr1
+}
+
+atf_test_case check_srcdir
+check_srcdir_head()
+{
+ atf_set "descr" "Verifies the ability to grab srcdir from a test case."
+}
+
+check_srcdir_body()
+{
+
+ h="$(atf_get_srcdir)/misc_helpers -s $(atf_get_srcdir)"
+ atf_check -s eq:0 -o ignore -e ignore -x \
+ "ATF_SRCDIR=$(atf_get_srcdir) ${h} tc_check_srcdir"
+}
+
+atf_init_test_cases()
+{
+
+ atf_add_test_case default_status
+ atf_add_test_case missing_body
+ atf_add_test_case expect_exit_fail
+ atf_add_test_case expect_signal
+ atf_add_test_case check_srcdir
+}
Index: contrib/atf/doc/atf.7.in
===================================================================
--- contrib/atf/doc/atf.7.in
+++ contrib/atf/doc/atf.7.in
@@ -22,7 +22,7 @@
.\" 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.
-.Dd September 14, 2014
+.Dd October 22, 2020
.Dt ATF 7
.Os
.Sh NAME
@@ -64,6 +64,8 @@
C programming interface.
.It Xr atf-c++ 3
C++ programming interface.
+.It Xr atf-lua 3
+Lua programming interface.
.It Xr atf-sh 3
.Xr sh 1
programming interface.

File Metadata

Mime Type
text/plain
Expires
Fri, May 2, 8:23 PM (13 h, 36 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
17911822
Default Alt Text
D26928.diff (74 KB)

Event Timeline