16f5e9bd5SScott Kruger#!/usr/bin/env python 26f5e9bd5SScott Krugerimport fnmatch 36f5e9bd5SScott Krugerimport glob 46f5e9bd5SScott Krugerimport inspect 56f5e9bd5SScott Krugerimport os 66f5e9bd5SScott Krugerimport optparse 76f5e9bd5SScott Krugerimport pickle 86f5e9bd5SScott Krugerimport re 96f5e9bd5SScott Krugerimport sys 106f5e9bd5SScott Kruger 116f5e9bd5SScott Krugerthisfile = os.path.abspath(inspect.getfile(inspect.currentframe())) 126f5e9bd5SScott Krugerpdir = os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(thisfile))))) 136f5e9bd5SScott Krugersys.path.insert(0, os.path.join(pdir, 'config')) 146f5e9bd5SScott Kruger 156f5e9bd5SScott Krugerimport testparse 166f5e9bd5SScott Krugerfrom gmakegentest import nameSpace 176f5e9bd5SScott Kruger 186f5e9bd5SScott Kruger 196f5e9bd5SScott Kruger""" 206f5e9bd5SScott Kruger Tool for querying the tests. 216f5e9bd5SScott Kruger 226f5e9bd5SScott Kruger Which tests to query? Two options: 236f5e9bd5SScott Kruger 1. Query only the tests that are run for a given configuration. 246f5e9bd5SScott Kruger 2. Query all of the test files in the source directory 256f5e9bd5SScott Kruger For #1: 266f5e9bd5SScott Kruger Use dataDict as written out by gmakegentest.py in $PETSC_ARCH/$TESTBASE 276f5e9bd5SScott Kruger For #2: 286f5e9bd5SScott Kruger Walk the entire tree parsing the files as we go along using testparse. 296f5e9bd5SScott Kruger The tree walker is simpler than what is in gmakegentest.py 306f5e9bd5SScott Kruger 316f5e9bd5SScott Kruger The dataDict follows that generated by testparse. gmakegentest.py does 326f5e9bd5SScott Kruger further manipulations of the dataDict to handle things like for loops 336f5e9bd5SScott Kruger so if using #2, those modifications are not included. 346f5e9bd5SScott Kruger 356f5e9bd5SScott Kruger Querying: 366f5e9bd5SScott Kruger The dataDict dictionary is then "inverted" to create a dictionary with the 376f5e9bd5SScott Kruger range of field values as keys and list test names as the values. This 386f5e9bd5SScott Kruger allows fast searching 396f5e9bd5SScott Kruger 406f5e9bd5SScott Kruger""" 416f5e9bd5SScott Kruger 4285bc9deeSScott Krugerdef isFile(maybeFile): 4385bc9deeSScott Kruger ext=os.path.splitext(maybeFile)[1] 4485bc9deeSScott Kruger if not ext: return False 4585bc9deeSScott Kruger if ext not in ['.c','.cxx','.cpp','F90','F','cu']: return False 4685bc9deeSScott Kruger return True 4785bc9deeSScott Kruger 4885bc9deeSScott Krugerdef pathToLabel(path): 4985bc9deeSScott Kruger """ 5085bc9deeSScott Kruger Because the scripts have a non-unique naming, the pretty-printing 5185bc9deeSScott Kruger needs to convey the srcdir and srcfile. There are two ways of doing this. 5285bc9deeSScott Kruger """ 5385bc9deeSScott Kruger # Strip off any top-level directories or spaces 5485bc9deeSScott Kruger path=path.strip().replace(pdir,'') 5585bc9deeSScott Kruger path=path.replace('src/','') 5685bc9deeSScott Kruger if isFile(path): 5785bc9deeSScott Kruger prefix=os.path.dirname(path).replace("/","_") 5885bc9deeSScott Kruger suffix=os.path.splitext(os.path.basename(path))[0] 5985bc9deeSScott Kruger label=prefix+"-"+suffix+'_*' 6085bc9deeSScott Kruger else: 6185bc9deeSScott Kruger path=path.rstrip('/') 6285bc9deeSScott Kruger label=path.replace("/","_")+"-*" 6385bc9deeSScott Kruger return label 6485bc9deeSScott Kruger 6585bc9deeSScott Krugerdef get_value(varset): 6685bc9deeSScott Kruger """ 6785bc9deeSScott Kruger Searching args is a bit funky: 6885bc9deeSScott Kruger Consider 6985bc9deeSScott Kruger args: -ksp_monitor_short -pc_type ml -ksp_max_it 3 7085bc9deeSScott Kruger Search terms are: 7185bc9deeSScott Kruger ksp_monitor, 'pc_type ml', ksp_max_it 7285bc9deeSScott Kruger Also ignore all loops 7385bc9deeSScott Kruger -pc_fieldsplit_diag_use_amat {{0 1}} 7485bc9deeSScott Kruger Gives: pc_fieldsplit_diag_use_amat as the search term 7585bc9deeSScott Kruger Also ignore -f ... (use matrices from file) because I'll assume 7685bc9deeSScott Kruger that this kind of information isn't needed for testing. If it's 7785bc9deeSScott Kruger a separate search than just grep it 7885bc9deeSScott Kruger """ 7985bc9deeSScott Kruger if varset.startswith('-f '): return None 8085bc9deeSScott Kruger 8185bc9deeSScott Kruger # First remove loops 8285bc9deeSScott Kruger value=re.sub('{{.*}}','',varset) 8385bc9deeSScott Kruger # Next remove - 8485bc9deeSScott Kruger value=varset.lstrip("-") 8585bc9deeSScott Kruger # Get rid of numbers 8685bc9deeSScott Kruger value=re.sub(r"[+-]? *(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?",'',value) 8785bc9deeSScott Kruger # return without spaces 8885bc9deeSScott Kruger return value.strip() 8985bc9deeSScott Kruger 9085bc9deeSScott Krugerdef query(invDict,fields,labels): 916f5e9bd5SScott Kruger """ 926f5e9bd5SScott Kruger Search the keys using fnmatch to find matching names and return list with 936f5e9bd5SScott Kruger the results 946f5e9bd5SScott Kruger """ 9585bc9deeSScott Kruger setlist=[] # setlist is a list of lists that set opertions will operate on 9685bc9deeSScott Kruger llist=labels.replace('|',',').split(',') 9785bc9deeSScott Kruger i=-1 9885bc9deeSScott Kruger for field in fields.replace('|',',').split(','): 9985bc9deeSScott Kruger i+=1 10085bc9deeSScott Kruger label=llist[i] 10185bc9deeSScott Kruger if field == 'name': 10285bc9deeSScott Kruger if '/' in label: 10385bc9deeSScott Kruger label=pathToLabel(label) 104f538e455SScott Kruger elif label.startswith('src'): 105f538e455SScott Kruger label=label.lstrip('src').lstrip('*') 10685bc9deeSScott Kruger setlist.append(fnmatch.filter(invDict['name'],label)) 10785bc9deeSScott Kruger continue 1085b6dee57SScott Kruger 10985bc9deeSScott Kruger foundLabel=False # easy to do if you misspell argument search 11085bc9deeSScott Kruger for key in invDict[field]: 1116f5e9bd5SScott Kruger if fnmatch.filter([key],label): 11285bc9deeSScott Kruger foundLabel=True 1136f5e9bd5SScott Kruger # Do not return values with not unless label itself has not 1146f5e9bd5SScott Kruger if label.startswith('!') and not key.startswith('!'): continue 1156f5e9bd5SScott Kruger if not label.startswith('!') and key.startswith('!'): continue 11685bc9deeSScott Kruger setlist.append(invDict[field][key]) 11785bc9deeSScott Kruger if not foundLabel: 11885bc9deeSScott Kruger setlist.append([]) 1196f5e9bd5SScott Kruger 12085bc9deeSScott Kruger # Now process the union and intersection operators based on setlist 12185bc9deeSScott Kruger allresults=[] 12285bc9deeSScott Kruger # Union 12385bc9deeSScott Kruger i=-1 12485bc9deeSScott Kruger for ufield in fields.split(','): 12585bc9deeSScott Kruger i+=1 12685bc9deeSScott Kruger if '|' in ufield: 12785bc9deeSScott Kruger # Intersection 12885bc9deeSScott Kruger label=llist[i] 12985bc9deeSScott Kruger results=set(setlist[i]) 13085bc9deeSScott Kruger for field in ufield.split('|')[1:]: 13185bc9deeSScott Kruger i+=1 13285bc9deeSScott Kruger label=llist[i] 13385bc9deeSScott Kruger results=results.intersection(set(setlist[i])) 13485bc9deeSScott Kruger allresults+=list(results) 13585bc9deeSScott Kruger else: 13685bc9deeSScott Kruger allresults+=setlist[i] 1376f5e9bd5SScott Kruger 13885bc9deeSScott Kruger # remove duplicate entries and sort to give consistent results 13985bc9deeSScott Kruger uniqlist=list(set(allresults)) 14085bc9deeSScott Kruger uniqlist.sort() 14185bc9deeSScott Kruger return uniqlist 14285bc9deeSScott Kruger 14385bc9deeSScott Krugerdef get_inverse_dictionary(dataDict,fields,srcdir): 1446f5e9bd5SScott Kruger """ 1456f5e9bd5SScott Kruger Create a dictionary with the values of field as the keys, and the name of 1466f5e9bd5SScott Kruger the tests as the results. 1476f5e9bd5SScott Kruger """ 1486f5e9bd5SScott Kruger invDict={} 14985bc9deeSScott Kruger # Comma-delimited lists denote union 15085bc9deeSScott Kruger for field in fields.replace('|',',').split(','): 15185bc9deeSScott Kruger if field not in invDict: 15285bc9deeSScott Kruger if field == 'name': 15385bc9deeSScott Kruger invDict[field]=[] # List for ease 15485bc9deeSScott Kruger else: 15585bc9deeSScott Kruger invDict[field]={} 1566f5e9bd5SScott Kruger for root in dataDict: 1576f5e9bd5SScott Kruger for exfile in dataDict[root]: 1586f5e9bd5SScott Kruger for test in dataDict[root][exfile]: 159aec279ffSScott Kruger if test in testparse.buildkeys: continue 1606f5e9bd5SScott Kruger defroot = testparse.getDefaultOutputFileRoot(test) 16185bc9deeSScott Kruger fname=nameSpace(defroot,os.path.relpath(root,srcdir)) 1625b6dee57SScott Kruger if field == 'name': 16385bc9deeSScott Kruger invDict['name'].append(fname) 1645b6dee57SScott Kruger continue 1655b6dee57SScott Kruger if field not in dataDict[root][exfile][test]: continue 1666f5e9bd5SScott Kruger values=dataDict[root][exfile][test][field] 1676f5e9bd5SScott Kruger 16885bc9deeSScott Kruger if not field == 'args' and not field == 'diff_args': 1696f5e9bd5SScott Kruger for val in values.split(): 17085bc9deeSScott Kruger if val in invDict[field]: 17185bc9deeSScott Kruger invDict[field][val].append(fname) 1726f5e9bd5SScott Kruger else: 17385bc9deeSScott Kruger invDict[field][val] = [fname] 17485bc9deeSScott Kruger else: 17585bc9deeSScott Kruger # Args are funky. 17685bc9deeSScott Kruger for varset in re.split('(^|\W)-(?=[a-zA-Z])',values): 17785bc9deeSScott Kruger val=get_value(varset) 17885bc9deeSScott Kruger if not val: continue 17985bc9deeSScott Kruger if val in invDict[field]: 18085bc9deeSScott Kruger invDict[field][val].append(fname) 18185bc9deeSScott Kruger else: 18285bc9deeSScott Kruger invDict[field][val] = [fname] 18385bc9deeSScott Kruger # remove duplicate entries (multiple test/file) 18485bc9deeSScott Kruger if not field == 'name': 18585bc9deeSScott Kruger for val in invDict[field]: 18685bc9deeSScott Kruger invDict[field][val]=list(set(invDict[field][val])) 18785bc9deeSScott Kruger 1886f5e9bd5SScott Kruger return invDict 1896f5e9bd5SScott Kruger 1904e028dedSScott Krugerdef get_gmakegentest_data(testdir,petsc_dir,petsc_arch): 1916f5e9bd5SScott Kruger """ 1926f5e9bd5SScott Kruger Write out the dataDict into a pickle file 1936f5e9bd5SScott Kruger """ 1946f5e9bd5SScott Kruger # This needs to be consistent with gmakegentest.py of course 1954e028dedSScott Kruger pkl_file=os.path.join(testdir,'datatest.pkl') 1964e028dedSScott Kruger # If it doesn't exist, then we need to regenerate 1974e028dedSScott Kruger if not os.path.exists(pkl_file): 1984e028dedSScott Kruger startdir=os.path.abspath(os.curdir) 1994e028dedSScott Kruger os.chdir(petsc_dir) 2004e028dedSScott Kruger args='--petsc-dir='+petsc_dir+' --petsc-arch='+petsc_arch+' --testdir='+testdir 2014e028dedSScott Kruger buf = os.popen('config/gmakegentest.py '+args).read() 2024e028dedSScott Kruger os.chdir(startdir) 2034e028dedSScott Kruger 2044e028dedSScott Kruger fd = open(pkl_file, 'rb') 2056f5e9bd5SScott Kruger dataDict=pickle.load(fd) 2066f5e9bd5SScott Kruger fd.close() 2076f5e9bd5SScott Kruger return dataDict 2086f5e9bd5SScott Kruger 2096f5e9bd5SScott Krugerdef walktree(top): 2106f5e9bd5SScott Kruger """ 2116f5e9bd5SScott Kruger Walk a directory tree, starting from 'top' 2126f5e9bd5SScott Kruger """ 2136f5e9bd5SScott Kruger verbose = False 2146f5e9bd5SScott Kruger dataDict = {} 2156f5e9bd5SScott Kruger alldatafiles = [] 2166f5e9bd5SScott Kruger for root, dirs, files in os.walk(top, topdown=False): 2176f5e9bd5SScott Kruger if root == 'output': continue 2186f5e9bd5SScott Kruger if '.dSYM' in root: continue 2196f5e9bd5SScott Kruger if verbose: print(root) 2206f5e9bd5SScott Kruger 2216f5e9bd5SScott Kruger dataDict[root] = {} 2226f5e9bd5SScott Kruger 2236f5e9bd5SScott Kruger for exfile in files: 2246f5e9bd5SScott Kruger # Ignore emacs files 2256f5e9bd5SScott Kruger if exfile.startswith("#") or exfile.startswith(".#"): continue 2266f5e9bd5SScott Kruger ext=os.path.splitext(exfile)[1] 2276f5e9bd5SScott Kruger if ext[1:] not in ['c','cxx','cpp','cu','F90','F']: continue 2286f5e9bd5SScott Kruger 2296f5e9bd5SScott Kruger # Convenience 2306f5e9bd5SScott Kruger fullex = os.path.join(root, exfile) 2316f5e9bd5SScott Kruger if verbose: print(' --> '+fullex) 2326f5e9bd5SScott Kruger dataDict[root].update(testparse.parseTestFile(fullex, 0)) 2336f5e9bd5SScott Kruger 2346f5e9bd5SScott Kruger return dataDict 2356f5e9bd5SScott Kruger 23685bc9deeSScott Krugerdef do_query(use_source, startdir, srcdir, testdir, petsc_dir, petsc_arch, 23785bc9deeSScott Kruger fields, labels, searchin): 2386f5e9bd5SScott Kruger """ 2396f5e9bd5SScott Kruger Do the actual query 2406f5e9bd5SScott Kruger This part of the code is placed here instead of main() 2416f5e9bd5SScott Kruger to show how one could translate this into ipython/jupyer notebook 2426f5e9bd5SScott Kruger commands for more advanced queries 2436f5e9bd5SScott Kruger """ 2446f5e9bd5SScott Kruger # Get dictionary 2456f5e9bd5SScott Kruger if use_source: 2466f5e9bd5SScott Kruger dataDict=walktree(startdir) 2476f5e9bd5SScott Kruger else: 2484e028dedSScott Kruger dataDict=get_gmakegentest_data(testdir, petsc_dir, petsc_arch) 2496f5e9bd5SScott Kruger 2506f5e9bd5SScott Kruger # Get inverse dictionary for searching 25185bc9deeSScott Kruger invDict=get_inverse_dictionary(dataDict, fields, srcdir) 2526f5e9bd5SScott Kruger 2536f5e9bd5SScott Kruger # Now do query 25485bc9deeSScott Kruger resList=query(invDict, fields, labels) 25585bc9deeSScott Kruger 25685bc9deeSScott Kruger # Filter results using searchin 25785bc9deeSScott Kruger newresList=[] 25885bc9deeSScott Kruger if searchin.strip(): 25985bc9deeSScott Kruger for key in resList: 26085bc9deeSScott Kruger if fnmatch.filter([key],searchin): 26185bc9deeSScott Kruger newresList.append(key) 26285bc9deeSScott Kruger resList=newresList 2636f5e9bd5SScott Kruger 2646f5e9bd5SScott Kruger # Print in flat list suitable for use by gmakefile.test 2656f5e9bd5SScott Kruger print(' '.join(resList)) 2666f5e9bd5SScott Kruger 2676f5e9bd5SScott Kruger return 2686f5e9bd5SScott Kruger 2696f5e9bd5SScott Krugerdef main(): 2706f5e9bd5SScott Kruger parser = optparse.OptionParser(usage="%prog [options] field match_pattern") 2716f5e9bd5SScott Kruger parser.add_option('-s', '--startdir', dest='startdir', 2726f5e9bd5SScott Kruger help='Where to start the recursion if not srcdir', 2736f5e9bd5SScott Kruger default='') 274aec279ffSScott Kruger parser.add_option('-p', '--petsc-dir', dest='petsc_dir', 275aec279ffSScott Kruger help='Set PETSC_DIR different from environment', 2766f5e9bd5SScott Kruger default=os.environ.get('PETSC_DIR')) 2776f5e9bd5SScott Kruger parser.add_option('-a', '--petsc-arch', dest='petsc_arch', 2786f5e9bd5SScott Kruger help='Set PETSC_ARCH different from environment', 2796f5e9bd5SScott Kruger default=os.environ.get('PETSC_ARCH')) 2806f5e9bd5SScott Kruger parser.add_option('--srcdir', dest='srcdir', 2816f5e9bd5SScott Kruger help='Set location of sources different from PETSC_DIR/src. Must be full path.', 2826f5e9bd5SScott Kruger default='src') 2836f5e9bd5SScott Kruger parser.add_option('-t', '--testdir', dest='testdir', 2846f5e9bd5SScott Kruger help='Test directory if not PETSC_ARCH/tests. Must be full path', 2856f5e9bd5SScott Kruger default='tests') 2866f5e9bd5SScott Kruger parser.add_option('-u', '--use-source', action="store_false", 2876f5e9bd5SScott Kruger dest='use_source', 2886f5e9bd5SScott Kruger help='Query all sources rather than those configured in PETSC_ARCH') 28985bc9deeSScott Kruger parser.add_option('-i', '--searchin', dest='searchin', 29085bc9deeSScott Kruger help='Filter results from the arguments', 29185bc9deeSScott Kruger default='') 2926f5e9bd5SScott Kruger 2936f5e9bd5SScott Kruger opts, args = parser.parse_args() 2946f5e9bd5SScott Kruger 2956f5e9bd5SScott Kruger # Argument Sanity checks 2966f5e9bd5SScott Kruger if len(args) != 2: 2976f5e9bd5SScott Kruger parser.print_usage() 2986f5e9bd5SScott Kruger print('Arguments: ') 2996f5e9bd5SScott Kruger print(' field: Field to search for; e.g., requires') 3005b6dee57SScott Kruger print(' To just match names, use "name"') 3016f5e9bd5SScott Kruger print(' match_pattern: Matching pattern for field; e.g., cuda') 3026f5e9bd5SScott Kruger return 3036f5e9bd5SScott Kruger 3046f5e9bd5SScott Kruger # Process arguments and options -- mostly just paths here 3056f5e9bd5SScott Kruger field=args[0] 3066f5e9bd5SScott Kruger match=args[1] 30785bc9deeSScott Kruger searchin=opts.searchin 3086f5e9bd5SScott Kruger 3096f5e9bd5SScott Kruger petsc_dir = opts.petsc_dir 3106f5e9bd5SScott Kruger petsc_arch = opts.petsc_arch 3116f5e9bd5SScott Kruger petsc_full_arch = os.path.join(petsc_dir, petsc_arch) 3126f5e9bd5SScott Kruger 313*58780e5dSStefano Zampini if petsc_arch == '': 314*58780e5dSStefano Zampini petsc_full_src = os.path.join(petsc_dir, 'share', 'petsc', 'examples', 'src') 315*58780e5dSStefano Zampini else: 3166f5e9bd5SScott Kruger if opts.srcdir == 'src': 3176f5e9bd5SScott Kruger petsc_full_src = os.path.join(petsc_dir, 'src') 3186f5e9bd5SScott Kruger else: 3196f5e9bd5SScott Kruger petsc_full_src = opts.srcdir 3206f5e9bd5SScott Kruger if opts.testdir == 'tests': 3216f5e9bd5SScott Kruger petsc_full_test = os.path.join(petsc_full_arch, 'tests') 3226f5e9bd5SScott Kruger else: 3236f5e9bd5SScott Kruger petsc_full_test = opts.testdir 3246f5e9bd5SScott Kruger if opts.startdir: 3256f5e9bd5SScott Kruger startdir=opts.startdir=petsc_full_src 3266f5e9bd5SScott Kruger else: 3276f5e9bd5SScott Kruger startdir=petsc_full_src 3286f5e9bd5SScott Kruger 3296f5e9bd5SScott Kruger # Options Sanity checks 3306f5e9bd5SScott Kruger if not os.path.isdir(petsc_dir): 3316f5e9bd5SScott Kruger print("PETSC_DIR must be a directory") 3326f5e9bd5SScott Kruger return 3336f5e9bd5SScott Kruger 3346f5e9bd5SScott Kruger if not opts.use_source: 3356f5e9bd5SScott Kruger if not os.path.isdir(petsc_full_arch): 3366f5e9bd5SScott Kruger print("PETSC_DIR/PETSC_ARCH must be a directory") 3376f5e9bd5SScott Kruger return 3386f5e9bd5SScott Kruger elif not os.path.isdir(petsc_full_test): 3396f5e9bd5SScott Kruger print("Testdir must be a directory"+petsc_full_test) 3406f5e9bd5SScott Kruger return 3416f5e9bd5SScott Kruger else: 3426f5e9bd5SScott Kruger if not os.path.isdir(petsc_full_src): 3436f5e9bd5SScott Kruger print("Source directory must be a directory"+petsc_full_src) 3446f5e9bd5SScott Kruger return 3456f5e9bd5SScott Kruger 3466f5e9bd5SScott Kruger # Do the actual query 3474e028dedSScott Kruger do_query(opts.use_source, startdir, petsc_full_src, petsc_full_test, 34885bc9deeSScott Kruger petsc_dir, petsc_arch, field, match, searchin) 3496f5e9bd5SScott Kruger 3506f5e9bd5SScott Kruger return 3516f5e9bd5SScott Kruger 3526f5e9bd5SScott Kruger 3536f5e9bd5SScott Krugerif __name__ == "__main__": 3546f5e9bd5SScott Kruger main() 355