Page Menu
Home
FreeBSD
Search
Configure Global Search
Log In
Files
F116030878
D26928.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Flag For Later
Award Token
Size
74 KB
Referenced Files
None
Subscribers
None
D26928.diff
View Options
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
Details
Attached
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)
Attached To
Mode
D26928: Add atf-lua, a lua analog to atf-sh(1) and atf-sh(3)
Attached
Detach File
Event Timeline
Log In to Comment