xref: /petsc/config/query_tests.py (revision 4c8fdceaee2187f6ed586d920f30b56dbb227b33)
1#!/usr/bin/env python
2import fnmatch
3import glob
4import inspect
5import os
6import optparse
7import pickle
8import re
9import sys
10
11thisfile = os.path.abspath(inspect.getfile(inspect.currentframe()))
12pdir = os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(thisfile)))))
13sys.path.insert(0, os.path.join(pdir, 'config'))
14
15import testparse
16from gmakegentest import nameSpace
17
18
19"""
20  Tool for querying the tests.
21
22  Which tests to query?  Two options:
23      1. Query only the tests that are run for a given configuration.
24      2. Query all of the test files in the source directory
25  For #1:
26     Use dataDict as written out by gmakegentest.py in $PETSC_ARCH/$TESTBASE
27  For #2:
28     Walk the entire tree parsing the files as we go along using testparse.
29     The tree walker is simpler than what is in gmakegentest.py
30
31  The dataDict follows that generated by testparse.  gmakegentest.py does
32  further manipulations of the dataDict to handle things like for loops
33  so if using #2, those modifications are not included.
34
35  Querying:
36      The dataDict dictionary is then "inverted" to create a dictionary with the
37      range of field values as keys and list test names as the values.  This
38      allows fast searching
39
40"""
41
42def query(invDict,label):
43    """
44    Search the keys using fnmatch to find matching names and return list with
45    the results
46    """
47    results=[]
48    if 'name' in invDict:
49        return fnmatch.filter(invDict['name'],label)
50
51    for key in invDict:
52        if fnmatch.filter([key],label):
53            # Do not return values with not unless label itself has not
54            if label.startswith('!') and not key.startswith('!'): continue
55            if not label.startswith('!') and key.startswith('!'): continue
56            results += invDict[key]
57
58    return results
59
60def get_inverse_dictionary(dataDict,field,srcdir):
61    """
62    Create a dictionary with the values of field as the keys, and the name of
63    the tests as the results.
64    """
65    invDict={}
66    if field == 'name': invDict['name']=[]
67    for root in dataDict:
68      for exfile in dataDict[root]:
69        for test in dataDict[root][exfile]:
70          if test in testparse.buildkeys: continue
71          defroot = testparse.getDefaultOutputFileRoot(test)
72          name=nameSpace(defroot,os.path.relpath(root,srcdir))
73          if field == 'name':
74              invDict['name'].append(name)
75              continue
76          if field not in dataDict[root][exfile][test]: continue
77          values=dataDict[root][exfile][test][field]
78
79          for val in values.split():
80              if val in invDict:
81                  invDict[val].append(name)
82              else:
83                  invDict[val] = [name]
84    return invDict
85
86def get_gmakegentest_data(testdir):
87    """
88     Write out the dataDict into a pickle file
89    """
90    # This needs to be consistent with gmakegentest.py of course
91    fd = open(os.path.join(testdir,'datatest.pkl'), 'rb')
92    dataDict=pickle.load(fd)
93    fd.close()
94    return dataDict
95
96def walktree(top):
97    """
98    Walk a directory tree, starting from 'top'
99    """
100    verbose = False
101    dataDict = {}
102    alldatafiles = []
103    for root, dirs, files in os.walk(top, topdown=False):
104        if "examples" not in root: continue
105        if root == 'output': continue
106        if '.dSYM' in root: continue
107        if verbose: print(root)
108
109        dataDict[root] = {}
110
111        for exfile in files:
112            # Ignore emacs files
113            if exfile.startswith("#") or exfile.startswith(".#"): continue
114            ext=os.path.splitext(exfile)[1]
115            if ext[1:] not in ['c','cxx','cpp','cu','F90','F']: continue
116
117            # Convenience
118            fullex = os.path.join(root, exfile)
119            if verbose: print('   --> '+fullex)
120            dataDict[root].update(testparse.parseTestFile(fullex, 0))
121
122    return dataDict
123
124def do_query(use_source, startdir, srcdir, testdir, field, label):
125    """
126    Do the actual query
127    This part of the code is placed here instead of main()
128    to show how one could translate this into ipython/jupyer notebook
129    commands for more advanced queries
130    """
131    # Get dictionary
132    if use_source:
133        dataDict=walktree(startdir)
134    else:
135        dataDict=get_gmakegentest_data(testdir)
136
137    # Get inverse dictionary for searching
138    invDict=get_inverse_dictionary(dataDict, field, srcdir)
139    #print(invDict)
140
141    # Now do query
142    resList=query(invDict, label)
143
144    # Print in flat list suitable for use by gmakefile.test
145    print(' '.join(resList))
146
147    return
148
149def main():
150    parser = optparse.OptionParser(usage="%prog [options] field match_pattern")
151    parser.add_option('-s', '--startdir', dest='startdir',
152                      help='Where to start the recursion if not srcdir',
153                      default='')
154    parser.add_option('-p', '--petsc-dir', dest='petsc_dir',
155                      help='Set PETSC_DIR different from environment',
156                      default=os.environ.get('PETSC_DIR'))
157    parser.add_option('-a', '--petsc-arch', dest='petsc_arch',
158                      help='Set PETSC_ARCH different from environment',
159                      default=os.environ.get('PETSC_ARCH'))
160    parser.add_option('--srcdir', dest='srcdir',
161                      help='Set location of sources different from PETSC_DIR/src.  Must be full path.',
162                      default='src')
163    parser.add_option('-t', '--testdir', dest='testdir',
164                      help='Test directory if not PETSC_ARCH/tests.  Must be full path',
165                      default='tests')
166    parser.add_option('-u', '--use-source', action="store_false",
167                      dest='use_source',
168                      help='Query all sources rather than those configured in PETSC_ARCH')
169
170    opts, args = parser.parse_args()
171
172    # Argument Sanity checks
173    if len(args) != 2:
174        parser.print_usage()
175        print('Arguments: ')
176        print('  field:          Field to search for; e.g., requires')
177        print('                  To just match names, use "name"')
178        print('  match_pattern:  Matching pattern for field; e.g., cuda')
179        return
180
181    # Process arguments and options -- mostly just paths here
182    field=args[0]
183    match=args[1]
184
185    petsc_dir = opts.petsc_dir
186    petsc_arch = opts.petsc_arch
187    petsc_full_arch = os.path.join(petsc_dir, petsc_arch)
188
189    if opts.srcdir == 'src':
190      petsc_full_src = os.path.join(petsc_dir, 'src')
191    else:
192      petsc_full_src = opts.srcdir
193    if opts.testdir == 'tests':
194      petsc_full_test = os.path.join(petsc_full_arch, 'tests')
195    else:
196      petsc_full_test = opts.testdir
197    if opts.startdir:
198      startdir=opts.startdir=petsc_full_src
199    else:
200      startdir=petsc_full_src
201
202    # Options Sanity checks
203    if not os.path.isdir(petsc_dir):
204        print("PETSC_DIR must be a directory")
205        return
206
207    if not opts.use_source:
208        if not os.path.isdir(petsc_full_arch):
209            print("PETSC_DIR/PETSC_ARCH must be a directory")
210            return
211        elif not os.path.isdir(petsc_full_test):
212            print("Testdir must be a directory"+petsc_full_test)
213            return
214    else:
215        if not os.path.isdir(petsc_full_src):
216            print("Source directory must be a directory"+petsc_full_src)
217            return
218
219    # Do the actual query
220    do_query(opts.use_source, startdir, petsc_full_src, petsc_full_test, field, match)
221
222    return
223
224
225if __name__ == "__main__":
226        main()
227