diff options
Diffstat (limited to 'chromium/third_party/WebKit/Tools/Scripts/webkitpy/bindings/main.py')
-rw-r--r-- | chromium/third_party/WebKit/Tools/Scripts/webkitpy/bindings/main.py | 380 |
1 files changed, 164 insertions, 216 deletions
diff --git a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/bindings/main.py b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/bindings/main.py index 663fd67b1f0..04e11e52aff 100644 --- a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/bindings/main.py +++ b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/bindings/main.py @@ -22,11 +22,29 @@ # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # +from contextlib import contextmanager +import filecmp +import fnmatch import os import shutil +import sys import tempfile -from webkitpy.common.checkout.scm.detection import detect_scm_system -from webkitpy.common.system.executive import ScriptError + +from webkitpy.common.system.executive import Executive + +# Source/ path is needed both to find input IDL files, and to import other +# Python modules. +module_path = os.path.dirname(__file__) +source_path = os.path.normpath(os.path.join(module_path, os.pardir, os.pardir, + os.pardir, os.pardir, 'Source')) +sys.path.append(source_path) # for Source/bindings imports + +import bindings.scripts.compute_interfaces_info_individual +from bindings.scripts.compute_interfaces_info_individual import compute_info_individual, info_individual +import bindings.scripts.compute_interfaces_info_overall +from bindings.scripts.compute_interfaces_info_overall import compute_interfaces_info_overall, interfaces_info +from bindings.scripts.idl_compiler import IdlCompilerV8 + PASS_MESSAGE = 'All tests PASS!' FAIL_MESSAGE = """Some tests FAIL! @@ -40,188 +58,124 @@ NOTRY=true TBR=(someone in Source/bindings/OWNERS or WATCHLISTS:bindings) """ -# Python compiler is incomplete; skip IDLs with unimplemented features -SKIP_PYTHON = set([ - 'TestCustomAccessors.idl', - 'TestEventTarget.idl', - 'TestException.idl', +DEPENDENCY_IDL_FILES = frozenset([ 'TestImplements.idl', - 'TestInterface.idl', - 'TestInterfaceImplementedAs.idl', - 'TestNamedConstructor.idl', - 'TestNode.idl', - 'TestObject.idl', - 'TestOverloadedConstructors.idl', + 'TestImplements2.idl', + 'TestImplements3.idl', 'TestPartialInterface.idl', - 'TestTypedefs.idl', + 'TestPartialInterface2.idl', ]) -input_directory = os.path.join('bindings', 'tests', 'idls') -support_input_directory = os.path.join('bindings', 'tests', 'idls', 'testing') -reference_directory = os.path.join('bindings', 'tests', 'results') -reference_event_names_filename = os.path.join(reference_directory, 'EventInterfaces.in') - - -class ScopedTempFileProvider(object): - def __init__(self): - self.files = [] - self.directories = [] - - def __del__(self): - for filename in self.files: - os.remove(filename) - for directory in self.directories: - shutil.rmtree(directory) - - def newtempfile(self): - file_handle, path = tempfile.mkstemp() - self.files.append(path) - return file_handle, path - - def newtempdir(self): - path = tempfile.mkdtemp() - self.directories.append(path) - return path - -provider = ScopedTempFileProvider() - - -class BindingsTests(object): - def __init__(self, reset_results, test_python, verbose, executive): - self.reset_results = reset_results - self.test_python = test_python - self.verbose = verbose - self.executive = executive - _, self.interface_dependencies_filename = provider.newtempfile() - _, self.derived_sources_list_filename = provider.newtempfile() - # Generate output into the reference directory if resetting results, or - # a temp directory if not. - if reset_results: - self.output_directory = reference_directory - else: - self.output_directory = provider.newtempdir() - self.output_directory_py = provider.newtempdir() - self.event_names_filename = os.path.join(self.output_directory, 'EventInterfaces.in') - - def run_command(self, cmd): - output = self.executive.run_command(cmd) - if output: - print output - - def generate_from_idl_pl(self, idl_file): - cmd = ['perl', '-w', - '-Ibindings/scripts', - '-Ibuild/scripts', - '-Icore/scripts', - '-I../../JSON/out/lib/perl5', - 'bindings/scripts/generate_bindings.pl', - # idl include directories (path relative to generate-bindings.pl) - '--include', '.', - '--outputDir', self.output_directory, - '--interfaceDependenciesFile', self.interface_dependencies_filename, - '--idlAttributesFile', 'bindings/IDLExtendedAttributes.txt', - idl_file] - try: - self.run_command(cmd) - except ScriptError, e: - print 'ERROR: generate_bindings.pl: ' + os.path.basename(idl_file) - print e.output - return e.exit_code - return 0 - def generate_from_idl_py(self, idl_file): - cmd = ['python', - 'bindings/scripts/unstable/idl_compiler.py', - '--output-dir', self.output_directory_py, - '--idl-attributes-file', 'bindings/IDLExtendedAttributes.txt', - '--include', '.', - '--interface-dependencies-file', - self.interface_dependencies_filename, - idl_file] - try: - self.run_command(cmd) - except ScriptError, e: - print 'ERROR: idl_compiler.py: ' + os.path.basename(idl_file) - print e.output - return e.exit_code - return 0 +test_input_directory = os.path.join(source_path, 'bindings', 'tests', 'idls') +reference_directory = os.path.join(source_path, 'bindings', 'tests', 'results') + + +@contextmanager +def TemporaryDirectory(): + """Wrapper for tempfile.mkdtemp() so it's usable with 'with' statement. + + Simple backport of tempfile.TemporaryDirectory from Python 3.2. + """ + name = tempfile.mkdtemp() + try: + yield name + finally: + shutil.rmtree(name) + + +def generate_interface_dependencies(): + def idl_paths_recursive(directory): + # This is slow, especially on Windows, due to os.walk making + # excess stat() calls. Faster versions may appear in Python 3.5 or + # later: + # https://github.com/benhoyt/scandir + # http://bugs.python.org/issue11406 + idl_paths = [] + for dirpath, _, files in os.walk(directory): + idl_paths.extend(os.path.join(dirpath, filename) + for filename in fnmatch.filter(files, '*.idl')) + return idl_paths + + # We compute interfaces info for *all* IDL files, not just test IDL + # files, as code generator output depends on inheritance (both ancestor + # chain and inherited extended attributes), and some real interfaces + # are special-cased, such as Node. + # + # For example, when testing the behavior of interfaces that inherit + # from Node, we also need to know that these inherit from EventTarget, + # since this is also special-cased and Node inherits from EventTarget, + # but this inheritance information requires computing dependencies for + # the real Node.idl file. + + # 2-stage computation: individual, then overall + # + # Properly should compute separately by component (currently test + # includes are invalid), but that's brittle (would need to update this file + # for each new component) and doesn't test the code generator any better + # than using a single component. + for idl_filename in idl_paths_recursive(source_path): + compute_info_individual(idl_filename, 'tests') + info_individuals = [info_individual()] + compute_interfaces_info_overall(info_individuals) + + +def bindings_tests(output_directory, verbose): + executive = Executive() + + def diff(filename1, filename2): + # Python's difflib module is too slow, especially on long output, so + # run external diff(1) command + cmd = ['diff', + '-u', # unified format + '-N', # treat absent files as empty + filename1, + filename2] + # Return output and don't raise exception, even though diff(1) has + # non-zero exit if files differ. + return executive.run_command(cmd, error_handler=lambda x: None) + + def delete_cache_files(): + # FIXME: Instead of deleting cache files, don't generate them. + cache_files = [os.path.join(output_directory, output_file) + for output_file in os.listdir(output_directory) + if (output_file in ('lextab.py', # PLY lex + 'lextab.pyc', + 'parsetab.pickle') or # PLY yacc + output_file.endswith('.cache'))] # Jinja + for cache_file in cache_files: + os.remove(cache_file) + + def identical_file(reference_filename, output_filename): + reference_basename = os.path.basename(reference_filename) - def generate_interface_dependencies(self): - idl_files_list_file, main_idl_files_list_filename = provider.newtempfile() - idl_paths = [os.path.join(input_directory, input_file) - for input_file in os.listdir(input_directory) - if input_file.endswith('.idl')] - idl_files_list_contents = ''.join(idl_path + '\n' - for idl_path in idl_paths) - os.write(idl_files_list_file, idl_files_list_contents) - support_idl_files_list_file, support_idl_files_list_filename = provider.newtempfile() - support_idl_paths = [os.path.join(support_input_directory, input_file) - for input_file in os.listdir(support_input_directory) - if input_file.endswith('.idl')] - support_idl_files_list_contents = ''.join(idl_path + '\n' - for idl_path in support_idl_paths) - os.write(support_idl_files_list_file, support_idl_files_list_contents) - - # Dummy files, required by compute_dependencies but not checked - _, window_constructors_file = provider.newtempfile() - _, workerglobalscope_constructors_file = provider.newtempfile() - _, sharedworkerglobalscope_constructors_file = provider.newtempfile() - _, dedicatedworkerglobalscope_constructors_file = provider.newtempfile() - _, serviceworkersglobalscope_constructors_file = provider.newtempfile() - cmd = ['python', - 'bindings/scripts/compute_dependencies.py', - '--main-idl-files-list', main_idl_files_list_filename, - '--support-idl-files-list', support_idl_files_list_filename, - '--interface-dependencies-file', self.interface_dependencies_filename, - '--bindings-derived-sources-file', self.derived_sources_list_filename, - '--window-constructors-file', window_constructors_file, - '--workerglobalscope-constructors-file', workerglobalscope_constructors_file, - '--sharedworkerglobalscope-constructors-file', sharedworkerglobalscope_constructors_file, - '--dedicatedworkerglobalscope-constructors-file', dedicatedworkerglobalscope_constructors_file, - '--serviceworkerglobalscope-constructors-file', serviceworkersglobalscope_constructors_file, - '--event-names-file', self.event_names_filename, - '--write-file-only-if-changed', '0'] - - if self.reset_results and self.verbose: - print 'Reset results: EventInterfaces.in' - try: - self.run_command(cmd) - except ScriptError, e: - print 'ERROR: compute_dependencies.py' - print e.output - return e.exit_code - return 0 + if not os.path.isfile(reference_filename): + print 'Missing reference file!' + print '(if adding new test, update reference files)' + print reference_basename + print + return False - def identical_file(self, reference_filename, output_filename): - reference_basename = os.path.basename(reference_filename) - cmd = ['diff', - '-u', - '-N', - reference_filename, - output_filename] - try: - self.run_command(cmd) - except ScriptError, e: - # run_command throws an exception on diff (b/c non-zero exit code) + if not filecmp.cmp(reference_filename, output_filename): + # cmp is much faster than diff, and usual case is "no differance", + # so only run diff if cmp detects a difference print 'FAIL: %s' % reference_basename - print e.output + print diff(reference_filename, output_filename) return False - if self.verbose: + if verbose: print 'PASS: %s' % reference_basename return True - def identical_output_files(self, output_directory): + def identical_output_files(): file_pairs = [(os.path.join(reference_directory, output_file), os.path.join(output_directory, output_file)) - for output_file in os.listdir(output_directory) - # FIXME: add option to compiler to not generate tables - if output_file != 'parsetab.py'] - return all([self.identical_file(reference_filename, output_filename) + for output_file in os.listdir(output_directory)] + return all([identical_file(reference_filename, output_filename) for (reference_filename, output_filename) in file_pairs]) - def no_excess_files(self): - generated_files = set(os.listdir(self.output_directory)) + def no_excess_files(): + generated_files = set(os.listdir(output_directory)) generated_files.add('.svn') # Subversion working copy directory excess_files = [output_file for output_file in os.listdir(reference_directory) @@ -233,52 +187,46 @@ class BindingsTests(object): return False return True - def run_tests(self): - # Generate output, immediately dying on failure - if self.generate_interface_dependencies(): - return False - - for directory in [input_directory, support_input_directory]: - for input_filename in os.listdir(directory): - if not input_filename.endswith('.idl'): - continue - idl_path = os.path.join(directory, input_filename) - if self.generate_from_idl_pl(idl_path): - return False - if self.reset_results and self.verbose: - print 'Reset results: %s' % input_filename - if not self.test_python: - continue - if (input_filename in SKIP_PYTHON or - directory == support_input_directory): - if self.verbose: - print 'SKIP: %s' % input_filename - continue - if self.generate_from_idl_py(idl_path): - return False - - # Detect all changes - passed = self.identical_file(reference_event_names_filename, - self.event_names_filename) - passed &= self.identical_output_files(self.output_directory) - if self.test_python: - if self.verbose: - print - print 'Python:' - passed &= self.identical_output_files(self.output_directory_py) - passed &= self.no_excess_files() - return passed - - def main(self): - current_scm = detect_scm_system(os.curdir) - os.chdir(os.path.join(current_scm.checkout_root, 'Source')) - - all_tests_passed = self.run_tests() - if all_tests_passed: - if self.verbose: - print - print PASS_MESSAGE - return 0 - print - print FAIL_MESSAGE - return -1 + try: + generate_interface_dependencies() + idl_compiler = IdlCompilerV8(output_directory, + interfaces_info=interfaces_info, + only_if_changed=True) + + idl_basenames = [filename + for filename in os.listdir(test_input_directory) + if (filename.endswith('.idl') and + # Dependencies aren't built + # (they are used by the dependent) + filename not in DEPENDENCY_IDL_FILES)] + for idl_basename in idl_basenames: + idl_path = os.path.realpath( + os.path.join(test_input_directory, idl_basename)) + idl_compiler.compile_file(idl_path) + if verbose: + print 'Compiled: %s' % filename + finally: + delete_cache_files() + + # Detect all changes + passed = identical_output_files() + passed &= no_excess_files() + + if passed: + if verbose: + print + print PASS_MESSAGE + return 0 + print + print FAIL_MESSAGE + return 1 + + +def run_bindings_tests(reset_results, verbose): + # Generate output into the reference directory if resetting results, or + # a temp directory if not. + if reset_results: + print 'Resetting results' + return bindings_tests(reference_directory, verbose) + with TemporaryDirectory() as temp_dir: + return bindings_tests(temp_dir, verbose) |