• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Copyright (C) 2011 Google Inc.  All rights reserved.
2#
3# Redistribution and use in source and binary forms, with or without
4# modification, are permitted provided that the following conditions
5# are met:
6# 1. Redistributions of source code must retain the above copyright
7#    notice, this list of conditions and the following disclaimer.
8# 2. Redistributions in binary form must reproduce the above copyright
9#    notice, this list of conditions and the following disclaimer in the
10#    documentation and/or other materials provided with the distribution.
11#
12# THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
13# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
14# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
15# PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
16# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
17# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
18# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
19# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
20# OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
21# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
22# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
23#
24
25import os
26import shutil
27import tempfile
28from webkitpy.common.checkout.scm.detection import detect_scm_system
29from webkitpy.common.system.executive import ScriptError
30
31PASS_MESSAGE = 'All tests PASS!'
32FAIL_MESSAGE = """Some tests FAIL!
33To update the reference files, execute:
34    run-bindings-tests --reset-results
35
36If the failures are not due to your changes, test results may be out of sync;
37please rebaseline them in a separate CL, after checking that tests fail in ToT.
38In CL, please set:
39NOTRY=true
40TBR=(someone in Source/bindings/OWNERS or WATCHLISTS:bindings)
41"""
42
43# Python compiler is incomplete; skip IDLs with unimplemented features
44SKIP_PYTHON = set([
45    'TestCustomAccessors.idl',
46    'TestEventTarget.idl',
47    'TestException.idl',
48    'TestImplements.idl',
49    'TestInterface.idl',
50    'TestInterfaceImplementedAs.idl',
51    'TestNamedConstructor.idl',
52    'TestNode.idl',
53    'TestObject.idl',
54    'TestOverloadedConstructors.idl',
55    'TestPartialInterface.idl',
56    'TestTypedefs.idl',
57])
58
59input_directory = os.path.join('bindings', 'tests', 'idls')
60support_input_directory = os.path.join('bindings', 'tests', 'idls', 'testing')
61reference_directory = os.path.join('bindings', 'tests', 'results')
62reference_event_names_filename = os.path.join(reference_directory, 'EventInterfaces.in')
63
64
65class ScopedTempFileProvider(object):
66    def __init__(self):
67        self.files = []
68        self.directories = []
69
70    def __del__(self):
71        for filename in self.files:
72            os.remove(filename)
73        for directory in self.directories:
74            shutil.rmtree(directory)
75
76    def newtempfile(self):
77        file_handle, path = tempfile.mkstemp()
78        self.files.append(path)
79        return file_handle, path
80
81    def newtempdir(self):
82        path = tempfile.mkdtemp()
83        self.directories.append(path)
84        return path
85
86provider = ScopedTempFileProvider()
87
88
89class BindingsTests(object):
90    def __init__(self, reset_results, test_python, verbose, executive):
91        self.reset_results = reset_results
92        self.test_python = test_python
93        self.verbose = verbose
94        self.executive = executive
95        _, self.interface_dependencies_filename = provider.newtempfile()
96        _, self.derived_sources_list_filename = provider.newtempfile()
97        # Generate output into the reference directory if resetting results, or
98        # a temp directory if not.
99        if reset_results:
100            self.output_directory = reference_directory
101        else:
102            self.output_directory = provider.newtempdir()
103        self.output_directory_py = provider.newtempdir()
104        self.event_names_filename = os.path.join(self.output_directory, 'EventInterfaces.in')
105
106    def run_command(self, cmd):
107        output = self.executive.run_command(cmd)
108        if output:
109            print output
110
111    def generate_from_idl_pl(self, idl_file):
112        cmd = ['perl', '-w',
113               '-Ibindings/scripts',
114               '-Ibuild/scripts',
115               '-Icore/scripts',
116               '-I../../JSON/out/lib/perl5',
117               'bindings/scripts/generate_bindings.pl',
118               # idl include directories (path relative to generate-bindings.pl)
119               '--include', '.',
120               '--outputDir', self.output_directory,
121               '--interfaceDependenciesFile', self.interface_dependencies_filename,
122               '--idlAttributesFile', 'bindings/IDLExtendedAttributes.txt',
123               idl_file]
124        try:
125            self.run_command(cmd)
126        except ScriptError, e:
127            print 'ERROR: generate_bindings.pl: ' + os.path.basename(idl_file)
128            print e.output
129            return e.exit_code
130        return 0
131
132    def generate_from_idl_py(self, idl_file):
133        cmd = ['python',
134               'bindings/scripts/unstable/idl_compiler.py',
135               '--output-dir', self.output_directory_py,
136               '--idl-attributes-file', 'bindings/IDLExtendedAttributes.txt',
137               '--include', '.',
138               '--interface-dependencies-file',
139               self.interface_dependencies_filename,
140               idl_file]
141        try:
142            self.run_command(cmd)
143        except ScriptError, e:
144            print 'ERROR: idl_compiler.py: ' + os.path.basename(idl_file)
145            print e.output
146            return e.exit_code
147        return 0
148
149    def generate_interface_dependencies(self):
150        idl_files_list_file, main_idl_files_list_filename = provider.newtempfile()
151        idl_paths = [os.path.join(input_directory, input_file)
152                     for input_file in os.listdir(input_directory)
153                     if input_file.endswith('.idl')]
154        idl_files_list_contents = ''.join(idl_path + '\n'
155                                          for idl_path in idl_paths)
156        os.write(idl_files_list_file, idl_files_list_contents)
157        support_idl_files_list_file, support_idl_files_list_filename = provider.newtempfile()
158        support_idl_paths = [os.path.join(support_input_directory, input_file)
159                     for input_file in os.listdir(support_input_directory)
160                     if input_file.endswith('.idl')]
161        support_idl_files_list_contents = ''.join(idl_path + '\n'
162                                          for idl_path in support_idl_paths)
163        os.write(support_idl_files_list_file, support_idl_files_list_contents)
164
165        # Dummy files, required by compute_dependencies but not checked
166        _, window_constructors_file = provider.newtempfile()
167        _, workerglobalscope_constructors_file = provider.newtempfile()
168        _, sharedworkerglobalscope_constructors_file = provider.newtempfile()
169        _, dedicatedworkerglobalscope_constructors_file = provider.newtempfile()
170        _, serviceworkersglobalscope_constructors_file = provider.newtempfile()
171        cmd = ['python',
172               'bindings/scripts/compute_dependencies.py',
173               '--main-idl-files-list', main_idl_files_list_filename,
174               '--support-idl-files-list', support_idl_files_list_filename,
175               '--interface-dependencies-file', self.interface_dependencies_filename,
176               '--bindings-derived-sources-file', self.derived_sources_list_filename,
177               '--window-constructors-file', window_constructors_file,
178               '--workerglobalscope-constructors-file', workerglobalscope_constructors_file,
179               '--sharedworkerglobalscope-constructors-file', sharedworkerglobalscope_constructors_file,
180               '--dedicatedworkerglobalscope-constructors-file', dedicatedworkerglobalscope_constructors_file,
181               '--serviceworkerglobalscope-constructors-file', serviceworkersglobalscope_constructors_file,
182               '--event-names-file', self.event_names_filename,
183               '--write-file-only-if-changed', '0']
184
185        if self.reset_results and self.verbose:
186            print 'Reset results: EventInterfaces.in'
187        try:
188            self.run_command(cmd)
189        except ScriptError, e:
190            print 'ERROR: compute_dependencies.py'
191            print e.output
192            return e.exit_code
193        return 0
194
195    def identical_file(self, reference_filename, output_filename):
196        reference_basename = os.path.basename(reference_filename)
197        cmd = ['diff',
198               '-u',
199               '-N',
200               reference_filename,
201               output_filename]
202        try:
203            self.run_command(cmd)
204        except ScriptError, e:
205            # run_command throws an exception on diff (b/c non-zero exit code)
206            print 'FAIL: %s' % reference_basename
207            print e.output
208            return False
209
210        if self.verbose:
211            print 'PASS: %s' % reference_basename
212        return True
213
214    def identical_output_files(self, output_directory):
215        file_pairs = [(os.path.join(reference_directory, output_file),
216                       os.path.join(output_directory, output_file))
217                      for output_file in os.listdir(output_directory)
218                      # FIXME: add option to compiler to not generate tables
219                      if output_file != 'parsetab.py']
220        return all([self.identical_file(reference_filename, output_filename)
221                    for (reference_filename, output_filename) in file_pairs])
222
223    def no_excess_files(self):
224        generated_files = set(os.listdir(self.output_directory))
225        generated_files.add('.svn')  # Subversion working copy directory
226        excess_files = [output_file
227                        for output_file in os.listdir(reference_directory)
228                        if output_file not in generated_files]
229        if excess_files:
230            print ('Excess reference files! '
231                  '(probably cruft from renaming or deleting):\n' +
232                  '\n'.join(excess_files))
233            return False
234        return True
235
236    def run_tests(self):
237        # Generate output, immediately dying on failure
238        if self.generate_interface_dependencies():
239            return False
240
241        for directory in [input_directory, support_input_directory]:
242            for input_filename in os.listdir(directory):
243                if not input_filename.endswith('.idl'):
244                    continue
245                idl_path = os.path.join(directory, input_filename)
246                if self.generate_from_idl_pl(idl_path):
247                    return False
248                if self.reset_results and self.verbose:
249                    print 'Reset results: %s' % input_filename
250                if not self.test_python:
251                    continue
252                if (input_filename in SKIP_PYTHON or
253                    directory == support_input_directory):
254                    if self.verbose:
255                        print 'SKIP: %s' % input_filename
256                    continue
257                if self.generate_from_idl_py(idl_path):
258                    return False
259
260        # Detect all changes
261        passed = self.identical_file(reference_event_names_filename,
262                                     self.event_names_filename)
263        passed &= self.identical_output_files(self.output_directory)
264        if self.test_python:
265            if self.verbose:
266                print
267                print 'Python:'
268            passed &= self.identical_output_files(self.output_directory_py)
269        passed &= self.no_excess_files()
270        return passed
271
272    def main(self):
273        current_scm = detect_scm_system(os.curdir)
274        os.chdir(os.path.join(current_scm.checkout_root, 'Source'))
275
276        all_tests_passed = self.run_tests()
277        if all_tests_passed:
278            if self.verbose:
279                print
280                print PASS_MESSAGE
281            return 0
282        print
283        print FAIL_MESSAGE
284        return -1
285