3 * Timeout extension for test vector framework
5 * (c) 2024 Straylight/Edgeware
8 /*----- Licensing notice --------------------------------------------------*
10 * This file is part of the mLib utilities library.
12 * mLib is free software: you can redistribute it and/or modify it under
13 * the terms of the GNU Library General Public License as published by
14 * the Free Software Foundation; either version 2 of the License, or (at
15 * your option) any later version.
17 * mLib is distributed in the hope that it will be useful, but WITHOUT
18 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
19 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
20 * License for more details.
22 * You should have received a copy of the GNU Library General Public
23 * License along with mLib. If not, write to the Free Software
24 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
28 /*----- Header files ------------------------------------------------------*/
35 #include <sys/types.h>
40 /*----- Main code ---------------------------------------------------------*/
42 static void reset(struct tvec_timeoutctx *tc)
44 const struct tvec_timeoutenv *te = tc->te;
46 tc->timer = te->timer; tc->t = te->t; tc->f &= ~TVTF_SETMASK;
49 /* --- @tvec_timeoutsetup@ --- *
51 * Arguments: @struct tvec_state *tv@ = test vector state
52 * @const struct tvec_env *env@ = environment description
53 * @void *pctx@ = parent context (ignored)
54 * @void *ctx@ = context pointer to initialize
58 * Use: Initialize a timeout environment context.
60 * The environment description should be a @struct
64 void tvec_timeoutsetup(struct tvec_state *tv, const struct tvec_env *env,
65 void *pctx, void *ctx)
67 struct tvec_timeoutctx *tc = ctx;
68 const struct tvec_timeoutenv *te = (struct tvec_timeoutenv *)env;
69 const struct tvec_env *subenv = te->env;
76 if (subenv && subenv->ctxsz) tc->subctx = xmalloc(subenv->ctxsz);
77 if (subenv && subenv->setup) subenv->setup(tv, subenv, tc, tc->subctx);
80 /* --- @tvec_timeoutset@ --- *
82 * Arguments: @struct tvec_state *tv@ = test vector state
83 * @const char *var@ = variable name to set
84 * @void *ctx@ = context pointer
86 * Returns: %$+1$% on success, %$0$% if the variable name was not
87 * recognized, or %$-1$% on any other error.
89 * Use: Set a special variable. The following special variables are
92 * * %|@timeout|% is the number of seconds (or other unit) to
93 * wait before giving up and killing the test process. The
94 * string may also include a keyword %|REAL|%, %|VIRTUAL|%,
95 * or %|PROF|% to select the timer.
97 * Unrecognized variables are passed to the subordinate
98 * environment, if there is one.
101 int tvec_timeoutset(struct tvec_state *tv, const char *var, void *ctx)
103 struct tvec_timeoutctx *tc = ctx;
104 const struct tvec_timeoutenv *te = tc->te;
105 const struct tvec_env *subenv = te->env;
107 double t = 0.0; unsigned tmr = 0;
108 const char *p; char *q; size_t pos;
113 #define f_all (f_time | f_timer)
115 if (STRCMP(var, ==, "@timeout")) {
116 /* Parse a timeout specification. */
118 /* If we've already set one then report an error. */
119 if (tc->f&TVTF_SETTMO) { rc = tvec_dupreg(tv, var); goto end; }
122 /* Continue until we run out of things. */
124 /* Read the next word. */
125 DRESET(&d); if (tvec_readword(tv, &d, ";", 0)) break;
128 /* Start parsing the word. */
131 /* Check for timer tokens. */
132 if (!(f&f_timer) && STRCMP(p, ==, "REAL"))
133 { tmr = ITIMER_REAL; f |= f_timer; }
134 else if (!(f&f_timer) && STRCMP(p, ==, "VIRTUAL"))
135 { tmr = ITIMER_VIRTUAL; f |= f_timer; }
136 else if (!(f&f_timer) && STRCMP(p, ==, "PROF"))
137 { tmr = ITIMER_PROF; f |= f_timer; }
139 /* Otherwise, check for durations. */
140 else if (!(f&f_time)) {
142 /* Skip leading stuff that isn't digits. This is a hedge against
143 * @strtod@ interpreting something unhelpful like a NaN.
145 if (*p == '+' || *p == '-') p++;
148 tvec_syntax(tv, *d.buf, "floating-point number");
152 /* Parse the number and check that it's reasonable. */
153 errno = 0; t = strtod(p, &q); f |= f_time;
155 tvec_error(tv, "invalid floating-point number `%s': %s",
156 d.buf, strerror(errno));
160 tvec_error(tv, "invalid duration `%s': %s",
161 d.buf, strerror(errno));
165 /* We're now on the lookout for units. If there's nothing here then
166 * fetch the next word.
169 tvec_skipspc(tv); pos = d.len;
170 if (!tvec_readword(tv, &d, ";", 0)) pos++;
174 /* Match various units. */
175 if (!*q || STRCMP(q, ==, "s") || STRCMP(q, ==, "sec"))
178 else if (STRCMP(q, ==, "ds")) t *= 1e-1;
179 else if (STRCMP(q, ==, "cs")) t *= 1e-2;
180 else if (STRCMP(q, ==, "ms")) t *= 1e-3;
181 else if (STRCMP(q, ==, "us") || STRCMP(q, ==, "µs")) t *= 1e-6;
182 else if (STRCMP(q, ==, "ns")) t *= 1e-9;
183 else if (STRCMP(q, ==, "ps")) t *= 1e-12;
184 else if (STRCMP(q, ==, "fs")) t *= 1e-15;
185 else if (STRCMP(q, ==, "as")) t *= 1e-18;
186 else if (STRCMP(q, ==, "zs")) t *= 1e-21;
187 else if (STRCMP(q, ==, "ys")) t *= 1e-24;
189 else if (STRCMP(q, ==, "das")) t *= 1e+1;
190 else if (STRCMP(q, ==, "hs")) t *= 1e+2;
191 else if (STRCMP(q, ==, "ks")) t *= 1e+3;
192 else if (STRCMP(q, ==, "Ms")) t *= 1e+6;
193 else if (STRCMP(q, ==, "Gs")) t *= 1e+9;
194 else if (STRCMP(q, ==, "Ts")) t *= 1e+12;
195 else if (STRCMP(q, ==, "Ps")) t *= 1e+15;
196 else if (STRCMP(q, ==, "Es")) t *= 1e+18;
197 else if (STRCMP(q, ==, "Zs")) t *= 1e+21;
198 else if (STRCMP(q, ==, "Ys")) t *= 1e+24;
200 else if (STRCMP(q, ==, "m") || STRCMP(q, ==, "min")) t *= 60;
201 else if (STRCMP(q, ==, "h") || STRCMP(q, ==, "hr")) t *= 3600;
202 else if (STRCMP(q, ==, "d") || STRCMP(q, ==, "dy")) t *= 86400;
203 else if (STRCMP(q, ==, "y") || STRCMP(q, ==, "yr")) t *= 31557600;
205 /* Not a unit specification after all. If we've already selected a
206 * timer, then this is just junk so report the error. Otherwise, we
207 * snarfed the next token too early, so move it to the start of the
208 * buffer and go round again.
212 { rc = tvec_syntax(tv, *q, "end-of-line"); goto end; }
213 pos = q - d.buf; d.len -= pos;
214 memmove(d.buf, q, d.len + 1);
219 /* If we've read all that we need to, then stop. */
220 if (!(~f&f_all)) break;
223 /* If we didn't get anything, that's a problem. */
225 rc = tvec_syntax(tv, fgetc(tv->fp), "timeout or timer keyword");
229 /* Make sure there's nothing else on the line. */
230 rc = tvec_flushtoeol(tv, 0); if (rc) goto end;
231 if (f&f_time) tc->t = t;
232 if (f&f_timer) tc->timer = tmr;
233 tc->f |= TVTF_SETTMO;
236 } else if (subenv && subenv->set)
237 /* Not one of ours: pass it on to the sub-environment. */
238 rc = subenv->set(tv, var, tc->subctx);
240 /* No subenvironment. Report the error. */
253 /* --- @tvec_timeoutbefore@ --- *
255 * Arguments: @struct tvec_state *tv@ = test vector state
256 * @void *ctx@ = context pointer
260 * Use: Invoke the subordinate environment's @before@ function to
261 * prepare for the test.
264 void tvec_timeoutbefore(struct tvec_state *tv, void *ctx)
266 struct tvec_timeoutctx *tc = ctx;
267 const struct tvec_timeoutenv *te = tc->te;
268 const struct tvec_env *subenv = te->env;
270 /* Just call the subsidiary environment. */
271 if (subenv && subenv->before) subenv->before(tv, tc->subctx);
274 /* --- @tvec_timeoutrun@ --- *
276 * Arguments: @struct tvec_state *tv@ = test vector state
277 * @tvec_testfn *fn@ = test function to run
278 * @void *ctx@ = context pointer for the test function
282 * Use: Runs a test with a timeout attached.
285 void tvec_timeoutrun(struct tvec_state *tv, tvec_testfn *fn, void *ctx)
287 struct tvec_timeoutctx *tc = ctx;
288 const struct tvec_timeoutenv *te = tc->te;
289 const struct tvec_env *subenv = te->env;
290 struct itimerval itv;
292 itv.it_interval.tv_sec = 0; itv.it_interval.tv_usec = 0;
293 itv.it_value.tv_sec = tc->t;
294 itv.it_value.tv_usec = (tc->t - itv.it_value.tv_sec)*1e6;
296 if (setitimer(tc->timer, &itv, 0))
297 tvec_skip(tv, "failed to set interval timer: %s", strerror(errno));
299 if (subenv && subenv->run) subenv->run(tv, fn, tc->subctx);
300 else fn(tv->in, tv->out, tc->subctx);
302 itv.it_interval.tv_sec = 0; itv.it_interval.tv_usec = 0;
303 itv.it_value.tv_sec = 0; itv.it_value.tv_usec = 0;
304 if (setitimer(tc->timer, &itv, 0))
305 tvec_error(tv, "failed to disarm interval timer: %s", strerror(errno));
309 /* --- @tvec_timeoutafter@ --- *
311 * Arguments: @struct tvec_state *tv@ = test vector state
312 * @void *ctx@ = context pointer
316 * Use: Invoke the subordinate environment's @after@ function to
317 * clean up after the test.
320 void tvec_timeoutafter(struct tvec_state *tv, void *ctx)
322 struct tvec_timeoutctx *tc = ctx;
323 const struct tvec_timeoutenv *te = tc->te;
324 const struct tvec_env *subenv = te->env;
326 /* Reset variables. */
329 /* Pass the call on to the subsidiary environment. */
330 if (subenv && subenv->after) subenv->after(tv, tc->subctx);
333 /* --- @tvec_timeoutteardown@ --- *
335 * Arguments: @struct tvec_state *tv@ = test vector state
336 * @void *ctx@ = context pointer
340 * Use: Tear down the timeoutmark environment.
343 void tvec_timeoutteardown(struct tvec_state *tv, void *ctx)
345 struct tvec_timeoutctx *tc = ctx;
346 const struct tvec_timeoutenv *te = tc->te;
347 const struct tvec_env *subenv = te->env;
349 /* Just call the subsidiary environment. */
350 if (subenv && subenv->teardown) subenv->teardown(tv, tc->subctx);
353 /*----- That's all, folks -------------------------------------------------*/