X-Git-Url: https://www.chiark.greenend.org.uk/ucgi/~mdw/git/mLib/blobdiff_plain/31d0247cc58abc0b0720aa7e9972011c5a66995c..c81c35dfd10050ffef85d57dc2ad73f52f38a3f2:/test/tvec-timeout.c diff --git a/test/tvec-timeout.c b/test/tvec-timeout.c index ca86b5c..d9a614b 100644 --- a/test/tvec-timeout.c +++ b/test/tvec-timeout.c @@ -83,7 +83,8 @@ void tvec_timeoutsetup(struct tvec_state *tv, const struct tvec_env *env, * @const char *var@ = variable name to set * @void *ctx@ = context pointer * - * Returns: Zero on success, @-1@ on failure. + * Returns: %$+1$% on success, %$0$% if the variable name was not + * recognized, or %$-1$% on any other error. * * Use: Set a special variable. The following special variables are * supported. @@ -112,12 +113,22 @@ int tvec_timeoutset(struct tvec_state *tv, const char *var, void *ctx) #define f_all (f_time | f_timer) if (STRCMP(var, ==, "@timeout")) { + /* Parse a timeout specification. */ + + /* If we've already set one then report an error. */ if (tc->f&TVTF_SETTMO) { rc = tvec_dupreg(tv, var); goto end; } for (;;) { + /* Continue until we run out of things. */ + + /* Read the next word. */ DRESET(&d); if (tvec_readword(tv, &d, ";", 0)) break; timeout_primed: + + /* Start parsing the word. */ p = d.buf; + + /* Check for timer tokens. */ if (!(f&f_timer) && STRCMP(p, ==, "REAL")) { tmr = ITIMER_REAL; f |= f_timer; } else if (!(f&f_timer) && STRCMP(p, ==, "VIRTUAL")) @@ -125,26 +136,42 @@ int tvec_timeoutset(struct tvec_state *tv, const char *var, void *ctx) else if (!(f&f_timer) && STRCMP(p, ==, "PROF")) { tmr = ITIMER_PROF; f |= f_timer; } + /* Otherwise, check for durations. */ else if (!(f&f_time)) { + + /* Skip leading stuff that isn't digits. This is a hedge against + * @strtod@ interpreting something unhelpful like a NaN. + */ if (*p == '+' || *p == '-') p++; if (*p == '.') p++; if (!ISDIGIT(*p)) { tvec_syntax(tv, *d.buf, "floating-point number"); rc = -1; goto end; } + + /* Parse the number and check that it's reasonable. */ errno = 0; t = strtod(p, &q); f |= f_time; if (errno) { tvec_error(tv, "invalid floating-point number `%s': %s", d.buf, strerror(errno)); rc = -1; goto end; } + if (t < 0) { + tvec_error(tv, "invalid duration `%s': %s", + d.buf, strerror(errno)); + rc = -1; goto end; + } + /* We're now on the lookout for units. If there's nothing here then + * fetch the next word. + */ if (!*q) { tvec_skipspc(tv); pos = d.len; if (!tvec_readword(tv, &d, ";", 0)) pos++; q = d.buf + pos; } + /* Match various units. */ if (!*q || STRCMP(q, ==, "s") || STRCMP(q, ==, "sec")) /* nothing to do */; @@ -175,32 +202,45 @@ int tvec_timeoutset(struct tvec_state *tv, const char *var, void *ctx) else if (STRCMP(q, ==, "d") || STRCMP(q, ==, "dy")) t *= 86400; else if (STRCMP(q, ==, "y") || STRCMP(q, ==, "yr")) t *= 31557600; + /* Not a unit specification after all. If we've already selected a + * timer, then this is just junk so report the error. Otherwise, we + * snarfed the next token too early, so move it to the start of the + * buffer and go round again. + */ else { if (f&f_timer) { rc = tvec_syntax(tv, *q, "end-of-line"); goto end; } pos = q - d.buf; d.len -= pos; - memmove(d.buf, q, d.len); + memmove(d.buf, q, d.len + 1); goto timeout_primed; } } + /* If we've read all that we need to, then stop. */ if (!(~f&f_all)) break; } + /* If we didn't get anything, that's a problem. */ if (!f) { rc = tvec_syntax(tv, fgetc(tv->fp), "timeout or timer keyword"); goto end; } + + /* Make sure there's nothing else on the line. */ rc = tvec_flushtoeol(tv, 0); if (rc) goto end; if (f&f_time) tc->t = t; if (f&f_timer) tc->timer = tmr; tc->f |= TVTF_SETTMO; + rc = 1; } else if (subenv && subenv->set) + /* Not one of ours: pass it on to the sub-environment. */ rc = subenv->set(tv, var, tc->subctx); else + /* No subenvironment. Report the error. */ rc = 0; + /* Done. */ end: dstr_destroy(&d); return (rc); @@ -231,83 +271,83 @@ void tvec_timeoutbefore(struct tvec_state *tv, void *ctx) if (subenv && subenv->before) subenv->before(tv, tc->subctx); } -/* --- @tvec_timeoutafter@ --- * +/* --- @tvec_timeoutrun@ --- * * * Arguments: @struct tvec_state *tv@ = test vector state - * @void *ctx@ = context pointer + * @tvec_testfn *fn@ = test function to run + * @void *ctx@ = context pointer for the test function * * Returns: --- * - * Use: Invoke the subordinate environment's @after@ function to - * clean up after the test. + * Use: Runs a test with a timeout attached. */ -void tvec_timeoutafter(struct tvec_state *tv, void *ctx) +void tvec_timeoutrun(struct tvec_state *tv, tvec_testfn *fn, void *ctx) { struct tvec_timeoutctx *tc = ctx; const struct tvec_timeoutenv *te = tc->te; const struct tvec_env *subenv = te->env; + struct itimerval itv; - /* Reset variables. */ - reset(tc); + itv.it_interval.tv_sec = 0; itv.it_interval.tv_usec = 0; + itv.it_value.tv_sec = tc->t; + itv.it_value.tv_usec = (tc->t - itv.it_value.tv_sec)*1e6; - /* Pass the call on to the subsidiary environment. */ - if (subenv && subenv->after) subenv->after(tv, tc->subctx); + if (setitimer(tc->timer, &itv, 0)) + tvec_skip(tv, "failed to set interval timer: %s", strerror(errno)); + else { + if (subenv && subenv->run) subenv->run(tv, fn, tc->subctx); + else fn(tv->in, tv->out, tc->subctx); + + itv.it_interval.tv_sec = 0; itv.it_interval.tv_usec = 0; + itv.it_value.tv_sec = 0; itv.it_value.tv_usec = 0; + if (setitimer(tc->timer, &itv, 0)) + tvec_error(tv, "failed to disarm interval timer: %s", strerror(errno)); + } } -/* --- @tvec_timeoutteardown@ --- * +/* --- @tvec_timeoutafter@ --- * * * Arguments: @struct tvec_state *tv@ = test vector state * @void *ctx@ = context pointer * * Returns: --- * - * Use: Tear down the timeoutmark environment. + * Use: Invoke the subordinate environment's @after@ function to + * clean up after the test. */ -void tvec_timeoutteardown(struct tvec_state *tv, void *ctx) +void tvec_timeoutafter(struct tvec_state *tv, void *ctx) { struct tvec_timeoutctx *tc = ctx; const struct tvec_timeoutenv *te = tc->te; const struct tvec_env *subenv = te->env; - /* Just call the subsidiary environment. */ - if (subenv && subenv->teardown) subenv->teardown(tv, tc->subctx); + /* Reset variables. */ + reset(tc); + + /* Pass the call on to the subsidiary environment. */ + if (subenv && subenv->after) subenv->after(tv, tc->subctx); } -/* --- @tvec_timeoutrun@ --- * +/* --- @tvec_timeoutteardown@ --- * * * Arguments: @struct tvec_state *tv@ = test vector state - * @tvec_testfn *fn@ = test function to run - * @void *ctx@ = context pointer for the test function + * @void *ctx@ = context pointer * * Returns: --- * - * Use: Runs a test with a timeout attached. + * Use: Tear down the timeoutmark environment. */ -void tvec_timeoutrun(struct tvec_state *tv, tvec_testfn *fn, void *ctx) +void tvec_timeoutteardown(struct tvec_state *tv, void *ctx) { struct tvec_timeoutctx *tc = ctx; const struct tvec_timeoutenv *te = tc->te; const struct tvec_env *subenv = te->env; - struct itimerval itv; - - itv.it_interval.tv_sec = 0; itv.it_interval.tv_usec = 0; - itv.it_value.tv_sec = tc->t; - itv.it_value.tv_usec = (tc->t - itv.it_value.tv_sec)*1e6; - - if (setitimer(tc->timer, &itv, 0)) - tvec_skip(tv, "failed to set interval timer: %s", strerror(errno)); - else { - if (subenv && subenv->run) subenv->run(tv, fn, tc->subctx); - else fn(tv->in, tv->out, tc->subctx); - itv.it_interval.tv_sec = 0; itv.it_interval.tv_usec = 0; - itv.it_value.tv_sec = 0; itv.it_value.tv_usec = 0; - if (setitimer(tc->timer, &itv, 0)) - tvec_error(tv, "failed to disarm interval timer: %s", strerror(errno)); - } + /* Just call the subsidiary environment. */ + if (subenv && subenv->teardown) subenv->teardown(tv, tc->subctx); } /*----- That's all, folks -------------------------------------------------*/