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