1 /* $NetBSD: t_api.c,v 1.7 2014/12/10 04:38:02 christos Exp $ */ 2 3 /* 4 * Copyright (C) 2004, 2005, 2007-2010, 2013, 2014 Internet Systems Consortium, Inc. ("ISC") 5 * Copyright (C) 1999-2003 Internet Software Consortium. 6 * 7 * Permission to use, copy, modify, and/or distribute this software for any 8 * purpose with or without fee is hereby granted, provided that the above 9 * copyright notice and this permission notice appear in all copies. 10 * 11 * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH 12 * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 13 * AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, 14 * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 15 * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE 16 * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 17 * PERFORMANCE OF THIS SOFTWARE. 18 */ 19 20 /* Id: t_api.c,v 1.68 2010/12/21 04:20:23 marka Exp */ 21 22 /*! \file */ 23 24 #include <config.h> 25 26 #include <ctype.h> 27 #include <errno.h> 28 #include <limits.h> 29 #include <signal.h> 30 #include <stdarg.h> 31 #include <stdio.h> 32 #include <stdlib.h> 33 #include <time.h> 34 #include <unistd.h> 35 36 #ifndef WIN32 37 #include <sys/wait.h> 38 #else 39 #include <direct.h> 40 #endif 41 42 #include <isc/boolean.h> 43 #include <isc/commandline.h> 44 #include <isc/print.h> 45 #include <isc/string.h> 46 #include <isc/mem.h> 47 48 #include <dns/compress.h> 49 #include <dns/result.h> 50 51 #include "include/tests/t_api.h" 52 53 static const char *Usage = 54 "\t-a : run all tests\n" 55 "\t-b <dir> : chdir to dir before running tests" 56 "\t-c <config_file> : use specified config file\n" 57 "\t-d <debug_level> : set debug level to debug_level\n" 58 "\t-h : print test info\n" 59 "\t-u : print usage info\n" 60 "\t-n <test_name> : run specified test name\n" 61 "\t-t <test_number> : run specified test number\n" 62 "\t-x : don't execute tests in a subproc\n" 63 "\t-q <timeout> : use 'timeout' as the timeout value\n"; 64 /*!< 65 * -a --> run all tests 66 * -b dir --> chdir to dir before running tests 67 * -c config --> use config file 'config' 68 * -d --> turn on api debugging 69 * -h --> print out available test names 70 * -u --> print usage info 71 * -n name --> run test named name 72 * -tn --> run test n 73 * -x --> don't execute testcases in a subproc 74 * -q timeout --> use 'timeout' as the timeout value 75 */ 76 77 #define T_MAXTESTS 256 /*% must be 0 mod 8 */ 78 #define T_MAXENV 256 79 #define T_DEFAULT_CONFIG "t_config" 80 #define T_BUFSIZ 256 81 #define T_BIGBUF 4096 82 83 #define T_TCTOUT 60 84 85 int T_debug; 86 int T_timeout; 87 pid_t T_pid; 88 static const char * T_config; 89 static char T_tvec[T_MAXTESTS / 8]; 90 static char * T_env[T_MAXENV + 1]; 91 static char T_buf[T_BIGBUF]; 92 static char * T_dir; 93 #ifdef WIN32 94 static testspec_t T_testlist[T_MAXTESTS]; 95 #endif 96 97 static int 98 t_initconf(const char *path); 99 100 static int 101 t_dumpconf(const char *path); 102 103 static int 104 t_putinfo(const char *key, const char *info); 105 106 static char * 107 t_getdate(char *buf, size_t buflen); 108 109 static void 110 printhelp(void); 111 112 static void 113 printusage(void); 114 115 static int T_int; 116 117 static void 118 t_sighandler(int sig) { 119 T_int = sig; 120 } 121 122 int 123 #ifndef WIN32 124 main(int argc, char **argv) 125 #else 126 t_main(int argc, char **argv) 127 #endif 128 { 129 int c; 130 int tnum; 131 #ifndef WIN32 132 int subprocs; 133 pid_t deadpid; 134 int status; 135 #endif 136 int len; 137 isc_boolean_t first; 138 testspec_t *pts; 139 #ifndef WIN32 140 struct sigaction sa; 141 #endif 142 143 isc_mem_debugging = ISC_MEM_DEBUGRECORD; 144 first = ISC_TRUE; 145 #ifndef WIN32 146 subprocs = 1; 147 #endif 148 T_timeout = T_TCTOUT; 149 150 /* 151 * -a option is now default. 152 */ 153 memset(T_tvec, 0xff, sizeof(T_tvec)); 154 155 /* 156 * Parse args. 157 */ 158 while ((c = isc_commandline_parse(argc, argv, ":at:c:d:n:huxq:b:")) 159 != -1) { 160 if (c == 'a') { 161 /* 162 * Flag all tests to be run. 163 */ 164 memset(T_tvec, 0xff, sizeof(T_tvec)); 165 } 166 else if (c == 'b') { 167 T_dir = isc_commandline_argument; 168 } 169 else if (c == 't') { 170 tnum = atoi(isc_commandline_argument); 171 if ((tnum > 0) && (tnum < T_MAXTESTS)) { 172 if (first) { 173 /* 174 * Turn off effect of -a default 175 * and allow multiple -t and -n 176 * options. 177 */ 178 memset(T_tvec, 0, sizeof(T_tvec)); 179 first = ISC_FALSE; 180 } 181 /* 182 * Flag test tnum to be run. 183 */ 184 tnum -= 1; 185 T_tvec[tnum / 8] |= (0x01 << (tnum % 8)); 186 } 187 } 188 else if (c == 'c') { 189 T_config = isc_commandline_argument; 190 } 191 else if (c == 'd') { 192 T_debug = atoi(isc_commandline_argument); 193 } 194 else if (c == 'n') { 195 pts = &T_testlist[0]; 196 tnum = 0; 197 while (pts->pfv != NULL) { 198 if (! strcmp(pts->func_name, 199 isc_commandline_argument)) { 200 if (first) { 201 memset(T_tvec, 0, 202 sizeof(T_tvec)); 203 first = ISC_FALSE; 204 } 205 T_tvec[tnum/8] |= (0x01 << (tnum%8)); 206 break; 207 } 208 ++pts; 209 ++tnum; 210 } 211 if (pts->pfv == NULL) { 212 fprintf(stderr, "no such test %s\n", 213 isc_commandline_argument); 214 exit(1); 215 } 216 } 217 else if (c == 'h') { 218 printhelp(); 219 exit(0); 220 } 221 else if (c == 'u') { 222 printusage(); 223 exit(0); 224 } 225 else if (c == 'x') { 226 #ifndef WIN32 227 subprocs = 0; 228 #endif 229 } 230 else if (c == 'q') { 231 T_timeout = atoi(isc_commandline_argument); 232 } 233 else if (c == ':') { 234 fprintf(stderr, "Option -%c requires an argument\n", 235 isc_commandline_option); 236 exit(1); 237 } 238 else if (c == '?') { 239 fprintf(stderr, "Unrecognized option -%c\n", 240 isc_commandline_option); 241 exit(1); 242 } 243 } 244 245 /* 246 * Set cwd. 247 */ 248 249 if (T_dir != NULL && chdir(T_dir) != 0) { 250 fprintf(stderr, "chdir %s failed\n", T_dir); 251 exit(1); 252 } 253 254 /* 255 * We don't want buffered output. 256 */ 257 258 (void)setbuf(stdout, NULL); 259 (void)setbuf(stderr, NULL); 260 261 /* 262 * Setup signals. 263 */ 264 265 #ifndef WIN32 266 sa.sa_flags = 0; 267 sigfillset(&sa.sa_mask); 268 269 sa.sa_handler = t_sighandler; 270 (void)sigaction(SIGINT, &sa, NULL); 271 (void)sigaction(SIGALRM, &sa, NULL); 272 #endif 273 274 /* 275 * Output start stanza to journal. 276 */ 277 278 snprintf(T_buf, sizeof(T_buf), "%s:", argv[0]); 279 len = strlen(T_buf); 280 (void) t_getdate(T_buf + len, T_BIGBUF - len); 281 t_putinfo("S", T_buf); 282 283 /* 284 * Setup the test environment using the config file. 285 */ 286 287 if (T_config == NULL) 288 T_config = T_DEFAULT_CONFIG; 289 290 t_initconf(T_config); 291 if (T_debug) 292 t_dumpconf(T_config); 293 294 /* 295 * Now invoke all the test cases. 296 */ 297 298 tnum = 0; 299 pts = &T_testlist[0]; 300 while (*pts->pfv != NULL) { 301 if (T_tvec[tnum / 8] & (0x01 << (tnum % 8))) { 302 #ifndef WIN32 303 if (subprocs) { 304 T_pid = fork(); 305 if (T_pid == 0) { 306 (*pts->pfv)(); 307 exit(0); 308 } else if (T_pid > 0) { 309 310 T_int = 0; 311 sa.sa_handler = t_sighandler; 312 (void)sigaction(SIGALRM, &sa, NULL); 313 alarm(T_timeout); 314 315 deadpid = (pid_t) -1; 316 while (deadpid != T_pid) { 317 deadpid = 318 waitpid(T_pid, &status, 0); 319 if (deadpid == T_pid) { 320 if (WIFSIGNALED(status)) { 321 if (WTERMSIG(status) == 322 SIGTERM) 323 t_info( 324 "the test case timed out\n"); 325 else 326 t_info( 327 "the test case caused exception %d\n", 328 WTERMSIG(status)); 329 t_result(T_UNRESOLVED); 330 } 331 } else if ((deadpid == -1) && 332 (errno == EINTR) && 333 T_int) { 334 kill(T_pid, SIGTERM); 335 T_int = 0; 336 } 337 else if ((deadpid == -1) && 338 ((errno == ECHILD) || 339 (errno == ESRCH))) 340 break; 341 } 342 343 alarm(0); 344 sa.sa_handler = SIG_IGN; 345 (void)sigaction(SIGALRM, &sa, NULL); 346 } else { 347 t_info("fork failed, errno == %d\n", 348 errno); 349 t_result(T_UNRESOLVED); 350 } 351 } 352 else { 353 (*pts->pfv)(); 354 } 355 #else 356 (*pts->pfv)(); 357 #endif 358 } 359 ++pts; 360 ++tnum; 361 } 362 363 snprintf(T_buf, sizeof(T_buf), "%s:", argv[0]); 364 len = strlen(T_buf); 365 (void) t_getdate(T_buf + len, T_BIGBUF - len); 366 t_putinfo("E", T_buf); 367 368 return(0); 369 } 370 371 void 372 t_assert(const char *component, int anum, int class, const char *what, ...) { 373 va_list args; 374 char buf[T_BIGBUF]; 375 376 (void)printf("T:%s:%d:%s\n", component, anum, class == T_REQUIRED ? 377 "A" : "C"); 378 379 /* 380 * Format text to a buffer. 381 */ 382 va_start(args, what); 383 (void)vsnprintf(buf, sizeof(buf), what, args); 384 va_end(args); 385 386 (void)t_putinfo("A", buf); 387 (void)printf("\n"); 388 } 389 390 void 391 t_info(const char *format, ...) { 392 va_list args; 393 char buf[T_BIGBUF]; 394 395 va_start(args, format); 396 (void) vsnprintf(buf, sizeof(buf), format, args); 397 va_end(args); 398 (void) t_putinfo("I", buf); 399 } 400 401 void 402 t_result(int result) { 403 const char *p; 404 405 switch (result) { 406 case T_PASS: 407 p = "PASS"; 408 break; 409 case T_FAIL: 410 p = "FAIL"; 411 break; 412 case T_UNRESOLVED: 413 p = "UNRESOLVED"; 414 break; 415 case T_SKIPPED: 416 p = "SKIPPED"; 417 break; 418 case T_UNTESTED: 419 p = "UNTESTED"; 420 break; 421 case T_THREADONLY: 422 p = "THREADONLY"; 423 break; 424 case T_PKCS11ONLY: 425 p = "PKCS11ONLY"; 426 break; 427 default: 428 p = "UNKNOWN"; 429 break; 430 } 431 printf("R:%s\n", p); 432 } 433 434 char * 435 t_getenv(const char *name) { 436 char *n; 437 char **p; 438 size_t len; 439 440 n = NULL; 441 if (name && *name) { 442 443 p = &T_env[0]; 444 len = strlen(name); 445 446 while (*p != NULL) { 447 if (strncmp(*p, name, len) == 0) { 448 if ( *(*p + len) == '=') { 449 n = *p + len + 1; 450 break; 451 } 452 } 453 ++p; 454 } 455 } 456 return(n); 457 } 458 459 /* 460 * 461 * Read in the config file at path, initializing T_env. 462 * 463 * note: no format checking for now ... 464 * 465 */ 466 467 static int 468 t_initconf(const char *path) { 469 470 int n; 471 int rval; 472 char **p; 473 FILE *fp; 474 475 rval = -1; 476 477 fp = fopen(path, "r"); 478 if (fp != NULL) { 479 n = 0; 480 p = &T_env[0]; 481 while (n < T_MAXENV) { 482 *p = t_fgetbs(fp); 483 if (*p == NULL) 484 break; 485 if ((**p == '#') || (strchr(*p, '=') == NULL)) { 486 /* 487 * Skip comments and other junk. 488 */ 489 (void)free(*p); 490 continue; 491 } 492 ++p; ++n; 493 } 494 (void)fclose(fp); 495 rval = 0; 496 } 497 498 return (rval); 499 } 500 501 /* 502 * 503 * Dump T_env to stdout. 504 * 505 */ 506 507 static int 508 t_dumpconf(const char *path) { 509 int rval; 510 char **p; 511 FILE *fp; 512 513 rval = -1; 514 fp = fopen(path, "r"); 515 if (fp != NULL) { 516 p = &T_env[0]; 517 while (*p != NULL) { 518 printf("C:%s\n", *p); 519 ++p; 520 } 521 (void) fclose(fp); 522 rval = 0; 523 } 524 return(rval); 525 } 526 527 /* 528 * 529 * Read a newline or EOF terminated string from fp. 530 * On success: 531 * return a malloc'd buf containing the string with 532 * the newline converted to a '\0'. 533 * On error: 534 * return NULL. 535 * 536 * Caller is responsible for freeing buf. 537 * 538 */ 539 540 char * 541 t_fgetbs(FILE *fp) { 542 int c; 543 size_t n; 544 size_t size; 545 char *buf, *old; 546 char *p; 547 548 n = 0; 549 size = T_BUFSIZ; 550 old = buf = (char *) malloc(T_BUFSIZ * sizeof(char)); 551 552 if (buf != NULL) { 553 p = buf; 554 while ((c = fgetc(fp)) != EOF) { 555 556 if ((c == '\r') || (c == '\n')) 557 break; 558 559 *p++ = c; 560 ++n; 561 if ( n >= size ) { 562 size += T_BUFSIZ; 563 buf = (char *)realloc(buf, 564 size * sizeof(char)); 565 if (buf == NULL) 566 goto err; 567 old = buf; 568 p = buf + n; 569 } 570 } 571 *p = '\0'; 572 if (c == EOF && n == 0U) { 573 free(buf); 574 return (NULL); 575 } 576 return (buf); 577 } else { 578 err: 579 if (old != NULL) 580 free(old); 581 fprintf(stderr, "malloc/realloc failed %d", errno); 582 return(NULL); 583 } 584 } 585 586 /* 587 * 588 * Put info to log, using key. 589 * For now, just dump it out. 590 * Later format into pretty lines. 591 * 592 */ 593 594 static int 595 t_putinfo(const char *key, const char *info) { 596 int rval; 597 598 /* 599 * For now. 600 */ 601 rval = printf("%s:%s", key, info); 602 return(rval); 603 } 604 605 static char * 606 t_getdate(char *buf, size_t buflen) { 607 size_t n; 608 time_t t; 609 struct tm *p; 610 611 t = time(NULL); 612 p = localtime(&t); 613 n = strftime(buf, buflen - 1, "%A %d %B %H:%M:%S %Y\n", p); 614 return(n != 0U ? buf : NULL); 615 } 616 617 /* 618 * Some generally used utilities. 619 */ 620 struct dns_errormap { 621 isc_result_t result; 622 const char *text; 623 } dns_errormap[] = { 624 { ISC_R_SUCCESS, "ISC_R_SUCCESS" }, 625 { ISC_R_EXISTS, "ISC_R_EXISTS" }, 626 { ISC_R_NOTFOUND, "ISC_R_NOTFOUND" }, 627 { ISC_R_NOSPACE, "ISC_R_NOSPACE" }, 628 { ISC_R_UNEXPECTED, "ISC_R_UNEXPECTED" }, 629 { ISC_R_UNEXPECTEDEND, "ISC_R_UNEXPECTEDEND" }, 630 { ISC_R_RANGE, "ISC_R_RANGE" }, 631 { DNS_R_LABELTOOLONG, "DNS_R_LABELTOOLONG" }, 632 { DNS_R_BADESCAPE, "DNS_R_BADESCAPE" }, 633 /* { DNS_R_BADBITSTRING, "DNS_R_BADBITSTRING" }, */ 634 /* { DNS_R_BITSTRINGTOOLONG, "DNS_R_BITSTRINGTOOLONG"}, */ 635 { DNS_R_EMPTYLABEL, "DNS_R_EMPTYLABEL" }, 636 { DNS_R_BADDOTTEDQUAD, "DNS_R_BADDOTTEDQUAD" }, 637 { DNS_R_UNKNOWN, "DNS_R_UNKNOWN" }, 638 { DNS_R_BADLABELTYPE, "DNS_R_BADLABELTYPE" }, 639 { DNS_R_BADPOINTER, "DNS_R_BADPOINTER" }, 640 { DNS_R_TOOMANYHOPS, "DNS_R_TOOMANYHOPS" }, 641 { DNS_R_DISALLOWED, "DNS_R_DISALLOWED" }, 642 { DNS_R_EXTRATOKEN, "DNS_R_EXTRATOKEN" }, 643 { DNS_R_EXTRADATA, "DNS_R_EXTRADATA" }, 644 { DNS_R_TEXTTOOLONG, "DNS_R_TEXTTOOLONG" }, 645 { DNS_R_SYNTAX, "DNS_R_SYNTAX" }, 646 { DNS_R_BADCKSUM, "DNS_R_BADCKSUM" }, 647 { DNS_R_BADAAAA, "DNS_R_BADAAAA" }, 648 { DNS_R_NOOWNER, "DNS_R_NOOWNER" }, 649 { DNS_R_NOTTL, "DNS_R_NOTTL" }, 650 { DNS_R_BADCLASS, "DNS_R_BADCLASS" }, 651 { DNS_R_PARTIALMATCH, "DNS_R_PARTIALMATCH" }, 652 { DNS_R_NEWORIGIN, "DNS_R_NEWORIGIN" }, 653 { DNS_R_UNCHANGED, "DNS_R_UNCHANGED" }, 654 { DNS_R_BADTTL, "DNS_R_BADTTL" }, 655 { DNS_R_NOREDATA, "DNS_R_NOREDATA" }, 656 { DNS_R_CONTINUE, "DNS_R_CONTINUE" }, 657 { DNS_R_DELEGATION, "DNS_R_DELEGATION" }, 658 { DNS_R_GLUE, "DNS_R_GLUE" }, 659 { DNS_R_DNAME, "DNS_R_DNAME" }, 660 { DNS_R_CNAME, "DNS_R_CNAME" }, 661 { DNS_R_NXDOMAIN, "DNS_R_NXDOMAIN" }, 662 { DNS_R_NXRRSET, "DNS_R_NXRRSET" }, 663 { DNS_R_BADDB, "DNS_R_BADDB" }, 664 { DNS_R_ZONECUT, "DNS_R_ZONECUT" }, 665 { DNS_R_NOTZONETOP, "DNS_R_NOTZONETOP" }, 666 { DNS_R_SEENINCLUDE, "DNS_R_SEENINCLUDE" }, 667 { DNS_R_SINGLETON, "DNS_R_SINGLETON" }, 668 { (isc_result_t)0, NULL } 669 }; 670 671 isc_result_t 672 t_dns_result_fromtext(char *name) { 673 674 isc_result_t result; 675 struct dns_errormap *pmap; 676 677 result = ISC_R_UNEXPECTED; 678 679 pmap = dns_errormap; 680 while (pmap->text != NULL) { 681 if (strcmp(name, pmap->text) == 0) 682 break; 683 ++pmap; 684 } 685 686 if (pmap->text != NULL) 687 result = pmap->result; 688 689 return (result); 690 } 691 692 struct dc_method_map { 693 unsigned int dc_method; 694 const char *text; 695 } dc_method_map[] = { 696 697 { DNS_COMPRESS_NONE, "DNS_COMPRESS_NONE" }, 698 { DNS_COMPRESS_GLOBAL14, "DNS_COMPRESS_GLOBAL14" }, 699 { DNS_COMPRESS_ALL, "DNS_COMPRESS_ALL" }, 700 { 0, NULL } 701 }; 702 703 unsigned int 704 t_dc_method_fromtext(char *name) { 705 unsigned int dc_method; 706 struct dc_method_map *pmap; 707 708 dc_method = DNS_COMPRESS_NONE; 709 710 pmap = dc_method_map; 711 while (pmap->text != NULL) { 712 if (strcmp(name, pmap->text) == 0) 713 break; 714 ++pmap; 715 } 716 717 if (pmap->text != NULL) 718 dc_method = pmap->dc_method; 719 720 return(dc_method); 721 } 722 723 int 724 t_bustline(char *line, char **toks) { 725 int cnt; 726 char *p; 727 728 cnt = 0; 729 if (line && *line) { 730 while ((p = strtok(line, "\t")) && (cnt < T_MAXTOKS)) { 731 *toks++ = p; 732 line = NULL; 733 ++cnt; 734 } 735 } 736 return(cnt); 737 } 738 739 static void 740 printhelp(void) { 741 int cnt; 742 testspec_t *pts; 743 744 cnt = 1; 745 pts = &T_testlist[0]; 746 747 printf("Available tests:\n"); 748 while (pts->func_name) { 749 printf("\t%d\t%s\n", cnt, pts->func_name); 750 ++pts; 751 ++cnt; 752 } 753 } 754 755 static void 756 printusage(void) { 757 printf("Usage:\n%s\n", Usage); 758 } 759 760 int 761 t_eval(const char *filename, int (*func)(char **), int nargs) { 762 FILE *fp; 763 char *p; 764 int line; 765 int cnt; 766 int result; 767 int tresult; 768 int nfails; 769 int nprobs; 770 int npass; 771 char *tokens[T_MAXTOKS + 1]; 772 773 tresult = T_UNTESTED; 774 npass = 0; 775 nfails = 0; 776 nprobs = 0; 777 778 fp = fopen(filename, "r"); 779 if (fp != NULL) { 780 line = 0; 781 while ((p = t_fgetbs(fp)) != NULL) { 782 783 ++line; 784 785 /* 786 * Skip comment lines. 787 */ 788 if ((isspace((unsigned char)*p)) || (*p == '#')) { 789 (void)free(p); 790 continue; 791 } 792 793 cnt = t_bustline(p, tokens); 794 if (cnt == nargs) { 795 tresult = func(tokens); 796 switch (tresult) { 797 case T_PASS: 798 ++npass; 799 break; 800 case T_FAIL: 801 ++nfails; 802 break; 803 case T_SKIPPED: 804 case T_UNTESTED: 805 break; 806 default: 807 ++nprobs; 808 break; 809 } 810 } else { 811 t_info("bad format in %s at line %d\n", 812 filename, line); 813 ++nprobs; 814 } 815 816 (void)free(p); 817 } 818 (void)fclose(fp); 819 } else { 820 t_info("Missing datafile %s\n", filename); 821 ++nprobs; 822 } 823 824 result = T_UNRESOLVED; 825 826 if (nfails == 0 && nprobs == 0 && npass > 0) 827 result = T_PASS; 828 else if (nfails > 0) 829 result = T_FAIL; 830 else if (npass == 0) 831 result = tresult; 832 833 return (result); 834 } 835 836 #ifdef WIN32 837 void 838 t_settests(const testspec_t list[]) { 839 int tnum; 840 const testspec_t *pts; 841 842 memset(T_testlist, 0, sizeof(T_testlist)); 843 844 pts = &list[0]; 845 for (tnum = 0; tnum < T_MAXTESTS - 1; pts++, tnum++) { 846 if (*pts->pfv == NULL) 847 break; 848 T_testlist[tnum] = *pts; 849 } 850 } 851 #endif 852