1 | /* |
2 | Name: imtest.c |
3 | Purpose: Test driver for imath library. |
4 | Author: M. J. Fromberger |
5 | |
6 | Copyright (C) 2002-2008 Michael J. Fromberger, All Rights Reserved. |
7 | |
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: |
14 | |
15 | The above copyright notice and this permission notice shall be included in |
16 | all copies or substantial portions of the Software. |
17 | |
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 |
24 | SOFTWARE. |
25 | |
26 | Reads tests from input files or standard input, and runs them. Tests have |
27 | the form: |
28 | |
29 | code:inputs:outputs |
30 | |
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 |
33 | is: |
34 | |
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 |
40 | |
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 |
44 | |
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). |
50 | |
51 | Results are written to standard output in the following formats: |
52 | |
53 | filename<tab>line<tab>number<tab>result<eoln> |
54 | filename<tab>line<tab>number<tab>result<tab>message<eoln> |
55 | |
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. |
60 | |
61 | The exit status is 0 if all tests passed, 1 if one or more tests failed or |
62 | had errors. |
63 | |
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). |
67 | */ |
68 | |
69 | #include <assert.h> |
70 | #include <ctype.h> |
71 | #include <errno.h> |
72 | #include <limits.h> |
73 | #include <stdio.h> |
74 | #include <stdlib.h> |
75 | #include <string.h> |
76 | #include <time.h> |
77 | |
78 | #include "imath.h" |
79 | #include "imdrover.h" |
80 | |
81 | #ifdef LINE_MAX |
82 | #undef LINE_MAX |
83 | #endif |
84 | |
85 | #define LINE_MAX 4096 |
86 | |
87 | typedef struct { |
88 | char *code; |
89 | int num_inputs; |
90 | int num_outputs; |
91 | test_f call; |
92 | } test_t; |
93 | |
94 | test_t g_tests[] = { |
95 | /* What it does... */ |
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 */ |
157 | }; |
158 | |
159 | char g_line[LINE_MAX]; |
160 | |
161 | extern mp_result imath_errno; |
162 | extern char *imath_errmsg; |
163 | |
164 | const char *g_imath_strerr[] = {"MP_OK" , "MP_TRUE" , "MP_MEMORY" , "MP_RANGE" , |
165 | "MP_UNDEF" , "MP_TRUNC" , "MP_BADARG" }; |
166 | |
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); |
178 | |
179 | int main(int argc, char *argv[]) { |
180 | int exit_status = 0; |
181 | |
182 | init_testing(); |
183 | |
184 | if (argc == 1) { |
185 | fprintf(stderr, format: "[reading from stdin]\n" ); |
186 | if (!process_file(file_name: "-" , stdin, stdout)) exit_status = 1; |
187 | } else { |
188 | FILE *ifp; |
189 | int i; |
190 | |
191 | for (i = 1; i < argc; ++i) { |
192 | if (strcmp(s1: argv[i], s2: "-" ) == 0) { |
193 | ifp = stdin; |
194 | } else if ((ifp = fopen(filename: argv[i], modes: "r" )) == NULL) { |
195 | fprintf(stderr, format: "Cannot open '%s': %s\n" , argv[i], strerror(errno)); |
196 | return 1; |
197 | } |
198 | if (!process_file(file_name: argv[i], ifp, stdout)) exit_status = 1; |
199 | |
200 | fclose(stream: ifp); |
201 | } |
202 | } |
203 | return exit_status; |
204 | } |
205 | |
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; |
212 | |
213 | start = clock(); |
214 | while ((line_num = read_line(ifp, line: g_line, LINE_MAX)) != 0) { |
215 | testspec_t t; |
216 | t.line = line_num; |
217 | t.file = file_name; |
218 | if (parse_line(line: g_line, t: &t)) { |
219 | if ((res = run_test(test_num: ++test_num, t: &t, ofp)) < 0) { |
220 | ++num_bogus; |
221 | } else if (res == 0) { |
222 | ++num_failed; |
223 | } |
224 | free_test(t: &t); |
225 | } else { |
226 | fprintf(stderr, format: "Line %d: Incorrect input syntax.\n" , line_num); |
227 | ++num_bogus; |
228 | } |
229 | } |
230 | finish = clock(); |
231 | |
232 | fprintf(stream: ofp, |
233 | format: "# %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)); |
236 | |
237 | return num_failed == 0 && num_bogus == 0; |
238 | } |
239 | |
240 | int read_line(FILE *ifp, char *line, int limit) { |
241 | static FILE *current_fp = NULL; |
242 | static int current_line = 0; |
243 | |
244 | if (ifp != current_fp) { |
245 | current_fp = ifp; |
246 | current_line = 0; |
247 | } |
248 | |
249 | do { |
250 | if (fgets(s: line, n: limit, stream: ifp) == NULL) return 0; |
251 | |
252 | ++current_line; |
253 | } while (is_blank(line)); |
254 | |
255 | trim_line(line); |
256 | return current_line; |
257 | } |
258 | |
259 | /** Removes leading and trailing whitespace from a zero-terminated `line`. */ |
260 | void trim_line(char *line) { |
261 | int len; |
262 | char *fnw = line; |
263 | |
264 | /* Remove leading whitespace */ |
265 | while (isspace((unsigned char)*fnw)) ++fnw; |
266 | |
267 | len = strlen(s: fnw); |
268 | memmove(dest: line, src: fnw, n: len); |
269 | |
270 | /* Remove trailing whitespace (including linefeeds) */ |
271 | fnw = line + len - 1; |
272 | while (fnw >= line && isspace((unsigned char)*fnw)) *fnw-- = '\0'; |
273 | } |
274 | |
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; |
279 | |
280 | return *line == '\0' || *line == '#'; |
281 | } |
282 | |
283 | int parse_line(char *line, testspec_t *t) { |
284 | char *code_brk, *in_brk; |
285 | int num_fields; |
286 | |
287 | if ((code_brk = strchr(s: line, c: ':')) == NULL) return 0; |
288 | if ((in_brk = strchr(s: code_brk + 1, c: ':')) == NULL) return 0; |
289 | |
290 | *code_brk = '\0'; |
291 | t->code = line; |
292 | *in_brk = '\0'; |
293 | |
294 | num_fields = count_fields(line: code_brk + 1, delim: ','); |
295 | t->num_inputs = num_fields; |
296 | t->input = NULL; |
297 | |
298 | num_fields = count_fields(line: in_brk + 1, delim: ','); |
299 | t->num_outputs = num_fields; |
300 | t->output = NULL; |
301 | |
302 | if (t->num_inputs > 0) { |
303 | t->input = calloc(nmemb: t->num_inputs, size: sizeof(char *)); |
304 | parse_fields(line: code_brk + 1, delim: ',', start: t->input); |
305 | } |
306 | if (t->num_outputs > 0) { |
307 | t->output = calloc(nmemb: t->num_outputs, size: sizeof(char *)); |
308 | parse_fields(line: in_brk + 1, delim: ',', start: t->output); |
309 | } |
310 | return 1; |
311 | } |
312 | |
313 | /** Returns the number of `delim` separated fields occur in `line`. */ |
314 | int count_fields(char *line, int delim) { |
315 | int count = 1; |
316 | |
317 | if (*line == '\0') return 0; |
318 | |
319 | while (*line) { |
320 | if (*line == (char)delim && *(line + 1) != '\0') ++count; |
321 | ++line; |
322 | } |
323 | return count; |
324 | } |
325 | |
326 | void parse_fields(char *line, int delim, char **start) { |
327 | int pos = 0; |
328 | |
329 | start[pos++] = line; |
330 | while ((line = strchr(s: line, c: delim)) != NULL) { |
331 | *line++ = '\0'; |
332 | start[pos++] = line; |
333 | } |
334 | } |
335 | |
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. |
339 | |
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) { |
343 | test_t info; |
344 | |
345 | /* Look up and reality check test parameters */ |
346 | if (find_test(code: t->code, info: &info) < 0) { |
347 | fprintf(stderr, format: "Line %d: Test code '%s' is unknown.\n" , t->line, t->code); |
348 | return -1; |
349 | } else { |
350 | int errs = 0; |
351 | |
352 | if (info.num_inputs >= 0 && t->num_inputs != info.num_inputs) { |
353 | fprintf(stderr, |
354 | format: "Line %d: Wrong number of inputs to %s (want %d, have %d)\n" , |
355 | t->line, t->code, info.num_inputs, t->num_inputs); |
356 | ++errs; |
357 | } |
358 | if (info.num_outputs >= 0 && t->num_outputs != info.num_outputs) { |
359 | fprintf(stderr, |
360 | format: "Line %d: Wrong number of outputs to %s (want %d, have %d)\n" , |
361 | t->line, t->code, info.num_outputs, t->num_outputs); |
362 | ++errs; |
363 | } |
364 | if (errs) { |
365 | fprintf(stderr, format: "Line %d: %d error(s), skipping this test.\n" , t->line, |
366 | errs); |
367 | return -1; |
368 | } |
369 | } |
370 | |
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(stream: ofp, format: "%s\t%d\t%d\tOK\n" , t->file, t->line, test_num); |
376 | return 1; |
377 | } else if (imath_errno >= MP_BADARG) { |
378 | fprintf(stream: ofp, format: "%s\t%d\t%d\t%s\n" , t->file, t->line, test_num, |
379 | error_string(res: imath_errno)); |
380 | } else { |
381 | fprintf(stream: ofp, format: "%s\t%d\t%d\tFAILED\t%s\n" , t->file, t->line, test_num, |
382 | imath_errmsg); |
383 | } |
384 | return 0; |
385 | } |
386 | |
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) { |
390 | int i = 0; |
391 | |
392 | while (g_tests[i].code != NULL) { |
393 | if (strcmp(s1: g_tests[i].code, s2: code) == 0) { |
394 | *info = g_tests[i]; |
395 | return i; |
396 | } |
397 | ++i; |
398 | } |
399 | return -1; |
400 | } |
401 | |
402 | /** Releases the memory occupied by a test case invocation. */ |
403 | void free_test(testspec_t *t) { |
404 | assert(t != NULL); |
405 | |
406 | if (t->input != NULL) { |
407 | free(ptr: t->input); |
408 | t->input = NULL; |
409 | } |
410 | if (t->output != NULL) { |
411 | free(ptr: t->output); |
412 | t->output = NULL; |
413 | } |
414 | } |
415 | |
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) { |
420 | int v = abs(x: res); |
421 | |
422 | return (char *)g_imath_strerr[v]; |
423 | } |
424 | |
425 | /* Here there be dragons */ |
426 | |