aboutsummaryrefslogtreecommitdiff
path: root/contrib/atf/atf-sh
diff options
context:
space:
mode:
Diffstat (limited to 'contrib/atf/atf-sh')
-rw-r--r--contrib/atf/atf-sh/.gitignore2
-rw-r--r--contrib/atf/atf-sh/atf-check.115
-rw-r--r--contrib/atf/atf-sh/atf-check.cpp128
-rw-r--r--contrib/atf/atf-sh/atf-sh.311
-rw-r--r--contrib/atf/atf-sh/atf_check_test.sh24
-rw-r--r--contrib/atf/atf-sh/libatf-sh.subr32
-rw-r--r--contrib/atf/atf-sh/misc_helpers.sh48
7 files changed, 246 insertions, 14 deletions
diff --git a/contrib/atf/atf-sh/.gitignore b/contrib/atf/atf-sh/.gitignore
new file mode 100644
index 000000000000..a29438f1a9b8
--- /dev/null
+++ b/contrib/atf/atf-sh/.gitignore
@@ -0,0 +1,2 @@
+atf-check
+atf-sh
diff --git a/contrib/atf/atf-sh/atf-check.1 b/contrib/atf/atf-sh/atf-check.1
index a423e3ac3b1c..b03058e8442c 100644
--- a/contrib/atf/atf-sh/atf-check.1
+++ b/contrib/atf/atf-sh/atf-check.1
@@ -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 March 6, 2017
+.Dd June 21, 2020
.Dt ATF-CHECK 1
.Os
.Sh NAME
@@ -120,6 +120,14 @@ as a shell command line, executing it with the system shell defined by
.Va ATF_SHELL .
You should avoid using this flag if at all possible to prevent shell quoting
issues.
+.It Fl r Ar timeout[:interval]
+Repeats failed checks until the
+.Ar timeout
+(in seconds) expires.
+If unspecified, the default
+.Ar interval
+(in milliseconds) is 50 ms.
+This can be used to wait for an expected update to the contents of a file.
.El
.Sh ENVIRONMENT
.Bl -tag -width ATFXSHELLXX -compact
@@ -157,6 +165,11 @@ atf_check -s signal:sigsegv my_program
# Combined checks
atf_check -o match:foo -o not-match:bar echo foo baz
+
+# Wait 5 seconds for a line to show up in a file
+( sleep 2 ; echo "testing 123" > $test_path ) &
+atf-check -o ignore -e ignore -s exit:0 -r 5 \e
+ grep "testing 123" $test_path
.Ed
.Sh SEE ALSO
.Xr atf-sh 1
diff --git a/contrib/atf/atf-sh/atf-check.cpp b/contrib/atf/atf-sh/atf-check.cpp
index 414b64ea91f0..38ab527aab54 100644
--- a/contrib/atf/atf-sh/atf-check.cpp
+++ b/contrib/atf/atf-sh/atf-check.cpp
@@ -29,6 +29,7 @@ extern "C" {
#include <limits.h>
#include <signal.h>
+#include <stdint.h>
#include <unistd.h>
}
@@ -53,6 +54,10 @@ extern "C" {
#include "atf-c++/detail/sanity.hpp"
#include "atf-c++/detail/text.hpp"
+static const useconds_t seconds_in_useconds = (1000 * 1000);
+static const useconds_t mseconds_in_useconds = 1000;
+static const useconds_t useconds_in_nseconds = 1000;
+
// ------------------------------------------------------------------------
// Auxiliary functions.
// ------------------------------------------------------------------------
@@ -162,6 +167,33 @@ public:
} // anonymous namespace
+static useconds_t
+get_monotonic_useconds(void)
+{
+ struct timespec ts;
+ useconds_t res;
+ int rc;
+
+ rc = clock_gettime(CLOCK_MONOTONIC, &ts);
+ if (rc != 0)
+ throw std::runtime_error("clock_gettime: " +
+ std::string(strerror(errno)));
+
+ res = ts.tv_sec * seconds_in_useconds;
+ res += ts.tv_nsec / useconds_in_nseconds;
+ return res;
+}
+
+static bool
+timo_expired(useconds_t timeout)
+{
+
+ if (get_monotonic_useconds() >= timeout)
+ return true;
+ return false;
+}
+
+
static int
parse_exit_code(const std::string& str)
{
@@ -216,7 +248,7 @@ parse_signal(const std::string& str)
if (signo == INT_MIN) {
try {
return atf::text::to_type< int >(str);
- } catch (std::runtime_error) {
+ } catch (const std::runtime_error&) {
throw atf::application::usage_error("Invalid signal name or number "
"in -s option");
}
@@ -306,6 +338,62 @@ parse_output_check_arg(const std::string& arg)
return output_check(type, negated, arg.substr(delimiter + 1));
}
+static void
+parse_repeat_check_arg(const std::string& arg, useconds_t *m_timo,
+ useconds_t *m_interval)
+{
+ const std::string::size_type delimiter = arg.find(':');
+ const bool has_interval = (delimiter != std::string::npos);
+ const std::string timo_str = arg.substr(0, delimiter);
+
+ long l;
+ char *end;
+
+ // There is no reason this couldn't be a non-integer number of seconds,
+ // this was just easy to do for now.
+ errno = 0;
+ l = strtol(timo_str.c_str(), &end, 10);
+ if (errno == ERANGE)
+ throw atf::application::usage_error("Bogus timeout in seconds");
+ else if (errno != 0)
+ throw atf::application::usage_error("Timeout must be a number");
+
+ if (*end != 0)
+ throw atf::application::usage_error("Timeout must be a number");
+
+ *m_timo = get_monotonic_useconds() + (l * seconds_in_useconds);
+ // 50 milliseconds is chosen arbitrarily. There is a tradeoff between
+ // longer and shorter poll times. A shorter poll time makes for faster
+ // tests. A longer poll time makes for lower CPU overhead for the polled
+ // operation. 50ms is chosen with these tradeoffs in mind: on
+ // microcontrollers, the hope is that we can still avoid meaningful CPU use
+ // with a small test every 50ms. And on typical fast x86 hardware, our
+ // tests can be much more precise with time wasted than they typically are
+ // without this feature.
+ *m_interval = 50 * mseconds_in_useconds;
+
+ if (!has_interval)
+ return;
+
+ const std::string intv_str = arg.substr(delimiter + 1, std::string::npos);
+
+ // Same -- this could be non-integer milliseconds.
+ errno = 0;
+ l = strtol(intv_str.c_str(), &end, 10);
+ if (errno == ERANGE)
+ throw atf::application::usage_error(
+ "Bogus repeat interval in milliseconds");
+ else if (errno != 0)
+ throw atf::application::usage_error(
+ "Repeat interval must be a number");
+
+ if (*end != 0)
+ throw atf::application::usage_error(
+ "Repeat interval must be a number");
+
+ *m_interval = l * mseconds_in_useconds;
+}
+
static
std::string
flatten_argv(char* const* argv)
@@ -694,8 +782,12 @@ run_output_checks(const std::vector< output_check >& checks,
namespace {
class atf_check : public atf::application::app {
+ bool m_rflag;
bool m_xflag;
+ useconds_t m_timo;
+ useconds_t m_interval;
+
std::vector< status_check > m_status_checks;
std::vector< output_check > m_stdout_checks;
std::vector< output_check > m_stderr_checks;
@@ -722,6 +814,7 @@ const char* atf_check::m_description =
atf_check::atf_check(void) :
app(m_description, "atf-check(1)"),
+ m_rflag(false),
m_xflag(false)
{
}
@@ -765,6 +858,8 @@ atf_check::specific_options(void)
opts.insert(option('e', "action:arg", "Handle stderr. Action must be "
"one of: empty ignore file:<path> inline:<val> match:regexp "
"save:<path>"));
+ opts.insert(option('r', "timeout[:interval]", "Repeat failed check until "
+ "the timeout expires."));
opts.insert(option('x', "", "Execute command as a shell command"));
return opts;
@@ -786,6 +881,11 @@ atf_check::process_option(int ch, const char* arg)
m_stderr_checks.push_back(parse_output_check_arg(arg));
break;
+ case 'r':
+ m_rflag = true;
+ parse_repeat_check_arg(arg, &m_timo, &m_interval);
+ break;
+
case 'x':
m_xflag = true;
break;
@@ -803,9 +903,6 @@ atf_check::main(void)
int status = EXIT_FAILURE;
- std::auto_ptr< atf::check::check_result > r =
- m_xflag ? execute_with_shell(m_argv) : execute(m_argv);
-
if (m_status_checks.empty())
m_status_checks.push_back(status_check(sc_exit, false, EXIT_SUCCESS));
else if (m_status_checks.size() > 1) {
@@ -818,12 +915,23 @@ atf_check::main(void)
if (m_stderr_checks.empty())
m_stderr_checks.push_back(output_check(oc_empty, false, ""));
- if ((run_status_checks(m_status_checks, *r) == false) ||
- (run_output_checks(*r, "stderr") == false) ||
- (run_output_checks(*r, "stdout") == false))
- status = EXIT_FAILURE;
- else
- status = EXIT_SUCCESS;
+ do {
+ std::auto_ptr< atf::check::check_result > r =
+ m_xflag ? execute_with_shell(m_argv) : execute(m_argv);
+
+ if ((run_status_checks(m_status_checks, *r) == false) ||
+ (run_output_checks(*r, "stderr") == false) ||
+ (run_output_checks(*r, "stdout") == false))
+ status = EXIT_FAILURE;
+ else
+ status = EXIT_SUCCESS;
+
+ if (m_rflag && status == EXIT_FAILURE) {
+ if (timo_expired(m_timo))
+ break;
+ usleep(m_interval);
+ }
+ } while (m_rflag && status == EXIT_FAILURE);
return status;
}
diff --git a/contrib/atf/atf-sh/atf-sh.3 b/contrib/atf/atf-sh/atf-sh.3
index 974c2aee1009..5d1119b2b5dc 100644
--- a/contrib/atf/atf-sh/atf-sh.3
+++ b/contrib/atf/atf-sh/atf-sh.3
@@ -22,13 +22,14 @@
.\" 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 March 6, 2017
+.Dd June 08, 2017
.Dt ATF-SH 3
.Os
.Sh NAME
.Nm atf_add_test_case ,
.Nm atf_check ,
.Nm atf_check_equal ,
+.Nm atf_check_not_equal ,
.Nm atf_config_get ,
.Nm atf_config_has ,
.Nm atf_expect_death ,
@@ -55,6 +56,9 @@
.Nm atf_check_equal
.Qq expected_expression
.Qq actual_expression
+.Nm atf_check_not_equal
+.Qq expected_expression
+.Qq actual_expression
.Nm atf_config_get
.Qq var_name
.Nm atf_config_has
@@ -310,6 +314,11 @@ 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.
+.It Nm atf_check_not_equal Qo expected_expression Qc Qo actual_expression Qc
+This function takes two expressions, evaluates them and, if their
+results are equal, 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
.Sh EXAMPLES
The following shows a complete test program with a single test case that
diff --git a/contrib/atf/atf-sh/atf_check_test.sh b/contrib/atf/atf-sh/atf_check_test.sh
index 9e3cfb955f68..6fe2bb775274 100644
--- a/contrib/atf/atf-sh/atf_check_test.sh
+++ b/contrib/atf/atf-sh/atf_check_test.sh
@@ -164,6 +164,30 @@ equal_body()
grep '^failed: \${x} != \${y} (a != b)$' resfile
}
+atf_test_case not_equal
+not_equal_head()
+{
+ atf_set "descr" "Verifies that atf_check_not_equal works"
+}
+not_equal_body()
+{
+ h="$(atf_get_srcdir)/misc_helpers -s $(atf_get_srcdir)"
+
+ atf_check -s eq:0 -o ignore -e ignore -x "${h} atf_check_not_equal_ok"
+
+ atf_check -s eq:1 -o ignore -e ignore -x \
+ "${h} -r resfile atf_check_not_equal_fail"
+ atf_check -s eq:0 -o ignore -e empty grep '^failed: a == b (a == b)$' \
+ resfile
+
+ atf_check -s eq:0 -o ignore -e ignore -x "${h} atf_check_not_equal_eval_ok"
+
+ atf_check -s eq:1 -o ignore -e ignore -x \
+ "${h} -r resfile atf_check_not_equal_eval_fail"
+ atf_check -s eq:0 -o ignore -e empty \
+ grep '^failed: \${x} == \${y} (a == b)$' resfile
+}
+
atf_test_case flush_stdout_on_death
flush_stdout_on_death_body()
{
diff --git a/contrib/atf/atf-sh/libatf-sh.subr b/contrib/atf/atf-sh/libatf-sh.subr
index a078975400af..a2478b6a9be1 100644
--- a/contrib/atf/atf-sh/libatf-sh.subr
+++ b/contrib/atf/atf-sh/libatf-sh.subr
@@ -101,6 +101,23 @@ atf_check_equal()
}
#
+# atf_check_not_equal expected_expression actual_expression
+#
+# Checks that expected_expression's value does not match actual_expression's
+# and, if it does, raises an error. Ideally expected_expression and
+# actual_expression should be provided quoted (not expanded) so that
+# the error message is helpful; otherwise it will only show the values,
+# not the expressions themselves.
+#
+atf_check_not_equal()
+{
+ eval _val1=\"${1}\"
+ eval _val2=\"${2}\"
+ test "${_val1}" != "${_val2}" || \
+ atf_fail "${1} == ${2} (${_val1} == ${_val2})"
+}
+
+#
# atf_config_get varname [defvalue]
#
# Prints the value of a configuration variable. If it is not
@@ -536,7 +553,18 @@ _atf_list_tcs()
#
_atf_normalize()
{
- echo ${1} | tr .- __
+ # Check if the string contains any of the forbidden characters using
+ # POSIX parameter expansion (the ${var//} string substitution is
+ # unfortunately not supported in POSIX sh) and only use tr(1) then.
+ # tr(1) is generally not a builtin, so doing the substring check first
+ # avoids unnecessary fork()+execve() calls. As this function is called
+ # many times in each test script startup, those overheads add up
+ # (especially when running on emulated platforms such as QEMU).
+ if [ "${1#*[.-]}" != "$1" ]; then
+ echo "$1" | tr .- __
+ else
+ echo "$1"
+ fi
}
#
@@ -734,7 +762,7 @@ main()
;;
esac
done
- shift `expr ${OPTIND} - 1`
+ shift $((OPTIND - 1))
case ${Source_Dir} in
/*)
diff --git a/contrib/atf/atf-sh/misc_helpers.sh b/contrib/atf/atf-sh/misc_helpers.sh
index ca0f4650d99b..a2b2c0b53d73 100644
--- a/contrib/atf/atf-sh/misc_helpers.sh
+++ b/contrib/atf/atf-sh/misc_helpers.sh
@@ -139,6 +139,50 @@ atf_check_equal_eval_fail_body()
atf_check_equal '${x}' '${y}'
}
+atf_test_case atf_check_not_equal_ok
+atf_check_not_equal_ok_head()
+{
+ atf_set "descr" "Helper test case for the t_atf_check test program"
+}
+atf_check_not_equal_ok_body()
+{
+ atf_check_not_equal a b
+}
+
+atf_test_case atf_check_not_equal_fail
+atf_check_not_equal_fail_head()
+{
+ atf_set "descr" "Helper test case for the t_atf_check test program"
+}
+atf_check_not_equal_fail_body()
+{
+ atf_check_not_equal a a
+}
+
+atf_test_case atf_check_not_equal_eval_ok
+atf_check_not_equal_eval_ok_head()
+{
+ atf_set "descr" "Helper test case for the t_atf_check test program"
+}
+atf_check_not_equal_eval_ok_body()
+{
+ x=a
+ y=b
+ atf_check_not_equal '${x}' '${y}'
+}
+
+atf_test_case atf_check_not_equal_eval_fail
+atf_check_not_equal_eval_fail_head()
+{
+ atf_set "descr" "Helper test case for the t_atf_check test program"
+}
+atf_check_not_equal_eval_fail_body()
+{
+ x=a
+ y=a
+ atf_check_not_equal '${x}' '${y}'
+}
+
atf_test_case atf_check_flush_stdout
atf_check_flush_stdout_head()
{
@@ -285,6 +329,10 @@ atf_init_test_cases()
atf_add_test_case atf_check_equal_fail
atf_add_test_case atf_check_equal_eval_ok
atf_add_test_case atf_check_equal_eval_fail
+ atf_add_test_case atf_check_not_equal_ok
+ atf_add_test_case atf_check_not_equal_fail
+ atf_add_test_case atf_check_not_equal_eval_ok
+ atf_add_test_case atf_check_not_equal_eval_fail
atf_add_test_case atf_check_flush_stdout
# Add helper tests for t_config.