|
| 1 | +#!/usr/bin/env python3 |
| 2 | + |
| 3 | +import chardet |
| 4 | +import os |
| 5 | +import re |
| 6 | + |
| 7 | +# Class representing a single test. |
| 8 | +class Test: |
| 9 | + UNKNOWN = 0 |
| 10 | + PREPROCESS = 1 |
| 11 | + COMPILE = 2 |
| 12 | + LINK = 3 |
| 13 | + RUN = 4 |
| 14 | + |
| 15 | + # int, [os.path], [str], [str], bool |
| 16 | + def __init__(self, kind, sources, options, targets, expected_fail): |
| 17 | + self.kind = kind |
| 18 | + |
| 19 | + # The sources needed by the test. This will have at least one element. |
| 20 | + # The first element of the list will be the "main" file. The rest must |
| 21 | + # be in the order in which they should be compiled. The elements will be |
| 22 | + # the basename's of the file because all dependent files are in the |
| 23 | + # same directory, so there is no need to have the full (or relative) |
| 24 | + # path. |
| 25 | + self.sources = sources |
| 26 | + |
| 27 | + # The command-line options needed by the test. |
| 28 | + self.options = options |
| 29 | + |
| 30 | + # The optional targets for the test. |
| 31 | + self.target = targets |
| 32 | + |
| 33 | + # Whether the test is expected to fail. |
| 34 | + self.expected_fail = expected_fail |
| 35 | + |
| 36 | + # Whether to use separate compilation for this test (is this even |
| 37 | + # necessary?) |
| 38 | + self.separate_compilation = False |
| 39 | + |
| 40 | + def __str__(self): |
| 41 | + m = ['', 'PREPROCESS', 'COMPILE', 'LINK', 'RUN'] |
| 42 | + return '{}: {}'.format(m[self.kind], self.sources) |
| 43 | + |
| 44 | +# Checks if the basename of a file has a Fortran extension. |
| 45 | +re_fortran = re.compile('^.+[.][Ff].*$') |
| 46 | + |
| 47 | +# Checks if the line has a preprocess annotation. |
| 48 | +re_preprocess = re.compile('[{][ ]*dg-do preprocess[ ]*[}]') |
| 49 | + |
| 50 | +# Checks if the line has a compile annotation. |
| 51 | +# FIXME: Add match for target |
| 52 | +re_compile = re.compile('[{][ ]*dg-do[ ]*compile[ ]*(.*)[}]') |
| 53 | + |
| 54 | +# Checks if the line has a link annotation. |
| 55 | +re_link = re.compile('[{][ ]*dg-do[ ]*link[ ]+(.*)[}]') |
| 56 | + |
| 57 | +# Checks if the line has a run annotation or lto-run annotation. |
| 58 | +# FIXME: Add match for target |
| 59 | +re_run = re.compile('[{][ ]*dg-(lto-)?do[ ]*run[ ]+(.*)[}]') |
| 60 | + |
| 61 | +# Checks if the line has an additional-sources annotation. |
| 62 | +re_sources = re.compile('[{][ ]*dg-additional-sources[ ]*["]?([^"])["]?[ ]*[}]') |
| 63 | + |
| 64 | +# Checks if the line has a dg-compile-aux-modules annotation. |
| 65 | +re_aux_modules = re.compile( |
| 66 | + '[{][ ]*dg-compile-aux-modules[ ]*["]?([^"])["]?[}]' |
| 67 | +) |
| 68 | + |
| 69 | +# Checks if the line has an options or additional-options annotation. The |
| 70 | +# option may have an optional target. |
| 71 | +re_options = re.compile( |
| 72 | + '[{][ ]*dg-(additional-)?options[ ]*["]?([^\"]*)["]?[ ]*[}][ ]*([{][ ]*target[ ]*(.+)?[ ][}])?' |
| 73 | +) |
| 74 | + |
| 75 | +# Checks if the line has a shouldfail annotation. |
| 76 | +re_shouldfail = re.compile('[{][ ]*dg-shouldfail[ ]*.*[}]') |
| 77 | + |
| 78 | +# Checks if the line has a dg-error annotation. |
| 79 | +# TODO: There may be |
| 80 | +re_error = re.compile('[{][ ]*dg-error[ ]*[}]') |
| 81 | + |
| 82 | +# Get the n-th level ancestor of the given file. The 1st level ancestor is |
| 83 | +# the directory containing the file. The 2nd level ancestor is the parent of |
| 84 | +# that directory and so on. |
| 85 | +# os.path, int -> os.path |
| 86 | +def get_ancestor(f, n): |
| 87 | + anc = f |
| 88 | + for _ in range(0, n): |
| 89 | + anc = os.path.dirname(anc) |
| 90 | + return anc |
| 91 | + |
| 92 | +# Get the encoding of the file. |
| 93 | +# os.path -> str |
| 94 | +def get_encoding(filepath): |
| 95 | + with open(filepath, 'rb') as f: |
| 96 | + return chardet.detect(f.read())['encoding'] |
| 97 | + return None |
| 98 | + |
| 99 | +# Get the lines in the file. |
| 100 | +# os.path -> [str] |
| 101 | +def get_lines(filepath): |
| 102 | + lines = [] |
| 103 | + try: |
| 104 | + with open(filepath, 'r', encoding = get_encoding(filepath)) as f: |
| 105 | + lines = f.readlines() |
| 106 | + except: |
| 107 | + print('WARNING: Could not open file:', os.path.basename(filepath)) |
| 108 | + finally: |
| 109 | + return lines |
| 110 | + |
| 111 | +# Collect the subdirectories of the gfortran directory which may contain tests. |
| 112 | +# os.path -> [os.path] |
| 113 | +def get_subdirs(gfortran): |
| 114 | + regression = os.path.join(gfortran, 'regression') |
| 115 | + torture = os.path.join(gfortran, 'torture') |
| 116 | + |
| 117 | + subdirs = [regression] |
| 118 | + for root, dirs, _ in os.walk(regression): |
| 119 | + subdirs.extend([os.path.join(root, d) for d in dirs]) |
| 120 | + subdirs.append(torture) |
| 121 | + for root, dirs, _ in os.walk(torture): |
| 122 | + subdirs.extend([os.path.join(root, d) for d in dirs]) |
| 123 | + return subdirs |
| 124 | + |
| 125 | +# Try to match the line with the regex. If the line matches, add the match |
| 126 | +# object to the MOUT list and return True. Otherwise, leave the MOUT list |
| 127 | +# unchanged and return False. |
| 128 | +# re, str, [re.MATCH] -> bool |
| 129 | +def try_match(regex, line, mout): |
| 130 | + m = regex.search(line) |
| 131 | + if m: |
| 132 | + mout.append(m) |
| 133 | + return True |
| 134 | + return False |
| 135 | + |
| 136 | +# () -> int |
| 137 | +def main(): |
| 138 | + root = get_ancestor(os.path.realpath(__file__), 4) |
| 139 | + gfortran = os.path.join(root, 'Fortran', 'gfortran') |
| 140 | + subdirs = get_subdirs(gfortran) |
| 141 | + |
| 142 | + for subdir in subdirs: |
| 143 | + print(subdir) |
| 144 | + files = [] |
| 145 | + for e in os.scandir(subdir): |
| 146 | + if e.is_file() and re_fortran.match(e.name): |
| 147 | + files.append(e.path) |
| 148 | + |
| 149 | + # Find all the files that are dependencies of some file that is the |
| 150 | + # main file in a test. |
| 151 | + dependencies = set([]) |
| 152 | + for filename in files: |
| 153 | + for l in get_lines(filename): |
| 154 | + mout = [] |
| 155 | + if try_match(re_sources, l, mout): |
| 156 | + m = mout[0] |
| 157 | + d = os.path.dirname(filename) |
| 158 | + for src in m[1].split(): |
| 159 | + dependencies.add(os.path.join(d, src)) |
| 160 | + print(dependencies) |
| 161 | + return 0 |
| 162 | + |
| 163 | + tests = [] |
| 164 | + for f in files: |
| 165 | + if f in dependencies: |
| 166 | + continue |
| 167 | + filename = os.path.basename(f) |
| 168 | + kind = None |
| 169 | + sources = [filename] |
| 170 | + options = [] |
| 171 | + targets = [] |
| 172 | + expect_error = False |
| 173 | + |
| 174 | + for l in get_lines(f): |
| 175 | + mout = [] |
| 176 | + if try_match(re_preprocess, l, mout): |
| 177 | + kind = Test.PREPROCESS |
| 178 | + elif try_match(re_compile, l, mout): |
| 179 | + kind = Test.COMPILE |
| 180 | + # TODO: Handle the optional target. |
| 181 | + elif try_match(re_link, l, mout): |
| 182 | + kind = Test.LINK |
| 183 | + # TODO: Assume that this has an optional target. |
| 184 | + elif try_match(re_run, l, mout): |
| 185 | + kind = Test.RUN |
| 186 | + # TODO: Handle the optional target. |
| 187 | + elif try_match(re_shouldfail, l, mout) or \ |
| 188 | + try_match(re_error, l, mout): |
| 189 | + expect_error = True |
| 190 | + elif try_match(re_sources, l, mout) or \ |
| 191 | + try_match(re_aux_modules, l, mout): |
| 192 | + m = mout[0] |
| 193 | + sources.extend(m[1].split()) |
| 194 | + elif try_match(re_options, l, mout): |
| 195 | + m = mout[0] |
| 196 | + options.extend(m[2].split()) |
| 197 | + # TODO: Handle the optional target. |
| 198 | + if kind: |
| 199 | + test = Test(kind, sources, options, targets, expect_error) |
| 200 | + tests.append(test) |
| 201 | + elif len(sources) > 1: |
| 202 | + print( |
| 203 | + ' WARNING: Additional sources without action annotation:', |
| 204 | + filename |
| 205 | + ) |
| 206 | + elif len(options) > 1: |
| 207 | + print( |
| 208 | + ' WARNING: Compile options without action annotation:', |
| 209 | + filename |
| 210 | + ) |
| 211 | + elif expect_error: |
| 212 | + print( |
| 213 | + ' WARNING: Expect error set without action annotation', |
| 214 | + filename |
| 215 | + ) |
| 216 | + else: |
| 217 | + pass |
| 218 | + # print(' WARNING: No action annotation:', filename) |
| 219 | + for t in tests: |
| 220 | + print(str(t)) |
| 221 | + # print(' ', len(tests)) |
| 222 | + |
| 223 | +if __name__ == '__main__': |
| 224 | + exit(main()) |
0 commit comments