blob: 471b4f6a40e71225dda9375b24b1d17a2018d88d [file] [log] [blame]
# 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')