d9a614b3f6b065516e86a5f3084f91bb420fd76b
[mLib] / test / tvec-timeout.c
1 /* -*-c-*-
2 *
3 * Timeout extension for test vector framework
4 *
5 * (c) 2024 Straylight/Edgeware
6 */
7
8 /*----- Licensing notice --------------------------------------------------*
9 *
10 * This file is part of the mLib utilities library.
11 *
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.
16 *
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.
21 *
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,
25 * USA.
26 */
27
28 /*----- Header files ------------------------------------------------------*/
29
30 #include <ctype.h>
31 #include <errno.h>
32 #include <string.h>
33
34 #include <sys/time.h>
35 #include <sys/types.h>
36 #include <unistd.h>
37
38 #include "tvec.h"
39
40 /*----- Main code ---------------------------------------------------------*/
41
42 static void reset(struct tvec_timeoutctx *tc)
43 {
44 const struct tvec_timeoutenv *te = tc->te;
45
46 tc->timer = te->timer; tc->t = te->t; tc->f &= ~TVTF_SETMASK;
47 }
48
49 /* --- @tvec_timeoutsetup@ --- *
50 *
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
55 *
56 * Returns: ---
57 *
58 * Use: Initialize a timeout environment context.
59 *
60 * The environment description should be a @struct
61 * tvec_timeoutenv@.
62 */
63
64 void tvec_timeoutsetup(struct tvec_state *tv, const struct tvec_env *env,
65 void *pctx, void *ctx)
66 {
67 struct tvec_timeoutctx *tc = ctx;
68 const struct tvec_timeoutenv *te = (struct tvec_timeoutenv *)env;
69 const struct tvec_env *subenv = te->env;
70
71 tc->te = te;
72
73 reset(tc);
74
75 tc->subctx = 0;
76 if (subenv && subenv->ctxsz) tc->subctx = xmalloc(subenv->ctxsz);
77 if (subenv && subenv->setup) subenv->setup(tv, subenv, tc, tc->subctx);
78 }
79
80 /* --- @tvec_timeoutset@ --- *
81 *
82 * Arguments: @struct tvec_state *tv@ = test vector state
83 * @const char *var@ = variable name to set
84 * @void *ctx@ = context pointer
85 *
86 * Returns: %$+1$% on success, %$0$% if the variable name was not
87 * recognized, or %$-1$% on any other error.
88 *
89 * Use: Set a special variable. The following special variables are
90 * supported.
91 *
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.
96 *
97 * Unrecognized variables are passed to the subordinate
98 * environment, if there is one.
99 */
100
101 int tvec_timeoutset(struct tvec_state *tv, const char *var, void *ctx)
102 {
103 struct tvec_timeoutctx *tc = ctx;
104 const struct tvec_timeoutenv *te = tc->te;
105 const struct tvec_env *subenv = te->env;
106 dstr d = DSTR_INIT;
107 double t = 0.0; unsigned tmr = 0;
108 const char *p; char *q; size_t pos;
109 int rc;
110 unsigned f = 0;
111 #define f_time 1u
112 #define f_timer 2u
113 #define f_all (f_time | f_timer)
114
115 if (STRCMP(var, ==, "@timeout")) {
116 /* Parse a timeout specification. */
117
118 /* If we've already set one then report an error. */
119 if (tc->f&TVTF_SETTMO) { rc = tvec_dupreg(tv, var); goto end; }
120
121 for (;;) {
122 /* Continue until we run out of things. */
123
124 /* Read the next word. */
125 DRESET(&d); if (tvec_readword(tv, &d, ";", 0)) break;
126 timeout_primed:
127
128 /* Start parsing the word. */
129 p = d.buf;
130
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; }
138
139 /* Otherwise, check for durations. */
140 else if (!(f&f_time)) {
141
142 /* Skip leading stuff that isn't digits. This is a hedge against
143 * @strtod@ interpreting something unhelpful like a NaN.
144 */
145 if (*p == '+' || *p == '-') p++;
146 if (*p == '.') p++;
147 if (!ISDIGIT(*p)) {
148 tvec_syntax(tv, *d.buf, "floating-point number");
149 rc = -1; goto end;
150 }
151
152 /* Parse the number and check that it's reasonable. */
153 errno = 0; t = strtod(p, &q); f |= f_time;
154 if (errno) {
155 tvec_error(tv, "invalid floating-point number `%s': %s",
156 d.buf, strerror(errno));
157 rc = -1; goto end;
158 }
159 if (t < 0) {
160 tvec_error(tv, "invalid duration `%s': %s",
161 d.buf, strerror(errno));
162 rc = -1; goto end;
163 }
164
165 /* We're now on the lookout for units. If there's nothing here then
166 * fetch the next word.
167 */
168 if (!*q) {
169 tvec_skipspc(tv); pos = d.len;
170 if (!tvec_readword(tv, &d, ";", 0)) pos++;
171 q = d.buf + pos;
172 }
173
174 /* Match various units. */
175 if (!*q || STRCMP(q, ==, "s") || STRCMP(q, ==, "sec"))
176 /* nothing to do */;
177
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;
188
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;
199
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;
204
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.
209 */
210 else {
211 if (f&f_timer)
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);
215 goto timeout_primed;
216 }
217 }
218
219 /* If we've read all that we need to, then stop. */
220 if (!(~f&f_all)) break;
221 }
222
223 /* If we didn't get anything, that's a problem. */
224 if (!f) {
225 rc = tvec_syntax(tv, fgetc(tv->fp), "timeout or timer keyword");
226 goto end;
227 }
228
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;
234 rc = 1;
235
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);
239 else
240 /* No subenvironment. Report the error. */
241 rc = 0;
242
243 /* Done. */
244 end:
245 dstr_destroy(&d);
246 return (rc);
247
248 #undef f_time
249 #undef f_timer
250 #undef f_all
251 }
252
253 /* --- @tvec_timeoutbefore@ --- *
254 *
255 * Arguments: @struct tvec_state *tv@ = test vector state
256 * @void *ctx@ = context pointer
257 *
258 * Returns: ---
259 *
260 * Use: Invoke the subordinate environment's @before@ function to
261 * prepare for the test.
262 */
263
264 void tvec_timeoutbefore(struct tvec_state *tv, void *ctx)
265 {
266 struct tvec_timeoutctx *tc = ctx;
267 const struct tvec_timeoutenv *te = tc->te;
268 const struct tvec_env *subenv = te->env;
269
270 /* Just call the subsidiary environment. */
271 if (subenv && subenv->before) subenv->before(tv, tc->subctx);
272 }
273
274 /* --- @tvec_timeoutrun@ --- *
275 *
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
279 *
280 * Returns: ---
281 *
282 * Use: Runs a test with a timeout attached.
283 */
284
285 void tvec_timeoutrun(struct tvec_state *tv, tvec_testfn *fn, void *ctx)
286 {
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;
291
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;
295
296 if (setitimer(tc->timer, &itv, 0))
297 tvec_skip(tv, "failed to set interval timer: %s", strerror(errno));
298 else {
299 if (subenv && subenv->run) subenv->run(tv, fn, tc->subctx);
300 else fn(tv->in, tv->out, tc->subctx);
301
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));
306 }
307 }
308
309 /* --- @tvec_timeoutafter@ --- *
310 *
311 * Arguments: @struct tvec_state *tv@ = test vector state
312 * @void *ctx@ = context pointer
313 *
314 * Returns: ---
315 *
316 * Use: Invoke the subordinate environment's @after@ function to
317 * clean up after the test.
318 */
319
320 void tvec_timeoutafter(struct tvec_state *tv, void *ctx)
321 {
322 struct tvec_timeoutctx *tc = ctx;
323 const struct tvec_timeoutenv *te = tc->te;
324 const struct tvec_env *subenv = te->env;
325
326 /* Reset variables. */
327 reset(tc);
328
329 /* Pass the call on to the subsidiary environment. */
330 if (subenv && subenv->after) subenv->after(tv, tc->subctx);
331 }
332
333 /* --- @tvec_timeoutteardown@ --- *
334 *
335 * Arguments: @struct tvec_state *tv@ = test vector state
336 * @void *ctx@ = context pointer
337 *
338 * Returns: ---
339 *
340 * Use: Tear down the timeoutmark environment.
341 */
342
343 void tvec_timeoutteardown(struct tvec_state *tv, void *ctx)
344 {
345 struct tvec_timeoutctx *tc = ctx;
346 const struct tvec_timeoutenv *te = tc->te;
347 const struct tvec_env *subenv = te->env;
348
349 /* Just call the subsidiary environment. */
350 if (subenv && subenv->teardown) subenv->teardown(tv, tc->subctx);
351 }
352
353 /*----- That's all, folks -------------------------------------------------*/