3 Purpose: Test driver for imath library.
4 Author: M. J. Fromberger
6 Copyright (C) 2002-2008 Michael J. Fromberger, All Rights Reserved.
8 Permission is hereby granted, free of charge, to any person obtaining a copy
9 of this software and associated documentation files (the "Software"), to deal
10 in the Software without restriction, including without limitation the rights
11 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 copies of the Software, and to permit persons to whom the Software is
13 furnished to do so, subject to the following conditions:
15 The above copyright notice and this permission notice shall be included in
16 all copies or substantial portions of the Software.
18 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26 Reads tests from input files or standard input, and runs them. Tests have
31 The 'code' is a string identifying the test to be performed. The inputs and
32 outputs are comma-separated sequences of values. The format of each input
35 1005 number in decimal notation (signs ok)
36 #x-C0E number in hexadecimal notation
37 #b1011 number in binary notation
38 #o37750 number in octal notation
39 =k use register k for this input
41 For rational tests, the following syntax is also legal:
42 @5.33 use decimal notation (for rationals only)
43 may be combined with radix notation, e.g. #x@A0.5C
45 Each output is a string representing the value to which the corresponding
46 result is compared in order to pass the test. By default, tests are expected
47 to succeed (i.e., return MP_OK). To specify an alternate return value, use
48 the notation $RESULT, where RESULT is the name of an error (e.g., MP_MEMORY,
49 MP_UNDEF, etc.) or a numeric result denoted $#number (e.g., $#-5).
51 Results are written to standard output in the following formats:
53 filename<tab>line<tab>number<tab>result<eoln>
54 filename<tab>line<tab>number<tab>result<tab>message<eoln>
56 The filename and line give the offset of the test in its input file, the
57 number is the numbet of the test among all inputs, starting from 1.
58 The result is a textual description of the result code returned by the
59 operation being tested.
61 The exit status is 0 if all tests passed, 1 if one or more tests failed or
64 Note: There is currently a fixed limit on the length of lines by this test
65 ---- driver. You can increase it if you wish, but the code doesn't check;
66 lines over the length are truncated (split).
96 {"initu", 2, 1, test_init
}, /* r0 = uv(r1) */
97 {"initv", 2, 1, test_init
}, /* r0 = v(r1) */
98 {"setu", 2, 1, test_set
}, /* r0 = uv(r1) */
99 {"setv", 2, 1, test_set
}, /* r0 = v(r1) */
100 {"neg", 2, 1, test_neg
}, /* r1 = -r0 */
101 {"abs", 2, 1, test_abs
}, /* r1 = |r0| */
102 {"add", 3, 1, test_add
}, /* r3 = r1 + r2 */
103 {"addv", 3, 1, test_add
}, /* r3 = r1 + v(r2) */
104 {"sub", 3, 1, test_sub
}, /* r3 = r1 - r2 */
105 {"subv", 3, 1, test_sub
}, /* r3 = r1 - v(r2) */
106 {"mul", 3, 1, test_mul
}, /* r3 = r1 * r2 */
107 {"mulp2", 3, 1, test_mulp2
}, /* r3 = r1 * 2^v(r2) */
108 {"mulv", 3, 1, test_mulv
}, /* r3 = r1 * v(r2) */
109 {"sqr", 2, 1, test_sqr
}, /* r2 = r1 * r1 */
110 {"div", 4, 2, test_div
}, /* r2 = r1 / r2, r3 = r1 % r2 */
111 {"divp2", 4, 2, test_divp2
}, /* r2 = r1 / 2^v(r2),r3 = r1 % 2^v(r2)*/
112 {"divv", 3, 2, test_divv
}, /* r2 = r1 / v(r2), r3 = r1 % v(r2) */
113 {"expt", 3, 1, test_expt
}, /* r3 = r1 ^ v(r2) */
114 {"exptv", 3, 1, test_exptv
}, /* r3 = v(r1) ^ v(r2) */
115 {"exptf", 3, 1, test_exptf
}, /* r3 = r1 ^ r2 */
116 {"mod", 3, 1, test_mod
}, /* r3 = r1 % r2 */
117 {"gcd", 3, 1, test_gcd
}, /* r3 = gcd(r1, r2) */
118 {"egcd", 5, 3, test_egcd
}, /* r3 = gcd(r1, r2) = r1*r4 + r2*r5 */
119 {"lcm", 3, 1, test_lcm
}, /* r3 = lcm(r1, r2) */
120 {"sqrt", 2, 1, test_sqrt
}, /* r2 = sqrt(r1) */
121 {"root", 3, 1, test_root
}, /* r3 = r1^(1/v(r2)) */
122 {"invmod", 3, 1, test_invmod
}, /* r3 = r1^-1 mod r2 */
123 {"emod", 4, 1, test_exptmod
}, /* r4 = r1^r2 mod r3 */
124 {"emodev", 4, 1, test_exptmod_ev
}, /* r4 = r1^v(r2) mod r3 */
125 {"emodbv", 4, 1, test_exptmod_bv
}, /* r4 = v(r1)^r2 mod r3 */
126 {"cmp", 2, 1, test_comp
}, /* rtn = compare(r1, r2) */
127 {"cmpu", 2, 1, test_ucomp
}, /* rtn = compare(|r1|, |r2|) */
128 {"cmpz", 1, 1, test_zcomp
}, /* rtn = compare(r1, 0) */
129 {"cmpv", 2, 1, test_vcomp
}, /* rtn = compare(r1, v(r2)) */
130 {"cmpuv", 2, 1, test_uvcomp
}, /* rtn = compare(r1, v(r2)) */
131 {"tostr", 2, 1, test_tostr
}, /* r1: value, r2: radix, o1: result */
132 {"tobin", 1, 1, test_tobin
}, /* r1: value, o1: result binary */
133 {"readbin", 1, 1, test_read_binary
}, /* r1: 2's comp, o1: result value */
134 {"to-uns", 1, 1, test_to_uns
}, /* r1: value, o1: result binary */
135 {"readuns", 1, 1, test_read_uns
}, /* r1: unsigned, o1: result value */
136 {"to-int", 1, 1, test_to_int
}, /* r1: value, o1: result */
137 {"to-uint", 1, 1, test_to_uint
}, /* r1: value, o1: result */
138 {"meta", -1, -1, test_meta
},
139 {"qneg", 2, 1, test_qneg
}, /* r2 = -r1 */
140 {"qrecip", 2, 1, test_qrecip
}, /* r2 = 1 / r1 */
141 {"qabs", 2, 1, test_qabs
}, /* r2 = |r1| */
142 {"qadd", 3, 1, test_qadd
}, /* r3 = r1 + r2 */
143 {"qsub", 3, 1, test_qsub
}, /* r3 = r1 - r2 */
144 {"qmul", 3, 1, test_qmul
}, /* r3 = r1 * r2 */
145 {"qdiv", 3, 1, test_qdiv
}, /* r3 = r1 / r2 */
146 {"qaddz", 3, 1, test_qaddz
}, /* r3 = r1 + r2 */
147 {"qsubz", 3, 1, test_qsubz
}, /* r3 = r1 - r2 */
148 {"qmulz", 3, 1, test_qmulz
}, /* r3 = r1 * r2 */
149 {"qdivz", 3, 1, test_qdivz
}, /* r3 = r1 / r2 */
150 {"qexpt", 3, 1, test_qexpt
}, /* r3 = r1 ^ v(r2) */
151 {"qtostr", 2, 1, test_qtostr
}, /* r1: value, r2: radix; o1: result */
152 {"qtodec", 4, 1, test_qtodec
}, /* r1: val, r2: rdx, r3: prec,
153 r4: rounding mode; o1: res */
154 {"qrdec", 2, 1, test_qrdec
}, /* r1: dec, r2: rdx; o1: result value */
155 {"isprime", 1, 1, test_is_prime
}, /* rtn = prime(r1) ? MP_TRUE : MP_FALSE */
156 {NULL
, 0, 0, NULL
} /* end of list marker */
159 char g_line
[LINE_MAX
];
161 extern mp_result imath_errno
;
162 extern char *imath_errmsg
;
164 const char *g_imath_strerr
[] = {"MP_OK", "MP_TRUE", "MP_MEMORY", "MP_RANGE",
165 "MP_UNDEF", "MP_TRUNC", "MP_BADARG"};
167 bool process_file(char *file_name
, FILE *ifp
, FILE *ofp
);
168 int read_line(FILE *ifp
, char *line
, int limit
);
169 void trim_line(char *line
);
170 int is_blank(char *line
);
171 int parse_line(char *line
, testspec_t
*t
);
172 int count_fields(char *line
, int delim
);
173 void parse_fields(char *line
, int delim
, char **start
);
174 int run_test(int test_num
, testspec_t
*t
, FILE *ofp
);
175 void free_test(testspec_t
*t
);
176 int find_test(char *code
, test_t
*info
);
177 char *error_string(mp_result res
);
179 int main(int argc
, char *argv
[]) {
185 fprintf(stderr
, "[reading from stdin]\n");
186 if (!process_file("-", stdin
, stdout
)) exit_status
= 1;
191 for (i
= 1; i
< argc
; ++i
) {
192 if (strcmp(argv
[i
], "-") == 0) {
194 } else if ((ifp
= fopen(argv
[i
], "r")) == NULL
) {
195 fprintf(stderr
, "Cannot open '%s': %s\n", argv
[i
], strerror(errno
));
198 if (!process_file(argv
[i
], ifp
, stdout
)) exit_status
= 1;
206 /** Reads and runs test cases from `ifp` and writes test results to `ofp`. The
207 given `file_name` is used for cosmetic attribution. The return value is
208 true if all tests passed, false if any tests failed or had errors. */
209 bool process_file(char *file_name
, FILE *ifp
, FILE *ofp
) {
210 int res
, line_num
, test_num
= 0, num_failed
= 0, num_bogus
= 0;
211 clock_t start
, finish
;
214 while ((line_num
= read_line(ifp
, g_line
, LINE_MAX
)) != 0) {
218 if (parse_line(g_line
, &t
)) {
219 if ((res
= run_test(++test_num
, &t
, ofp
)) < 0) {
221 } else if (res
== 0) {
226 fprintf(stderr
, "Line %d: Incorrect input syntax.\n", line_num
);
233 "# %s %d tests: %d passed, %d failed, %d errors. (%.2f seconds)\n",
234 file_name
, test_num
, (test_num
- num_failed
- num_bogus
), num_failed
,
235 num_bogus
, ((double)(finish
- start
) / CLOCKS_PER_SEC
));
237 return num_failed
== 0 && num_bogus
== 0;
240 int read_line(FILE *ifp
, char *line
, int limit
) {
241 static FILE *current_fp
= NULL
;
242 static int current_line
= 0;
244 if (ifp
!= current_fp
) {
250 if (fgets(line
, limit
, ifp
) == NULL
) return 0;
253 } while (is_blank(line
));
259 /** Removes leading and trailing whitespace from a zero-terminated `line`. */
260 void trim_line(char *line
) {
264 /* Remove leading whitespace */
265 while (isspace((unsigned char)*fnw
)) ++fnw
;
268 memmove(line
, fnw
, len
);
270 /* Remove trailing whitespace (including linefeeds) */
271 fnw
= line
+ len
- 1;
272 while (fnw
>= line
&& isspace((unsigned char)*fnw
)) *fnw
-- = '\0';
275 /** Reports whether a zero-terminated `line` contains only whitespace after a
276 line-trailing comment (`# ...`) is removed. */
277 int is_blank(char *line
) {
278 while (*line
&& *line
!= '#' && isspace((unsigned char)*line
)) ++line
;
280 return *line
== '\0' || *line
== '#';
283 int parse_line(char *line
, testspec_t
*t
) {
284 char *code_brk
, *in_brk
;
287 if ((code_brk
= strchr(line
, ':')) == NULL
) return 0;
288 if ((in_brk
= strchr(code_brk
+ 1, ':')) == NULL
) return 0;
294 num_fields
= count_fields(code_brk
+ 1, ',');
295 t
->num_inputs
= num_fields
;
298 num_fields
= count_fields(in_brk
+ 1, ',');
299 t
->num_outputs
= num_fields
;
302 if (t
->num_inputs
> 0) {
303 t
->input
= calloc(t
->num_inputs
, sizeof(char *));
304 parse_fields(code_brk
+ 1, ',', t
->input
);
306 if (t
->num_outputs
> 0) {
307 t
->output
= calloc(t
->num_outputs
, sizeof(char *));
308 parse_fields(in_brk
+ 1, ',', t
->output
);
313 /** Returns the number of `delim` separated fields occur in `line`. */
314 int count_fields(char *line
, int delim
) {
317 if (*line
== '\0') return 0;
320 if (*line
== (char)delim
&& *(line
+ 1) != '\0') ++count
;
326 void parse_fields(char *line
, int delim
, char **start
) {
330 while ((line
= strchr(line
, delim
)) != NULL
) {
336 /** Runs the test cases specified by `t`, and writes its results to `ofp`. The
337 `test_num` is used in log output and should reflect the global ordering of
338 tests, but is not otherwise interpreted by this function.
340 This function returns 0 if the test succeeds, 1 if the test fails, and -1
341 if the test is broken (e.g., its code is unknown). */
342 int run_test(int test_num
, testspec_t
*t
, FILE *ofp
) {
345 /* Look up and reality check test parameters */
346 if (find_test(t
->code
, &info
) < 0) {
347 fprintf(stderr
, "Line %d: Test code '%s' is unknown.\n", t
->line
, t
->code
);
352 if (info
.num_inputs
>= 0 && t
->num_inputs
!= info
.num_inputs
) {
354 "Line %d: Wrong number of inputs to %s (want %d, have %d)\n",
355 t
->line
, t
->code
, info
.num_inputs
, t
->num_inputs
);
358 if (info
.num_outputs
>= 0 && t
->num_outputs
!= info
.num_outputs
) {
360 "Line %d: Wrong number of outputs to %s (want %d, have %d)\n",
361 t
->line
, t
->code
, info
.num_outputs
, t
->num_outputs
);
365 fprintf(stderr
, "Line %d: %d error(s), skipping this test.\n", t
->line
,
371 /* If return value is true, just print a generic OK message;
372 otherwise, it is assumed that imath_errno has been set to
373 a value indicating the problem. */
374 if ((info
.call
)(t
, ofp
)) {
375 fprintf(ofp
, "%s\t%d\t%d\tOK\n", t
->file
, t
->line
, test_num
);
377 } else if (imath_errno
>= MP_BADARG
) {
378 fprintf(ofp
, "%s\t%d\t%d\t%s\n", t
->file
, t
->line
, test_num
,
379 error_string(imath_errno
));
381 fprintf(ofp
, "%s\t%d\t%d\tFAILED\t%s\n", t
->file
, t
->line
, test_num
,
387 /** Locates the run instructions for the specified test `code`, and if they are
388 found populates `*info` with a copy. It returns -1 if `code` is unknown. */
389 int find_test(char *code
, test_t
*info
) {
392 while (g_tests
[i
].code
!= NULL
) {
393 if (strcmp(g_tests
[i
].code
, code
) == 0) {
402 /** Releases the memory occupied by a test case invocation. */
403 void free_test(testspec_t
*t
) {
406 if (t
->input
!= NULL
) {
410 if (t
->output
!= NULL
) {
416 /** Returns a static label string describing `res`. Note that this is not the
417 same as the error string returned by `mp_error_string`, but corresponds to
418 the spelling of the constant for its value. */
419 char *error_string(mp_result res
) {
422 return (char *)g_imath_strerr
[v
];
425 /* Here there be dragons */