|  | #!/usr/bin/env python3 | 
|  | # Copyright 2022 The Chromium Authors | 
|  | # Use of this source code is governed by a BSD-style license that can be | 
|  | # found in the LICENSE file. | 
|  | """Refreshes existing WPR archive files from live Autofill Server | 
|  |  | 
|  | $ tools/captured_sites/refresh.py [site_name] | 
|  |  | 
|  | This script attempts to capture the process of refreshing a site's Autofill | 
|  | Server Predictions. | 
|  |  | 
|  | It will loop through the given sites and run the refresh process which hits | 
|  | the Autofill Server to receive fresh Autofill Server Predictions. It then | 
|  | removes the existing WPR file's predictions, and merges in the update ones. | 
|  |  | 
|  | With no arguments or just an '*', the script will run through all non-disabled | 
|  | sites in the testcases.json file. | 
|  |  | 
|  | An optional argument of [site_name] can be provided to refresh a single site. | 
|  | """ | 
|  |  | 
|  | from __future__ import print_function | 
|  |  | 
|  | import argparse | 
|  | import json | 
|  | import os | 
|  | import signal | 
|  | import sys | 
|  | import subprocess | 
|  |  | 
|  | _BASE_FOLDER = 'chrome/test/data/autofill/captured_sites/artifacts' | 
|  | _TELEMETRY_BIN_FOLDER = ('third_party/catapult/telemetry/telemetry/bin/' | 
|  | 'linux/x86_64/') | 
|  | _TRIMMED_FOLDER = os.path.join(_BASE_FOLDER, 'trimmed') | 
|  | _REFRESH_FOLDER = os.path.join(_BASE_FOLDER, 'refresh') | 
|  | _MERGED_FOLDER = os.path.join(_BASE_FOLDER, 'merged') | 
|  | _PRINT_ONLY = False | 
|  |  | 
|  |  | 
|  | class Refresh(): | 
|  | def collect_sites(self, testcases_file): | 
|  | with open(testcases_file, 'r') as file: | 
|  | content = json.load(file) | 
|  | self.sites = content["tests"] | 
|  | filtered = list(filter(lambda a: 'disabled' not in a, self.sites)) | 
|  | return filtered | 
|  |  | 
|  | def refresh_site(self, site_name): | 
|  | """Run the Refresh test for the given site_name. This process will create | 
|  | a new .wpr archive in the captured_sites/refresh folder. Runs the process | 
|  | with flags: | 
|  | --store-log to keep text log | 
|  | --release to run against release build | 
|  | --background to run with xvfb.py.""" | 
|  | command_args = [ | 
|  | 'tools/captured_sites/control.py', 'refresh', '--store-log', | 
|  | '--release', '--background', site_name | 
|  | ] | 
|  | _make_process_call(command_args, _PRINT_ONLY) | 
|  |  | 
|  | def delete_existing_predictions(self, site_name): | 
|  | """Use httparchive go tool to remove any existing Server Predictions stored | 
|  | in the current .wpr archive and create a trimmed version in the | 
|  | captured_sites/trimmed folder.""" | 
|  | host_domains = ['clients1.google.com', 'content-autofill.googleapis.com'] | 
|  | existing_wpr_archive = os.path.join(_BASE_FOLDER, '%s.wpr' % site_name) | 
|  | trimmed_wpr_archive = os.path.join(_TRIMMED_FOLDER, '%s.wpr' % site_name) | 
|  | first_trim = True | 
|  |  | 
|  | for host_domain in host_domains: | 
|  | to_trim_wpr_archive = trimmed_wpr_archive | 
|  | if first_trim: | 
|  | to_trim_wpr_archive = existing_wpr_archive | 
|  | first_trim = False | 
|  |  | 
|  | command_args = [ | 
|  | _TELEMETRY_BIN_FOLDER + 'httparchive', 'trim', '--host', host_domain, | 
|  | to_trim_wpr_archive, trimmed_wpr_archive | 
|  | ] | 
|  | _make_process_call(command_args, _PRINT_ONLY) | 
|  |  | 
|  | def merge_new_predictions(self, site_name): | 
|  | """Use httparchive go tool to merge the .wpr file in refresh/ folder with | 
|  | the .wpr file in trimmed/ folder and create a new .wpr file in the | 
|  | merged/ folder.""" | 
|  | trimmed_wpr_archive = os.path.join(_TRIMMED_FOLDER, '%s.wpr' % site_name) | 
|  | fresh_wpr_archive = os.path.join(_REFRESH_FOLDER, '%s.wpr' % site_name) | 
|  | merged_wpr_archive = os.path.join(_MERGED_FOLDER, '%s.wpr' % site_name) | 
|  |  | 
|  | command_args = [ | 
|  | _TELEMETRY_BIN_FOLDER + 'httparchive', 'merge', trimmed_wpr_archive, | 
|  | fresh_wpr_archive, merged_wpr_archive | 
|  | ] | 
|  | _make_process_call(command_args, _PRINT_ONLY) | 
|  |  | 
|  | def update_expectations(self, site_name): | 
|  | """Update .test file expectations to reflect the changes in the newly merged | 
|  | Server Predictions""" | 
|  | cmd = '...' | 
|  | #TODO(crbug.com/40216356) | 
|  | print('Not Implemented') | 
|  |  | 
|  |  | 
|  | def _parse_args(): | 
|  | parser = argparse.ArgumentParser( | 
|  | formatter_class=argparse.RawTextHelpFormatter) | 
|  | parser.usage = __doc__ | 
|  | parser.add_argument('site_name', | 
|  | nargs='?', | 
|  | default='*', | 
|  | help=('The site name which should have a match in ' | 
|  | 'testcases.json. Use * to indicate all enumerated ' | 
|  | 'sites in that file.')) | 
|  | return parser.parse_args() | 
|  |  | 
|  |  | 
|  | def _make_process_call(command_args, print_only): | 
|  | command_text = ' '.join(command_args) | 
|  | print(command_text) | 
|  | if print_only: | 
|  | return | 
|  |  | 
|  | if not os.path.exists(command_args[0]): | 
|  | raise EnvironmentError('Cannot locate binary to execute. ' | 
|  | 'Ensure that working directory is chromium/src') | 
|  | subprocess.call(command_text, shell=True) | 
|  |  | 
|  |  | 
|  | def _create_subfolders(): | 
|  | assert os.path.isdir(_BASE_FOLDER), ('Expecting path "%s" to exist in your ' | 
|  | 'chromium checkout' % _BASE_FOLDER) | 
|  | if not os.path.isdir(_MERGED_FOLDER): | 
|  | os.mkdir(_MERGED_FOLDER) | 
|  | if not os.path.isdir(_REFRESH_FOLDER): | 
|  | os.mkdir(_REFRESH_FOLDER) | 
|  | if not os.path.isdir(_TRIMMED_FOLDER): | 
|  | os.mkdir(_TRIMMED_FOLDER) | 
|  |  | 
|  |  | 
|  | def _handle_signal(sig, _): | 
|  | """Handles received signals to make sure spawned test process are killed. | 
|  |  | 
|  | sig (int): An integer representing the received signal, for example SIGTERM. | 
|  | """ | 
|  |  | 
|  | # Don't do any cleanup here, instead, leave it to the finally blocks. | 
|  | # Assumption is based on https://docs.python.org/3/library/sys.html#sys.exit: | 
|  | # cleanup actions specified by finally clauses of try statements are honored. | 
|  |  | 
|  | # https://tldp.org/LDP/abs/html/exitcodes.html: | 
|  | # Exit code 128+n -> Fatal error signal "n". | 
|  | print('Signal to quit received') | 
|  | sys.exit(128 + sig) | 
|  |  | 
|  |  | 
|  | def main(): | 
|  | for sig in (signal.SIGTERM, signal.SIGINT): | 
|  | signal.signal(sig, _handle_signal) | 
|  |  | 
|  | _create_subfolders() | 
|  |  | 
|  | options = _parse_args() | 
|  |  | 
|  | r = Refresh() | 
|  |  | 
|  | if options.site_name == '*': | 
|  | sites = r.collect_sites(os.path.join(_BASE_FOLDER, 'testcases.json')) | 
|  | print('Refreshing %d sites from the testcases file' % len(sites)) | 
|  | else: | 
|  | sites = [{'site_name': options.site_name}] | 
|  | print('Refreshing single site "%s"' % options.site_name) | 
|  |  | 
|  | for site in sites: | 
|  | site_name = site['site_name'] | 
|  | print('Refreshing Server Predictions for "%s"' % site_name) | 
|  | r.refresh_site(site_name) | 
|  | r.delete_existing_predictions(site_name) | 
|  | r.merge_new_predictions(site_name) | 
|  | print('Merged WPR archives have been written to "%s"' % _MERGED_FOLDER) | 
|  |  | 
|  |  | 
|  | if __name__ == '__main__': | 
|  | sys.exit(main()) |