chiark / gitweb /
@@@ tvec and tty mess
[mLib] / test / tvec-main.c
1 /* -*-c-*-
2  *
3  * Main entry point for test-vector processing
4  *
5  * (c) 2023 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 "config.h"
31
32 #include <ctype.h>
33 #include <errno.h>
34 #include <stdio.h>
35 #include <stdlib.h>
36 #include <string.h>
37
38 #include <unistd.h>
39
40 #include "bench.h"
41 #include "dstr.h"
42 #include "macros.h"
43 #include "mdwopt.h"
44 #include "quis.h"
45 #include "report.h"
46
47 #include "tvec.h"
48 #include "tvec-adhoc.h"
49 #include "tvec-bench.h"
50 #include "tvec-types.h"
51
52 /*----- Main code ---------------------------------------------------------*/
53
54 /* Table of output formats. */
55 static struct tvec_output *default_human_output(FILE *fp)
56   { return (tvec_humanoutput(fp, 0, 0)); }
57
58 static const struct outform {
59   const char *name;
60   struct tvec_output *(*makefn)(FILE *fp);
61 } outtab[] = {
62   { "human",            default_human_output },
63   { "machine",          tvec_machineoutput },
64   { "tap",              tvec_tapoutput },
65   { 0,                  0 }
66 };
67
68 /* Common benchmark state. */
69 static struct bench_state bench;
70
71 /* --- @find_outform@ ---
72  *
73  * Arguments:   @const char *p@ = output name
74  *
75  * Returns:     Pointer to output format record.
76  *
77  * Use:         Looks up an output format by name.  Reports a fatal error if
78  *              no matching record is found.
79  */
80
81 static const struct outform *find_outform(const char *p)
82 {
83   const struct outform *best = 0, *of;
84   size_t n = strlen(p);
85
86   for (of = outtab; of->name; of++)
87     if (!STRNCMP(p, ==, of->name, n))
88       ;
89     else if (!of->name[n])
90       return (of);
91     else if (best)
92       die(2, "ambiguous output format name (`%s' or `%s'?)",
93           best->name, of->name);
94     else
95       best = of;
96   if (best) return (best);
97   else die(2, "unknown output format `%s'", optarg);
98 }
99
100 /* --- @version@, @usage@, @help@ --- *
101  *
102  * Arguments:   @FILE *fp@ = stream to write on
103  *
104  * Returns:     ---
105  *
106  * Use:         Output information about the program.
107  */
108
109 static void version(FILE *fp)
110   { pquis(fp, "$, mLib test-vector framework version " VERSION "\n"); }
111
112 static void usage(FILE *fp)
113 {
114   pquis(fp, "\
115 usage: $ [-B CONFIG] [-f FORMAT] [-o OUTPUT] [-t SECS] [TEST-FILE ...]\n\
116 ");
117 }
118
119 static void help(FILE *fp)
120 {
121   version(fp); fputc('\n', fp);
122   usage(fp); fputs("\
123 Help options:\n\
124   -h, --help                    show this help text.\n\
125   -v, --version                 show version number.\n\
126   -u, --usage                   show usage synopsis.\n\
127 \n\
128 Main options:\n\
129   -B, --bench-config=CONFIG     set benchmark configuration string.\n\
130   -f, --format=FORMAT           produce output in FORMAT.\n\
131   -o, --output=OUTPUT           write output to OUTPUT file.\n\
132   -t, --target-time=DURATION    run benchmarks for DURATION.\n\
133 \n\
134 Automake driver options:\n\
135       --test-name NAME          set the test name (ignored).\n\
136       --log-file PATH           write log to PATH.\n\
137       --trs-file PATH           write rest results to PATH.\n\
138       --color-tests yes|no      produce coloured output.\n\
139       --expect-failure yes|no   must be `no'.\n\
140       --enable-hard-errors yes|no (ignored).\n\
141 ", fp);
142 }
143
144 struct bench_resource {
145   pool_resource r;
146   struct bench_state **bst;
147 };
148
149 static void release_bench(pool_resource *r)
150 {
151   struct bench_resource *br = (struct bench_resource *)r;
152
153   if (*br->bst) { bench_destroy(*br->bst); *br->bst = 0; }
154 }
155
156 /* --- @tvec_parseargs@ --- *
157  *
158  * Arguments:   @int argc@ = number of command-line arguments
159  *              @char *argv[]@ = vector of argument strings
160  *              @struct tvec_state *tv_out@ = test vector state to initialize
161  *              @int *argpos_out@ = where to leave unread argument index
162  *              @const struct tvec_config *config@ = test vector
163  *                      configuration
164  *
165  * Returns:     ---
166  *
167  * Use:         Parse arguments and set up the test vector state @*tv_out@.
168  *              If errors occur, print messages to standard error and exit
169  *              with status 2.
170  *
171  *              This function also establishes a common benchmark state.
172  */
173
174 void tvec_parseargs(int argc, char *argv[], struct tvec_state *tv_out,
175                     int *argpos_out, const struct tvec_config *config)
176 {
177   FILE *ofp = 0;
178   const struct outform *of = 0;
179   struct tvec_output *o;
180   const char *benchconf = 0;
181   const char *logfile = 0, *trsfile = 0;
182   struct tvec_amargs am;
183   double benchtime = 1.0, scale;
184   struct bench_timer *tm;
185   struct bench_resource *br;
186   const char *p; char *q;
187   int opt;
188   unsigned f = 0;
189 #define f_bogus 1u
190 #define f_bench 2u
191 #define f_automake 4u
192
193   enum {
194     OPT_TESTNAME = 0x100,
195     OPT_LOGFILE,
196     OPT_TRSFILE,
197     OPT_COLOUR,
198     OPT_XFAIL,
199     OPT_HARDERR
200   };
201
202   static const struct option options[] = {
203     { "help",           0,              0,      'h' },
204     { "version",        0,              0,      'v' },
205     { "usage",          0,              0,      'u' },
206
207     { "bench-config",   OPTF_ARGREQ,    0,      'B' },
208     { "format",         OPTF_ARGREQ,    0,      'f' },
209     { "output",         OPTF_ARGREQ,    0,      'o' },
210     { "target-time",    OPTF_ARGREQ,    0,      't' },
211
212     { "test-name",      OPTF_ARGREQ,    0,      OPT_TESTNAME },
213     { "log-file",       OPTF_ARGREQ,    0,      OPT_LOGFILE },
214     { "trs-file",       OPTF_ARGREQ,    0,      OPT_TRSFILE },
215     { "color-tests",    OPTF_ARGREQ,    0,      OPT_COLOUR },
216     { "expect-failure", OPTF_ARGREQ,    0,      OPT_XFAIL },
217     { "enable-hard-errors", OPTF_ARGREQ, 0,     OPT_HARDERR },
218     { 0,                0,              0,      0 }
219   };
220
221   ego(argv[0]);
222   am.f = 0; am.name = 0;
223   for (;;) {
224     opt = mdwopt(argc, argv, "hvu" "B:f:o:t:", options, 0, 0, 0);
225     if (opt < 0) break;
226     switch (opt) {
227       case 'h': help(stdout); exit(0);
228       case 'v': version(stdout); exit(0);
229       case 'u': usage(stdout); exit(0);
230
231       case 'B': benchconf = optarg; f |= f_bench; break;
232       case 'f': of = find_outform(optarg); break;
233       case 'o':
234         if (ofp) fclose(ofp);
235         ofp = fopen(optarg, "w");
236         if (!ofp)
237           die(2, "failed to open `%s' for writing: %s",
238               optarg, strerror(errno));
239         break;
240       case 't':
241         if (*optarg != '.' && !ISDIGIT(*optarg)) goto bad_time;
242         errno = 0; benchtime = strtod(optarg, &q);
243         if (errno) goto bad_time;
244         p = q;
245         if (ISSPACE(*p)) {
246           do p++; while (ISSPACE(*p));
247           if (!*p) goto bad_time;
248         }
249         if (tvec_parsedurunit(&scale, &p)) goto bad_time;
250         if (*p) goto bad_time;
251         benchtime *= scale; f |= f_bench;
252         break;
253       bad_time:
254         die(2, "invalid time duration `%s'", optarg);
255
256       case OPT_TESTNAME: f |= f_automake; am.name = optarg; break;
257       case OPT_LOGFILE: f |= f_automake; logfile = optarg; break;
258       case OPT_TRSFILE: f |= f_automake; trsfile = optarg; break;
259       case OPT_COLOUR:
260         f |= f_automake;
261         if (STRCMP(optarg, ==, "yes")) am.f |= TVAF_COLOUR;
262         else if (STRCMP(optarg, ==, "no")) am.f &= ~TVAF_COLOUR;
263         else die(2, "invalid `--color-tests' value `%s'", optarg);
264         break;
265       case OPT_XFAIL:
266         f |= f_automake;
267         if (STRCMP(optarg, ==, "yes"))
268           die(2, "expected failure not supported");
269         else if (STRCMP(optarg, !=, "no"))
270           die(2, "invalid `--expect-failure' value `%s'", optarg);
271         break;
272       case OPT_HARDERR:
273         f |= f_automake;
274         if (STRCMP(optarg, !=, "yes") && STRCMP(optarg, !=, "no"))
275           die(2, "invalid `--enable-hard-errors' value `%s'", optarg);
276         break;
277
278       default:
279         f |= f_bogus;
280         break;
281     }
282   }
283   if (f&f_bogus) { usage(stderr); exit(2); }
284
285   if (f&f_automake) {
286     if (of || ofp)
287       die(2, "can't set output file or driver in Automake driver");
288     if (!am.name || !logfile || !trsfile)
289       die(2, "must set all of `--test-name', `--log-file', and `--trs-file' "
290           "for Automake driver");
291     am.log = fopen(logfile, "w");
292       if (!am.log)
293         die(2, "failed to open log file `%s': %s",
294             logfile, strerror(errno));
295     am.trs = fopen(trsfile, "w");
296       if (!am.log)
297         die(2, "failed to open test-results file `%s': %s",
298             trsfile, strerror(errno));
299     o = tvec_amoutput(&am);
300   } else {
301     if (!ofp) ofp = stdout;
302     if (!of) {
303       p = getenv("TVEC_FORMAT");
304       if (p) of = find_outform(p);
305     }
306     if (of) o = of->makefn(ofp);
307     else o = tvec_dfltoutput(ofp);
308   }
309
310   tm = bench_createtimer(benchconf);
311   if (tm) {
312     bench_init(&bench, tm); bench.target_s = benchtime;
313     tvec_benchstate = &bench;
314   } else if (f&f_bench) {
315     moan("failed to create benchmark timer");
316     if (!getenv("MLIB_BENCH_DEBUG"))
317       moan("set `MLIB_BENCH_DEBUG=t' in the envionment for more detail");
318     exit(2);
319   }
320
321   tvec_begin(tv_out, config, o); *argpos_out = optind;
322   POOL_NEW(br, tv_out->p_session);
323   br->bst = &tvec_benchstate;
324   POOL_ADD(tv_out->p_session, &br->r, release_bench);
325
326 #undef f_bogus
327 #undef f_bench
328 #undef f_automake
329 }
330
331 /* --- @tvec_readstdin@, @tvec_readfile@, @tvec_readarg@ --- *
332  *
333  * Arguments:   @struct tvec_state *tv@ = test vector state
334  *              @const char *file@ = pathname of file to read
335  *              @const char *arg@ = argument to interpret
336  *
337  * Returns:     Zero on success, @-1@ on error.
338  *
339  * Use:         Read test vector data from stdin or a named file.  The
340  *              @tvec_readarg@ function reads from stdin if @arg@ is `%|-|%',
341  *              and from the named file otherwise.
342  */
343
344 int tvec_readstdin(struct tvec_state *tv)
345   { return (tvec_read(tv, "<stdin>", stdin)); }
346
347 int tvec_readfile(struct tvec_state *tv, const char *file)
348 {
349   FILE *fp = 0;
350   int rc;
351
352   fp = fopen(file, "r");
353   if (!fp) {
354     tvec_error(tv, "failed to open `%s' for reading: %s",
355                file, strerror(errno));
356     rc = -1; goto end;
357   }
358   if (tvec_read(tv, file, fp)) { rc = -1; goto end; }
359   rc = 0;
360 end:
361   if (fp) fclose(fp);
362   return (rc);
363 }
364
365 int tvec_readarg(struct tvec_state *tv, const char *arg)
366 {
367   int rc;
368
369   if (STRCMP(arg, ==, "-")) rc = tvec_readstdin(tv);
370   else rc = tvec_readfile(tv, arg);
371   return (rc);
372 }
373
374 /* --- @tvec_readdflt@ --- *
375  *
376  * Arguments:   @struct tvec_state *tv@ = test vector state
377  *              @const char *dflt@ = defsault filename or null
378  *
379  * Returns:     Zero on success, @-1@ on error.
380  *
381  * Use:         Reads from the default test vector data.  If @file@ is null,
382  *              then read from standard input, unless that's a terminal; if
383  *              @file@ is not null, then read the named file, looking in the
384  *              directory named by the `%|srcdir|%' environment variable if
385  *              that's set, or otherwise in the current directory.
386  */
387
388 int tvec_readdflt(struct tvec_state *tv, const char *dflt)
389 {
390   dstr d = DSTR_INIT;
391   const char *p;
392   int rc;
393
394   if (dflt) {
395     p = getenv("srcdir");
396     if (p) { dstr_putf(&d, "%s/%s", p, dflt); dflt = d.buf; }
397     rc = tvec_readfile(tv, dflt);
398   } else if (isatty(0))
399     rc = tvec_error(tv, "use `-' to force reading from interactive stdin");
400   else
401     rc = tvec_readstdin(tv);
402   dstr_destroy(&d);
403   return (rc);
404 }
405
406 /* --- @tvec_readargs@ --- *
407  *
408  * Arguments:   @int argc@ = number of command-line arguments
409  *              @char *argv[]@ = vector of argument strings
410  *              @struct tvec_state *tv@ = test vector state
411  *              @int *argpos_inout@ = current argument position (updated)
412  *              @const char *dflt@ = default filename or null
413  *
414  * Returns:     Zero on success, @-1@ on error.
415  *
416  * Use:         Reads from the sources indicated by the command-line
417  *              arguments, in order, interpreting each as for @tvec_readarg@;
418  *              if no arguments are given then read from @dflt@ as for
419  *              @tvec_readdflt@.
420  */
421
422 int tvec_readargs(int argc, char *argv[], struct tvec_state *tv,
423                    int *argpos_inout, const char *dflt)
424 {
425   int i = *argpos_inout;
426   int rc;
427
428   if (i == argc)
429     rc = tvec_readdflt(tv, dflt);
430   else {
431     rc = 0;
432     while (i < argc)
433       if (tvec_readarg(tv, argv[i++])) rc = -1;
434   }
435   *argpos_inout = i;
436   return (rc);
437 }
438
439 /* --- @tvec_main@ --- *
440  *
441  * Arguments:   @int argc@ = number of command-line arguments
442  *              @char *argv[]@ = vector of argument strings
443  *              @const struct tvec_config *config@ = test vector
444  *                      configuration
445  *              @const char *dflt@ = default filename or null
446  *
447  * Returns:     Exit code.
448  *
449  * Use:         All-in-one test vector front-end.  Parse options from the
450  *              command-line as for @tvec_parseargs@, and then process the
451  *              remaining positional arguments as for @tvec_readargs@.  The
452  *              function constructs and disposes of a test vector state.
453  */
454
455 int tvec_main(int argc, char *argv[],
456               const struct tvec_config *config, const char *dflt)
457 {
458   struct tvec_state tv;
459   int argpos;
460
461   tvec_parseargs(argc, argv, &tv, &argpos, config);
462   tvec_readargs(argc, argv, &tv, &argpos, dflt);
463   return (tvec_end(&tv));
464 }
465
466 /*----- That's all, folks -------------------------------------------------*/