1#!/usr/bin/env python 2# Author: Pierre Schnizer <schnizer@users.sourceforge.net> 2016, 2017 3# $Id: process_tests.py,v 1.1 2016/04/18 17:08:30 schnizer Exp $ 4"""extracts the tests defined in test_sf* funcions and converts them to 5unittests 6 7Reads all test_* files from GSL source directory. 8Extracts the test_sf macros one by one. 9The arguments of the macros are converted into sf_test_types cls instances 10These can be used later on to convert automatically tests from its 11""" 12from __future__ import print_function, division 13import re 14 15 16import sf_test_types 17import generate_sf_tests 18 19# Patterns for finding the test macros 20test_macro_match = re.compile("^.*(?P<macro>TEST_SF.*?)\s*?\(.*$") 21# Up to now only the macro TEST_SF() and TEST_SF_2 is converted 22macro_args_match = re.compile("^.*?TEST_SF\s*?(?P<params>\(.*\))\s*?$") 23macro_args_rlx_match = re.compile("^.*?TEST_SF_RLX\s*?(?P<params>\(.*\))\s*?$") 24macro_args_theta_match = re.compile("^.*?TEST_SF_THETA\s*?(?P<params>\(.*\))\s*?$") 25macro_args2_match = re.compile("^.*?TEST_SF_2\s*?(?P<params>\(.*\))\s*?$") 26 27_pattern_arg = ".+?" 28_pattern_sf_result_arg = '\(.+?\)' 29_test_sf_args = ( 30 r'\((?P<s>%s),(?P<func>%s), (?P<args>%s),(?P<result>%s),\s*(?P<tolerance>%s),\s*(?P<status>%s)\)' 31 %(_pattern_arg, _pattern_arg, _pattern_sf_result_arg, _pattern_arg, _pattern_arg, _pattern_arg) 32 ) 33analyse_macro_match = re.compile(_test_sf_args) 34 35_test_sf_args_theta = ( 36 r'\((?P<s>%s),(?P<func>%s), (?P<args>%s),(?P<result>%s),\s*(?P<tolerance>%s)\)' 37 %(_pattern_arg, _pattern_arg, _pattern_sf_result_arg, _pattern_arg, _pattern_arg) 38 ) 39analyse_macro_match_theta = re.compile(_test_sf_args_theta) 40 41_test_sf2_args = ( 42 r'\((?P<s>%s),(?P<func>%s), (?P<args>%s),\s*(?P<result1>%s),\s*(?P<tolerance1>%s),\s*(?P<result2>%s),\s*(?P<tolerance2>%s),\s*(?P<status>%s)\)' 43 %(_pattern_arg, _pattern_arg, _pattern_sf_result_arg, _pattern_arg, _pattern_arg, _pattern_arg, _pattern_arg, _pattern_arg) 44 ) 45analyse_macro2_match = re.compile(_test_sf2_args) 46 47#test_list_file_name = "test_list.dump" 48 49func_exclude_list = ( 50 "gsl_sf_coupling_3j_e", 51 "gsl_sf_exp_mult_e", 52 "gsl_sf_exp_mult_err_e", 53 "gsl_sf_lambert_W0_e", 54 "gsl_sf_lambert_Wm1_e", 55 #"gsl_sf_mathieu_ce_e" 56 #"gsl_sf_mathieu_se_e" 57 ) 58 59 60 61def dbl_str_to_int(a_str): 62 """Convert a double to int checking that rounding error is tolerable 63 64 Some GSL tests set an int value with classical double format. 65 """ 66 test_val = float(a_str) 67 prep = round(test_val) 68 diff = test_val - prep 69 70 a_diff = abs(diff) 71 if a_diff == 0: 72 result = int(prep) 73 return result 74 75 msg = "Can not convert '%s' reliably to int diff = '%s'" %(a_str, diff) 76 raise ValueError(msg) 77 78 79 parts = a_str.split(".") 80 l = len(parts) 81 if l == 0: 82 result = int(a_str) 83 return result 84 85 elif l == 1: 86 result = int(parts[0]) 87 return result 88 89 elif l == 2: 90 test = int(parts[1]) 91 92 if test != 0: 93 msg = "Can not convert '%s' split to '%s' to int" %(a_str, parts) 94 raise ValueError(msg) 95 96 result = int(parts[0]) 97 return result 98 99 else: 100 msg = "Can not convert '%s' split to '%d' parts to int" %(a_str, l) 101 raise ValueError(msg) 102 103class _build_sf_params: 104 """Each macro call is stored in an instance of _t_store which is an instance 105 of a subclass of _test_sf_params. The info stored there is then later used 106 for writing the particular test case """ 107 108 _t_store = None 109 def __init__(self, macro_args_line): 110 self._text = macro_args_line 111 self._store = None 112 # To be implemented in the subclass. Should create an instance of 113 # _t_store and feed the marco arguments to this instance 114 self._HandleText() 115 116 def GetStore(self): 117 return self._store 118 119 def __str__(self): 120 return str(self._store) 121 122 def _ConvertNonNumericArg(self, arg, func_name): 123 "" 124 if "gsl_sf_airy" in func_name: 125 if arg == 'm': 126 return "GSL_MODE_DEFAULT" 127 else: 128 raise ValueError("airy test variable '%s' unknown" % (arg,)) 129 130 elif "gsl_sf_ellint" in func_name: 131 if arg == 'mode': 132 return "GSL_MODE_DEFAULT" 133 if arg == "GSL_NAN": 134 return "GSL_NAN" 135 # else: 136 # raise ValueError("ellint test variable '%s' unknown" % (arg,)) 137 138 return arg 139 140 def _ConvertArg(self, arg, func_name): 141 tmp = None 142 try: 143 tmp = int(arg) 144 except ValueError: 145 pass 146 147 if tmp == None: 148 try: 149 tmp = float(arg) 150 except ValueError: 151 pass 152 153 if tmp == None: 154 tmp = self._ConvertNonNumericArg(arg, func_name) 155 156 if tmp == None: 157 result = arg 158 else: 159 result = tmp 160 161 return result 162 163 164 def _HandleArgs_ForFunc(self, func_name, args): 165 """ 166 multiply_e x... has to be set to 0.2 DBL_MAX 167 168 Test for some functions require special treatment: values with '.0' for 169 int arguments 170 """ 171 172 if "sf_multiply_e" in func_name: 173 x_rep = "(0.2 * DBL_MAX)" 174 result = [] 175 for arg in args: 176 arg = arg.replace("x", x_rep) 177 result.append(arg) 178 179 return result 180 181 182 first_arg_to_int = 0 183 184 if "sf_zetam1_int_e" in func_name: 185 first_arg_to_int = 1 186 187 if "sf_zeta_int_e" in func_name: 188 first_arg_to_int = 1 189 190 if "sf_exprel_n_e" in func_name: 191 #(s, gsl_sf_exprel_n_e, (1263131.0, 1261282.3637, &r), 545.0113107238425900305428360, TEST_TOL4, GSL_SUCCESS) 192 first_arg_to_int = 1 193 194 if "sf_laguerre_n_e" in func_name: 195 arg0 = args[0] 196 197 if arg0 == "1e5": 198 arg0 = int(1e5) 199 elif arg0 == "1e5+1": 200 arg0 = int(1e5+1) 201 elif arg0 == "1e6+1": 202 arg0 = int(1e6+1) 203 elif arg0 == "5e6+1": 204 arg0 = int(1e6+1) 205 elif arg0 == "8e6+1": 206 arg0 = int(8e6+1) 207 elif arg0 == "1e7+1": 208 arg0 = int(1e7+1) 209 elif arg0 == "1e8+1": 210 arg0 = int(1e8+1) 211 elif arg0 == "1e9+1": 212 arg0 = int(1e8+1) 213 else: 214 first_arg_to_int = 1 215 args = (arg0,) + args[1:] 216 217 if first_arg_to_int == 1: 218 # First arg should be an int 219 arg = args[0] 220 try: 221 arg = dbl_str_to_int(arg) 222 except ValueError as ve: 223 msg = "func '%s' args '%s'" %(func_name, args) 224 ve.args += (msg, ) 225 raise ve 226 227 args = (arg,) + args[1:] 228 return args 229 230 else: 231 return args 232 233 raise ValueError("Should not end up here") 234 235 236class build_sf_params(_build_sf_params): 237 """Used for the arguments found in the macro TEST_SF() 238 """ 239 _t_store = sf_test_types.test_sf_params 240 241 def _HandleArgs(self, args_text, func_name): 242 """Process the arguments to the functions 243 """ 244 assert(args_text[0] == "(") 245 assert(args_text[-1] == ")") 246 247 args_text = args_text[1:-1] 248 249 args = args_text.split(",") 250 251 gsl_sf_result = args[-1] 252 gsl_sf_result = gsl_sf_result.strip() 253 if gsl_sf_result != "&r": 254 msg = "Failed to see gsl_sf_result for func '%s'(%s): '%s' <-gsl_sf_result" 255 raise ValueError(msg %(func_name, args_text, gsl_sf_result)) 256 257 args = args[:-1] 258 259 args = tuple(map(lambda s: s.strip(), args)) 260 args = self._HandleArgs_ForFunc(func_name, args) 261 262 l = [] 263 for arg in args: 264 tmp = self._ConvertArg(arg, func_name) 265 l.append(tmp) 266 args = tuple(l) 267 return args 268 269 def _HandleText(self): 270 assert(self._t_store) 271 text = self._text 272 m = analyse_macro_match.match(text) 273 assert(m) 274 d = m.groupdict() 275 func = d["func"] 276 args = d["args"] 277 result = d["result"] 278 status = d["status"] 279 tolerance = d["tolerance"] 280 281 func = func.strip() 282 args = args.strip() 283 result = result.strip() 284 status = status.strip() 285 text = text.strip() 286 tolerance = tolerance.strip() 287 288 if func in func_exclude_list: 289 return 290 291 args = self._HandleArgs(args, func) 292 293 store = self._t_store( 294 func = func , 295 result = result, 296 args = args , 297 status = status, 298 tolerance = tolerance, 299 text = text 300 ) 301 302 self._store = store 303 304 305class build_sf_params_rlx(build_sf_params): 306 """RLX same as SF ... but what's the difference? 307 """ 308 _t_store = sf_test_types.test_sf_params_rlx 309 310class build_sf_params_theta(build_sf_params): 311 """signature similar to TEST_SF ... but returns always success? 312 """ 313 _t_store = sf_test_types.test_sf_params_theta 314 def _HandleArgs(self, args_text, func_name): 315 """Process the arguments to the functions 316 """ 317 assert(args_text[0] == "(") 318 assert(args_text[-1] == ")") 319 320 args_text = args_text[1:-1] 321 322 # No reference to GSL struct for this test macro 323 # as for TEST_SF 324 args = args_text.split(",") 325 326 args = tuple(map(lambda s: s.strip(), args)) 327 args = self._HandleArgs_ForFunc(func_name, args) 328 329 l = [] 330 for arg in args: 331 tmp = self._ConvertArg(arg, func_name) 332 l.append(tmp) 333 args = tuple(l) 334 return args 335 336 def _HandleText(self): 337 assert(self._t_store) 338 text = self._text 339 m = analyse_macro_match_theta.match(text) 340 assert(m) 341 d = m.groupdict() 342 func = d["func"] 343 args = d["args"] 344 result = d["result"] 345 status = "GSL_SUCCESS" 346 tolerance = d["tolerance"] 347 348 func = func.strip() 349 args = args.strip() 350 result = result.strip() 351 status = status.strip() 352 text = text.strip() 353 tolerance = tolerance.strip() 354 355 if func in func_exclude_list: 356 return 357 358 test = 0 359 try: 360 args = self._HandleArgs(args, func) 361 test = 1 362 finally: 363 if test == 0: 364 print("Handled macro text '%s' for TEST_SF_THETA" %(text,)) 365 366 store = self._t_store( 367 func = func , 368 result = result, 369 args = args , 370 status = status, 371 tolerance = tolerance, 372 text = text 373 ) 374 375 self._store = store 376 377 378class build_sf_params_2(_build_sf_params): 379 """Used for the arguments found in the macro TEST_SF() 380 """ 381 _t_store = sf_test_types.test_sf_params_2 382 def _HandleArgs(self, args_text, func_name): 383 """ 384 Arguments to the functions 385 """ 386 assert(args_text[0] == "(") 387 assert(args_text[-1] == ")") 388 389 args_text = args_text[1:-1] 390 391 args = args_text.split(",") 392 393 # Last to shall be gsl sf result 394 for cnt in range(2): 395 arg = args[-2 + cnt] 396 gsl_sf_result = arg 397 gsl_sf_result = gsl_sf_result.strip() 398 token = "&r%d" %(cnt+1,) 399 if gsl_sf_result != token: 400 msg = "Failed to find gsl_sf_result %d for func '%s'(%s): '%s' <- pointer to gsl_sf_result '%s'" 401 raise ValueError(msg %(cnt+1, func_name, args_text, gsl_sf_result, token)) 402 403 args = args[:-2] 404 405 args = tuple(map(lambda s: s.strip(), args)) 406 args = self._HandleArgs_ForFunc(func_name, args) 407 408 l = [] 409 for arg in args: 410 tmp = self._ConvertArg(arg, func_name) 411 l.append(tmp) 412 args = tuple(l) 413 return args 414 415 def _HandleText(self): 416 assert(self._t_store) 417 text = self._text 418 m = analyse_macro2_match.match(text) 419 assert(m) 420 d = m.groupdict() 421 func = d["func"] 422 args = d["args"] 423 status = d["status"] 424 result1 = d["result1"] 425 tolerance1 = d["tolerance1"] 426 result2 = d["result2"] 427 tolerance2 = d["tolerance2"] 428 429 func = func.strip() 430 args = args.strip() 431 status = status.strip() 432 text = text.strip() 433 result1 = result1.strip() 434 result2 = result2.strip() 435 tolerance1 = tolerance1.strip() 436 tolerance2 = tolerance2.strip() 437 438 if func in func_exclude_list: 439 return 440 441 args = self._HandleArgs(args, func) 442 443 store = self._t_store( 444 func = func , 445 args = args , 446 status = status, 447 result1 = result1, 448 tolerance1 = tolerance1, 449 result2 = result2, 450 tolerance2 = tolerance2, 451 text = text 452 ) 453 self._store = store 454 455 456def handle_match_test_sf(line): 457 """handle a line containing TEST_SF 458 459 460 line is expected to contain a TEST_SF() macro line 461 """ 462 m = macro_args_match.match(line) 463 if not m: 464 print("Can not extract macro args from line '%s'" %(line,)) 465 return 466 467 assert(m) 468 d = m.groupdict() 469 params = d["params"] 470 c = build_sf_params(params) 471 return c 472 473def handle_match_test_sf_theta(line): 474 """handle a line containing TEST_SF_THETA 475 476 477 line is expected to contain a TEST_SF_THETA() macro line 478 """ 479 m = macro_args_theta_match.match(line) 480 if not m: 481 print("Can not extract macro args from line '%s'" %(line,)) 482 return 483 484 assert(m) 485 d = m.groupdict() 486 params = d["params"] 487 c = build_sf_params_theta(params) 488 return c 489 490def handle_match_test_sf_rlx(line): 491 """handle a line containing TEST_SF_RLX 492 493 494 line is expected to contain a TEST_SF_RLX() macro line 495 """ 496 # XXX same pattern as SF but different functionality 497 m = macro_args_rlx_match.match(line) 498 if not m: 499 print("Can not extract macro args from line '%s'" %(line,)) 500 return 501 502 assert(m) 503 d = m.groupdict() 504 params = d["params"] 505 c = build_sf_params_rlx(params) 506 print("RLX Test:", c) 507 assert(c is not None) 508 return c 509 510def handle_match_test_sf_2(line): 511 """extract a TEST_SF_2 test 512 513 Args: 514 line: is expected to contain a TEST_SF_2() macro line 515 """ 516 m = macro_args2_match.match(line) 517 if not m: 518 print("Can not extract macro args from line '%s'" %(line,)) 519 return 520 521 assert(m) 522 d = m.groupdict() 523 params = d["params"] 524 c = build_sf_params_2(params) 525 return c 526 527 528 529_comment_start = re.compile(r"(\s*)(/\*.*)$") 530_comment_end = re.compile(r"(.*?\*/)(.*)$") 531 532def extract_comment(lines, max_lines = None): 533 """ 534 535 Args: 536 lines 537 538 Used to pass the original copyright along 539 """ 540 541 if max_lines is None: 542 max_lines = 200 543 else: 544 max_lines = int(max_lines) 545 546 for cnt in range(max_lines): 547 line = lines.pop(0) 548 m = _comment_start.match(line) 549 if m != None: 550 break 551 else: 552 msg = "Did not expect the comment not to start in %d lines" 553 raise ValueError(msg % (max_lines)) 554 555 comment_text = [line] 556 lines = [line] + lines 557 for cnt in range(max_lines): 558 # first comment started 559 line = lines.pop(0) 560 comment_text.append(line) 561 m = _comment_end.match(line) 562 if m != None: 563 # Fist comment finished 564 break 565 else: 566 msg = "Did not expect the comment not to finish in %d lines" 567 raise ValueError(msg % (max_lines)) 568 569 return comment_text, lines 570 571 572_extended_start = re.compile(r"\s*#ifdef(.*?)(\s.*)?$") 573_extended_end = re.compile(r"\s*#endif(.*?)(\s.*)?$") 574_comment_line = re.compile(r"^(.*)(/\*.*?\*/)(.*)$") 575 576def handle_one_test_file(a_file_name, verbose = None): 577 """ 578 seach for lines containing a macro starting with TEST_SF 579 580 Args: 581 a_file_name : name of a file containing TEST_SF lines 582 """ 583 try: 584 fp = open(a_file_name, "rt") 585 text = fp.readlines() 586 finally: 587 fp.close() 588 del fp 589 590 first_comment, text = extract_comment(text) 591 cnt_first_comment = len(first_comment) 592 593 # Remove trailing new lines: easier info print below .... 594 text = map(lambda line: line.strip(), text) 595 #--------------------------------------------------------------------------- 596 # Extract part which is between 597 # #ifdef 598 # #endif 599 _extended_test = False 600 # Exclude extended tests defined by 601 cnt = cnt_first_comment 602 cleaned_lines = [] 603 for line in text: 604 cnt +=1 605 if _extended_test == False: 606 et = _extended_start.match(line) 607 if et is not None: 608 _extended_test = True 609 grps = et.groups() 610 assert(len(grps) == 2) 611 fmt = ("'%s' %d extended test start with label: '%s' ignoring '%s'" + 612 "\n\t line '%s'") 613 if verbose: 614 print(fmt %(a_file_name, cnt, grps[0], grps[1], line)) 615 cleaned_lines.append(" ") 616 continue 617 else: 618 cleaned_lines.append(line) 619 620 elif _extended_test == True: 621 et = _extended_end.match(line) 622 if et is not None: 623 grps = et.groups() 624 assert(len(grps) == 2) 625 fmt = ("\t'%s' %d extended test end: '%s' ignoring '%s' " + 626 "\n\t line '%s'") 627 if verbose: 628 print(fmt %(a_file_name, cnt, grps[0], grps[1], line)) 629 _extended_test = False 630 else: 631 print("\tSkipped extended test", line) 632 cleaned_lines.append(" ") 633 else: 634 raise ValueError("value '%s' of _extended_test unknown" %(_extended_test,)) 635 #---------------------------------------------------------------------- 636 637 638 #--------------------------------------------------------------------------- 639 # Extract commment 640 text = cleaned_lines 641 cleaned_lines = [] 642 643 _comment_part = False 644 cnt = cnt_first_comment 645 for line in text: 646 cnt += 1 647 648 #print(line) 649 650 #---------------------------------------------------------------------- 651 # Exclude single line comments .... 652 m = _comment_line.match(line) 653 if m is not None: 654 orig_line = line 655 grps = m.groups() 656 assert(len(grps) == 3) 657 line = grps[0] + " " + grps[2] 658 fmt = ("%s:%d Ignoring comment in line '%s';\n\t continuing with %s;" + 659 "\n\t original line '%s'\n") 660 if verbose: 661 print(fmt % (a_file_name, cnt, grps[2], line, orig_line) ) 662 663 # ----------------------------------------------------------- 664 # State: comment part or not 665 # Exclude multi line comments .... 666 if _comment_part == False: 667 m = _comment_start.match(line) 668 if m is not None: 669 _comment_part = True 670 orig_line = line 671 grps = m.groups() 672 assert(len(grps) == 2) 673 line = grps[0] 674 fmt = ("%s:%d Comment start: Continuing with '%s'" + 675 " Ignoring part '%s' of line '%s'") 676 if verbose: 677 print(fmt %(a_file_name, cnt, line, grps[1], orig_line)) 678 elif _comment_part == True: 679 m = _comment_end.match(line) 680 if m is None: 681 fmt = "\tIgnoring line (part of comment) '%s' " 682 if verbose: 683 print(fmt %(line)) 684 # Keep it to the lines count of the original file 685 cleaned_lines.append(" ") 686 continue 687 else: 688 _comment_part = False 689 grp = m.groups() 690 orig_line = line 691 grps = m.groups() 692 assert(len(grps) == 2) 693 line = grps[1] 694 fmt = ("%s:%d Comment end:" + 695 "\n\t Continuing with '%s' Ignoring part '%s'" + 696 "\n\t of line '%s'" ) 697 if verbose: 698 print(fmt %(a_file_name, cnt, line, grps[0], orig_line)) 699 else: 700 raise ValueError("value '%s' of _comment_part unknown" %(_comment_part,)) 701 702 cleaned_lines.append(line) 703 704 # I want to extract the TEST_SF macros. Some of them span over more than 705 # one line. 706 text = cleaned_lines 707 text = map(lambda line: line.strip(), text) 708 text = "".join(text) 709 text = text.split(";") 710 711 712 713 all_tests = [] 714 cnt_val = 0 715 cnt_other = 0 716 cnt = cnt_first_comment 717 for line in text: 718 cnt += 1 719 #---------------------------------------------------------------------- 720 # Search for tests .... 721 m = test_macro_match.match(line) 722 if m: 723 macro_name = m.groupdict()["macro"] 724 #print("'%s' : '%s'" %(macro_name, line) ) 725 if macro_name == "TEST_SF": 726 c = handle_match_test_sf(line) 727 all_tests.append(c) 728 elif macro_name == "TEST_SF_RLX": 729 #print("Handling macro TEST_SF_RLX: '%s'" %(line,) ) 730 c = handle_match_test_sf_rlx(line) 731 all_tests.append(c) 732 elif macro_name == "TEST_SF_THETA": 733 #print("Handling macro TEST_SF_RLX: '%s'" %(line,) ) 734 c = handle_match_test_sf_theta(line) 735 all_tests.append(c) 736 elif macro_name == "TEST_SF_2": 737 #print("Handling macro TEST_SF_2: '%s' not yet implemented" %(line,) ) 738 c = handle_match_test_sf_2(line) 739 all_tests.append(c) 740 pass 741 elif macro_name == "TEST_SF_VAL": 742 #print("Handling macro TEST_SF_VAL: '%s' not yet implemented" %(line,) ) 743 cnt_val += 1 744 pass 745 else: 746 print("Handling macro '%s': '%s' not yet implemented" %(macro_name, line,) ) 747 cnt_other += 1 748 pass 749 #return 750 #---------------------------------------------------------------------- 751 752 print ("Still missing %d TEST_SF_VAL and %d other tests" %(cnt_val, cnt_other)) 753 return first_comment, all_tests 754 755