| Differences between
and this patch
- a/Tools/ChangeLog +63 lines
Lines 1-3 a/Tools/ChangeLog_sec1
1
2015-03-30  Youenn Fablet  <youenn.fablet@crf.canon.fr>
2
3
        Add support for automated W3C conformance testing
4
        https://bugs.webkit.org/show_bug.cgi?id=134766
5
6
        Reviewed by NOBODY (OOPS!).
7
8
        Scripts/check-w3c-conformance is the executable to test W3C test conformance.
9
        Results are stored by default in a WebKitBuild sub-folder (see Port.w3c_conformance_results_directory).
10
        Scripts/webkitpy/w3c/conformance_checker.py is the main script.
11
        It uses test importer to clone and import the tests.
12
        It then uses run-webkit-test to run the tests and generate the -expected.txt files for testharness tests.
13
        From the -expected.txt files, it computes whether the test is passing, partially passing or failing.
14
        Results are outputted through TestResultsManager as a json file that can be loaded using w3c_conformance_results.html.
15
        Draft Test expectations and import rules are also generated in the same folder.
16
17
        * Scripts/check-w3c-conformance: Added.
18
        * Scripts/webkitpy/port/base.py:
19
        (Port.w3c_conformance_results_directory):
20
        * Scripts/webkitpy/w3c/conformance_checker.py: Added.
21
        (create_argument_parser):
22
        (ConformanceChecker):
23
        (ConformanceChecker.__init__):
24
        (ConformanceChecker._print_rwt_output_line):
25
        (ConformanceChecker.run_tests):
26
        (ConformanceChecker.init_test_importer):
27
        (ConformanceChecker.test_suites):
28
        (ConformanceChecker.check_conformance):
29
        (ConformanceChecker.print_summary_to_console):
30
        (ConformanceChecker.import_expectation_updates):
31
        (ConformanceChecker.test_expectation_updates):
32
        (ConformanceChecker.write_conformance_results):
33
        (TestResultsConformanceParser):
34
        (TestResultsConformanceParser.__init__):
35
        (TestResultsConformanceParser._get_test_results):
36
        (TestResultsConformanceParser._js_test_conformance):
37
        (TestResultsConformanceParser._conformance_status):
38
        (TestResultsConformanceParser.w3c_test_name):
39
        (TestResultsConformanceParser.compute_conformance_results):
40
        (configure_logging):
41
        (configure_logging.LogHandler):
42
        (configure_logging.LogHandler.format):
43
        (main):
44
        * Scripts/webkitpy/w3c/conformance_checker_unittest.py: Added.
45
        (ConformancCheckerTest):
46
        (ConformancCheckerTest.setUp):
47
        (ConformancCheckerTest.write_expected_file):
48
        (ConformancCheckerTest.test_w3c_name_conversion):
49
        (ConformancCheckerTest.test_json_result_parsing):
50
        (ConformancCheckerTest.test_missing_test):
51
        (ConformancCheckerTest.test_ref_test_timeout):
52
        (ConformancCheckerTest.test_ref_test_crash):
53
        (ConformancCheckerTest.test_ref_test_fail):
54
        (ConformancCheckerTest.test_js_test_pass):
55
        (ConformancCheckerTest.test_js_test_partial):
56
        (ConformancCheckerTest.test_js_test_fail):
57
        (ConformancCheckerTest.test_js_test_crash):
58
        (ConformancCheckerTest.test_js_test_timeout):
59
        (ConformancCheckerTest.test_import_expectations_update):
60
        (ConformancCheckerTest.test_test_expectations_update):
61
        * Scripts/webkitpy/w3c/test_downloader.py:
62
        (TestDownloader.all_test_paths):
63
1
2015-03-27  Gyuyoung Kim  <gyuyoung.kim@samsung.com>
64
2015-03-27  Gyuyoung Kim  <gyuyoung.kim@samsung.com>
2
65
3
        [CMake] Remove unnecessary INCLUDE_IF_EXISTS macro 
66
        [CMake] Remove unnecessary INCLUDE_IF_EXISTS macro 
- a/Tools/Scripts/check-w3c-conformance +31 lines
Line 0 a/Tools/Scripts/check-w3c-conformance_sec1
1
#!/usr/bin/env python
2
3
# Copyright (c) 2015, Canon Inc. All rights reserved.
4
#
5
# Redistribution and use in source and binary forms, with or without
6
# modification, are permitted provided that the following conditions
7
# are met:
8
# 1.  Redistributions of source code must retain the above copyright
9
#     notice, this list of conditions and the following disclaimer.
10
# 2.  Redistributions in binary form must reproduce the above copyright
11
#     notice, this list of conditions and the following disclaimer in the
12
#     documentation and/or other materials provided with the distribution.
13
# 3.  Neither the name of Canon Inc. nor the names of
14
#     its contributors may be used to endorse or promote products derived
15
#     from this software without specific prior written permission.
16
#
17
# THIS SOFTWARE IS PROVIDED BY CANON INC. AND ITS CONTRIBUTORS "AS IS" AND ANY
18
# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20
# DISCLAIMED. IN NO EVENT SHALL CANON INC. AND ITS CONTRIBUTORS BE LIABLE FOR
21
# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
23
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
24
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
25
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
28
"""Wrapper around webkitpy/w3c/conformance_checker.py"""
29
from webkitpy.common import multiprocessing_bootstrap
30
31
multiprocessing_bootstrap.run('webkitpy', 'w3c', 'conformance_checker.py')
- a/Tools/Scripts/webkitpy/port/base.py +4 lines
Lines 784-789 class Port(object): a/Tools/Scripts/webkitpy/port/base.py_sec1
784
    def python_unittest_results_directory(self):
784
    def python_unittest_results_directory(self):
785
        return self._build_path('python-unittest-results')
785
        return self._build_path('python-unittest-results')
786
786
787
    def w3c_conformance_results_directory(self):
788
        """Absolute path to the place to store the W3C conformance results."""
789
        return self._build_path('w3c-conformance-results')
790
787
    def default_results_directory(self):
791
    def default_results_directory(self):
788
        """Absolute path to the default place to store the test results."""
792
        """Absolute path to the default place to store the test results."""
789
        # Results are store relative to the built products to make it easy
793
        # Results are store relative to the built products to make it easy
- a/Tools/Scripts/webkitpy/w3c/conformance_checker.py +357 lines
Line 0 a/Tools/Scripts/webkitpy/w3c/conformance_checker.py_sec1
1
# Copyright (c) 2015, Canon 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
# 3.  Neither the name of Canon Inc. nor the names of
12
#     its contributors may be used to endorse or promote products derived
13
#     from this software without specific prior written permission.
14
#
15
# THIS SOFTWARE IS PROVIDED BY CANON INC. AND ITS CONTRIBUTORS "AS IS" AND ANY
16
# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
17
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
18
# DISCLAIMED. IN NO EVENT SHALL CANON INC. AND ITS CONTRIBUTORS BE LIABLE FOR
19
# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
21
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
22
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
23
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25
26
"""
27
 This script evaluates W3C tests conformance by:
28
    - importing W3C tests as WebKit tests
29
    - generating expected.txt files (using run-webkit-tests)
30
    - computing whether a js test is passing or not by checking PASS/FAIL assertions outputted within the expected.txt files
31
"""
32
33
import argparse
34
import json
35
import logging
36
import optparse
37
import sys
38
39
from webkitpy.common.host import Host
40
from webkitpy.common.system.executive import Executive
41
from webkitpy.layout_tests.layout_package import json_results_generator
42
from webkitpy.layout_tests.models.test_expectations import TestExpectations
43
from webkitpy.port import configuration_options, platform_options
44
from webkitpy.w3c.test_importer import TestImporter
45
from webkitpy.w3c.test_importer import parse_args as parse_importer_args
46
from webkitpy.w3c.test_downloader import TestDownloader
47
48
_log = logging.getLogger(__name__)
49
50
51
def create_argument_parser():
52
    parser = argparse.ArgumentParser()
53
54
    for opt in platform_options(use_globs=False):
55
        parser.add_argument(opt.get_opt_string(), action=opt.action, dest=opt.dest, const=getattr(opt, 'const'), help=getattr(opt, 'help'))
56
    parser.add_argument('--dump-render-tree', '-1', dest='webkit_test_runner', action='store_false', default=True,
57
        help='Set WebKit architecture WebKit1 or WebKit2 (default WK2).')
58
59
    parser.add_argument('--debug', dest='configuration', action='store_const', const='Debug', default='Release',
60
        help='Use "Debug" configuration. Default is "Release"')
61
    parser.add_argument('-q', dest='quiet', action='store_true', default=False,
62
        help='Print minimal log.')
63
    parser.add_argument('-v', dest='verbose', action='store_true', default=False,
64
        help='Print maximal log.')
65
    parser.add_argument('-t', '--test-path', action='append', dest='test_paths', default=[],
66
        help='Import and run only tests in the supplied subdirectory of the w3c_test_directory. Can be supplied multiple times to give multiple paths')
67
    parser.add_argument('-s', '--skip-refresh', action='store_false', default=True, dest='refresh',
68
        help='Disable refresh of tests (git checkout  + import). By default, tests are refreshed.')
69
    parser.add_argument('--no-fetch', action='store_false', dest='fetch', default=True,
70
        help='Do not fetch the CSS/WPT repositories. By default, repositories are fetched.')
71
72
    return parser
73
74
75
class ConformanceChecker(object):
76
77
    def __init__(self, port, options):
78
        self._port = port
79
        self._filesystem = port.host.filesystem
80
        self._options = options
81
        self._conformance_results = None
82
83
        self.WPT_TESTSUITE = 'web-platform-tests'
84
        self.layout_w3c_directory = self._filesystem.join('imported', 'w3c')
85
86
        self.results_directory = self._port.w3c_conformance_results_directory()
87
        self.layout_tests_result_directory = self._filesystem.join(self.results_directory, 'layout-test-results')
88
        self.imported_tests_directory = self._filesystem.join(self.results_directory, 'imported-tests')
89
        self.runned_tests_directory = self._filesystem.join(self.results_directory, 'runned-tests')
90
91
        self.init_test_importer()
92
93
    def _print_rwt_output_line(self, line):
94
        if not self._options.verbose:
95
            if line.startswith('['):
96
                tokens = line.split(' ')
97
                sys.stdout.write('\r' + tokens[0] + ' ' + tokens[1].replace(self.runned_tests_directory, '')[1:] + ' ')
98
                sys.stdout.flush()
99
            elif line == '':
100
                sys.stdout.write('\n')
101
        else:
102
            sys.stdout.write(line)
103
104
    def run_tests(self, tests):
105
        run_webkit_args = [self._port.path_to_script('run-webkit-tests'), '--no-retry-failures', '--no-show-results', '--full-results-html']
106
107
        test_list_filename = self._filesystem.join(self.runned_tests_directory, 'test_list.txt')
108
        self._filesystem.write_text_file(test_list_filename, '\n'.join(tests))
109
        run_webkit_args.append('--test-list=' + test_list_filename)
110
111
        if self._filesystem.exists(self.layout_tests_result_directory):
112
            self._filesystem.rmtree(self.layout_tests_result_directory)
113
        run_webkit_args.append('--results-directory=' + self.layout_tests_result_directory)
114
115
        run_webkit_args.append('--wptserver-doc-root=' + self._filesystem.join(self.runned_tests_directory, self.WPT_TESTSUITE))
116
117
        if self._options.configuration:
118
            run_webkit_args.append('--target=' + self._options.configuration)
119
        if self._options.platform:
120
            run_webkit_args.append('--platform=' + self._options.platform)
121
        if not self._options.webkit_test_runner:
122
            run_webkit_args.append('-1')
123
        if not self._options.verbose:
124
            run_webkit_args.append('-q')
125
126
        process = Executive().popen(run_webkit_args, stdout=Executive.PIPE, stderr=Executive.STDOUT)
127
        while True:
128
            line = process.stdout.readline()
129
            self._print_rwt_output_line(line)
130
            if line == '':
131
                break
132
        process.wait()
133
134
    def init_test_importer(self):
135
        args = ['-d', self.imported_tests_directory, '--ignore-expectations']
136
137
        for test_path in self._options.test_paths:
138
            args.append('-t')
139
            args.append(test_path)
140
141
        if not self._options.fetch:
142
            args.append('--no-fetch')
143
144
        if self._options.verbose:
145
            args.append('-v')
146
        elif self._options.quiet:
147
            logging.getLogger('webkitpy.w3c.test_importer').setLevel(logging.WARN)
148
            logging.getLogger('webkitpy.w3c.test_converter').setLevel(logging.WARN)
149
            logging.getLogger('webkitpy.w3c.test_downloader').setLevel(logging.WARN)
150
151
        options, args = parse_importer_args(args)
152
        self.test_importer = TestImporter(self._port.host, None, options)
153
154
    def test_suites(self):
155
        return self._options.test_paths if self._options.test_paths else self.test_importer.test_downloader().all_test_paths()
156
157
    def check_conformance(self):
158
        if self._options.refresh is True:
159
            _log.info('Importing tests...')
160
            self._filesystem.rmtree(self.imported_tests_directory)
161
            self.test_importer.do_import()
162
            # FIXME: We should install WPT modules in imported tests directory to not reinstall them for each run.
163
        else:
164
            _log.info('Skipping test import...')
165
166
        # Regenerate runned_test_directory to start in a clean state (no -expected.txt files in particular).
167
        self._filesystem.rmtree(self.runned_tests_directory)
168
        self._filesystem.copytree(self.imported_tests_directory, self.runned_tests_directory)
169
170
        _log.info('Running tests...')
171
        tests = self._port.tests([self._filesystem.join(self.runned_tests_directory, test_suite) for test_suite in self.test_suites()])
172
        self.run_tests(tests)
173
174
        _log.info('Parsing test results...')
175
        json_file_path = self._filesystem.join(self.layout_tests_result_directory, 'full_results.json')
176
        if not self._filesystem.exists(json_file_path):
177
            _log.error('Cannot read run-webkit-tests result file "%s". run-webkit-tests may have run into some errors. Use -v option\nAborting...' % json_file_path)
178
            return False
179
        self._conformance_results = TestResultsConformanceParser(self._port, self.runned_tests_directory).compute_conformance_results(tests, json_file_path)
180
181
        _log.info('Generating conformance results...')
182
        self._filesystem.maybe_make_directory(self.results_directory)
183
        self.write_conformance_results()
184
185
        _log.info('Generating expectation updates...')
186
        self._filesystem.write_text_file(self._filesystem.join(self.results_directory, 'TestExpectations'), '\n'.join(self.test_expectation_updates()))
187
        self._filesystem.write_text_file(self._filesystem.join(self.results_directory, 'ImportExpectations'), '\n'.join(self.import_expectation_updates()))
188
        return True
189
190
    def print_summary_to_console(self):
191
        num_pass = num_partial = num_fail = num_error = 0
192
        for key, value in self._conformance_results.iteritems():
193
            result = value['status']
194
            if result == 'PASS':
195
                num_pass += 1
196
            elif result == 'PARTIAL':
197
                num_partial += 1
198
            elif result == 'FAIL':
199
                num_fail += 1
200
            else:
201
                num_error += 1
202
        _log.info('Run ' + str(num_pass + num_fail + num_partial + num_error) + ' tests')
203
        _log.info('Pass: ' + str(num_pass))
204
        _log.info('Partial: ' + str(num_partial))
205
        _log.info('Fail: ' + str(num_fail))
206
        _log.info('Error: ' + str(num_error))
207
208
    def import_expectation_updates(self):
209
        import_expectations = []
210
        for test_suite in self.test_suites():
211
            import_expectations.append('# %s [ Pass ]' % self._filesystem.join(self.layout_w3c_directory, test_suite))
212
213
        import_expectations.append('')
214
215
        for test in self._conformance_results.keys():
216
            status = self._conformance_results[test]['status']
217
            if not(status == 'PASS' or status == 'PARTIAL'):
218
                import_expectations.append('%s [ Skip ]' % test)
219
        return import_expectations
220
221
    def test_expectation_updates(self):
222
        test_expectations = ['# W3C test infrastructure folders']
223
        for test_repository in self.test_importer.test_downloader().test_repositories:
224
            for path in test_repository['paths_to_import']:
225
                file_path = self._filesystem.join(self.runned_tests_directory, path)
226
                if (self._filesystem.isdir(file_path)):
227
                    test_expectations.append('%s [ Skip ]' % self._filesystem.join(self.layout_w3c_directory, path))
228
229
        test_expectations.append('# Partially passing tests')
230
        for test in self._conformance_results.keys():
231
            if self._conformance_results[test]['status'] == 'PARTIAL':
232
                test_expectations.append('%s [ Pass ]' % self._filesystem.join(self.layout_w3c_directory, test))
233
        return test_expectations
234
235
    def write_conformance_results(self):
236
        _JSON_PREFIX = 'ADD_CONFORMANCE_RESULTS('
237
        _JSON_SUFFIX = ');'
238
239
        test_run_info = {}
240
        test_run_info['port_name'] = self._port.port_name
241
        test_run_info['test_repositories'] = self.test_importer.test_downloader().test_repositories
242
        test_run_info['results_directory'] = self._filesystem.relpath(self.layout_tests_result_directory, self.results_directory)
243
        test_run_info['tests_directory'] = self._filesystem.relpath(self.runned_tests_directory, self.results_directory)
244
245
        results = {}
246
        results['conformance_results'] = self._conformance_results
247
        results.update(test_run_info)
248
249
        json_string = json.dumps(results, indent=4, sort_keys=True, separators=(',', ':'))
250
        json_string = _JSON_PREFIX + json_string + _JSON_SUFFIX
251
        self._filesystem.write_text_file(self._filesystem.join(self.results_directory, 'w3c_conformance_results.json'), json_string)
252
253
        results_file = self._port.path_from_webkit_base('LayoutTests', 'imported', 'w3c', 'resources', 'w3c_conformance_results.html')
254
        if not self._filesystem.exists(results_file):
255
            _log.warning('w3c_conformance_results.html template file cannot be found and copied to conformance result directory')
256
            return
257
        self._filesystem.copyfile(results_file, self._filesystem.join(self.results_directory, 'w3c_conformance_results.html'))
258
259
260
class TestResultsConformanceParser(object):
261
    def __init__(self, port, base_directory):
262
        self._port = port
263
        self._filesystem = port.host.filesystem
264
        self._base_directory = base_directory
265
266
    def _get_test_results(self, tests, test_dict, path):
267
        for key in test_dict.keys():
268
            _path = self._filesystem.join(path, key)
269
            _potential_test = test_dict[key]
270
            # Is it a test entry or a sub-directory
271
            if 'report' in _potential_test:
272
                # Store the test
273
                tests[_path] = _potential_test
274
            else:
275
                self._get_test_results(tests, _potential_test, _path)
276
277
    def _js_test_conformance(self, expected_text):
278
        # Test is said to PASS if at least one line is PASS and no line is FAIL
279
        hasFail = False
280
        hasPass = False
281
        for line in expected_text.splitlines():
282
            if line.startswith('PASS'):
283
                hasPass = True
284
            elif line.startswith('FAIL'):
285
                hasFail = True
286
        if hasPass:
287
            return 'PASS' if not hasFail else 'PARTIAL'
288
        if hasFail:
289
            return 'FAIL'
290
        return 'ERROR'
291
292
    def _conformance_status(self, test_name, test_results):
293
        if not test_name in test_results:
294
            return {'type': 'reftest', 'status': 'PASS'}
295
296
        test_result = test_results[test_name]
297
        test_type = 'jstest' if ('is_missing_text' in test_result and test_result['is_missing_text']) else 'reftest'
298
299
        reason = test_result['actual']
300
        if reason == 'CRASH' or reason == 'TIMEOUT':
301
            return {'type': test_type, 'status': reason}
302
303
        report = test_result['report']
304
        if report == 'MISSING':
305
            text = self._port.expected_text(test_name)
306
            if not(text is None or len(text) == 0):
307
                return {'type': test_type, 'status': self._js_test_conformance(text)}
308
        elif report == 'REGRESSION':
309
            return {'type': test_type, 'status': 'FAIL'}
310
311
        return {'type': test_type, 'status': 'ERROR'}
312
313
    def w3c_test_name(self, test):
314
        return self._filesystem.relpath(test, self._base_directory)
315
316
    def compute_conformance_results(self, tests, json_results_filepath):
317
        test_results = {}
318
        results_json = json_results_generator.load_json(self._filesystem, json_results_filepath)
319
        self._get_test_results(test_results, results_json['tests'], self._filesystem.sep if self._filesystem.isabs(self._base_directory) else '')
320
321
        results = {}
322
        for test_name in tests:
323
            results[self.w3c_test_name(test_name)] = self._conformance_status(test_name, test_results)
324
        return results
325
326
327
def configure_logging():
328
    class LogHandler(logging.StreamHandler):
329
        def format(self, record):
330
            if record.levelno > logging.INFO:
331
                return "%s: %s" % (record.levelname, record.getMessage())
332
            return record.getMessage()
333
334
    logger = logging.getLogger()
335
    logger.setLevel(logging.INFO)
336
    handler = LogHandler()
337
    handler.setLevel(logging.INFO)
338
    logger.addHandler(handler)
339
    return handler
340
341
342
def main(argv, stdout, stderr):
343
    configure_logging()
344
    host = Host()
345
    parser = create_argument_parser()
346
    options, args = parser.parse_known_args(argv)
347
    # FIXME: We should pass all argparse options to the port factory.
348
    port = host.port_factory.get(options.platform, optparse.Values({'configuration': options.configuration, 'webkit_test_runner': options.webkit_test_runner}))
349
350
    runner = ConformanceChecker(port, options)
351
    if runner.check_conformance():
352
        runner.print_summary_to_console()
353
        _log.info("Results exported in '" + runner.results_directory + "'")
354
355
356
if '__main__' == __name__:
357
    sys.exit(main(sys.argv[1:], sys.stdout, sys.stderr))
- a/Tools/Scripts/webkitpy/w3c/conformance_checker_unittest.py +118 lines
Line 0 a/Tools/Scripts/webkitpy/w3c/conformance_checker_unittest.py_sec1
1
# Copyright (c) 2015, Canon 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
# 3.  Neither the name of Canon Inc. nor the names of
12
#     its contributors may be used to endorse or promote products derived
13
#     from this software without specific prior written permission.
14
#
15
# THIS SOFTWARE IS PROVIDED BY CANON INC. AND ITS CONTRIBUTORS "AS IS" AND ANY
16
# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
17
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
18
# DISCLAIMED. IN NO EVENT SHALL CANON INC. AND ITS CONTRIBUTORS BE LIABLE FOR
19
# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
21
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
22
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
23
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25
26
import unittest
27
28
from webkitpy.w3c.conformance_checker import TestResultsConformanceParser, ConformanceChecker, create_argument_parser
29
from webkitpy.port.base import Port
30
from webkitpy.common.system.filesystem_mock import MockFileSystem
31
from webkitpy.common.host_mock import MockHost
32
33
34
class ConformancCheckerTest(unittest.TestCase):
35
    def setUp(self):
36
        self.base_dir = '/tmp/conformance-directory/w3c-conformance-results/runned-tests'
37
        self.host = MockHost()
38
        self.fs = self.host.filesystem
39
        self.port = Port(self.host, 'MockPort')
40
41
        self.w3c_tests = ['test1/pass.html', 'missing.html', 'pass.html', 'fail.html', 'partial.html', 'crash.html', 'timeout.html', 'ref_pass.html', 'ref_fail.html', 'ref_crash.html', 'ref_timeout.html']
42
        self.runned_tests = [self.fs.join(self.base_dir, test) for test in self.w3c_tests]
43
        self.json_file_path = self.fs.join(self.base_dir, 'results_dir', 'results.json')
44
        self.fs.write_text_file(self.json_file_path,
45
                'ADD_RESULTS({"tests":{"":{"tmp":{"conformance-directory":{"w3c-conformance-results":{"runned-tests":{"test1":{"pass.html":{"report":"MISSING","expected":"PASS","is_missing_text":true,"actual":"MISSING"}},"pass.html":{"report":"MISSING","expected":"PASS","is_missing_text":true,"actual":"MISSING"},"fail.html":{"report":"MISSING","expected":"PASS","is_missing_text":true,"actual":"MISSING"},"partial.html":{"report":"MISSING","expected":"PASS","is_missing_text":true,"actual":"MISSING"},"crash.html":{"report":"MISSING","expected":"PASS","is_missing_text":true,"actual":"CRASH"},"timeout.html":{"report":"REGRESSION","expected":"PASS","is_missing_text":true,"actual":"TIMEOUT"}, "ref_fail.html":{"report":"REGRESSION","expected":"PASS","actual":"IMAGE"}, "ref_crash.html":{"report":"REGRESSION","expected":"PASS","actual":"CRASH"}, "ref_timeout.html":{"report":"REGRESSION","expected":"PASS","actual":"TIMEOUT"}}}}}}},"skipped":0,"num_regressions":1,"interrupted":false,"num_missing":6,"layout_tests_dir":"LayoutTests","version":3,"num_passes":0,"pixel_tests_enabled":false,"has_pretty_patch":true,"fixable":10,"num_flaky":0,"uses_expectations_file":true});')
46
        self.write_expected_file('test1/pass.html', 'PASS this is a passing test\n')
47
        self.write_expected_file('pass.html', 'CONSOLE: this is a warning line\nPASS this is a passing test\n')
48
        self.write_expected_file('fail.html', 'CONSOLE: this is a warning line\nFAIL this is a failing test\n')
49
        self.write_expected_file('partial.html', 'CONSOLE: this is a warning line\nPASS this is a passing test\nFAIL this is a failing test\n')
50
51
        self.parser = TestResultsConformanceParser(self.port, self.base_dir)
52
        self.conformance_results = self.parser.compute_conformance_results(self.runned_tests, self.json_file_path)
53
        options, args = create_argument_parser().parse_known_args(['-t .'])
54
        self.conformance_checker = ConformanceChecker(self.port, options)
55
        self.conformance_checker._conformance_results = self.conformance_results
56
57
    def write_expected_file(self, test, result):
58
        self.fs.write_text_file(self.port.expected_filename(self.fs.join(self.base_dir, test), '.txt'), result)
59
60
    def test_w3c_name_conversion(self):
61
        self.assertEquals(self.parser.w3c_test_name(self.runned_tests[0]), self.w3c_tests[0])
62
63
    def test_json_result_parsing(self):
64
        self.assertItemsEqual(self.conformance_results.keys(), self.w3c_tests)
65
66
    def test_missing_test(self):
67
        self.assertEquals(self.conformance_results['missing.html']['type'], 'reftest')
68
        self.assertEquals(self.conformance_results['missing.html']['status'], 'PASS')
69
70
    def test_ref_test_timeout(self):
71
        self.assertEquals(self.conformance_results['ref_timeout.html']['type'], 'reftest')
72
        self.assertEquals(self.conformance_results['ref_timeout.html']['status'], 'TIMEOUT')
73
74
    def test_ref_test_crash(self):
75
        self.assertEquals(self.conformance_results['ref_crash.html']['type'], 'reftest')
76
        self.assertEquals(self.conformance_results['ref_crash.html']['status'], 'CRASH')
77
78
    def test_ref_test_fail(self):
79
        self.assertEquals(self.conformance_results['ref_fail.html']['type'], 'reftest')
80
        self.assertEquals(self.conformance_results['ref_fail.html']['status'], 'FAIL')
81
82
    def test_js_test_pass(self):
83
        self.assertEquals(self.conformance_results['pass.html']['type'], 'jstest')
84
        self.assertEquals(self.conformance_results['pass.html']['status'], 'PASS')
85
        self.assertEquals(self.conformance_results['test1/pass.html']['type'], 'jstest')
86
        self.assertEquals(self.conformance_results['test1/pass.html']['status'], 'PASS')
87
88
    def test_js_test_partial(self):
89
        self.assertEquals(self.conformance_results['partial.html']['type'], 'jstest')
90
        self.assertEquals(self.conformance_results['partial.html']['status'], 'PARTIAL')
91
92
    def test_js_test_fail(self):
93
        self.assertEquals(self.conformance_results['fail.html']['type'], 'jstest')
94
        self.assertEquals(self.conformance_results['fail.html']['status'], 'FAIL')
95
96
    def test_js_test_crash(self):
97
        self.assertEquals(self.conformance_results['crash.html']['type'], 'jstest')
98
        self.assertEquals(self.conformance_results['crash.html']['status'], 'CRASH')
99
100
    def test_js_test_timeout(self):
101
        self.assertEquals(self.conformance_results['timeout.html']['type'], 'jstest')
102
        self.assertEquals(self.conformance_results['timeout.html']['status'], 'TIMEOUT')
103
104
    def test_import_expectations_update(self):
105
        expectations = '\n'.join(self.conformance_checker.import_expectation_updates())
106
        self.assertTrue('timeout.html [ Skip ]' in expectations)
107
        self.assertTrue('crash.html [ Skip ]' in expectations)
108
        self.assertTrue('fail.html [ Skip ]' in expectations)
109
        self.assertTrue('ref_crash.html [ Skip ]' in expectations)
110
        self.assertTrue('ref_timeout.html [ Skip ]' in expectations)
111
        self.assertTrue('ref_fail.html [ Skip ]' in expectations)
112
        self.assertFalse('missing.html' in expectations)
113
        self.assertFalse('partial.html' in expectations)
114
        self.assertFalse('pass.html' in expectations)
115
116
    def test_test_expectations_update(self):
117
        expectations = '\n'.join(self.conformance_checker.test_expectation_updates())
118
        self.assertTrue('partial.html [ Pass ]' in expectations)
- a/Tools/Scripts/webkitpy/w3c/test_downloader.py +3 lines
Lines 81-86 class TestDownloader(object): a/Tools/Scripts/webkitpy/w3c/test_downloader.py_sec1
81
    def git(self, test_repository):
81
    def git(self, test_repository):
82
        return Git(test_repository, None, executive=self._host.executive, filesystem=self._filesystem)
82
        return Git(test_repository, None, executive=self._host.executive, filesystem=self._filesystem)
83
83
84
    def all_test_paths(self):
85
        return [test_repository.name for test_repository in self.test_repositories]
86
84
    def checkout_test_repository(self, revision, url, directory):
87
    def checkout_test_repository(self, revision, url, directory):
85
        git = self.git('.')
88
        git = self.git('.')
86
        if not self._filesystem.exists(directory):
89
        if not self._filesystem.exists(directory):
- a/LayoutTests/imported/w3c/ChangeLog +11 lines
Lines 1-3 a/LayoutTests/imported/w3c/ChangeLog_sec1
1
2015-03-30  Youenn Fablet  <youenn.fablet@crf.canon.fr>
2
3
        Add support for automated W3C conformance testing
4
        https://bugs.webkit.org/show_bug.cgi?id=134766
5
6
        Reviewed by NOBODY (OOPS!).
7
8
        The template to display conformance results.
9
10
        * resources/w3c_conformance_results.html: Added.
11
1
2015-03-13  Youenn Fablet  <youenn.fablet@crf.canon.fr>
12
2015-03-13  Youenn Fablet  <youenn.fablet@crf.canon.fr>
2
13
3
        WebKit test infrastructure should automate the process of cloning W3C test suite and importing tests from it
14
        WebKit test infrastructure should automate the process of cloning W3C test suite and importing tests from it
- a/LayoutTests/imported/w3c/resources/w3c_conformance_results.html +455 lines
Line 0 a/LayoutTests/imported/w3c/resources/w3c_conformance_results.html_sec1
1
<!DOCTYPE html>
2
<html>
3
<head>
4
<style>
5
#results {
6
    display: table;
7
    padding: 20px;
8
    clear: both;
9
}
10
#summary {
11
    margin-top: 20px;
12
    margin-bottom: 20px;
13
}
14
15
.test, .firstline {
16
    display: table;
17
}
18
.testname, .testresult, .firstLine > div {
19
    background-color: lavender;
20
    border-bottom:solid 1px white;
21
}
22
23
.firstLineCell{
24
    display: table-cell;
25
    text-align: center;
26
    width: 800px;
27
}
28
29
.secondLineCell {
30
    display: table-cell;
31
    padding-left: 50px;
32
    width: 750px;
33
}
34
35
.testname {
36
    display: table-cell;
37
    width: 900px;
38
    margin-left:20px;
39
}
40
41
.testnamelink {
42
    float:left;
43
    margin-right:10px;
44
}
45
46
.testresultlink  {
47
    float: left;
48
}
49
50
.testresultlink a {
51
    text-transform: capitalize;
52
}
53
54
.bugfilinglink  {
55
    text-align: right;
56
}
57
58
.port {
59
    display: table-cell;
60
    width: 250px;
61
    text-align: center;
62
}
63
64
.testresult {
65
    display: table-cell;
66
    width: 150px;
67
    text-align: center;
68
}
69
70
.testresult:not(:last-child) {
71
    padding-right: 100px;
72
}
73
74
.partial, .pass, .fail, .timeout, .error {
75
    text-align: center;
76
}
77
.pass a {
78
    color: green;
79
}
80
81
.partial a {
82
    color: yellow;
83
}
84
85
.fail a {
86
    color: orange;
87
}
88
89
.timeout a {
90
    color: red;
91
}
92
93
.crash a {
94
    color: red;
95
}
96
97
.error a {
98
    color: red;
99
}
100
101
.error-results-hist {
102
    background-color: red;
103
    float: left; 
104
}
105
 
106
.fail-results-hist {
107
    background-color: orange;
108
    float: left;
109
}
110
111
.partial-results-hist {
112
    background-color: yellow;
113
    float: left;
114
}
115
.pass-results-hist {
116
    background-color: green;
117
    float: left;
118
}
119
120
.module-name {
121
    display:table-cell;
122
    width: 800px;
123
}
124
125
.number-of-tests {
126
    display:inline-block;
127
    width: 50px;
128
}
129
130
.module-results {
131
    text-align: center;
132
    display:table-cell;
133
    width: 250px;
134
}
135
136
.histogram {
137
    display:inline-block;
138
    margin-left: 40px;
139
    margin-top: 5px;
140
    width:150px;
141
}
142
143
.module{
144
    background-color: lavender;
145
    border:solid 1px white;
146
    display:table;
147
}
148
149
.module-summary, .secondLine {
150
    display:table;
151
    background: lavender;
152
    border:solid 1px white;
153
    height: 25px;
154
    cursor:pointer;
155
}
156
157
.box{
158
    display: table;
159
}
160
161
.hidden-data {
162
    display: none;
163
}
164
165
.legend > div {
166
    float: left;
167
    margin-right: 5px;
168
    min-width: 20px;
169
}
170
171
.legend {
172
}
173
</style>
174
<script>
175
176
function testsDirectory(results)
177
{
178
    return (results.root_directory ? results.root_directory : results.port_name) + "/" + 
179
            (results.tests_directory ? results.tests_directory : "imported-tests")
180
}
181
182
function resultsDirectory(results)
183
{
184
    return (results.root_directory ? results.root_directory : results.port_name) + "/" + 
185
            (results.results_directory ? results.results_directory : "layout-test-results")
186
}
187
188
function githubTestURL(testname, results)
189
{
190
    for(var i = 0; i < results.test_repositories.length; i++) {
191
        repository = results.test_repositories[i]
192
        if (testname.indexOf(repository.name) == 0)
193
            return repository.url.replace(".git","") + "/blob/" + repository.revision + testname.replace(repository.name, "")
194
    }
195
    return "#cannot find test repository"
196
}
197
198
function bugzillaURL(title, comment)
199
{
200
    return "https://bugs.webkit.org/enter_bug.cgi?assigned_to=webkit-unassigned%40lists.webkit.org&product=WebKit&form_name=enter_bug&short_desc=" + title + "&comment=" + comment
201
}
202
203
function testFileLink(testname, results)
204
{
205
    return "<div class='testnamelink'><a href='" + testsDirectory(results) + "/" + testname + "'>" + testname + "</a></div>"
206
        + "<div class='githublink'>(<a href='" + githubTestURL(testname, results) + "'>github</a>)</div>" 
207
}
208
209
function testResultFileLink(testname, result, results)
210
{
211
    var suffix = "." + testname.split('.').pop()
212
    var new_suffix = "-expected.txt"
213
    result_status = result.status.toLowerCase().trim()
214
    if (result_status == "crash")
215
        new_suffix = "-crash-log.txt"
216
    if (result.type == "reftest")
217
        new_suffix = "-diffs.html"
218
    
219
    var link = "<div class='testresultlink'><a href='" + testsDirectory(results) + "/" + testname.replace(suffix, new_suffix) + "'>" + result_status + "</a></div>"
220
221
    if (result != "pass") {
222
        var title = encodeURIComponent("W3C " + testname + " test is " + result_status + "ing")
223
        var comment = encodeURIComponent(githubTestURL(testname, results) + " is " + result_status + "ing on " + results.port_name.toUpperCase() + ".")
224
        link += "<div class='bugfilinglink'><a href='" + bugzillaURL(title, comment) + "'>file bug</a></div>"
225
    }
226
227
    return link
228
}
229
230
function testSuiteStateHistogram(numError, numFail, numPartial, numPass)
231
{
232
    var total = numError + numPass + numPartial + numFail
233
234
    var html = '<div class="module-results">'
235
236
    html += '<div class="number-of-tests">' + numPass + '/' + total + '</div>'
237
238
    html += '<div class="histogram">'
239
    if (numError)
240
        html += '<div class="error-results-hist" style="width:' + (100 * numError / total) + '%">&nbsp;</div>'
241
    if (numFail)
242
        html += '<div class="fail-results-hist" style="width:' + (100 * numFail / total)  + '%">&nbsp;</div>'
243
    if (numPartial)
244
        html += '<div class="partial-results-hist" style="width:' + (100 * numPartial / total) + '%">&nbsp;</div>'
245
    if (numPass)
246
        html += '<div class="pass-results-hist" style="width:' + (100 * numPass / total) + '%">&nbsp;</div>'
247
    html += '</div>'
248
249
    return html + '</div>'
250
}
251
252
function testsuiteModules(results)
253
 {
254
    var modules = {}
255
    // First, try to only show module from URL hash
256
    var hash = window.location.hash.substring(1)
257
    var hasTest = false
258
    if (hash.length) {
259
        Object.keys(results.conformance_results).sort().forEach(function(key, i) {
260
            var testName = key
261
            if (testName.indexOf(hash) == 0) {
262
                hasTest = true
263
                var items = testName.split("/")
264
                var name = items[0]
265
                if (modules[name])
266
                    modules[name].push(key)
267
                else
268
                    modules[name] = [key]
269
            }
270
        })
271
    }
272
    if (hasTest)
273
        return modules
274
275
    // If nothing to show from URL hash, show all modules
276
    Object.keys(results.conformance_results).sort().forEach(function(key, i) {
277
        var testName = key
278
        var items = testName.split("/")
279
        var name = items[0]
280
        // In case of html module, we split display in sub-modules
281
        if (name == 'web-platform-tests' || name == 'csswg-tests' || name == 'html')
282
            name += '/' + items[1]
283
        if (modules[name])
284
            modules[name].push(key)
285
        else
286
            modules[name] = [key]
287
    })
288
    return modules
289
 }
290
291
function toggleTests(div)
292
{
293
    var box = div.nextElementSibling
294
    if (box)
295
        box.className = (box.className == 'box') ? 'hidden-data' : 'box'
296
}
297
298
function incrementSummary(result, summary)
299
{
300
    if (result.status == "PASS")
301
        summary.numPass++
302
    else if (result.status == "PARTIAL")
303
        summary.numPartial++
304
    else if (result.status == "FAIL")
305
        summary.numFail++
306
    else
307
        summary.numError++
308
}
309
310
function computeGlobalConformance(results)
311
{
312
    var summary = {numPass: 0, numPartial: 0, numFail: 0, numError: 0}
313
    for (var test in results.conformance_results) {
314
        if (results.conformance_results.hasOwnProperty(test))
315
            incrementSummary(results.conformance_results[test], summary)
316
    }
317
    return summary
318
}
319
320
function computeStatistics(resultsSummary, result, results)
321
{
322
    var summary = resultsSummary[results.port_name]
323
324
    if (!summary)
325
        resultsSummary[results.port_name] = summary = {numPass: 0, numPartial: 0, numFail: 0, numError: 0}
326
327
    incrementSummary(result, summary)
328
}
329
330
function addTestSuiteListOfResults(moduleName, tests, listOfResults, isOpen)
331
{
332
    var innerHTML = ""
333
    var resultsSummary =  {}
334
    tests.forEach(function(key, i) {
335
        innerHTML += "<div class='test'><div class='testname'>" + testFileLink(key, listOfResults[0]) + "</div>"
336
        listOfResults.forEach(function(results) {
337
            var result = results.conformance_results[key]
338
            if (!result) {
339
                result = "Missing"
340
                console.log("WARNING: test " + key + " is missing")
341
            }
342
            innerHTML += "<div class='testresult " + result.status.toLowerCase() + "'>" + testResultFileLink(key, result, results) + "</div>"
343
            computeStatistics(resultsSummary, result, results)
344
        })
345
        innerHTML += "</div>"
346
    })
347
    var html = '<div class="module"><div class="module-summary" onclick="toggleTests(event.currentTarget)"><div class="module-name">' + moduleName + '</div>'
348
    listOfResults.forEach(function(results) {
349
        var summary = resultsSummary[results.port_name]
350
        html += testSuiteStateHistogram(summary.numError, summary.numFail, summary.numPartial, summary.numPass)
351
    })
352
    html += '</div><div class="' + (isOpen ? "box" : "hidden-data") + '">'
353
    html += innerHTML
354
    html += '</div></div>'
355
    document.getElementById("results").innerHTML += html
356
}
357
358
function writeSummary()
359
{
360
    var html = "<div>W3C conformance results from run-webkit-test ("
361
    listOfResults.forEach(function(results) {
362
        html += "<a href='" + resultsDirectory(results) + "'>" + results.port_name + "</a> &nbsp;"
363
    })
364
    html += ")</div>"
365
366
    html += "<div> Generated <a href='TestExpectations'>test expectations</a> and <a href='ImportExpectations'>import expectations</a>.</div>"
367
368
    // FIXME: Check that all conformance results use the same test suite revisions.
369
370
    document.getElementById("summary").innerHTML = html
371
}
372
373
function displayResults()
374
{
375
    setTimeout(function()  {
376
        var firstLineHTML = "<div class='firstLine'><div class='firstLineCell'>Module/Test Name</div>"
377
        listOfResults.forEach(function(results) {
378
            firstLineHTML += "<div class='port'>" + results.port_name.toUpperCase() + "</div>"
379
        })
380
        firstLineHTML += "</div>"
381
        var secondLineHTML = "<div class='secondLine'><div class='secondLineCell'>Overall conformance</div>"
382
        listOfResults.forEach(function(results) {
383
            summary = computeGlobalConformance(results)
384
            secondLineHTML += testSuiteStateHistogram(summary.numError, summary.numFail, summary.numPartial, summary.numPass)
385
        })
386
        secondLineHTML += "</div>"
387
388
        document.getElementById("results").innerHTML = firstLineHTML + secondLineHTML
389
        var modules =  testsuiteModules(listOfResults[0])
390
        var keys = Object.keys(modules)
391
        keys.sort().forEach(function(key, i) {
392
            addTestSuiteListOfResults(key, modules[key], listOfResults, keys.length == 1)
393
        })
394
        writeSummary(listOfResults)
395
    }, 0)
396
}
397
398
 
399
listOfResults = [] 
400
function ADD_CONFORMANCE_RESULTS(results)
401
{
402
    results.root_directory = dirs[counter]
403
    listOfResults.push(results)
404
    setTimeout(loadScripts, 0)    
405
}
406
407
var counter
408
var dirs
409
function loadScripts(directories)
410
{
411
    if (directories) {
412
        dirs = directories
413
        counter = -1
414
    }
415
    if (counter < dirs.length - 1) {
416
        document.getElementById('summary').innerHTML = "Loading " + dirs[++counter] + "..."
417
        var head= document.getElementsByTagName('head')[0]
418
        var script= document.createElement('script')
419
        script.type= 'text/javascript'
420
        script.src= dirs[counter] + '/w3c_conformance_results.json'
421
        head.appendChild(script)
422
    }
423
    else {
424
        document.getElementById('summary').innerHTML = "Displaying results..."
425
        setTimeout(displayResults, 0)
426
    }
427
}
428
429
</script>
430
</head>
431
<body>
432
<div id="summary">
433
    Loading...
434
</div>
435
<div class="legend">
436
    <div class="error-results-hist legend">&nbsp;</div>
437
    <div>Error, Timeout or Crash</div>
438
    <div class="fail-results-hist legend">&nbsp;</div>
439
    <div> All assertions failing</div>
440
    <div class="partial-results-hist legend">&nbsp;</div>
441
    <div> Some assertions passing</div>
442
    <div class="pass-results-hist legend">&nbsp;</div>
443
    <div> All assertions passing</div>
444
</div>
445
<div id="results">
446
</div>
447
448
<script>
449
    setTimeout(function() {
450
        // Add as many folder containing w3c_conformance_results.json as needed in loadScripts array to display and compare the results.
451
        loadScripts(["."])
452
    }, 0)
453
</script>
454
</body>
455
</html>

Return to Bug 134766