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 42*85bc9deeSScott Krugerdef isFile(maybeFile): 43*85bc9deeSScott Kruger ext=os.path.splitext(maybeFile)[1] 44*85bc9deeSScott Kruger if not ext: return False 45*85bc9deeSScott Kruger if ext not in ['.c','.cxx','.cpp','F90','F','cu']: return False 46*85bc9deeSScott Kruger return True 47*85bc9deeSScott Kruger 48*85bc9deeSScott Krugerdef pathToLabel(path): 49*85bc9deeSScott Kruger """ 50*85bc9deeSScott Kruger Because the scripts have a non-unique naming, the pretty-printing 51*85bc9deeSScott Kruger needs to convey the srcdir and srcfile. There are two ways of doing this. 52*85bc9deeSScott Kruger """ 53*85bc9deeSScott Kruger # Strip off any top-level directories or spaces 54*85bc9deeSScott Kruger path=path.strip().replace(pdir,'') 55*85bc9deeSScott Kruger path=path.replace('src/','') 56*85bc9deeSScott Kruger if isFile(path): 57*85bc9deeSScott Kruger prefix=os.path.dirname(path).replace("/","_") 58*85bc9deeSScott Kruger suffix=os.path.splitext(os.path.basename(path))[0] 59*85bc9deeSScott Kruger label=prefix+"-"+suffix+'_*' 60*85bc9deeSScott Kruger else: 61*85bc9deeSScott Kruger path=path.rstrip('/') 62*85bc9deeSScott Kruger label=path.replace("/","_")+"-*" 63*85bc9deeSScott Kruger return label 64*85bc9deeSScott Kruger 65*85bc9deeSScott Krugerdef get_value(varset): 66*85bc9deeSScott Kruger """ 67*85bc9deeSScott Kruger Searching args is a bit funky: 68*85bc9deeSScott Kruger Consider 69*85bc9deeSScott Kruger args: -ksp_monitor_short -pc_type ml -ksp_max_it 3 70*85bc9deeSScott Kruger Search terms are: 71*85bc9deeSScott Kruger ksp_monitor, 'pc_type ml', ksp_max_it 72*85bc9deeSScott Kruger Also ignore all loops 73*85bc9deeSScott Kruger -pc_fieldsplit_diag_use_amat {{0 1}} 74*85bc9deeSScott Kruger Gives: pc_fieldsplit_diag_use_amat as the search term 75*85bc9deeSScott Kruger Also ignore -f ... (use matrices from file) because I'll assume 76*85bc9deeSScott Kruger that this kind of information isn't needed for testing. If it's 77*85bc9deeSScott Kruger a separate search than just grep it 78*85bc9deeSScott Kruger """ 79*85bc9deeSScott Kruger if varset.startswith('-f '): return None 80*85bc9deeSScott Kruger 81*85bc9deeSScott Kruger # First remove loops 82*85bc9deeSScott Kruger value=re.sub('{{.*}}','',varset) 83*85bc9deeSScott Kruger # Next remove - 84*85bc9deeSScott Kruger value=varset.lstrip("-") 85*85bc9deeSScott Kruger # Get rid of numbers 86*85bc9deeSScott Kruger value=re.sub(r"[+-]? *(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?",'',value) 87*85bc9deeSScott Kruger # return without spaces 88*85bc9deeSScott Kruger return value.strip() 89*85bc9deeSScott Kruger 90*85bc9deeSScott 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 """ 95*85bc9deeSScott Kruger setlist=[] # setlist is a list of lists that set opertions will operate on 96*85bc9deeSScott Kruger llist=labels.replace('|',',').split(',') 97*85bc9deeSScott Kruger i=-1 98*85bc9deeSScott Kruger for field in fields.replace('|',',').split(','): 99*85bc9deeSScott Kruger i+=1 100*85bc9deeSScott Kruger label=llist[i] 101*85bc9deeSScott Kruger if field == 'name': 102*85bc9deeSScott Kruger if '/' in label: 103*85bc9deeSScott Kruger label=pathToLabel(label) 104*85bc9deeSScott Kruger setlist.append(fnmatch.filter(invDict['name'],label)) 105*85bc9deeSScott Kruger continue 1065b6dee57SScott Kruger 107*85bc9deeSScott Kruger foundLabel=False # easy to do if you misspell argument search 108*85bc9deeSScott Kruger for key in invDict[field]: 1096f5e9bd5SScott Kruger if fnmatch.filter([key],label): 110*85bc9deeSScott Kruger foundLabel=True 1116f5e9bd5SScott Kruger # Do not return values with not unless label itself has not 1126f5e9bd5SScott Kruger if label.startswith('!') and not key.startswith('!'): continue 1136f5e9bd5SScott Kruger if not label.startswith('!') and key.startswith('!'): continue 114*85bc9deeSScott Kruger setlist.append(invDict[field][key]) 115*85bc9deeSScott Kruger if not foundLabel: 116*85bc9deeSScott Kruger setlist.append([]) 1176f5e9bd5SScott Kruger 118*85bc9deeSScott Kruger # Now process the union and intersection operators based on setlist 119*85bc9deeSScott Kruger allresults=[] 120*85bc9deeSScott Kruger # Union 121*85bc9deeSScott Kruger i=-1 122*85bc9deeSScott Kruger for ufield in fields.split(','): 123*85bc9deeSScott Kruger i+=1 124*85bc9deeSScott Kruger if '|' in ufield: 125*85bc9deeSScott Kruger # Intersection 126*85bc9deeSScott Kruger label=llist[i] 127*85bc9deeSScott Kruger results=set(setlist[i]) 128*85bc9deeSScott Kruger for field in ufield.split('|')[1:]: 129*85bc9deeSScott Kruger i+=1 130*85bc9deeSScott Kruger label=llist[i] 131*85bc9deeSScott Kruger results=results.intersection(set(setlist[i])) 132*85bc9deeSScott Kruger allresults+=list(results) 133*85bc9deeSScott Kruger else: 134*85bc9deeSScott Kruger allresults+=setlist[i] 1356f5e9bd5SScott Kruger 136*85bc9deeSScott Kruger # remove duplicate entries and sort to give consistent results 137*85bc9deeSScott Kruger uniqlist=list(set(allresults)) 138*85bc9deeSScott Kruger uniqlist.sort() 139*85bc9deeSScott Kruger return uniqlist 140*85bc9deeSScott Kruger 141*85bc9deeSScott Krugerdef get_inverse_dictionary(dataDict,fields,srcdir): 1426f5e9bd5SScott Kruger """ 1436f5e9bd5SScott Kruger Create a dictionary with the values of field as the keys, and the name of 1446f5e9bd5SScott Kruger the tests as the results. 1456f5e9bd5SScott Kruger """ 1466f5e9bd5SScott Kruger invDict={} 147*85bc9deeSScott Kruger # Comma-delimited lists denote union 148*85bc9deeSScott Kruger for field in fields.replace('|',',').split(','): 149*85bc9deeSScott Kruger if field not in invDict: 150*85bc9deeSScott Kruger if field == 'name': 151*85bc9deeSScott Kruger invDict[field]=[] # List for ease 152*85bc9deeSScott Kruger else: 153*85bc9deeSScott Kruger invDict[field]={} 1546f5e9bd5SScott Kruger for root in dataDict: 1556f5e9bd5SScott Kruger for exfile in dataDict[root]: 1566f5e9bd5SScott Kruger for test in dataDict[root][exfile]: 157aec279ffSScott Kruger if test in testparse.buildkeys: continue 1586f5e9bd5SScott Kruger defroot = testparse.getDefaultOutputFileRoot(test) 159*85bc9deeSScott Kruger fname=nameSpace(defroot,os.path.relpath(root,srcdir)) 1605b6dee57SScott Kruger if field == 'name': 161*85bc9deeSScott Kruger invDict['name'].append(fname) 1625b6dee57SScott Kruger continue 1635b6dee57SScott Kruger if field not in dataDict[root][exfile][test]: continue 1646f5e9bd5SScott Kruger values=dataDict[root][exfile][test][field] 1656f5e9bd5SScott Kruger 166*85bc9deeSScott Kruger if not field == 'args' and not field == 'diff_args': 1676f5e9bd5SScott Kruger for val in values.split(): 168*85bc9deeSScott Kruger if val in invDict[field]: 169*85bc9deeSScott Kruger invDict[field][val].append(fname) 1706f5e9bd5SScott Kruger else: 171*85bc9deeSScott Kruger invDict[field][val] = [fname] 172*85bc9deeSScott Kruger else: 173*85bc9deeSScott Kruger # Args are funky. 174*85bc9deeSScott Kruger for varset in re.split('(^|\W)-(?=[a-zA-Z])',values): 175*85bc9deeSScott Kruger val=get_value(varset) 176*85bc9deeSScott Kruger if not val: continue 177*85bc9deeSScott Kruger if val in invDict[field]: 178*85bc9deeSScott Kruger invDict[field][val].append(fname) 179*85bc9deeSScott Kruger else: 180*85bc9deeSScott Kruger invDict[field][val] = [fname] 181*85bc9deeSScott Kruger # remove duplicate entries (multiple test/file) 182*85bc9deeSScott Kruger if not field == 'name': 183*85bc9deeSScott Kruger for val in invDict[field]: 184*85bc9deeSScott Kruger invDict[field][val]=list(set(invDict[field][val])) 185*85bc9deeSScott Kruger 1866f5e9bd5SScott Kruger return invDict 1876f5e9bd5SScott Kruger 1884e028dedSScott Krugerdef get_gmakegentest_data(testdir,petsc_dir,petsc_arch): 1896f5e9bd5SScott Kruger """ 1906f5e9bd5SScott Kruger Write out the dataDict into a pickle file 1916f5e9bd5SScott Kruger """ 1926f5e9bd5SScott Kruger # This needs to be consistent with gmakegentest.py of course 1934e028dedSScott Kruger pkl_file=os.path.join(testdir,'datatest.pkl') 1944e028dedSScott Kruger # If it doesn't exist, then we need to regenerate 1954e028dedSScott Kruger if not os.path.exists(pkl_file): 1964e028dedSScott Kruger startdir=os.path.abspath(os.curdir) 1974e028dedSScott Kruger os.chdir(petsc_dir) 1984e028dedSScott Kruger args='--petsc-dir='+petsc_dir+' --petsc-arch='+petsc_arch+' --testdir='+testdir 1994e028dedSScott Kruger buf = os.popen('config/gmakegentest.py '+args).read() 2004e028dedSScott Kruger os.chdir(startdir) 2014e028dedSScott Kruger 2024e028dedSScott Kruger fd = open(pkl_file, 'rb') 2036f5e9bd5SScott Kruger dataDict=pickle.load(fd) 2046f5e9bd5SScott Kruger fd.close() 2056f5e9bd5SScott Kruger return dataDict 2066f5e9bd5SScott Kruger 2076f5e9bd5SScott Krugerdef walktree(top): 2086f5e9bd5SScott Kruger """ 2096f5e9bd5SScott Kruger Walk a directory tree, starting from 'top' 2106f5e9bd5SScott Kruger """ 2116f5e9bd5SScott Kruger verbose = False 2126f5e9bd5SScott Kruger dataDict = {} 2136f5e9bd5SScott Kruger alldatafiles = [] 2146f5e9bd5SScott Kruger for root, dirs, files in os.walk(top, topdown=False): 2156f5e9bd5SScott Kruger if root == 'output': continue 2166f5e9bd5SScott Kruger if '.dSYM' in root: continue 2176f5e9bd5SScott Kruger if verbose: print(root) 2186f5e9bd5SScott Kruger 2196f5e9bd5SScott Kruger dataDict[root] = {} 2206f5e9bd5SScott Kruger 2216f5e9bd5SScott Kruger for exfile in files: 2226f5e9bd5SScott Kruger # Ignore emacs files 2236f5e9bd5SScott Kruger if exfile.startswith("#") or exfile.startswith(".#"): continue 2246f5e9bd5SScott Kruger ext=os.path.splitext(exfile)[1] 2256f5e9bd5SScott Kruger if ext[1:] not in ['c','cxx','cpp','cu','F90','F']: continue 2266f5e9bd5SScott Kruger 2276f5e9bd5SScott Kruger # Convenience 2286f5e9bd5SScott Kruger fullex = os.path.join(root, exfile) 2296f5e9bd5SScott Kruger if verbose: print(' --> '+fullex) 2306f5e9bd5SScott Kruger dataDict[root].update(testparse.parseTestFile(fullex, 0)) 2316f5e9bd5SScott Kruger 2326f5e9bd5SScott Kruger return dataDict 2336f5e9bd5SScott Kruger 234*85bc9deeSScott Krugerdef do_query(use_source, startdir, srcdir, testdir, petsc_dir, petsc_arch, 235*85bc9deeSScott Kruger fields, labels, searchin): 2366f5e9bd5SScott Kruger """ 2376f5e9bd5SScott Kruger Do the actual query 2386f5e9bd5SScott Kruger This part of the code is placed here instead of main() 2396f5e9bd5SScott Kruger to show how one could translate this into ipython/jupyer notebook 2406f5e9bd5SScott Kruger commands for more advanced queries 2416f5e9bd5SScott Kruger """ 2426f5e9bd5SScott Kruger # Get dictionary 2436f5e9bd5SScott Kruger if use_source: 2446f5e9bd5SScott Kruger dataDict=walktree(startdir) 2456f5e9bd5SScott Kruger else: 2464e028dedSScott Kruger dataDict=get_gmakegentest_data(testdir, petsc_dir, petsc_arch) 2476f5e9bd5SScott Kruger 2486f5e9bd5SScott Kruger # Get inverse dictionary for searching 249*85bc9deeSScott Kruger invDict=get_inverse_dictionary(dataDict, fields, srcdir) 2506f5e9bd5SScott Kruger 2516f5e9bd5SScott Kruger # Now do query 252*85bc9deeSScott Kruger resList=query(invDict, fields, labels) 253*85bc9deeSScott Kruger 254*85bc9deeSScott Kruger # Filter results using searchin 255*85bc9deeSScott Kruger newresList=[] 256*85bc9deeSScott Kruger if searchin.strip(): 257*85bc9deeSScott Kruger for key in resList: 258*85bc9deeSScott Kruger if fnmatch.filter([key],searchin): 259*85bc9deeSScott Kruger newresList.append(key) 260*85bc9deeSScott Kruger resList=newresList 2616f5e9bd5SScott Kruger 2626f5e9bd5SScott Kruger # Print in flat list suitable for use by gmakefile.test 2636f5e9bd5SScott Kruger print(' '.join(resList)) 2646f5e9bd5SScott Kruger 2656f5e9bd5SScott Kruger return 2666f5e9bd5SScott Kruger 2676f5e9bd5SScott Krugerdef main(): 2686f5e9bd5SScott Kruger parser = optparse.OptionParser(usage="%prog [options] field match_pattern") 2696f5e9bd5SScott Kruger parser.add_option('-s', '--startdir', dest='startdir', 2706f5e9bd5SScott Kruger help='Where to start the recursion if not srcdir', 2716f5e9bd5SScott Kruger default='') 272aec279ffSScott Kruger parser.add_option('-p', '--petsc-dir', dest='petsc_dir', 273aec279ffSScott Kruger help='Set PETSC_DIR different from environment', 2746f5e9bd5SScott Kruger default=os.environ.get('PETSC_DIR')) 2756f5e9bd5SScott Kruger parser.add_option('-a', '--petsc-arch', dest='petsc_arch', 2766f5e9bd5SScott Kruger help='Set PETSC_ARCH different from environment', 2776f5e9bd5SScott Kruger default=os.environ.get('PETSC_ARCH')) 2786f5e9bd5SScott Kruger parser.add_option('--srcdir', dest='srcdir', 2796f5e9bd5SScott Kruger help='Set location of sources different from PETSC_DIR/src. Must be full path.', 2806f5e9bd5SScott Kruger default='src') 2816f5e9bd5SScott Kruger parser.add_option('-t', '--testdir', dest='testdir', 2826f5e9bd5SScott Kruger help='Test directory if not PETSC_ARCH/tests. Must be full path', 2836f5e9bd5SScott Kruger default='tests') 2846f5e9bd5SScott Kruger parser.add_option('-u', '--use-source', action="store_false", 2856f5e9bd5SScott Kruger dest='use_source', 2866f5e9bd5SScott Kruger help='Query all sources rather than those configured in PETSC_ARCH') 287*85bc9deeSScott Kruger parser.add_option('-i', '--searchin', dest='searchin', 288*85bc9deeSScott Kruger help='Filter results from the arguments', 289*85bc9deeSScott Kruger default='') 2906f5e9bd5SScott Kruger 2916f5e9bd5SScott Kruger opts, args = parser.parse_args() 2926f5e9bd5SScott Kruger 2936f5e9bd5SScott Kruger # Argument Sanity checks 2946f5e9bd5SScott Kruger if len(args) != 2: 2956f5e9bd5SScott Kruger parser.print_usage() 2966f5e9bd5SScott Kruger print('Arguments: ') 2976f5e9bd5SScott Kruger print(' field: Field to search for; e.g., requires') 2985b6dee57SScott Kruger print(' To just match names, use "name"') 2996f5e9bd5SScott Kruger print(' match_pattern: Matching pattern for field; e.g., cuda') 3006f5e9bd5SScott Kruger return 3016f5e9bd5SScott Kruger 3026f5e9bd5SScott Kruger # Process arguments and options -- mostly just paths here 3036f5e9bd5SScott Kruger field=args[0] 3046f5e9bd5SScott Kruger match=args[1] 305*85bc9deeSScott Kruger searchin=opts.searchin 3066f5e9bd5SScott Kruger 3076f5e9bd5SScott Kruger petsc_dir = opts.petsc_dir 3086f5e9bd5SScott Kruger petsc_arch = opts.petsc_arch 3096f5e9bd5SScott Kruger petsc_full_arch = os.path.join(petsc_dir, petsc_arch) 3106f5e9bd5SScott Kruger 3116f5e9bd5SScott Kruger if opts.srcdir == 'src': 3126f5e9bd5SScott Kruger petsc_full_src = os.path.join(petsc_dir, 'src') 3136f5e9bd5SScott Kruger else: 3146f5e9bd5SScott Kruger petsc_full_src = opts.srcdir 3156f5e9bd5SScott Kruger if opts.testdir == 'tests': 3166f5e9bd5SScott Kruger petsc_full_test = os.path.join(petsc_full_arch, 'tests') 3176f5e9bd5SScott Kruger else: 3186f5e9bd5SScott Kruger petsc_full_test = opts.testdir 3196f5e9bd5SScott Kruger if opts.startdir: 3206f5e9bd5SScott Kruger startdir=opts.startdir=petsc_full_src 3216f5e9bd5SScott Kruger else: 3226f5e9bd5SScott Kruger startdir=petsc_full_src 3236f5e9bd5SScott Kruger 3246f5e9bd5SScott Kruger # Options Sanity checks 3256f5e9bd5SScott Kruger if not os.path.isdir(petsc_dir): 3266f5e9bd5SScott Kruger print("PETSC_DIR must be a directory") 3276f5e9bd5SScott Kruger return 3286f5e9bd5SScott Kruger 3296f5e9bd5SScott Kruger if not opts.use_source: 3306f5e9bd5SScott Kruger if not os.path.isdir(petsc_full_arch): 3316f5e9bd5SScott Kruger print("PETSC_DIR/PETSC_ARCH must be a directory") 3326f5e9bd5SScott Kruger return 3336f5e9bd5SScott Kruger elif not os.path.isdir(petsc_full_test): 3346f5e9bd5SScott Kruger print("Testdir must be a directory"+petsc_full_test) 3356f5e9bd5SScott Kruger return 3366f5e9bd5SScott Kruger else: 3376f5e9bd5SScott Kruger if not os.path.isdir(petsc_full_src): 3386f5e9bd5SScott Kruger print("Source directory must be a directory"+petsc_full_src) 3396f5e9bd5SScott Kruger return 3406f5e9bd5SScott Kruger 3416f5e9bd5SScott Kruger # Do the actual query 3424e028dedSScott Kruger do_query(opts.use_source, startdir, petsc_full_src, petsc_full_test, 343*85bc9deeSScott Kruger petsc_dir, petsc_arch, field, match, searchin) 3446f5e9bd5SScott Kruger 3456f5e9bd5SScott Kruger return 3466f5e9bd5SScott Kruger 3476f5e9bd5SScott Kruger 3486f5e9bd5SScott Krugerif __name__ == "__main__": 3496f5e9bd5SScott Kruger main() 350