d9a614b3f6b065516e86a5f3084f91bb420fd76b
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 -------------------------------------------------*/