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