@@@ timeout wip
[mLib] / test / tvec-timeout.c
diff --git a/test/tvec-timeout.c b/test/tvec-timeout.c
new file mode 100644 (file)
index 0000000..ca86b5c
--- /dev/null
@@ -0,0 +1,313 @@
+/* -*-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 -------------------------------------------------*/