#!/usr/bin/env python # # Copyright (C) 2017 Igalia S.L. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Library General Public # License as published by the Free Software Foundation; either # version 2 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Library General Public License for more details. # # You should have received a copy of the GNU Library General Public License # along with this library; see the file COPYING.LIB. If not, write to # the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, # Boston, MA 02110-1301, USA. import hashlib import logging import optparse import os import sys from webkitpy.common.host import Host from webkitpy.common.system.logutils import configure_logging _log = logging.getLogger(__name__) host = Host() host.initialize_scm() checkout_root = host.scm().checkout_root layout_tests_directory = os.path.join(checkout_root, 'LayoutTests') platform_directory = os.path.join(layout_tests_directory, 'platform') IGNORED_FILE_NAMES = { 'TestExpectations', '.DS_Store' } txt_types = (".webarchive", ".txt") def remove_layout_test_path_prefix(full_path): return os.path.relpath(full_path, layout_tests_directory) def check_duplicate(platform, baseline_search_path, platform_test_result): def sha1(path): with file(path, 'rb') as f: return hashlib.sha1(f.read()).hexdigest() _log.debug(' Looking for duplicates of {0} in {1}'.format(remove_layout_test_path_prefix(platform_test_result), str(baseline_search_path))) prefix_len = len(os.path.join(platform_directory, platform)) + 1 test_result_filename = platform_test_result[prefix_len:] test_result_filename_base, test_result_filename_ext = os.path.splitext(platform_test_result[prefix_len:]) if test_result_filename_ext in txt_types: try_baseline_filenames = [test_result_filename_base + txt_type for txt_type in txt_types] else: try_baseline_filenames = [test_result_filename] platform_test_result_size = None platform_test_result_sha1 = None baseline_found = False for baseline_path in baseline_search_path: for try_baseline_filename in try_baseline_filenames: baseline_test_result = os.path.join(baseline_path, try_baseline_filename) if not os.path.isfile(baseline_test_result): _log.debug(' No result at {0}'.format(remove_layout_test_path_prefix(baseline_test_result))) continue _log.debug(' Comparing with {0}'.format(remove_layout_test_path_prefix(baseline_test_result))) baseline_found = True if platform_test_result_size is None: platform_test_result_size = os.path.getsize(platform_test_result) baseline_test_result_size = os.path.getsize(baseline_test_result) if platform_test_result_size != baseline_test_result_size: _log.debug(' File size is different') return False if platform_test_result_sha1 is None: platform_test_result_sha1 = sha1(platform_test_result) baseline_test_result_sha1 = sha1(baseline_test_result) if platform_test_result_sha1 != baseline_test_result_sha1: _log.debug(' File hash is different') return False _log.debug(' Duplicate found: {0} and {1}'.format(remove_layout_test_path_prefix(platform_test_result), remove_layout_test_path_prefix(baseline_test_result))) return baseline_found def platform_list(platform): if platform == 'all': return os.listdir(platform_directory) if os.path.isdir(os.path.join(platform_directory, platform)): return [platform] return [] def find_duplicates_in_path(baseline_search_path): duplicates = [] remaining_paths = baseline_search_path def find_duplicates(): _log.debug(' comparing files in {0} and {1}'.format(remove_layout_test_path_prefix(remaining_paths[0]), remove_layout_test_path_prefix(remaining_paths[1]))) for root, dirs, files in os.walk(remaining_paths[0]): for file_name in files: if file_name in IGNORED_FILE_NAMES: continue platform_test_result = os.path.join(root, file_name) if check_duplicate(remaining_paths[0], remaining_paths[1:], platform_test_result): duplicates.append(platform_test_result) while len(remaining_paths) > 1: find_duplicates() remaining_paths = remaining_paths[1:] return duplicates def check_platform(options, baseline_search_paths_checked): total = 0 for platform in platform_list(options.platform): try: _log.debug('Trying to create port for platform {0}'.format(platform)) port = host.port_factory.get(platform, options) except: _log.debug('Failed to create port object for {0}'.format(platform)) continue if not port.supports_layout_tests(): continue try: baseline_search_path = tuple([p for p in port.baseline_search_path() if os.path.isdir(p)] + [layout_tests_directory]) except: _log.warn('Error computing baseline search paths from {0}'.format(platform)) continue _log.info('Checking search paths [{0}]'.format(', '.join(remove_layout_test_path_prefix(p) for p in baseline_search_path[:-1]))) if baseline_search_path in baseline_search_paths_checked: continue baseline_search_paths_checked.add(baseline_search_path) duplicates = find_duplicates_in_path(baseline_search_path) if not duplicates: continue if options.no_delete: _log.info('Found the following duplicate results:') _log.info('\n'.join(remove_layout_test_path_prefix(p) for p in duplicates)) else: host.scm().delete_list(duplicates) duplicates_len = len(duplicates) total += duplicates_len _log.info('{0} found in {1} -> generic\n'.format(duplicates_len, ' -> '.join([os.path.basename(p) for p in baseline_search_path[:-1]]))) return total def main(): option_parser = optparse.OptionParser(usage='usage: %prog [options]') option_parser.add_option('-p', '--platform', action='store', dest='platform', default='all', help='Platform to check for duplicated results. By the default all platform are checked') option_parser.add_option('-n', '--no-delete', action='store_true', dest='no_delete', help='Do not delete the duplicated results found from the repository, list them instead') option_parser.add_option('-v', '--verbose', action='store_true', default=False, help='Enable verbose printing'), options, args = option_parser.parse_args() configure_logging(logger=_log) _log.setLevel(logging.DEBUG if options.verbose else logging.INFO) total = 0 baseline_search_paths_checked = set() options.webkit_test_runner = False total += check_platform(options, baseline_search_paths_checked) options.webkit_test_runner = True total += check_platform(options, baseline_search_paths_checked) if total: if options.no_delete: # FIXME: Without deletion the total isn't accurate, because directories which are parents of wk1 and wk2 dirs will be tested twice. _log.info('Found {0} duplicate results.'.format(total)) else: _log.info('{0} files have been removed from the repository. Check the status and commit if everything is correct'.format(total)) else: _log.info('No duplicated results found.') if __name__ == '__main__': sys.exit(main())