xref: /petsc/config/query_tests.py (revision 85bc9dee195d6141863862b2f4c680f880fcf46c)
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