| 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 | |