# Copyright (C) 2011 Google Inc. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions # are met: # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # # THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY # EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY # OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # 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.system.executive import Executive # engine/ 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, 'engine')) sys.path.append(source_path) # for engine/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! To update the reference files, execute: run-bindings-tests --reset-results If the failures are not due to your changes, test results may be out of sync; please rebaseline them in a separate CL, after checking that tests fail in ToT. In CL, please set: NOTRY=true TBR=(someone in engine/bindings/OWNERS or WATCHLISTS:bindings) """ DEPENDENCY_IDL_FILES = frozenset([ 'TestImplements.idl', 'TestImplements2.idl', 'TestImplements3.idl', 'TestPartialInterface.idl', 'TestPartialInterface2.idl', ]) 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) 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 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 diff(reference_filename, output_filename) return False if verbose: print 'PASS: %s' % reference_basename return True 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)] return all([identical_file(reference_filename, output_filename) for (reference_filename, output_filename) in file_pairs]) 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) if output_file not in generated_files] if excess_files: print ('Excess reference files! ' '(probably cruft from renaming or deleting):\n' + '\n'.join(excess_files)) return False return True 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) definition_name, _ = os.path.splitext(idl_basename) 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)