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 +.\" 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 +.\" 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 + * + * 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 +__FBSDID("$FreeBSD$"); + +#include + +#include +#include +#include +#include +#include +#include + +#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 <expout <experr <experr <>>" .. arg[0] .. "<<<") +for i in ipairs(arg) do + print(">>>" .. arg[i] .. "<<<") +end + +os.exit(0) +EOF + + cat >expout <>>./tp<<< +>>> a b <<< +>>>foo<<< +EOF + atf_check -s eq:0 -o file:expout -e empty ./tp ' a b ' foo + + cat >expout <>>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 + * + * 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 + * + * 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 +__FBSDID("$FreeBSD$"); + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#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 + * + * 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 +__FBSDID("$FreeBSD$"); + +#include + +#include +#include +#include +#include +#include + +#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 +-- +-- 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 +-- +-- 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 +# +# 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 <expout <expout_any <expout_sigusr1 <