+/* -*-c-*-
+ *
+ * Timeout extension for test vector framework
+ *
+ * (c) 2024 Straylight/Edgeware
+ */
+
+/*----- Licensing notice --------------------------------------------------*
+ *
+ * This file is part of the mLib utilities library.
+ *
+ * mLib is free software: you can redistribute it and/or modify it under
+ * the terms of the GNU Library General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or (at
+ * your option) any later version.
+ *
+ * mLib is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with mLib. If not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
+ * USA.
+ */
+
+/*----- Header files ------------------------------------------------------*/
+
+#include <ctype.h>
+#include <errno.h>
+#include <string.h>
+
+#include <sys/time.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "tvec.h"
+
+/*----- Main code ---------------------------------------------------------*/
+
+static void reset(struct tvec_timeoutctx *tc)
+{
+ const struct tvec_timeoutenv *te = tc->te;
+
+ tc->timer = te->timer; tc->t = te->t; tc->f &= ~TVTF_SETMASK;
+}
+
+/* --- @tvec_timeoutsetup@ --- *
+ *
+ * Arguments: @struct tvec_state *tv@ = test vector state
+ * @const struct tvec_env *env@ = environment description
+ * @void *pctx@ = parent context (ignored)
+ * @void *ctx@ = context pointer to initialize
+ *
+ * Returns: ---
+ *
+ * Use: Initialize a timeout environment context.
+ *
+ * The environment description should be a @struct
+ * tvec_timeoutenv@.
+ */
+
+void tvec_timeoutsetup(struct tvec_state *tv, const struct tvec_env *env,
+ void *pctx, void *ctx)
+{
+ struct tvec_timeoutctx *tc = ctx;
+ const struct tvec_timeoutenv *te = (struct tvec_timeoutenv *)env;
+ const struct tvec_env *subenv = te->env;
+
+ tc->te = te;
+
+ reset(tc);
+
+ tc->subctx = 0;
+ if (subenv && subenv->ctxsz) tc->subctx = xmalloc(subenv->ctxsz);
+ if (subenv && subenv->setup) subenv->setup(tv, subenv, tc, tc->subctx);
+}
+
+/* --- @tvec_timeoutset@ --- *
+ *
+ * Arguments: @struct tvec_state *tv@ = test vector state
+ * @const char *var@ = variable name to set
+ * @void *ctx@ = context pointer
+ *
+ * Returns: Zero on success, @-1@ on failure.
+ *
+ * Use: Set a special variable. The following special variables are
+ * supported.
+ *
+ * * %|@timeout|% is the number of seconds (or other unit) to
+ * wait before giving up and killing the test process. The
+ * string may also include a keyword %|REAL|%, %|VIRTUAL|%,
+ * or %|PROF|% to select the timer.
+ *
+ * Unrecognized variables are passed to the subordinate
+ * environment, if there is one.
+ */
+
+int tvec_timeoutset(struct tvec_state *tv, const char *var, void *ctx)
+{
+ struct tvec_timeoutctx *tc = ctx;
+ const struct tvec_timeoutenv *te = tc->te;
+ const struct tvec_env *subenv = te->env;
+ dstr d = DSTR_INIT;
+ double t = 0.0; unsigned tmr = 0;
+ const char *p; char *q; size_t pos;
+ int rc;
+ unsigned f = 0;
+#define f_time 1u
+#define f_timer 2u
+#define f_all (f_time | f_timer)
+
+ if (STRCMP(var, ==, "@timeout")) {
+ if (tc->f&TVTF_SETTMO) { rc = tvec_dupreg(tv, var); goto end; }
+
+ for (;;) {
+ DRESET(&d); if (tvec_readword(tv, &d, ";", 0)) break;
+ timeout_primed:
+ p = d.buf;
+ if (!(f&f_timer) && STRCMP(p, ==, "REAL"))
+ { tmr = ITIMER_REAL; f |= f_timer; }
+ else if (!(f&f_timer) && STRCMP(p, ==, "VIRTUAL"))
+ { tmr = ITIMER_VIRTUAL; f |= f_timer; }
+ else if (!(f&f_timer) && STRCMP(p, ==, "PROF"))
+ { tmr = ITIMER_PROF; f |= f_timer; }
+
+ else if (!(f&f_time)) {
+ if (*p == '+' || *p == '-') p++;
+ if (*p == '.') p++;
+ if (!ISDIGIT(*p)) {
+ tvec_syntax(tv, *d.buf, "floating-point number");
+ rc = -1; goto end;
+ }
+ errno = 0; t = strtod(p, &q); f |= f_time;
+ if (errno) {
+ tvec_error(tv, "invalid floating-point number `%s': %s",
+ d.buf, strerror(errno));
+ rc = -1; goto end;
+ }
+
+ if (!*q) {
+ tvec_skipspc(tv); pos = d.len;
+ if (!tvec_readword(tv, &d, ";", 0)) pos++;
+ q = d.buf + pos;
+ }
+
+ if (!*q || STRCMP(q, ==, "s") || STRCMP(q, ==, "sec"))
+ /* nothing to do */;
+
+ else if (STRCMP(q, ==, "ds")) t *= 1e-1;
+ else if (STRCMP(q, ==, "cs")) t *= 1e-2;
+ else if (STRCMP(q, ==, "ms")) t *= 1e-3;
+ else if (STRCMP(q, ==, "us") || STRCMP(q, ==, "µs")) t *= 1e-6;
+ else if (STRCMP(q, ==, "ns")) t *= 1e-9;
+ else if (STRCMP(q, ==, "ps")) t *= 1e-12;
+ else if (STRCMP(q, ==, "fs")) t *= 1e-15;
+ else if (STRCMP(q, ==, "as")) t *= 1e-18;
+ else if (STRCMP(q, ==, "zs")) t *= 1e-21;
+ else if (STRCMP(q, ==, "ys")) t *= 1e-24;
+
+ else if (STRCMP(q, ==, "das")) t *= 1e+1;
+ else if (STRCMP(q, ==, "hs")) t *= 1e+2;
+ else if (STRCMP(q, ==, "ks")) t *= 1e+3;
+ else if (STRCMP(q, ==, "Ms")) t *= 1e+6;
+ else if (STRCMP(q, ==, "Gs")) t *= 1e+9;
+ else if (STRCMP(q, ==, "Ts")) t *= 1e+12;
+ else if (STRCMP(q, ==, "Ps")) t *= 1e+15;
+ else if (STRCMP(q, ==, "Es")) t *= 1e+18;
+ else if (STRCMP(q, ==, "Zs")) t *= 1e+21;
+ else if (STRCMP(q, ==, "Ys")) t *= 1e+24;
+
+ else if (STRCMP(q, ==, "m") || STRCMP(q, ==, "min")) t *= 60;
+ else if (STRCMP(q, ==, "h") || STRCMP(q, ==, "hr")) t *= 3600;
+ else if (STRCMP(q, ==, "d") || STRCMP(q, ==, "dy")) t *= 86400;
+ else if (STRCMP(q, ==, "y") || STRCMP(q, ==, "yr")) t *= 31557600;
+
+ else {
+ if (f&f_timer)
+ { rc = tvec_syntax(tv, *q, "end-of-line"); goto end; }
+ pos = q - d.buf; d.len -= pos;
+ memmove(d.buf, q, d.len);
+ goto timeout_primed;
+ }
+ }
+
+ if (!(~f&f_all)) break;
+ }
+
+ if (!f) {
+ rc = tvec_syntax(tv, fgetc(tv->fp), "timeout or timer keyword");
+ goto end;
+ }
+ rc = tvec_flushtoeol(tv, 0); if (rc) goto end;
+ if (f&f_time) tc->t = t;
+ if (f&f_timer) tc->timer = tmr;
+ tc->f |= TVTF_SETTMO;
+
+ } else if (subenv && subenv->set)
+ rc = subenv->set(tv, var, tc->subctx);
+ else
+ rc = 0;
+
+end:
+ dstr_destroy(&d);
+ return (rc);
+
+#undef f_time
+#undef f_timer
+#undef f_all
+}
+
+/* --- @tvec_timeoutbefore@ --- *
+ *
+ * Arguments: @struct tvec_state *tv@ = test vector state
+ * @void *ctx@ = context pointer
+ *
+ * Returns: ---
+ *
+ * Use: Invoke the subordinate environment's @before@ function to
+ * prepare for the test.
+ */
+
+void tvec_timeoutbefore(struct tvec_state *tv, void *ctx)
+{
+ struct tvec_timeoutctx *tc = ctx;
+ const struct tvec_timeoutenv *te = tc->te;
+ const struct tvec_env *subenv = te->env;
+
+ /* Just call the subsidiary environment. */
+ if (subenv && subenv->before) subenv->before(tv, tc->subctx);
+}
+
+/* --- @tvec_timeoutafter@ --- *
+ *
+ * Arguments: @struct tvec_state *tv@ = test vector state
+ * @void *ctx@ = context pointer
+ *
+ * Returns: ---
+ *
+ * Use: Invoke the subordinate environment's @after@ function to
+ * clean up after the test.
+ */
+
+void tvec_timeoutafter(struct tvec_state *tv, void *ctx)
+{
+ struct tvec_timeoutctx *tc = ctx;
+ const struct tvec_timeoutenv *te = tc->te;
+ const struct tvec_env *subenv = te->env;
+
+ /* Reset variables. */
+ reset(tc);
+
+ /* Pass the call on to the subsidiary environment. */
+ if (subenv && subenv->after) subenv->after(tv, tc->subctx);
+}
+
+/* --- @tvec_timeoutteardown@ --- *
+ *
+ * Arguments: @struct tvec_state *tv@ = test vector state
+ * @void *ctx@ = context pointer
+ *
+ * Returns: ---
+ *
+ * Use: Tear down the timeoutmark environment.
+ */
+
+void tvec_timeoutteardown(struct tvec_state *tv, void *ctx)
+{
+ struct tvec_timeoutctx *tc = ctx;
+ const struct tvec_timeoutenv *te = tc->te;
+ const struct tvec_env *subenv = te->env;
+
+ /* Just call the subsidiary environment. */
+ if (subenv && subenv->teardown) subenv->teardown(tv, tc->subctx);
+}
+
+/* --- @tvec_timeoutrun@ --- *
+ *
+ * Arguments: @struct tvec_state *tv@ = test vector state
+ * @tvec_testfn *fn@ = test function to run
+ * @void *ctx@ = context pointer for the test function
+ *
+ * Returns: ---
+ *
+ * Use: Runs a test with a timeout attached.
+ */
+
+void tvec_timeoutrun(struct tvec_state *tv, tvec_testfn *fn, void *ctx)
+{
+ struct tvec_timeoutctx *tc = ctx;
+ const struct tvec_timeoutenv *te = tc->te;
+ const struct tvec_env *subenv = te->env;
+ struct itimerval itv;
+
+ itv.it_interval.tv_sec = 0; itv.it_interval.tv_usec = 0;
+ itv.it_value.tv_sec = tc->t;
+ itv.it_value.tv_usec = (tc->t - itv.it_value.tv_sec)*1e6;
+
+ if (setitimer(tc->timer, &itv, 0))
+ tvec_skip(tv, "failed to set interval timer: %s", strerror(errno));
+ else {
+ if (subenv && subenv->run) subenv->run(tv, fn, tc->subctx);
+ else fn(tv->in, tv->out, tc->subctx);
+
+ itv.it_interval.tv_sec = 0; itv.it_interval.tv_usec = 0;
+ itv.it_value.tv_sec = 0; itv.it_value.tv_usec = 0;
+ if (setitimer(tc->timer, &itv, 0))
+ tvec_error(tv, "failed to disarm interval timer: %s", strerror(errno));
+ }
+}
+
+/*----- That's all, folks -------------------------------------------------*/