diff options
author | Nikolai Kosjar <nikolai.kosjar@digia.com> | 2012-11-09 15:16:58 +0100 |
---|---|---|
committer | Erik Verbruggen <erik.verbruggen@digia.com> | 2012-11-13 14:08:44 +0100 |
commit | c870a64bad28bc52e4bf7239a78e166804fd2124 (patch) | |
tree | 695c16cfcba75ad91e0f7b6702508f86b0ed0919 /test_interpreter.py | |
parent | 6cf913c485cb9b90f013ad8569c843f53deba3a3 (diff) |
Add a script to test the interpreter on *.js files.
By default this will run ./v4 on all *.js files in test262 and generate
test reports based on the exit code and the output of the interpreter.
Change-Id: I5a90b9f4c5bf376c5c8df12b5e777d0626d9d82e
Reviewed-by: Erik Verbruggen <erik.verbruggen@digia.com>
Diffstat (limited to 'test_interpreter.py')
-rwxr-xr-x | test_interpreter.py | 460 |
1 files changed, 460 insertions, 0 deletions
diff --git a/test_interpreter.py b/test_interpreter.py new file mode 100755 index 0000000000..f69d96eb7b --- /dev/null +++ b/test_interpreter.py @@ -0,0 +1,460 @@ +#!/usr/bin/env python + +# +# See CommandLineProcessor.print_usage() below. +# + +# TODO: Consider checking "@non_strict_only". + +import datetime +import fnmatch +import getopt +import os +import platform +import resource +import subprocess +import threading +import sys + +### Settings & Command Line Processing ######################################## + +class Settings: + interpreter = 'v4' + interpreter_timeout = 60 # in s + modes = ['jit', 'interpret'] + printers = ['txt', 'html'] + verbose = False + input_dir = 'test262' + # List of patterns with unix shell-style wildcards, see also + # http://docs.python.org/2/library/fnmatch.html + # Example: + # blacklist_patterns = [ + # "test262/console/harness/cth*" + # ] + blacklist_patterns = [] + +class CommandLineProcessor: + """ Process command line arguments and overwrite values in Settings.""" + def __init__(self, args): + self.args = args + self.valid_modes = ['aot', 'compile', 'jit', 'interpret', 'llvm-jit'] + self.valid_printers = ['txt', 'html'] + + def print_usage(self): + sys.stdout.write("""\ +Usage: %s [-v] [-i interpreter] [-t timeout] [-d input_dir] [-p printers] [-m mode_list] + +Run an interpreter with different modes (mode_list) on all *.js files in +input_dir and generate test reports based on the exit code and the output of +the interpreter. + +Test reports are generated in TXT and HTML format. Paths to generated reports +are printed upon a finished run. + +Options: + -i, --interpreter Use the specified interpreter (default: '%s'). + -t, --timeout Timeout in seconds for the interpreter to process a single + file (default: '%s'). + -d, --input-dir *.js files will be searched in input_dir. (default: '%s'). + -m, --modes Run the interpreter with specified modes. mode_list is a + comma separated list (valid: '%s', + default: '%s'). + -p, --printers Print results by specified printers. printers is a comma + separated list (valid: '%s', default: '%s'). + -v, --verbose Print some more information, e.g. files are being + processed (default: not enabled). + +""" % ( + sys.argv[0], + Settings.interpreter, + Settings.interpreter_timeout, + Settings.input_dir, + ",".join(self.valid_modes), + ",".join(Settings.modes), + ",".join(self.valid_printers), + ",".join(Settings.printers), + )) + + def run(self): + try: + options, arguments = getopt.getopt(self.args, "hvm:i:d:p:t:", + ["help", "verbose", "modes=", "interpreter=", "input-dir=", "printers=", "timeout="]) + except getopt.error, msg: + sys.stdout.write("For help use -h, --help.") + sys.exit(2) + for option, argument in options: + if option in ("-h", "--help"): + self.print_usage() + sys.exit(0) + elif option in ("-m", "--modes"): + modes = filter(None, argument.split(',')) + if not modes: + sys.stdout.write("No modes provided.") + sys.stdout.write("For help use -h, --help.") + sys.exit(2) + for mode in modes: + if not mode in self.valid_modes: + sys.stdout.write("Mode '%s' is invalid." %(mode)) + sys.stdout.write("For help use -h, --help.") + sys.exit(2) + Settings.modes = modes + elif option in ("-p", "--printers"): + printers = filter(None, argument.split(',')) + if not printers: + sys.stdout.write("No printers provided.") + sys.stdout.write("For help use -h, --help.") + sys.exit(2) + for printer in printers: + if not printer in self.valid_printers: + sys.stdout.write("Printer '%s' is invalid." %(printer)) + sys.stdout.write("For help use -h, --help.") + sys.exit(2) + Settings.printers = printers + elif option in ("-i", "--interpreter"): + Settings.interpreter = argument + elif option in ("-d", "--input-dir"): + Settings.input_dir = argument + elif option in ("-t", "--interpreter-timeout"): + Settings.interpreter_timeout = int(argument) + elif option in ("-v", "--verbose"): + Settings.verbose = True + else: + sys.stdout.write("Unknown option '%s'." %(option)) + sys.stdout.write("For help use -h, --help.") + sys.exit(2) + +### Utils ##################################################################### + +def check_candidates(candidates): + """ Return files passing the black list Settings.blacklist_patterns. """ + input_files = [] + for candidate in candidates: + is_blacklisted = False + for p in Settings.blacklist_patterns: + if fnmatch.fnmatch(candidate, p): + if Settings.verbose: + sys.stdout.write("Will ignore '%s' since it matches black list filter '%s'.\n" %(candidate, p)) + is_blacklisted = True + break + if not is_blacklisted: + input_files.append(candidate) + return input_files + +def generic_footer(time_started, time_finished, total_pass, total_passn, total_fail, total_gout, total_time): + totals_processed = total_pass + total_passn + total_fail + total_gout + return """ +Key: + PASS: Exit code is zero and _no_ output generated. + PASSN: Exit code is non zero and *.js contains "@negative". + GOUT: Exit code is zero and output generated. + FAIL: Exit code is non zero. + TIME: Interpreter does not return within timeout of %d seconds. + +Totals: %d processed - %d passed, %d passn'ed, %d failed, %d gout'ed, %d timeout'ed. + +Started: %s +Finished: %s""" %( + Settings.interpreter_timeout, + totals_processed, total_pass, total_passn, total_fail, total_gout, total_time, + time_started.strftime("%A, %d. %B %Y %I:%M%p"), + time_finished.strftime("%A, %d. %B %Y %I:%M%p") + ) + +class Command(object): + def __init__(self, cmd): + self.cmd = cmd + self.process = None + + self.finished_within_timeout = False + self.exit_code = None + self.stdout = None + self.stderr = None + + def run(self, timeout): + def target(): + my_env = os.environ.copy() + my_env['IN_TEST_HARNESS'] = '1' + self.process = subprocess.Popen(self.cmd, stdout=subprocess.PIPE, + stderr=subprocess.PIPE, preexec_fn=self.__set_limits, env=my_env) + (self.stdout, self.stderr) = self.process.communicate() + + thread = threading.Thread(target=target) + thread.start() + thread.join(timeout) + if thread.is_alive(): + self.process.terminate() + thread.join() + else: + self.finished_within_timeout = True + self.exit_code = self.process.returncode + + def __set_limits(self): + if platform.system() in ('Linux', 'Darwin'): + one_gigabyte = 1024*1024*1024 + resource.setrlimit(resource.RLIMIT_AS, (one_gigabyte, one_gigabyte)) + resource.setrlimit(resource.RLIMIT_CORE, (0, 0)) + +class InterpreterRunnerInfo: + def __init__(self, info): + self.info = info + +class InterpreterResult: + def __init__(self, file_path, finished_within_timeout, code, output_stdout, output_stderr): + self.file_path = file_path + self.finished_within_timeout = finished_within_timeout + self.code = code + self.output_stdout= output_stdout + self.output_stderr = output_stderr + +class InterpreterRunner: + def __init__(self, input_files, label, extra_args, printers): + self.input_files = input_files + self.label = label + self.extra_args = extra_args + self.printers = printers + self.runner_info = InterpreterRunnerInfo(Settings.interpreter + ' ' + " ".join(self.extra_args)) + + def __printers_begin(self): + for printer in self.printers: + printer.reset() + printer.set_output_file("testresults_%s.%s" %(os.path.basename(Settings.interpreter), self.label)) + printer.set_runner_info(self.runner_info) + printer.print_header(datetime.datetime.now()) + + def __printers_end(self): + for printer in self.printers: + printer.print_footer(datetime.datetime.now()) + + def __printers_do(self, result): + for printer in self.printers: + printer.print_print(result) + + def run(self): + sys.stdout.write("Running interpreter '%s %s' for each test file.\n" + %(Settings.interpreter, " ".join(self.extra_args))) + self.__printers_begin() + for input_file in self.input_files: + if not os.path.exists(input_file): + sys.stderr.write("Warning: File '%s' does not exist anymore for interpreter '%s'.\n" + %(input_file, self.runner_info.info)) + break + if Settings.verbose: + sys.stdout.write(" %s\n" %(input_file)) + command = ['./' + Settings.interpreter] + self.extra_args + [input_file] + + process = Command(command) + process.run(timeout=Settings.interpreter_timeout) + if not process.finished_within_timeout: + sys.stderr.write("Warning: Interpreter '%s' did not finish within %d seconds processing file '%s'.\n" + %(self.runner_info.info, Settings.interpreter_timeout, input_file)) + result = InterpreterResult( + input_file, + process.finished_within_timeout, + process.exit_code, + process.stdout, + process.stderr + ) + self.__printers_do(result) + self.__printers_end() + +### Printers ################################################################## + +class AbstractInterpreterPrinter: + def __init__(self, file_suffix): + self.file_suffix = file_suffix + self.reset() + + def reset(self): + self.count_pass = 0 + self.count_passn = 0 + self.count_fail = 0 + self.count_gout = 0 + self.count_time = 0 + + def set_output_file(self, file_path): + self.output_file = open(file_path + self.file_suffix, 'w') + + def set_runner_info(self, runner_info): + self.runner_info = runner_info + + def print_header(self, time_started): + """Must be called by derived method to ensure correct totals.""" + self.reset() + self.time_started = time_started + + def print_print(self, result): + """ Returns result marker and update totals. + Must be called by derived method to ensure correct totals. """ + if result.finished_within_timeout: + if result.code != 0: + if '@negative' in open(result.file_path, 'r').read(1024): + result_marker = "PASSN" + self.count_passn = self.count_passn + 1 + else: + result_marker = "FAIL" + self.count_fail = self.count_fail + 1 + else: + if result.output_stdout or result.output_stderr: + result_marker = "GOUT" + self.count_gout = self.count_gout + 1 + else: + result_marker = "PASS" + self.count_pass = self.count_pass + 1 + else: + result_marker = "TIME" + self.count_time = self.count_time + 1 + return result_marker + + def print_footer(self, time_finished): + raise NotImplementedError, "Implement me" + + def generic_footer(self): + return generic_footer(self.time_started, self.time_finished, self.count_pass, + self.count_passn, self.count_fail, self.count_gout, self.count_time) + +class InterpreterTextPrinter(AbstractInterpreterPrinter): + def __init__(self): + AbstractInterpreterPrinter.__init__(self, '.txt') + + def print_header(self, time_started): + AbstractInterpreterPrinter.print_header(self, time_started) + self.output_file.write("********* Start testing of '%s' *********\n" %(Settings.interpreter)) + self.output_file.write("Config: %s\n" %(self.runner_info.info)) + self.output_file.flush() + + def print_print(self, result): + result_marker = AbstractInterpreterPrinter.print_print(self, result) + self.output_file.write("%-5s %-26s %s\n" %(result_marker, os.path.basename(result.file_path), + result.file_path)) + self.output_file.flush() + + def print_footer(self, time_finished): + self.time_finished = time_finished + self.output_file.write("********* Finished testing of '%s' *********\n" %(Settings.interpreter)) + self.output_file.write(AbstractInterpreterPrinter.generic_footer(self)) + self.output_file.flush() + sys.stdout.write("Generated '%s'.\n" %(self.output_file.name)) + +class InterpreterHtmlPrinter(AbstractInterpreterPrinter): + def __init__(self): + AbstractInterpreterPrinter.__init__(self, '.html') + + def print_header(self, time_started): + AbstractInterpreterPrinter.print_header(self, time_started) + + preamble = ("""\ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" + "http://www.w3.org/TR/html4/loose.dtd"> +<html> +<head> +<title>Test results for '%s'</title> +<style type="text/css"> + key { font-size: smaller; } + table { border-collapse: collapse; } + body { font-family: sans-serif; } + th { padding: 4px; text-align: left; } + td { padding-left: 3px; padding-right: 3px; } + .pass { text-align: center; background-color:#00FF00; } + .passn { text-align: center; background-color:#00DE00; } + .fail { text-align: center; background-color:#FF4649; } + .gout { text-align: center; background-color:#FFDCA8; } + .smaller { font-size: font:1.2em/1.5em; } +</style> +</head> +<body> +<h1>Test results for '%s'</h1> + +<table border="1" width="100%%"> + <tr> + <th style="text-align: center">Result</th> + <th>Test File</th> + <th>Interpreter Output (stdout <span style="color: red">stderr</span>)</th> + <th>Full Test File Path</th> + </tr> + +""" %( + self.runner_info.info, + self.runner_info.info + )) + self.output_file.write(preamble) + self.output_file.flush() + + def print_print(self, result): + result_marker = AbstractInterpreterPrinter.print_print(self, result) + has_output = result.output_stdout or result.output_stderr + output = result.output_stdout + "<span style='color: red'> " + result.output_stderr + "</span>" + if not has_output: + output = "" + row = ("""\ + <tr> + <td class="%s">%s</td> + <td>%s</td> + <td>%s</td> + <td>%s</td> + </tr>""" %( + result_marker.lower(), + result_marker, + os.path.basename(result.file_path), + output, + result.file_path + )) + + self.output_file.write(row) + self.output_file.flush() + + def print_footer(self, time_finished): + self.time_finished = time_finished + footer = ("""\ +</table> +<pre> +%s +</pre> +</body> +</html> +""" % (AbstractInterpreterPrinter.generic_footer(self))) + self.output_file.write(footer) + sys.stdout.write("Generated '%s'.\n" %(self.output_file.name)) + +### Main ###################################################################### + +def main(): + # Handle command line options + cmdline_processor = CommandLineProcessor(sys.argv[1:]) + cmdline_processor.run() + + # Sanity checks + if not os.path.exists(Settings.interpreter): + sys.stderr.write("Error: Interpreter '%s' not found.\n" %(Settings.interpreter)) + sys.exit(3) + if not os.path.exists(Settings.input_dir): + sys.stderr.write("Error: Directory with test data '%s' not found.\n" %(Settings.input_dir)) + sys.exit(3) + + # Collect files + candidates = [] + for root, dirnames, filenames in os.walk(Settings.input_dir): + for filename in fnmatch.filter(filenames, '*.js'): + candidates.append(os.path.join(root, filename)) + candidates.sort() + input_files = check_candidates(candidates) + + if not input_files: + sys.stderr.write("Error: No relevant test files found in '%s'.\n" %(Settings.input_dir)) + sys.exit(3) + if Settings.verbose: + sys.stdout.write("Will process %s files for each interpreter mode.\n" %(len(input_files))) + + # Run interpreter + printers = [] + for printer in Settings.printers: + if printer == "txt": + printers.append( InterpreterTextPrinter() ) + elif printer == "html": + printers.append( InterpreterHtmlPrinter() ) + + for mode in Settings.modes: + runner = InterpreterRunner(input_files, mode, ["--" + mode], printers) + runner.run() + +if __name__ == "__main__": + main() |