chiark / gitweb /
@@@ pool upgrade
[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 /* --- @tvec_parseargs@ --- *
145  *
146  * Arguments:   @int argc@ = number of command-line arguments
147  *              @char *argv[]@ = vector of argument strings
148  *              @struct tvec_state *tv_out@ = test vector state to initialize
149  *              @int *argpos_out@ = where to leave unread argument index
150  *              @const struct tvec_config *config@ = test vector
151  *                      configuration
152  *
153  * Returns:     ---
154  *
155  * Use:         Parse arguments and set up the test vector state @*tv_out@.
156  *              If errors occur, print messages to standard error and exit
157  *              with status 2.
158  *
159  *              This function also establishes a common benchmark state.
160  */
161
162 void tvec_parseargs(int argc, char *argv[], struct tvec_state *tv_out,
163                     int *argpos_out, const struct tvec_config *config)
164 {
165   FILE *ofp = 0;
166   const struct outform *of = 0;
167   struct tvec_output *o;
168   const char *benchconf = 0;
169   const char *logfile = 0, *trsfile = 0;
170   struct tvec_amargs am;
171   double benchtime = 1.0, scale;
172   struct bench_timer *tm;
173   const char *p; char *q;
174   int opt;
175   unsigned f = 0;
176 #define f_bogus 1u
177 #define f_bench 2u
178 #define f_automake 4u
179
180   enum {
181     OPT_TESTNAME = 0x100,
182     OPT_LOGFILE,
183     OPT_TRSFILE,
184     OPT_COLOUR,
185     OPT_XFAIL,
186     OPT_HARDERR
187   };
188
189   static const struct option options[] = {
190     { "help",           0,              0,      'h' },
191     { "version",        0,              0,      'v' },
192     { "usage",          0,              0,      'u' },
193
194     { "bench-config",   OPTF_ARGREQ,    0,      'B' },
195     { "format",         OPTF_ARGREQ,    0,      'f' },
196     { "output",         OPTF_ARGREQ,    0,      'o' },
197     { "target-time",    OPTF_ARGREQ,    0,      't' },
198
199     { "test-name",      OPTF_ARGREQ,    0,      OPT_TESTNAME },
200     { "log-file",       OPTF_ARGREQ,    0,      OPT_LOGFILE },
201     { "trs-file",       OPTF_ARGREQ,    0,      OPT_TRSFILE },
202     { "color-tests",    OPTF_ARGREQ,    0,      OPT_COLOUR },
203     { "expect-failure", OPTF_ARGREQ,    0,      OPT_XFAIL },
204     { "enable-hard-errors", OPTF_ARGREQ, 0,     OPT_HARDERR },
205     { 0,                0,              0,      0 }
206   };
207
208   ego(argv[0]);
209   am.f = 0; am.name = 0;
210   for (;;) {
211     opt = mdwopt(argc, argv, "hvu" "B:f:o:t:", options, 0, 0, 0);
212     if (opt < 0) break;
213     switch (opt) {
214       case 'h': help(stdout); exit(0);
215       case 'v': version(stdout); exit(0);
216       case 'u': usage(stdout); exit(0);
217
218       case 'B': benchconf = optarg; f |= f_bench; break;
219       case 'f': of = find_outform(optarg); break;
220       case 'o':
221         if (ofp) fclose(ofp);
222         ofp = fopen(optarg, "w");
223         if (!ofp)
224           die(2, "failed to open `%s' for writing: %s",
225               optarg, strerror(errno));
226         break;
227       case 't':
228         if (*optarg != '.' && !ISDIGIT(*optarg)) goto bad_time;
229         errno = 0; benchtime = strtod(optarg, &q);
230         if (errno) goto bad_time;
231         p = q;
232         if (ISSPACE(*p)) {
233           do p++; while (ISSPACE(*p));
234           if (!*p) goto bad_time;
235         }
236         if (tvec_parsedurunit(&scale, &p)) goto bad_time;
237         if (*p) goto bad_time;
238         benchtime *= scale; f |= f_bench;
239         break;
240       bad_time:
241         die(2, "invalid time duration `%s'", optarg);
242
243       case OPT_TESTNAME: f |= f_automake; am.name = optarg; break;
244       case OPT_LOGFILE: f |= f_automake; logfile = optarg; break;
245       case OPT_TRSFILE: f |= f_automake; trsfile = optarg; break;
246       case OPT_COLOUR:
247         f |= f_automake;
248         if (STRCMP(optarg, ==, "yes")) am.f |= TVAF_COLOUR;
249         else if (STRCMP(optarg, ==, "no")) am.f &= ~TVAF_COLOUR;
250         else die(2, "invalid `--color-tests' value `%s'", optarg);
251         break;
252       case OPT_XFAIL:
253         f |= f_automake;
254         if (STRCMP(optarg, ==, "yes"))
255           die(2, "expected failure not supported");
256         else if (STRCMP(optarg, !=, "no"))
257           die(2, "invalid `--expect-failure' value `%s'", optarg);
258         break;
259       case OPT_HARDERR:
260         f |= f_automake;
261         if (STRCMP(optarg, !=, "yes") && STRCMP(optarg, !=, "no"))
262           die(2, "invalid `--enable-hard-errors' value `%s'", optarg);
263         break;
264
265       default:
266         f |= f_bogus;
267         break;
268     }
269   }
270   if (f&f_bogus) { usage(stderr); exit(2); }
271
272   if (f&f_automake) {
273     if (of || ofp)
274       die(2, "can't set output file or driver in Automake driver");
275     if (!am.name || !logfile || !trsfile)
276       die(2, "must set all of `--test-name', `--log-file', and `--trs-file' "
277           "for Automake driver");
278     am.log = fopen(logfile, "w");
279       if (!am.log)
280         die(2, "failed to open log file `%s': %s",
281             logfile, strerror(errno));
282     am.trs = fopen(trsfile, "w");
283       if (!am.log)
284         die(2, "failed to open test-results file `%s': %s",
285             trsfile, strerror(errno));
286     o = tvec_amoutput(&am);
287   } else {
288     if (!ofp) ofp = stdout;
289     if (!of) {
290       p = getenv("TVEC_FORMAT");
291       if (p) of = find_outform(p);
292     }
293     if (of) o = of->makefn(ofp);
294     else o = tvec_dfltoutput(ofp);
295   }
296
297   tm = bench_createtimer(benchconf);
298   if (tm) {
299     bench_init(&bench, tm); bench.target_s = benchtime;
300     tvec_benchstate = &bench;
301   } else if (f&f_bench) {
302     moan("failed to create benchmark timer");
303     if (!getenv("MLIB_BENCH_DEBUG"))
304       moan("set `MLIB_BENCH_DEBUG=t' in the envionment for more detail");
305     exit(2);
306   }
307
308   tvec_begin(tv_out, config, o); *argpos_out = optind;
309
310 #undef f_bogus
311 #undef f_bench
312 #undef f_automake
313 }
314
315 /* --- @tvec_readstdin@, @tvec_readfile@, @tvec_readarg@ --- *
316  *
317  * Arguments:   @struct tvec_state *tv@ = test vector state
318  *              @const char *file@ = pathname of file to read
319  *              @const char *arg@ = argument to interpret
320  *
321  * Returns:     Zero on success, @-1@ on error.
322  *
323  * Use:         Read test vector data from stdin or a named file.  The
324  *              @tvec_readarg@ function reads from stdin if @arg@ is `%|-|%',
325  *              and from the named file otherwise.
326  */
327
328 int tvec_readstdin(struct tvec_state *tv)
329   { return (tvec_read(tv, "<stdin>", stdin)); }
330
331 int tvec_readfile(struct tvec_state *tv, const char *file)
332 {
333   FILE *fp = 0;
334   int rc;
335
336   fp = fopen(file, "r");
337   if (!fp) {
338     tvec_error(tv, "failed to open `%s' for reading: %s",
339                file, strerror(errno));
340     rc = -1; goto end;
341   }
342   if (tvec_read(tv, file, fp)) { rc = -1; goto end; }
343   rc = 0;
344 end:
345   if (fp) fclose(fp);
346   return (rc);
347 }
348
349 int tvec_readarg(struct tvec_state *tv, const char *arg)
350 {
351   int rc;
352
353   if (STRCMP(arg, ==, "-")) rc = tvec_readstdin(tv);
354   else rc = tvec_readfile(tv, arg);
355   return (rc);
356 }
357
358 /* --- @tvec_readdflt@ --- *
359  *
360  * Arguments:   @struct tvec_state *tv@ = test vector state
361  *              @const char *dflt@ = defsault filename or null
362  *
363  * Returns:     Zero on success, @-1@ on error.
364  *
365  * Use:         Reads from the default test vector data.  If @file@ is null,
366  *              then read from standard input, unless that's a terminal; if
367  *              @file@ is not null, then read the named file, looking in the
368  *              directory named by the `%|srcdir|%' environment variable if
369  *              that's set, or otherwise in the current directory.
370  */
371
372 int tvec_readdflt(struct tvec_state *tv, const char *dflt)
373 {
374   dstr d = DSTR_INIT;
375   const char *p;
376   int rc;
377
378   if (dflt) {
379     p = getenv("srcdir");
380     if (p) { dstr_putf(&d, "%s/%s", p, dflt); dflt = d.buf; }
381     rc = tvec_readfile(tv, dflt);
382   } else if (isatty(0))
383     rc = tvec_error(tv, "use `-' to force reading from interactive stdin");
384   else
385     rc = tvec_readstdin(tv);
386   dstr_destroy(&d);
387   return (rc);
388 }
389
390 /* --- @tvec_readargs@ --- *
391  *
392  * Arguments:   @int argc@ = number of command-line arguments
393  *              @char *argv[]@ = vector of argument strings
394  *              @struct tvec_state *tv@ = test vector state
395  *              @int *argpos_inout@ = current argument position (updated)
396  *              @const char *dflt@ = default filename or null
397  *
398  * Returns:     Zero on success, @-1@ on error.
399  *
400  * Use:         Reads from the sources indicated by the command-line
401  *              arguments, in order, interpreting each as for @tvec_readarg@;
402  *              if no arguments are given then read from @dflt@ as for
403  *              @tvec_readdflt@.
404  */
405
406 int tvec_readargs(int argc, char *argv[], struct tvec_state *tv,
407                    int *argpos_inout, const char *dflt)
408 {
409   int i = *argpos_inout;
410   int rc;
411
412   if (i == argc)
413     rc = tvec_readdflt(tv, dflt);
414   else {
415     rc = 0;
416     while (i < argc)
417       if (tvec_readarg(tv, argv[i++])) rc = -1;
418   }
419   *argpos_inout = i;
420   return (rc);
421 }
422
423 /* --- @tvec_main@ --- *
424  *
425  * Arguments:   @int argc@ = number of command-line arguments
426  *              @char *argv[]@ = vector of argument strings
427  *              @const struct tvec_config *config@ = test vector
428  *                      configuration
429  *              @const char *dflt@ = default filename or null
430  *
431  * Returns:     Exit code.
432  *
433  * Use:         All-in-one test vector front-end.  Parse options from the
434  *              command-line as for @tvec_parseargs@, and then process the
435  *              remaining positional arguments as for @tvec_readargs@.  The
436  *              function constructs and disposes of a test vector state.
437  */
438
439 int tvec_main(int argc, char *argv[],
440               const struct tvec_config *config, const char *dflt)
441 {
442   struct tvec_state tv;
443   int argpos;
444
445   tvec_parseargs(argc, argv, &tv, &argpos, config);
446   tvec_readargs(argc, argv, &tv, &argpos, dflt);
447   return (tvec_end(&tv));
448 }
449
450 /*----- That's all, folks -------------------------------------------------*/