|  | # Copyright (C) 2013 Adobe Systems Incorporated. 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 THE COPYRIGHT HOLDER "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 THE COPYRIGHT HOLDER 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. | 
|  |  | 
|  | """Logic for converting and copying files from a W3C repo. | 
|  |  | 
|  | This module is responsible for modifying and copying a subset of the tests from | 
|  | a local W3C repository source directory into a destination directory. | 
|  | """ | 
|  |  | 
|  | import logging | 
|  | import mimetypes | 
|  | import re | 
|  |  | 
|  | from webkitpy.common.webkit_finder import WebKitFinder | 
|  | from webkitpy.layout_tests.models.test_expectations import TestExpectationParser | 
|  | from webkitpy.w3c.test_parser import TestParser | 
|  | from webkitpy.w3c.test_converter import convert_for_webkit | 
|  |  | 
|  | _log = logging.getLogger(__name__) | 
|  |  | 
|  |  | 
|  | class TestCopier(object): | 
|  |  | 
|  | def __init__(self, host, source_repo_path, dest_dir_name='external'): | 
|  | """Initializes variables to prepare for copying and converting files. | 
|  |  | 
|  | Args: | 
|  | host: An instance of Host. | 
|  | source_repo_path: Path to the local checkout of a | 
|  | web-platform-tests or csswg-test repository. | 
|  | dest_dir_name: The name of the directory under the layout tests | 
|  | directory where imported tests should be copied to. | 
|  | TODO(qyearsley): This can be made into a constant. | 
|  | """ | 
|  | self.host = host | 
|  |  | 
|  | assert self.host.filesystem.exists(source_repo_path) | 
|  | self.source_repo_path = source_repo_path | 
|  | self.dest_dir_name = dest_dir_name | 
|  |  | 
|  | self.filesystem = self.host.filesystem | 
|  | self.webkit_finder = WebKitFinder(self.filesystem) | 
|  | self._webkit_root = self.webkit_finder.webkit_base() | 
|  | self.layout_tests_dir = self.webkit_finder.path_from_webkit_base('LayoutTests') | 
|  | self.destination_directory = self.filesystem.normpath( | 
|  | self.filesystem.join( | 
|  | self.layout_tests_dir, | 
|  | dest_dir_name, | 
|  | self.filesystem.basename(self.source_repo_path))) | 
|  | self.import_in_place = (self.source_repo_path == self.destination_directory) | 
|  | self.dir_above_repo = self.filesystem.dirname(self.source_repo_path) | 
|  | self.is_wpt = self.filesystem.basename(self.source_repo_path) == 'wpt' | 
|  |  | 
|  | self.import_list = [] | 
|  |  | 
|  | # This is just a FYI list of CSS properties that still need to be prefixed, | 
|  | # which may be output after importing. | 
|  | self._prefixed_properties = {} | 
|  |  | 
|  | def do_import(self): | 
|  | _log.info('Importing %s into %s', self.source_repo_path, self.destination_directory) | 
|  | self.find_importable_tests() | 
|  | self.import_tests() | 
|  |  | 
|  | def find_importable_tests(self): | 
|  | """Walks through the source directory to find what tests should be imported. | 
|  |  | 
|  | This function sets self.import_list, which contains information about how many | 
|  | tests are being imported, and their source and destination paths. | 
|  | """ | 
|  | paths_to_skip = self.find_paths_to_skip() | 
|  |  | 
|  | for root, dirs, files in self.filesystem.walk(self.source_repo_path): | 
|  | cur_dir = root.replace(self.dir_above_repo + '/', '') + '/' | 
|  | _log.debug('Scanning %s...', cur_dir) | 
|  | total_tests = 0 | 
|  | reftests = 0 | 
|  | jstests = 0 | 
|  |  | 
|  | # Files in 'tools' are not for browser testing, so we skip them. | 
|  | # See: http://web-platform-tests.org/writing-tests/general-guidelines.html#tools | 
|  | dirs_to_skip = ('.git', 'test-plan', 'tools') | 
|  |  | 
|  | # We copy all files in 'support', including HTML without metadata. | 
|  | # See: http://web-platform-tests.org/writing-tests/general-guidelines.html#support-files | 
|  | dirs_to_include = ('resources', 'support') | 
|  |  | 
|  | if dirs: | 
|  | for name in dirs_to_skip: | 
|  | if name in dirs: | 
|  | dirs.remove(name) | 
|  |  | 
|  | for path in paths_to_skip: | 
|  | path_base = path.replace(self.dest_dir_name + '/', '') | 
|  | path_base = path_base.replace(cur_dir, '') | 
|  | path_full = self.filesystem.join(root, path_base) | 
|  | if path_base in dirs: | 
|  | _log.info('Skipping: %s', path_full) | 
|  | dirs.remove(path_base) | 
|  | if self.import_in_place: | 
|  | self.filesystem.rmtree(path_full) | 
|  |  | 
|  | copy_list = [] | 
|  |  | 
|  | for filename in files: | 
|  | path_full = self.filesystem.join(root, filename) | 
|  | path_base = path_full.replace(self.source_repo_path + '/', '') | 
|  | path_base = self.destination_directory.replace(self.layout_tests_dir + '/', '') + '/' + path_base | 
|  | if path_base in paths_to_skip: | 
|  | if self.import_in_place: | 
|  | _log.debug('Pruning: %s', path_base) | 
|  | self.filesystem.remove(path_full) | 
|  | continue | 
|  | else: | 
|  | continue | 
|  | # FIXME: This block should really be a separate function, but the early-continues make that difficult. | 
|  |  | 
|  | # TODO(qyearsley): Remove the below block. | 
|  | if filename != '.gitignore' and (filename.startswith('.') or filename.endswith('.pl')): | 
|  | _log.debug('Skipping: %s', path_full) | 
|  | _log.debug('  Reason: Hidden files and perl scripts are not necessary.') | 
|  | continue | 
|  |  | 
|  | if filename == 'OWNERS' or filename == 'reftest.list': | 
|  | # See http://crbug.com/584660 and http://crbug.com/582838. | 
|  | _log.debug('Skipping: %s', path_full) | 
|  | _log.debug('  Reason: This file may cause Chromium presubmit to fail.') | 
|  | continue | 
|  |  | 
|  | mimetype = mimetypes.guess_type(path_full) | 
|  | if ('html' not in str(mimetype[0]) and | 
|  | 'application/xhtml+xml' not in str(mimetype[0]) and | 
|  | 'application/xml' not in str(mimetype[0])): | 
|  | copy_list.append({'src': path_full, 'dest': filename}) | 
|  | continue | 
|  |  | 
|  | if self.filesystem.basename(root) in dirs_to_include: | 
|  | copy_list.append({'src': path_full, 'dest': filename}) | 
|  | continue | 
|  |  | 
|  | test_parser = TestParser(path_full, self.host) | 
|  | test_info = test_parser.analyze_test() | 
|  | if test_info is None: | 
|  | copy_list.append({'src': path_full, 'dest': filename}) | 
|  | continue | 
|  |  | 
|  | if 'reference' in test_info.keys(): | 
|  | ref_path_full = test_info['reference'] | 
|  | if not self.filesystem.exists(ref_path_full): | 
|  | _log.warning('Skipping: %s', path_full) | 
|  | _log.warning('  Reason: Ref file "%s" was not found.', ref_path_full) | 
|  | continue | 
|  |  | 
|  | if not self.is_wpt: | 
|  | # For csswg-test, we still need to add a ref file | 
|  | # using WebKit naming conventions. See crbug.com/268729. | 
|  | # FIXME: Remove this when csswg-test is merged into wpt. | 
|  | test_basename = self.filesystem.basename(test_info['test']) | 
|  | ref_file = self.filesystem.splitext(test_basename)[0] + '-expected' | 
|  | ref_file += self.filesystem.splitext(ref_path_full)[1] | 
|  | copy_list.append({ | 
|  | 'src': test_info['reference'], | 
|  | 'dest': ref_file, | 
|  | 'reference_support_info': test_info['reference_support_info'], | 
|  | }) | 
|  |  | 
|  | reftests += 1 | 
|  | total_tests += 1 | 
|  | copy_list.append({'src': test_info['test'], 'dest': filename}) | 
|  |  | 
|  | elif 'jstest' in test_info.keys(): | 
|  | jstests += 1 | 
|  | total_tests += 1 | 
|  | copy_list.append({'src': path_full, 'dest': filename, 'is_jstest': True}) | 
|  |  | 
|  | if copy_list: | 
|  | # Only add this directory to the list if there's something to import | 
|  | self.import_list.append({'dirname': root, 'copy_list': copy_list, | 
|  | 'reftests': reftests, 'jstests': jstests, 'total_tests': total_tests}) | 
|  |  | 
|  | def find_paths_to_skip(self): | 
|  | paths_to_skip = set() | 
|  | port = self.host.port_factory.get() | 
|  | w3c_import_expectations_path = self.webkit_finder.path_from_webkit_base('LayoutTests', 'W3CImportExpectations') | 
|  | w3c_import_expectations = self.filesystem.read_text_file(w3c_import_expectations_path) | 
|  | parser = TestExpectationParser(port, all_tests=(), is_lint_mode=False) | 
|  | expectation_lines = parser.parse(w3c_import_expectations_path, w3c_import_expectations) | 
|  | for line in expectation_lines: | 
|  | if 'SKIP' in line.expectations: | 
|  | if line.specifiers: | 
|  | _log.warning('W3CImportExpectations:%s should not have any specifiers', line.line_numbers) | 
|  | continue | 
|  | paths_to_skip.add(line.name) | 
|  | return paths_to_skip | 
|  |  | 
|  | def import_tests(self): | 
|  | """Reads |self.import_list|, and converts and copies files to their destination.""" | 
|  | total_imported_tests = 0 | 
|  | total_imported_reftests = 0 | 
|  | total_imported_jstests = 0 | 
|  |  | 
|  | for dir_to_copy in self.import_list: | 
|  | total_imported_tests += dir_to_copy['total_tests'] | 
|  | total_imported_reftests += dir_to_copy['reftests'] | 
|  | total_imported_jstests += dir_to_copy['jstests'] | 
|  |  | 
|  | if not dir_to_copy['copy_list']: | 
|  | continue | 
|  |  | 
|  | orig_path = dir_to_copy['dirname'] | 
|  |  | 
|  | relative_dir = self.filesystem.relpath(orig_path, self.source_repo_path) | 
|  | dest_dir = self.filesystem.join(self.destination_directory, relative_dir) | 
|  |  | 
|  | if not self.filesystem.exists(dest_dir): | 
|  | self.filesystem.maybe_make_directory(dest_dir) | 
|  |  | 
|  | copied_files = [] | 
|  |  | 
|  | for file_to_copy in dir_to_copy['copy_list']: | 
|  | copied_file = self.copy_file(file_to_copy, dest_dir) | 
|  | if copied_file: | 
|  | copied_files.append(copied_file) | 
|  |  | 
|  | _log.info('') | 
|  | _log.info('Import complete') | 
|  | _log.info('') | 
|  | _log.info('IMPORTED %d TOTAL TESTS', total_imported_tests) | 
|  | _log.info('Imported %d reftests', total_imported_reftests) | 
|  | _log.info('Imported %d JS tests', total_imported_jstests) | 
|  | _log.info('Imported %d pixel/manual tests', total_imported_tests - total_imported_jstests - total_imported_reftests) | 
|  | _log.info('') | 
|  |  | 
|  | if self._prefixed_properties: | 
|  | _log.info('Properties needing prefixes (by count):') | 
|  | for prefixed_property in sorted(self._prefixed_properties, key=lambda p: self._prefixed_properties[p]): | 
|  | _log.info('  %s: %s', prefixed_property, self._prefixed_properties[prefixed_property]) | 
|  |  | 
|  | def copy_file(self, file_to_copy, dest_dir): | 
|  | """Converts and copies a file, if it should be copied. | 
|  |  | 
|  | Args: | 
|  | file_to_copy: A dict in a file copy list constructed by | 
|  | find_importable_tests, which represents one file to copy, including | 
|  | the keys: | 
|  | "src": Absolute path to the source location of the file. | 
|  | "destination": File name of the destination file. | 
|  | And possibly also the keys "reference_support_info" or "is_jstest". | 
|  | dest_dir: Path to the directory where the file should be copied. | 
|  |  | 
|  | Returns: | 
|  | The path to the new file, relative to the Blink root (//third_party/WebKit). | 
|  | """ | 
|  | source_path = self.filesystem.normpath(file_to_copy['src']) | 
|  | dest_path = self.filesystem.join(dest_dir, file_to_copy['dest']) | 
|  |  | 
|  | if self.filesystem.isdir(source_path): | 
|  | _log.error('%s refers to a directory', source_path) | 
|  | return None | 
|  |  | 
|  | if not self.filesystem.exists(source_path): | 
|  | _log.error('%s not found. Possible error in the test.', source_path) | 
|  | return None | 
|  |  | 
|  | reference_support_info = file_to_copy.get('reference_support_info') or None | 
|  |  | 
|  | if not self.filesystem.exists(self.filesystem.dirname(dest_path)): | 
|  | if not self.import_in_place: | 
|  | self.filesystem.maybe_make_directory(self.filesystem.dirname(dest_path)) | 
|  |  | 
|  | relpath = self.filesystem.relpath(dest_path, self.layout_tests_dir) | 
|  | # FIXME: Maybe doing a file diff is in order here for existing files? | 
|  | # In other words, there's no sense in overwriting identical files, but | 
|  | # there's no harm in copying the identical thing. | 
|  | _log.debug('  copying %s', relpath) | 
|  |  | 
|  | if self.should_try_to_convert(file_to_copy, source_path, dest_dir): | 
|  | converted_file = convert_for_webkit( | 
|  | dest_dir, filename=source_path, | 
|  | reference_support_info=reference_support_info, | 
|  | host=self.host) | 
|  | for prefixed_property in converted_file[0]: | 
|  | self._prefixed_properties.setdefault(prefixed_property, 0) | 
|  | self._prefixed_properties[prefixed_property] += 1 | 
|  |  | 
|  | self.filesystem.write_text_file(dest_path, converted_file[1]) | 
|  | else: | 
|  | if not self.import_in_place: | 
|  | self.filesystem.copyfile(source_path, dest_path) | 
|  | if self.filesystem.read_binary_file(source_path)[:2] == '#!': | 
|  | self.filesystem.make_executable(dest_path) | 
|  |  | 
|  | return dest_path.replace(self._webkit_root, '') | 
|  |  | 
|  | @staticmethod | 
|  | def should_try_to_convert(file_to_copy, source_path, dest_dir): | 
|  | """Checks whether we should try to modify the file when importing.""" | 
|  | if file_to_copy.get('is_jstest', False): | 
|  | return False | 
|  |  | 
|  | # Conversion is not necessary for any tests in wpt now; see http://crbug.com/654081. | 
|  | # Note, we want to move away from converting files, see http://crbug.com/663773. | 
|  | if re.search(r'[/\\]external[/\\]wpt[/\\]', dest_dir): | 
|  | return False | 
|  |  | 
|  | # Only HTML, XHTML and CSS files should be converted. | 
|  | mimetype, _ = mimetypes.guess_type(source_path) | 
|  | return mimetype in ('text/html', 'application/xhtml+xml', 'text/css') |