root/trunk/ProjectFortress/compiler_tests/calibrate_tests.py

Revision 4041, 9.5 KB (checked in by jrhil47, 4 months ago)

[errors] Major overhaul of application expression error messages. Fixed the sort on error messages to check numeric source file locations rather than merely lexicographic on strings. Added calibrate_tests.py script to reset the expected error output of the supplied test files.

Line 
1#!/usr/bin/python
2#
3# Calibrates the expected output for the given test files. Run this script
4# with a list of .test files to calibrate. For each one, it will run the
5# source file and get the REAL output, compare it against what was expected
6# in the .test file, and -- if they differ -- write back into the .test file
7# the actual output as expected.
8
9import sys
10import os, os.path
11import re
12import popen2
13
14LINE_DEF_REGEX = re.compile(r"^(\w+)(?:=(.*))?$")
15VALUE_SPACES_REGEX = re.compile(r"^ +", re.M)
16UNIX_PATH_REGEX = re.compile(r"[ ^](/[^/ ]+)+")
17FORTRESS_HOME = os.path.realpath(os.getenv("FORTRESS_HOME"))
18FORTRESS_PATH = "%s/bin/fortress" % FORTRESS_HOME
19COPYRIGHT = \
20"""#    Copyright 2009 Sun Microsystems, Inc.,
21#    4150 Network Circle, Santa Clara, California 95054, U.S.A.
22#    All rights reserved.
23#
24#    U.S. Government Rights - Commercial software.
25#    Government users are subject to the Sun Microsystems, Inc. standard
26#    license agreement and applicable provisions of the FAR and its supplements.
27#
28#    Use is subject to license terms.
29#
30#    This distribution may include materials developed by third parties.
31#
32#    Sun, Sun Microsystems, the Sun logo and Java are trademarks or registered
33#    trademarks of Sun Microsystems, Inc. in the U.S. and other countries.
34
35"""
36
37 # Get the value for ${FORTRESS_SOURCE_PATH}
38(tmp, _) = popen2.popen2("%s expand FORTRESS_SOURCE_PATH" % FORTRESS_PATH)
39FORTRESS_SOURCE_PATH = tmp.read().rstrip('\n')
40
41def main():
42    try:
43        if not os.path.isfile(FORTRESS_PATH):
44            raise RuntimeError("no fortress executable at %s" % FORTRESS_PATH)
45       
46        test_files = sys.argv[1:]
47        confirm(test_files)
48        count = sum(calibrate_test_file(f) for f in test_files)
49        print "* updated %d test%s" % (count, count != 1 and "s" or "")
50
51    except KeyboardInterrupt:
52        print "\nstopped"
53        sys.exit(1)
54       
55    except RuntimeError, e:
56        print str(e)
57        sys.exit(1)
58
59
60# Confirm that the user wants to do this!
61def confirm(test_files):
62    n = len(test_files)
63    prompt = "Are you sure you want to calibrate %d test%s? [y/n] " % (n, n != 1 and "s" or "")
64    choice = "X"
65    while choice not in "yn":
66        choice = raw_input(prompt).lower()
67    if choice != "y":
68        print "quitting..."
69        sys.exit(0)
70
71
72# Calibrate the given test file.
73def calibrate_test_file(test_file):
74    print "calibrating %s..." % test_file
75    try:
76   
77        # Get all the config info.
78        config = parse_test_file(test_file)
79   
80        # Get the source file.
81        source = get_source_file(config)
82        if not source: raise RuntimeError("error getting source file from %s" % test_file)
83   
84        # Which mode to run?
85        mode = get_mode(config)
86   
87        # Run fortress to get output.
88        (out, err) = run_fortress(mode, source, get_tests_dir(config))
89   
90        # Compare the outputs
91        (out_okay, err_okay) = compare_outputs(mode, config, out, err)
92        if out_okay and err_okay: return 0
93   
94        # If stdout different, add in the new one.
95        if not out_okay:
96            #print "* %s: different stdout!" % test_file
97            key = "%s_out_equals" % mode
98            replace_config_value(config, key, format_value(out))
99   
100        # If stderr different, add in the new one.
101        if not err_okay:
102            #print "* %s: different stderr!" % test_file
103            key = "%s_err_equals" % mode
104            replace_config_value(config, key, format_value(err))
105   
106        # Write back to file!
107        output_config(config, test_file)
108        print "* wrote out new %s" % test_file
109        return 1
110       
111    except RuntimeError, e:
112        print e
113        return 0
114
115# Output the given config into the given test file.
116def output_config(config, test_file):
117    f = open(test_file, 'w')
118    f.write(COPYRIGHT)
119    for (k, v) in config:
120        if v is None: line = k
121        else: line = "%s=%s" % (k, v)
122        f.write(line+"\n")
123    f.close()
124
125
126# Substitute in the config the new value for `name` for the old one.
127def replace_config_value(config, name, value):
128    for (i, (k, _)) in enumerate(config):
129        if k == name:
130            config[i] = (name, value)
131           
132            # Check if there were any possible file system paths.
133            m = UNIX_PATH_REGEX.search(value)
134            if m: print "* WARNING! possible unix path detected in output: %s" % m.group(0)
135            return
136
137
138# Format a value for use in the config.
139def format_value(value):
140    value = VALUE_SPACES_REGEX.sub("\\ ", value)
141    value = value.replace("\n", "\\n\\\n")
142    if value.endswith("\\\n"): value = value[:-2]
143    if "\n" in value: value = "\\\n" + value
144    return value
145
146
147# Figure out which mode to run fortress in. Works on compile and typecheck
148# (preferring compile).
149def get_mode(config):
150    compile = False
151    typecheck = False
152    for (k, _) in config:
153        if k == "compile": compile = True
154        if k == "typecheck": typecheck = True
155    if compile: return "compile"
156    elif typecheck: return "typecheck"
157    else: return None
158
159
160# Look in the given config for the source file.
161def get_source_file(config):
162    for (k, v) in config:
163        if k == "tests":
164            if v.endswith(".fss"): return v
165            elif v.endswith(".fsi"): return v
166            else: return "%s.fss" % v
167    return None
168
169
170# Get the directory for these tests. Returns (name, path) where name is the
171# name of the variable holding the value, and path is the value itself.
172def get_tests_dir(config):
173    for (k, v) in config:
174        if k.endswith("_TESTS_DIR"):
175            tests_dir = v.replace("${FORTRESS_AUTOHOME}", FORTRESS_HOME)
176            return (k, tests_dir)
177    raise RuntimeError("error locating tests dir")
178
179
180# Run fortress and return the stdout and stderr.
181def run_fortress(mode, source, tests_dir):
182    (out, _, err) = popen2.popen3("%s %s %s" % (FORTRESS_PATH, mode, source))
183    out, err = out.read(), err.read()
184
185    # Escape the slashes and quotes.
186    out = out.replace('\\', '\\\\').replace('"', '\\"')
187    err = err.replace('\\', '\\\\').replace('"', '\\"')
188   
189    # Replace the name of the tests directory.
190    tests_dir_name, tests_dir_path = tests_dir
191    out = out.replace(tests_dir_path, "${%s}" % tests_dir_name)
192    err = err.replace(tests_dir_path, "${%s}" % tests_dir_name)
193   
194    # Replace location of source files from repository.
195    out = out.replace(FORTRESS_SOURCE_PATH, "${FORTRESS_SOURCE_PATH}")
196    err = err.replace(FORTRESS_SOURCE_PATH, "${FORTRESS_SOURCE_PATH}")
197             
198    # Replace FORTRESS_HOME after everything else.
199    out = out.replace(FORTRESS_HOME, "${FORTRESS_AUTOHOME}")
200    err = err.replace(FORTRESS_HOME, "${FORTRESS_AUTOHOME}")
201    return (out, err)
202
203
204# Compare the stdout and stderr of the program with those expected.
205def compare_outputs(mode, config, out, err):
206    try:
207        # Get the expected outputs out of the config.
208        expected_err = None
209        expected_out = None
210        for (k, v) in config:
211            if k == "%s_err_equals" % mode:
212                expected_err = v.replace("\\n", "\n")
213            elif k == "%s_out_equals" % mode:
214                expected_out = v.replace("\\n", "\n")
215        if expected_err is None or expected_out is None:
216            raise RuntimeError
217       
218        # Turn the expected output into a regex to match against. Here we must
219        # perform magic to catch the "\ " spacing thing.
220        out_regex = re.compile(re.escape(expected_out).replace("\\\\\\ ", r" +"))
221        err_regex = re.compile(re.escape(expected_err).replace("\\\\\\ ", r" +"))
222       
223        # Compare!
224        out_okay = out_regex.match(out) is not None
225        err_okay = err_regex.match(err) is not None
226        return (out_okay, err_okay)
227
228    except RuntimeError:
229        raise RuntimeError("error comparing output")
230
231# Parse the test file, returning a dictionary of the config values.
232# `test_file` must be a file object opened for reading
233def parse_test_file(test_file):
234    # Try to open the file
235    f = open(test_file, 'r')
236    if not f: raise RuntimeError("error reading test file %s" % test_file)
237   
238    try:
239        config = []
240        name = None
241        for line in f:
242            line = line.strip()
243           
244            # If the line is empty, keep going
245            if (not name and not line) or line.startswith("#"): continue
246
247            # If not already in a value definition, match a definition.
248            if not name:
249                m = LINE_DEF_REGEX.match(line)
250               
251                if not m: raise RuntimeError()
252                name, value = m.group(1), m.group(2)
253                #print "found", name, value
254
255                # If this is not a key=value pair or one that is closed.
256                if value is None or not line.endswith("\\"):
257                    config.append((name, value))
258                    #print "closing", name
259                    name = None
260
261                # If this is a key=value pair that is open.
262                else:
263                    content = value[:-1]
264                    #print "eating", name
265
266            elif not line.endswith("\\"):
267                content += line
268                config.append((name, content))
269                #print "closing", name
270                name = None
271
272            # If we're in a def and it's still open, keep reading.
273            else:
274                content += line[:-1]
275                #print "eating", name
276
277        # We should be done now.
278        if name: raise RuntimeError()
279
280        return config
281
282    except RuntimeError:
283        raise RuntimeError("error parsing test file %s" % test_file)
284
285
286if __name__ == '__main__':
287    main()
Note: See TracBrowser for help on using the browser.