chiark / gitweb /
@@@ fltfmt wip
[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 const struct outform {
56   const char *name;
57   struct tvec_output *(*makefn)(FILE *fp);
58 } outtab[] = {
59   { "human",            tvec_humanoutput },
60   { "machine",          tvec_machineoutput },
61   { "tap",              tvec_tapoutput },
62   { 0,                  0 }
63 };
64
65 /* Configuration for ad-hoc testing. */
66 const struct tvec_config tvec_adhocconfig =
67   { 0, 1, 1, sizeof(struct tvec_reg) };
68
69 /* Common benchmark state. */
70 static struct bench_state bench;
71
72 /* --- @find_outform@ ---
73  *
74  * Arguments:   @const char *p@ = output name
75  *
76  * Returns:     Pointer to output format record.
77  *
78  * Use:         Looks up an output format by name.  Reports a fatal error if
79  *              no matching record is found.
80  */
81
82 static const struct outform *find_outform(const char *p)
83 {
84   const struct outform *best = 0, *of;
85   size_t n = strlen(p);
86
87   for (of = outtab; of->name; of++)
88     if (!STRNCMP(p, ==, of->name, n))
89       ;
90     else if (!of->name[n])
91       return (of);
92     else if (best)
93       die(2, "ambiguous output format name (`%s' or `%s'?)",
94           best->name, of->name);
95     else
96       best = of;
97   if (best) return (best);
98   else die(2, "unknown output format `%s'", optarg);
99 }
100
101 /* --- @version@, @usage@, @help@ --- *
102  *
103  * Arguments:   @FILE *fp@ = stream to write on
104  *
105  * Returns:     ---
106  *
107  * Use:         Output information about the program.
108  */
109
110 static void version(FILE *fp)
111   { pquis(fp, "$, mLib test-vector framework version " VERSION "\n"); }
112
113 static void usage(FILE *fp)
114 {
115   pquis(fp, "\
116 usage: $ [-B CONFIG] [-f FORMAT] [-o OUTPUT] [-t SECS] [TEST-FILE ...]\n\
117 ");
118 }
119
120 static void help(FILE *fp)
121 {
122   version(fp); fputc('\n', fp);
123   usage(fp); fputs("\
124 Options:\n\
125   -h, --help                    show this help text.\n\
126   -v, --version                 show version number.\n\
127   -u, --usage                   show usage synopsis.\n\
128 \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 ", fp);
134 }
135
136 /* --- @tvec_parseargs@ --- *
137  *
138  * Arguments:   @int argc@ = number of command-line arguments
139  *              @char *argv[]@ = vector of argument strings
140  *              @struct tvec_state *tv_out@ = test vector state to initialize
141  *              @int *argpos_out@ = where to leave unread argument index
142  *              @const struct tvec_config *cofig@ = test vector configuration
143  *
144  * Returns:     ---
145  *
146  * Use:         Parse arguments and set up the test vector state @*tv_out@.
147  *              If errors occur, print messages to standard error and exit
148  *              with status 2.
149  *
150  *              This function also establishes a common benchmark state.
151  */
152
153 void tvec_parseargs(int argc, char *argv[], struct tvec_state *tv_out,
154                     int *argpos_out, const struct tvec_config *config)
155 {
156   FILE *ofp = 0;
157   const struct outform *of = 0;
158   struct tvec_output *o;
159   const char *benchconf = 0;
160   double benchtime = 1.0, scale;
161   struct bench_timer *tm;
162   const char *p; char *q;
163   int opt;
164   unsigned f = 0;
165 #define f_bogus 1u
166 #define f_bench 2u
167
168   static const struct option options[] = {
169     { "help",           0,              0,      'h' },
170     { "version",        0,              0,      'v' },
171     { "usage",          0,              0,      'u' },
172
173     { "bench-config",   OPTF_ARGREQ,    0,      'B' },
174     { "format",         OPTF_ARGREQ,    0,      'f' },
175     { "output",         OPTF_ARGREQ,    0,      'o' },
176     { "target-time",    OPTF_ARGREQ,    0,      't' },
177     { 0,                0,              0,      0 }
178   };
179
180   ego(argv[0]);
181   for (;;) {
182     opt = mdwopt(argc, argv, "hvu" "B:f:o:t:", options, 0, 0, 0);
183     if (opt < 0) break;
184     switch (opt) {
185       case 'h': help(stdout); exit(0);
186       case 'v': version(stdout); exit(0);
187       case 'u': usage(stdout); exit(0);
188
189       case 'B': benchconf = optarg; f |= f_bench; break;
190       case 'f': of = find_outform(optarg); break;
191       case 'o':
192         if (ofp) fclose(ofp);
193         ofp = fopen(optarg, "w");
194         if (!ofp)
195           die(2, "failed to open `%s' for writing: %s",
196               optarg, strerror(errno));
197         break;
198       case 't':
199         if (*optarg != '.' && !ISDIGIT(*optarg)) goto bad_time;
200         errno = 0; benchtime = strtod(optarg, &q);
201         if (errno) goto bad_time;
202         p = q;
203         if (ISSPACE(*p)) {
204           do p++; while (ISSPACE(*p));
205           if (!*p) goto bad_time;
206         }
207         if (tvec_parsedurunit(&scale, &p)) goto bad_time;
208         if (*p) goto bad_time;
209         benchtime *= scale; f |= f_bench;
210         break;
211       bad_time:
212         die(2, "invalid time duration `%s'", optarg);
213
214       default:
215         f |= f_bogus;
216         break;
217     }
218   }
219   if (f&f_bogus) { usage(stderr); exit(2); }
220   if (!ofp) ofp = stdout;
221   if (!of) {
222     p = getenv("TVEC_FORMAT");
223     if (p) of = find_outform(p);
224   }
225   if (of) o = of->makefn(ofp);
226   else o = tvec_dfltout(ofp);
227
228   tm = bench_createtimer(benchconf);
229   if (tm) {
230     bench_init(&bench, tm); bench.target_s = benchtime;
231     tvec_benchstate = &bench;
232   } else if (f&f_bench) {
233     moan("failed to create benchmark timer");
234     if (!getenv("MLIB_BENCH_DEBUG"))
235       moan("set `MLIB_BENCH_DEBUG=t' in the envionment for more detail");
236     exit(2);
237   }
238
239   tvec_begin(tv_out, config, o); *argpos_out = optind;
240 }
241
242 /* --- @tvec_readstdin@, @tvec_readfile@, @tvec_readarg@ --- *
243  *
244  * Arguments:   @struct tvec_state *tv@ = test vector state
245  *              @const char *file@ = pathname of file to read
246  *              @const char *arg@ = argument to interpret
247  *
248  * Returns:     Zero on success, @-1@ on error.
249  *
250  * Use:         Read test vector data from stdin or a named file.  The
251  *              @tvec_readarg@ function reads from stdin if @arg@ is `%|-|%',
252  *              and from the named file otherwise.
253  */
254
255 int tvec_readstdin(struct tvec_state *tv)
256   { return (tvec_read(tv, "<stdin>", stdin)); }
257
258 int tvec_readfile(struct tvec_state *tv, const char *file)
259 {
260   FILE *fp = 0;
261   int rc;
262
263   fp = fopen(file, "r");
264   if (!fp) {
265     tvec_error(tv, "failed to open `%s' for reading: %s",
266                file, strerror(errno));
267     rc = -1; goto end;
268   }
269   if (tvec_read(tv, file, fp)) { rc = -1; goto end; }
270   rc = 0;
271 end:
272   if (fp) fclose(fp);
273   return (rc);
274 }
275
276 int tvec_readarg(struct tvec_state *tv, const char *arg)
277 {
278   int rc;
279
280   if (STRCMP(arg, ==, "-")) rc = tvec_readstdin(tv);
281   else rc = tvec_readfile(tv, arg);
282   return (rc);
283 }
284
285 /* --- @tvec_readdflt@ --- *
286  *
287  * Arguments:   @struct tvec_state *tv@ = test vector state
288  *              @const char *dflt@ = defsault filename or null
289  *
290  * Returns:     Zero on success, @-1@ on error.
291  *
292  * Use:         Reads from the default test vector data.  If @file@ is null,
293  *              then read from standard input, unless that's a terminal; if
294  *              @file@ is not null, then read the named file, looking in the
295  *              directory named by the `%|srcdir|%' environment variable if
296  *              that's set, or otherwise in the current directory.
297  */
298
299 int tvec_readdflt(struct tvec_state *tv, const char *dflt)
300 {
301   dstr d = DSTR_INIT;
302   const char *p;
303   int rc;
304
305   if (dflt) {
306     p = getenv("srcdir");
307     if (p) { dstr_putf(&d, "%s/%s", p, dflt); dflt = d.buf; }
308     rc = tvec_readfile(tv, dflt);
309   } else if (isatty(0))
310     rc = tvec_error(tv, "use `-' to force reading from interactive stdin");
311   else
312     rc = tvec_readstdin(tv);
313   dstr_destroy(&d);
314   return (rc);
315 }
316
317 /* --- @tvec_readargs@ --- *
318  *
319  * Arguments:   @int argc@ = number of command-line arguments
320  *              @char *argv[]@ = vector of argument strings
321  *              @struct tvec_state *tv@ = test vector state
322  *              @int *argpos_inout@ = current argument position (updated)
323  *              @const char *dflt@ = default filename or null
324  *
325  * Returns:     Zero on success, @-1@ on error.
326  *
327  * Use:         Reads from the sources indicated by the command-line
328  *              arguments, in order, interpreting each as for @tvec_readarg@;
329  *              if no arguments are given then read from @dflt@ as for
330  *              @tvec_readdflt@.
331  */
332
333 int tvec_readargs(int argc, char *argv[], struct tvec_state *tv,
334                    int *argpos_inout, const char *dflt)
335 {
336   int i = *argpos_inout;
337   int rc;
338
339   if (i == argc)
340     rc = tvec_readdflt(tv, dflt);
341   else {
342     rc = 0;
343     while (i < argc)
344       if (tvec_readarg(tv, argv[i++])) rc = -1;
345   }
346   *argpos_inout = i;
347   return (rc);
348 }
349
350 /* --- @tvec_main@ --- *
351  *
352  * Arguments:   @int argc@ = number of command-line arguments
353  *              @char *argv[]@ = vector of argument strings
354  *              @const struct tvec_config *cofig@ = test vector configuration
355  *              @const char *dflt@ = default filename or null
356  *
357  * Returns:     Exit code.
358  *
359  * Use:         All-in-one test vector front-end.  Parse options from the
360  *              command-line as for @tvec_parseargs@, and then process the
361  *              remaining positional arguments as for @tvec_readargs@.  The
362  *              function constructs and disposes of a test vector state.
363  */
364
365 int tvec_main(int argc, char *argv[],
366               const struct tvec_config *config, const char *dflt)
367 {
368   struct tvec_state tv;
369   int argpos;
370
371   tvec_parseargs(argc, argv, &tv, &argpos, config);
372   tvec_readargs(argc, argv, &tv, &argpos, dflt);
373   return (tvec_end(&tv));
374 }
375
376 /*----- That's all, folks -------------------------------------------------*/