chiark / gitweb /
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 -------------------------------------------------*/