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