chiark / gitweb /
@@@ timeout wip
[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:     Zero on success, @-1@ on failure.
87  *
88  * Use:         Set a special variable.  The following special variables are
89  *              supported.
90  *
91  *                * %|@timeout|% is the number of seconds (or other unit) to
92  *                  wait before giving up and killing the test process.  The
93  *                  string may also include a keyword %|REAL|%, %|VIRTUAL|%,
94  *                  or %|PROF|% to select the timer.
95  *
96  *              Unrecognized variables are passed to the subordinate
97  *              environment, if there is one.
98  */
99
100 int tvec_timeoutset(struct tvec_state *tv, const char *var, void *ctx)
101 {
102   struct tvec_timeoutctx *tc = ctx;
103   const struct tvec_timeoutenv *te = tc->te;
104   const struct tvec_env *subenv = te->env;
105   dstr d = DSTR_INIT;
106   double t = 0.0; unsigned tmr = 0;
107   const char *p; char *q; size_t pos;
108   int rc;
109   unsigned f = 0;
110 #define f_time 1u
111 #define f_timer 2u
112 #define f_all (f_time | f_timer)
113
114   if (STRCMP(var, ==, "@timeout")) {
115     if (tc->f&TVTF_SETTMO) { rc = tvec_dupreg(tv, var); goto end; }
116
117     for (;;) {
118       DRESET(&d); if (tvec_readword(tv, &d, ";", 0)) break;
119     timeout_primed:
120       p = d.buf;
121       if (!(f&f_timer) && STRCMP(p, ==, "REAL"))
122         { tmr = ITIMER_REAL; f |= f_timer;  }
123       else if (!(f&f_timer) && STRCMP(p, ==, "VIRTUAL"))
124         { tmr = ITIMER_VIRTUAL; f |= f_timer; }
125       else if (!(f&f_timer) && STRCMP(p, ==, "PROF"))
126         { tmr = ITIMER_PROF; f |= f_timer; }
127
128       else if (!(f&f_time)) {
129         if (*p == '+' || *p == '-') p++;
130         if (*p == '.') p++;
131         if (!ISDIGIT(*p)) {
132           tvec_syntax(tv, *d.buf, "floating-point number");
133           rc = -1; goto end;
134         }
135         errno = 0; t = strtod(p, &q); f |= f_time;
136         if (errno) {
137           tvec_error(tv, "invalid floating-point number `%s': %s",
138                      d.buf, strerror(errno));
139           rc = -1; goto end;
140         }
141
142         if (!*q) {
143           tvec_skipspc(tv); pos = d.len;
144           if (!tvec_readword(tv, &d, ";", 0)) pos++;
145           q = d.buf + pos;
146         }
147
148         if (!*q || STRCMP(q, ==, "s") || STRCMP(q, ==, "sec"))
149           /* nothing to do */;
150
151         else if (STRCMP(q, ==, "ds")) t *= 1e-1;
152         else if (STRCMP(q, ==, "cs")) t *= 1e-2;
153         else if (STRCMP(q, ==, "ms")) t *= 1e-3;
154         else if (STRCMP(q, ==, "us") || STRCMP(q, ==, "µs")) t *= 1e-6;
155         else if (STRCMP(q, ==, "ns")) t *= 1e-9;
156         else if (STRCMP(q, ==, "ps")) t *= 1e-12;
157         else if (STRCMP(q, ==, "fs")) t *= 1e-15;
158         else if (STRCMP(q, ==, "as")) t *= 1e-18;
159         else if (STRCMP(q, ==, "zs")) t *= 1e-21;
160         else if (STRCMP(q, ==, "ys")) t *= 1e-24;
161
162         else if (STRCMP(q, ==, "das")) t *= 1e+1;
163         else if (STRCMP(q, ==, "hs")) t *= 1e+2;
164         else if (STRCMP(q, ==, "ks")) t *= 1e+3;
165         else if (STRCMP(q, ==, "Ms")) t *= 1e+6;
166         else if (STRCMP(q, ==, "Gs")) t *= 1e+9;
167         else if (STRCMP(q, ==, "Ts")) t *= 1e+12;
168         else if (STRCMP(q, ==, "Ps")) t *= 1e+15;
169         else if (STRCMP(q, ==, "Es")) t *= 1e+18;
170         else if (STRCMP(q, ==, "Zs")) t *= 1e+21;
171         else if (STRCMP(q, ==, "Ys")) t *= 1e+24;
172
173         else if (STRCMP(q, ==, "m") || STRCMP(q, ==, "min")) t *= 60;
174         else if (STRCMP(q, ==, "h") || STRCMP(q, ==, "hr")) t *= 3600;
175         else if (STRCMP(q, ==, "d") || STRCMP(q, ==, "dy")) t *= 86400;
176         else if (STRCMP(q, ==, "y") || STRCMP(q, ==, "yr")) t *= 31557600;
177
178         else {
179           if (f&f_timer)
180             { rc = tvec_syntax(tv, *q, "end-of-line"); goto end; }
181           pos = q - d.buf; d.len -= pos;
182           memmove(d.buf, q, d.len);
183           goto timeout_primed;
184         }
185       }
186
187       if (!(~f&f_all)) break;
188     }
189
190     if (!f) {
191       rc = tvec_syntax(tv, fgetc(tv->fp), "timeout or timer keyword");
192       goto end;
193     }
194     rc = tvec_flushtoeol(tv, 0); if (rc) goto end;
195     if (f&f_time) tc->t = t;
196     if (f&f_timer) tc->timer = tmr;
197     tc->f |= TVTF_SETTMO;
198
199   } else if (subenv && subenv->set)
200     rc = subenv->set(tv, var, tc->subctx);
201   else
202     rc = 0;
203
204 end:
205   dstr_destroy(&d);
206   return (rc);
207
208 #undef f_time
209 #undef f_timer
210 #undef f_all
211 }
212
213 /* --- @tvec_timeoutbefore@ --- *
214  *
215  * Arguments:   @struct tvec_state *tv@ = test vector state
216  *              @void *ctx@ = context pointer
217  *
218  * Returns:     ---
219  *
220  * Use:         Invoke the subordinate environment's @before@ function to
221  *              prepare for the test.
222  */
223
224 void tvec_timeoutbefore(struct tvec_state *tv, void *ctx)
225 {
226   struct tvec_timeoutctx *tc = ctx;
227   const struct tvec_timeoutenv *te = tc->te;
228   const struct tvec_env *subenv = te->env;
229
230   /* Just call the subsidiary environment. */
231   if (subenv && subenv->before) subenv->before(tv, tc->subctx);
232 }
233
234 /* --- @tvec_timeoutafter@ --- *
235  *
236  * Arguments:   @struct tvec_state *tv@ = test vector state
237  *              @void *ctx@ = context pointer
238  *
239  * Returns:     ---
240  *
241  * Use:         Invoke the subordinate environment's @after@ function to
242  *              clean up after the test.
243  */
244
245 void tvec_timeoutafter(struct tvec_state *tv, void *ctx)
246 {
247   struct tvec_timeoutctx *tc = ctx;
248   const struct tvec_timeoutenv *te = tc->te;
249   const struct tvec_env *subenv = te->env;
250
251   /* Reset variables. */
252   reset(tc);
253
254   /* Pass the call on to the subsidiary environment. */
255   if (subenv && subenv->after) subenv->after(tv, tc->subctx);
256 }
257
258 /* --- @tvec_timeoutteardown@ --- *
259  *
260  * Arguments:   @struct tvec_state *tv@ = test vector state
261  *              @void *ctx@ = context pointer
262  *
263  * Returns:     ---
264  *
265  * Use:         Tear down the timeoutmark environment.
266  */
267
268 void tvec_timeoutteardown(struct tvec_state *tv, void *ctx)
269 {
270   struct tvec_timeoutctx *tc = ctx;
271   const struct tvec_timeoutenv *te = tc->te;
272   const struct tvec_env *subenv = te->env;
273
274   /* Just call the subsidiary environment. */
275   if (subenv && subenv->teardown) subenv->teardown(tv, tc->subctx);
276 }
277
278 /* --- @tvec_timeoutrun@ --- *
279  *
280  * Arguments:   @struct tvec_state *tv@ = test vector state
281  *              @tvec_testfn *fn@ = test function to run
282  *              @void *ctx@ = context pointer for the test function
283  *
284  * Returns:     ---
285  *
286  * Use:         Runs a test with a timeout attached.
287  */
288
289 void tvec_timeoutrun(struct tvec_state *tv, tvec_testfn *fn, void *ctx)
290 {
291   struct tvec_timeoutctx *tc = ctx;
292   const struct tvec_timeoutenv *te = tc->te;
293   const struct tvec_env *subenv = te->env;
294   struct itimerval itv;
295
296   itv.it_interval.tv_sec = 0; itv.it_interval.tv_usec = 0;
297   itv.it_value.tv_sec = tc->t;
298   itv.it_value.tv_usec = (tc->t - itv.it_value.tv_sec)*1e6;
299
300   if (setitimer(tc->timer, &itv, 0))
301     tvec_skip(tv, "failed to set interval timer: %s", strerror(errno));
302   else {
303     if (subenv && subenv->run) subenv->run(tv, fn, tc->subctx);
304     else fn(tv->in, tv->out, tc->subctx);
305
306     itv.it_interval.tv_sec = 0; itv.it_interval.tv_usec = 0;
307     itv.it_value.tv_sec = 0; itv.it_value.tv_usec = 0;
308     if (setitimer(tc->timer, &itv, 0))
309       tvec_error(tv, "failed to disarm interval timer: %s", strerror(errno));
310   }
311 }
312
313 /*----- That's all, folks -------------------------------------------------*/