158190644c30e1c4aa8e527f3503c58f841e0fcf3Derek Sollenberger#!/usr/bin/python 258190644c30e1c4aa8e527f3503c58f841e0fcf3Derek Sollenberger# -*- coding: utf-8 -*- 358190644c30e1c4aa8e527f3503c58f841e0fcf3Derek Sollenberger 458190644c30e1c4aa8e527f3503c58f841e0fcf3Derek Sollenbergerfrom __future__ import print_function 5e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenbergerimport argparse 658190644c30e1c4aa8e527f3503c58f841e0fcf3Derek Sollenbergerimport BaseHTTPServer 7e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenbergerimport json 858190644c30e1c4aa8e527f3503c58f841e0fcf3Derek Sollenbergerimport os 958190644c30e1c4aa8e527f3503c58f841e0fcf3Derek Sollenbergerimport os.path 10e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenbergerimport re 11e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenbergerimport subprocess 12e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenbergerimport sys 13e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenbergerimport tempfile 14e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenbergerimport urllib2 15e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger 16e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger# Grab the script path because that is where all the static assets are 17e27eefc4844477cee5d32f51ab45ff62020cdb36Derek SollenbergerSCRIPT_DIR = os.path.dirname(os.path.abspath(__file__)) 18e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger 19e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger# Find the tools directory for python imports 20e27eefc4844477cee5d32f51ab45ff62020cdb36Derek SollenbergerTOOLS_DIR = os.path.dirname(SCRIPT_DIR) 21e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger 22e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger# Find the root of the skia trunk for finding skpdiff binary 23e27eefc4844477cee5d32f51ab45ff62020cdb36Derek SollenbergerSKIA_ROOT_DIR = os.path.dirname(TOOLS_DIR) 24e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger 25e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger# Find the default location of gm expectations 26e27eefc4844477cee5d32f51ab45ff62020cdb36Derek SollenbergerDEFAULT_GM_EXPECTATIONS_DIR = os.path.join(SKIA_ROOT_DIR, 'expectations', 'gm') 27e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger 28e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger# Imports from within Skia 29e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenbergerif TOOLS_DIR not in sys.path: 30e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger sys.path.append(TOOLS_DIR) 31e27eefc4844477cee5d32f51ab45ff62020cdb36Derek SollenbergerGM_DIR = os.path.join(SKIA_ROOT_DIR, 'gm') 32e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenbergerif GM_DIR not in sys.path: 33e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger sys.path.append(GM_DIR) 34e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenbergerimport gm_json 35e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenbergerimport jsondiff 3658190644c30e1c4aa8e527f3503c58f841e0fcf3Derek Sollenberger 3758190644c30e1c4aa8e527f3503c58f841e0fcf3Derek Sollenberger# A simple dictionary of file name extensions to MIME types. The empty string 3858190644c30e1c4aa8e527f3503c58f841e0fcf3Derek Sollenberger# entry is used as the default when no extension was given or if the extension 3958190644c30e1c4aa8e527f3503c58f841e0fcf3Derek Sollenberger# has no entry in this dictionary. 4058190644c30e1c4aa8e527f3503c58f841e0fcf3Derek SollenbergerMIME_TYPE_MAP = {'': 'application/octet-stream', 4158190644c30e1c4aa8e527f3503c58f841e0fcf3Derek Sollenberger 'html': 'text/html', 4258190644c30e1c4aa8e527f3503c58f841e0fcf3Derek Sollenberger 'css': 'text/css', 4358190644c30e1c4aa8e527f3503c58f841e0fcf3Derek Sollenberger 'png': 'image/png', 44e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger 'js': 'application/javascript', 45e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger 'json': 'application/json' 4658190644c30e1c4aa8e527f3503c58f841e0fcf3Derek Sollenberger } 4758190644c30e1c4aa8e527f3503c58f841e0fcf3Derek Sollenberger 4858190644c30e1c4aa8e527f3503c58f841e0fcf3Derek Sollenberger 49e27eefc4844477cee5d32f51ab45ff62020cdb36Derek SollenbergerIMAGE_FILENAME_RE = re.compile(gm_json.IMAGE_FILENAME_PATTERN) 50e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger 51e27eefc4844477cee5d32f51ab45ff62020cdb36Derek SollenbergerSKPDIFF_INVOKE_FORMAT = '{} --jsonp=false -o {} -f {} {}' 52e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger 53e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger 54e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenbergerdef get_skpdiff_path(user_path=None): 55e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger """Find the skpdiff binary. 56e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger 57e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger @param user_path If none, searches in Release and Debug out directories of 58e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger the skia root. If set, checks that the path is a real file and 59e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger returns it. 60e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger """ 61e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger skpdiff_path = None 62e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger possible_paths = [] 63e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger 64e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger # Use the user given path, or try out some good default paths. 65e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger if user_path: 66e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger possible_paths.append(user_path) 67e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger else: 68e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger possible_paths.append(os.path.join(SKIA_ROOT_DIR, 'out', 69e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger 'Release', 'skpdiff')) 70e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger possible_paths.append(os.path.join(SKIA_ROOT_DIR, 'out', 710a657bbc2c6fc9daf699942e023050536d5ec95fDerek Sollenberger 'Release', 'skpdiff.exe')) 720a657bbc2c6fc9daf699942e023050536d5ec95fDerek Sollenberger possible_paths.append(os.path.join(SKIA_ROOT_DIR, 'out', 73e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger 'Debug', 'skpdiff')) 740a657bbc2c6fc9daf699942e023050536d5ec95fDerek Sollenberger possible_paths.append(os.path.join(SKIA_ROOT_DIR, 'out', 750a657bbc2c6fc9daf699942e023050536d5ec95fDerek Sollenberger 'Debug', 'skpdiff.exe')) 76e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger # Use the first path that actually points to the binary 77e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger for possible_path in possible_paths: 78e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger if os.path.isfile(possible_path): 79e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger skpdiff_path = possible_path 80e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger break 81e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger 82e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger # If skpdiff was not found, print out diagnostic info for the user. 83e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger if skpdiff_path is None: 84e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger print('Could not find skpdiff binary. Either build it into the ' + 85e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger 'default directory, or specify the path on the command line.') 86e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger print('skpdiff paths tried:') 87e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger for possible_path in possible_paths: 88e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger print(' ', possible_path) 89e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger return skpdiff_path 90e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger 91e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger 92e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenbergerdef download_file(url, output_path): 93e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger """Download the file at url and place it in output_path""" 94e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger reader = urllib2.urlopen(url) 95e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger with open(output_path, 'wb') as writer: 96e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger writer.write(reader.read()) 97e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger 98e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger 99e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenbergerdef download_gm_image(image_name, image_path, hash_val): 100e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger """Download the gm result into the given path. 101e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger 102e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger @param image_name The GM file name, for example imageblur_gpu.png. 103e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger @param image_path Path to place the image. 104e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger @param hash_val The hash value of the image. 105e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger """ 106e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger if hash_val is None: 107e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger return 108e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger 109e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger # Separate the test name from a image name 110e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger image_match = IMAGE_FILENAME_RE.match(image_name) 111e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger test_name = image_match.group(1) 112e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger 113e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger # Calculate the URL of the requested image 114e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger image_url = gm_json.CreateGmActualUrl( 115e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger test_name, gm_json.JSONKEY_HASHTYPE_BITMAP_64BITMD5, hash_val) 116e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger 117e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger # Download the image as requested 118e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger download_file(image_url, image_path) 119e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger 120e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger 121e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenbergerdef get_image_set_from_skpdiff(skpdiff_records): 122e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger """Get the set of all images references in the given records. 123e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger 124e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger @param skpdiff_records An array of records, which are dictionary objects. 125e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger """ 126e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger expected_set = frozenset([r['baselinePath'] for r in skpdiff_records]) 127e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger actual_set = frozenset([r['testPath'] for r in skpdiff_records]) 128e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger return expected_set | actual_set 129e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger 130e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger 131e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenbergerdef set_expected_hash_in_json(expected_results_json, image_name, hash_value): 132e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger """Set the expected hash for the object extracted from 133e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger expected-results.json. Note that this only work with bitmap-64bitMD5 hash 134e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger types. 135e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger 136e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger @param expected_results_json The Python dictionary with the results to 137e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger modify. 138e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger @param image_name The name of the image to set the hash of. 139e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger @param hash_value The hash to set for the image. 140e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger """ 141e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger expected_results = expected_results_json[gm_json.JSONKEY_EXPECTEDRESULTS] 142e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger 143e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger if image_name in expected_results: 144e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger expected_results[image_name][gm_json.JSONKEY_EXPECTEDRESULTS_ALLOWEDDIGESTS][0][1] = hash_value 145e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger else: 146e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger expected_results[image_name] = { 147e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger gm_json.JSONKEY_EXPECTEDRESULTS_ALLOWEDDIGESTS: 148e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger [ 149e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger [ 150e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger gm_json.JSONKEY_HASHTYPE_BITMAP_64BITMD5, 151e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger hash_value 152e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger ] 153e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger ] 154e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger } 155e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger 156e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger 157e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenbergerdef get_head_version(path): 158e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger """Get the version of the file at the given path stored inside the HEAD of 159e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger the git repository. It is returned as a string. 160e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger 161e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger @param path The path of the file whose HEAD is returned. It is assumed the 162e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger path is inside a git repo rooted at SKIA_ROOT_DIR. 163e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger """ 164e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger 165e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger # git-show will not work with absolute paths. This ensures we give it a path 1660a657bbc2c6fc9daf699942e023050536d5ec95fDerek Sollenberger # relative to the skia root. This path also has to use forward slashes, even 1670a657bbc2c6fc9daf699942e023050536d5ec95fDerek Sollenberger # on windows. 1680a657bbc2c6fc9daf699942e023050536d5ec95fDerek Sollenberger git_path = os.path.relpath(path, SKIA_ROOT_DIR).replace('\\', '/') 169e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger git_show_proc = subprocess.Popen(['git', 'show', 'HEAD:' + git_path], 170e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger stdout=subprocess.PIPE) 171e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger 172e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger # When invoked outside a shell, git will output the last committed version 173e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger # of the file directly to stdout. 174e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger git_version_content, _ = git_show_proc.communicate() 175e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger return git_version_content 176e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger 177e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger 178e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenbergerclass GMInstance: 179e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger """Information about a GM test result on a specific device: 180e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger - device_name = the name of the device that rendered it 181e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger - image_name = the GM test name and config 182e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger - expected_hash = the current expected hash value 183e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger - actual_hash = the actual hash value 184e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger - is_rebaselined = True if actual_hash is what is currently in the expected 185e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger results file, False otherwise. 186e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger """ 187e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger def __init__(self, 188e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger device_name, image_name, 189e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger expected_hash, actual_hash, 190e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger is_rebaselined): 191e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger self.device_name = device_name 192e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger self.image_name = image_name 193e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger self.expected_hash = expected_hash 194e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger self.actual_hash = actual_hash 195e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger self.is_rebaselined = is_rebaselined 196e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger 197e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger 198e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenbergerclass ExpectationsManager: 199e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger def __init__(self, expectations_dir, expected_name, updated_name, 200e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger skpdiff_path): 201e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger """ 202e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger @param expectations_dir The directory to traverse for results files. 203e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger This should resemble expectations/gm in the Skia trunk. 204e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger @param expected_name The name of the expected result files. These 205e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger are in the format of expected-results.json. 206e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger @param updated_name The name of the updated expected result files. 207e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger Normally this matches --expectations-filename-output for the 208e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger rebaseline.py tool. 209e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger @param skpdiff_path The path used to execute the skpdiff command. 210e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger """ 211e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger self._expectations_dir = expectations_dir 212e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger self._expected_name = expected_name 213e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger self._updated_name = updated_name 214e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger self._skpdiff_path = skpdiff_path 215e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger self._generate_gm_comparison() 216e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger 217e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger def _generate_gm_comparison(self): 218e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger """Generate all the data needed to compare GMs: 219e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger - determine which GMs changed 220e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger - download the changed images 221e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger - compare them with skpdiff 222e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger """ 223e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger 224e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger # Get the expectations and compare them with actual hashes 225e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger self._get_expectations() 226e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger 227e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger 228e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger # Create a temporary file tree that makes sense for skpdiff to operate 2290a657bbc2c6fc9daf699942e023050536d5ec95fDerek Sollenberger # on. We take the realpath of the new temp directory because some OSs 2300a657bbc2c6fc9daf699942e023050536d5ec95fDerek Sollenberger # (*cough* osx) put the temp directory behind a symlink that gets 2310a657bbc2c6fc9daf699942e023050536d5ec95fDerek Sollenberger # resolved later down the pipeline and breaks the image map. 2320a657bbc2c6fc9daf699942e023050536d5ec95fDerek Sollenberger image_output_dir = os.path.realpath(tempfile.mkdtemp('skpdiff')) 233e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger expected_image_dir = os.path.join(image_output_dir, 'expected') 234e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger actual_image_dir = os.path.join(image_output_dir, 'actual') 235e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger os.mkdir(expected_image_dir) 236e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger os.mkdir(actual_image_dir) 237e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger 238e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger # Download expected and actual images that differed into the temporary 239e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger # file tree. 240e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger self._download_expectation_images(expected_image_dir, actual_image_dir) 241e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger 242e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger # Invoke skpdiff with our downloaded images and place its results in the 243e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger # temporary directory. 244e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger self._skpdiff_output_path = os.path.join(image_output_dir, 245e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger 'skpdiff_output.json') 246e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger skpdiff_cmd = SKPDIFF_INVOKE_FORMAT.format(self._skpdiff_path, 247e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger self._skpdiff_output_path, 248e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger expected_image_dir, 249e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger actual_image_dir) 250e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger os.system(skpdiff_cmd) 251e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger self._load_skpdiff_output() 252e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger 253e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger 254e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger def _get_expectations(self): 255e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger """Fills self._expectations with GMInstance objects for each test whose 256e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger expectation is different between the following two files: 257e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger - the local filesystem's updated results file 258e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger - git's head version of the expected results file 259e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger """ 260e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger differ = jsondiff.GMDiffer() 261e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger self._expectations = [] 262e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger for root, dirs, files in os.walk(self._expectations_dir): 263e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger for expectation_file in files: 264e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger # There are many files in the expectations directory. We only 265e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger # care about expected results. 266e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger if expectation_file != self._expected_name: 267e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger continue 268e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger 269e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger # Get the name of the results file, and be sure there is an 270e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger # updated result to compare against. If there is not, there is 271e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger # no point in diffing this device. 272e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger expected_file_path = os.path.join(root, self._expected_name) 273e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger updated_file_path = os.path.join(root, self._updated_name) 274e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger if not os.path.isfile(updated_file_path): 275e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger continue 276e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger 277e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger # Always get the expected results from git because we may have 278e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger # changed them in a previous instance of the server. 279e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger expected_contents = get_head_version(expected_file_path) 280e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger updated_contents = None 281e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger with open(updated_file_path, 'rb') as updated_file: 282e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger updated_contents = updated_file.read() 283e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger 284e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger # Read the expected results on disk to determine what we've 285e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger # already rebaselined. 286e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger commited_contents = None 287e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger with open(expected_file_path, 'rb') as expected_file: 288e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger commited_contents = expected_file.read() 289e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger 290e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger # Find all expectations that did not match. 291e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger expected_diff = differ.GenerateDiffDictFromStrings( 292e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger expected_contents, 293e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger updated_contents) 294e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger 295e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger # Generate a set of images that have already been rebaselined 296e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger # onto disk. 297e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger rebaselined_diff = differ.GenerateDiffDictFromStrings( 298e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger expected_contents, 299e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger commited_contents) 300e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger 301e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger rebaselined_set = set(rebaselined_diff.keys()) 302e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger 303e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger # The name of the device corresponds to the name of the folder 304e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger # we are in. 305e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger device_name = os.path.basename(root) 306e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger 307e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger # Store old and new versions of the expectation for each GM 308e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger for image_name, hashes in expected_diff.iteritems(): 309e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger self._expectations.append( 310e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger GMInstance(device_name, image_name, 311e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger hashes['old'], hashes['new'], 312e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger image_name in rebaselined_set)) 313e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger 314e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger def _load_skpdiff_output(self): 315e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger """Loads the results of skpdiff and annotates them with whether they 316e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger have already been rebaselined or not. The resulting data is store in 317e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger self.skpdiff_records.""" 318e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger self.skpdiff_records = None 319e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger with open(self._skpdiff_output_path, 'rb') as skpdiff_output_file: 320e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger self.skpdiff_records = json.load(skpdiff_output_file)['records'] 321e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger for record in self.skpdiff_records: 322e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger record['isRebaselined'] = self.image_map[record['baselinePath']][1].is_rebaselined 323e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger 324e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger 325e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger def _download_expectation_images(self, expected_image_dir, actual_image_dir): 326e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger """Download the expected and actual images for the _expectations array. 327e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger 328e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger @param expected_image_dir The directory to download expected images 329e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger into. 330e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger @param actual_image_dir The directory to download actual images into. 331e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger """ 332e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger image_map = {} 333e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger 334e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger # Look through expectations and download their images. 335e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger for expectation in self._expectations: 336e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger # Build appropriate paths to download the images into. 337e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger expected_image_path = os.path.join(expected_image_dir, 338e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger expectation.device_name + '-' + 339e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger expectation.image_name) 340e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger 341e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger actual_image_path = os.path.join(actual_image_dir, 342e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger expectation.device_name + '-' + 343e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger expectation.image_name) 344e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger 345e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger print('Downloading %s for device %s' % ( 346e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger expectation.image_name, expectation.device_name)) 347e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger 348e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger # Download images 349e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger download_gm_image(expectation.image_name, 350e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger expected_image_path, 351e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger expectation.expected_hash) 352e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger 353e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger download_gm_image(expectation.image_name, 354e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger actual_image_path, 355e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger expectation.actual_hash) 356e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger 357e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger # Annotate the expectations with where the images were downloaded 358e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger # to. 359e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger expectation.expected_image_path = expected_image_path 360e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger expectation.actual_image_path = actual_image_path 361e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger 362e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger # Map the image paths back to the expectations. 363e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger image_map[expected_image_path] = (False, expectation) 364e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger image_map[actual_image_path] = (True, expectation) 365e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger 366e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger self.image_map = image_map 367e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger 368e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger def _set_expected_hash(self, device_name, image_name, hash_value): 369e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger """Set the expected hash for the image of the given device. This always 370e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger writes directly to the expected results file of the given device 371e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger 372e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger @param device_name The name of the device to write the hash to. 373e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger @param image_name The name of the image whose hash to set. 374e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger @param hash_value The value of the hash to set. 375e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger """ 376e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger 377e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger # Retrieve the expected results file as it is in the working tree 378e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger json_path = os.path.join(self._expectations_dir, device_name, 379e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger self._expected_name) 380e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger expectations = gm_json.LoadFromFile(json_path) 381e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger 382e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger # Set the specified hash. 383e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger set_expected_hash_in_json(expectations, image_name, hash_value) 384e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger 385e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger # Write it out to disk using gm_json to keep the formatting consistent. 386e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger gm_json.WriteToFile(expectations, json_path) 387e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger 388e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger def commit_rebaselines(self, rebaselines): 389e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger """Sets the expected results file to use the hashes of the images in 390e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger the rebaselines list. If a expected result image is not in rebaselines 391e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger at all, the old hash will be used. 392e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger 393e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger @param rebaselines A list of image paths to use the hash of. 394e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger """ 395e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger # Reset all expectations to their old hashes because some of them may 396e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger # have been set to the new hash by a previous call to this function. 397e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger for expectation in self._expectations: 398e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger expectation.is_rebaselined = False 399e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger self._set_expected_hash(expectation.device_name, 400e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger expectation.image_name, 401e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger expectation.expected_hash) 402e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger 403e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger # Take all the images to rebaseline 404e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger for image_path in rebaselines: 405e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger # Get the metadata about the image at the path. 406e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger is_actual, expectation = self.image_map[image_path] 407e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger 408e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger expectation.is_rebaselined = is_actual 409e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger expectation_hash = expectation.actual_hash if is_actual else\ 410e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger expectation.expected_hash 411e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger 412e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger # Write out that image's hash directly to the expected results file. 413e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger self._set_expected_hash(expectation.device_name, 414e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger expectation.image_name, 415e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger expectation_hash) 416e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger 417e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger self._load_skpdiff_output() 418e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger 419e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger 42058190644c30e1c4aa8e527f3503c58f841e0fcf3Derek Sollenbergerclass SkPDiffHandler(BaseHTTPServer.BaseHTTPRequestHandler): 42158190644c30e1c4aa8e527f3503c58f841e0fcf3Derek Sollenberger def send_file(self, file_path): 42258190644c30e1c4aa8e527f3503c58f841e0fcf3Derek Sollenberger # Grab the extension if there is one 42358190644c30e1c4aa8e527f3503c58f841e0fcf3Derek Sollenberger extension = os.path.splitext(file_path)[1] 42458190644c30e1c4aa8e527f3503c58f841e0fcf3Derek Sollenberger if len(extension) >= 1: 42558190644c30e1c4aa8e527f3503c58f841e0fcf3Derek Sollenberger extension = extension[1:] 42658190644c30e1c4aa8e527f3503c58f841e0fcf3Derek Sollenberger 42758190644c30e1c4aa8e527f3503c58f841e0fcf3Derek Sollenberger # Determine the MIME type of the file from its extension 42858190644c30e1c4aa8e527f3503c58f841e0fcf3Derek Sollenberger mime_type = MIME_TYPE_MAP.get(extension, MIME_TYPE_MAP['']) 42958190644c30e1c4aa8e527f3503c58f841e0fcf3Derek Sollenberger 43058190644c30e1c4aa8e527f3503c58f841e0fcf3Derek Sollenberger # Open the file and send it over HTTP 431e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger if os.path.isfile(file_path): 432e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger with open(file_path, 'rb') as sending_file: 433e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger self.send_response(200) 434e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger self.send_header('Content-type', mime_type) 435e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger self.end_headers() 436e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger self.wfile.write(sending_file.read()) 437e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger else: 438e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger self.send_error(404) 43958190644c30e1c4aa8e527f3503c58f841e0fcf3Derek Sollenberger 44058190644c30e1c4aa8e527f3503c58f841e0fcf3Derek Sollenberger def serve_if_in_dir(self, dir_path, file_path): 44158190644c30e1c4aa8e527f3503c58f841e0fcf3Derek Sollenberger # Determine if the file exists relative to the given dir_path AND exists 44258190644c30e1c4aa8e527f3503c58f841e0fcf3Derek Sollenberger # under the dir_path. This is to prevent accidentally serving files 44358190644c30e1c4aa8e527f3503c58f841e0fcf3Derek Sollenberger # outside the directory intended using symlinks, or '../'. 44458190644c30e1c4aa8e527f3503c58f841e0fcf3Derek Sollenberger real_path = os.path.normpath(os.path.join(dir_path, file_path)) 44558190644c30e1c4aa8e527f3503c58f841e0fcf3Derek Sollenberger if os.path.commonprefix([real_path, dir_path]) == dir_path: 44658190644c30e1c4aa8e527f3503c58f841e0fcf3Derek Sollenberger if os.path.isfile(real_path): 44758190644c30e1c4aa8e527f3503c58f841e0fcf3Derek Sollenberger self.send_file(real_path) 44858190644c30e1c4aa8e527f3503c58f841e0fcf3Derek Sollenberger return True 44958190644c30e1c4aa8e527f3503c58f841e0fcf3Derek Sollenberger return False 45058190644c30e1c4aa8e527f3503c58f841e0fcf3Derek Sollenberger 45158190644c30e1c4aa8e527f3503c58f841e0fcf3Derek Sollenberger def do_GET(self): 45258190644c30e1c4aa8e527f3503c58f841e0fcf3Derek Sollenberger # Simple rewrite rule of the root path to 'viewer.html' 45358190644c30e1c4aa8e527f3503c58f841e0fcf3Derek Sollenberger if self.path == '' or self.path == '/': 45458190644c30e1c4aa8e527f3503c58f841e0fcf3Derek Sollenberger self.path = '/viewer.html' 45558190644c30e1c4aa8e527f3503c58f841e0fcf3Derek Sollenberger 45658190644c30e1c4aa8e527f3503c58f841e0fcf3Derek Sollenberger # The [1:] chops off the leading '/' 45758190644c30e1c4aa8e527f3503c58f841e0fcf3Derek Sollenberger file_path = self.path[1:] 45858190644c30e1c4aa8e527f3503c58f841e0fcf3Derek Sollenberger 459e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger # Handle skpdiff_output.json manually because it is was processed by the 460e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger # server when it was started and does not exist as a file. 461e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger if file_path == 'skpdiff_output.json': 462e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger self.send_response(200) 463e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger self.send_header('Content-type', MIME_TYPE_MAP['json']) 464e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger self.end_headers() 465e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger 466e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger # Add JSONP padding to the JSON because the web page expects it. It 467e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger # expects it because it was designed to run with or without a web 468e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger # server. Without a web server, the only way to load JSON is with 469e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger # JSONP. 470e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger skpdiff_records = self.server.expectations_manager.skpdiff_records 471e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger self.wfile.write('var SkPDiffRecords = ') 472e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger json.dump({'records': skpdiff_records}, self.wfile) 473e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger self.wfile.write(';') 474e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger return 475e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger 476e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger # Attempt to send static asset files first. 477e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger if self.serve_if_in_dir(SCRIPT_DIR, file_path): 47858190644c30e1c4aa8e527f3503c58f841e0fcf3Derek Sollenberger return 47958190644c30e1c4aa8e527f3503c58f841e0fcf3Derek Sollenberger 480e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger # WARNING: Serving any file the user wants is incredibly insecure. Its 481e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger # redeeming quality is that we only serve gm files on a white list. 482e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger if self.path in self.server.image_set: 483e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger self.send_file(self.path) 48458190644c30e1c4aa8e527f3503c58f841e0fcf3Derek Sollenberger return 48558190644c30e1c4aa8e527f3503c58f841e0fcf3Derek Sollenberger 48658190644c30e1c4aa8e527f3503c58f841e0fcf3Derek Sollenberger # If no file to send was found, just give the standard 404 48758190644c30e1c4aa8e527f3503c58f841e0fcf3Derek Sollenberger self.send_error(404) 48858190644c30e1c4aa8e527f3503c58f841e0fcf3Derek Sollenberger 489e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger def do_POST(self): 490e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger if self.path == '/commit_rebaselines': 491e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger content_length = int(self.headers['Content-length']) 492e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger request_data = json.loads(self.rfile.read(content_length)) 493e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger rebaselines = request_data['rebaselines'] 494e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger self.server.expectations_manager.commit_rebaselines(rebaselines) 495e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger self.send_response(200) 496e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger self.send_header('Content-type', 'application/json') 497e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger self.end_headers() 498e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger self.wfile.write('{"success":true}') 499e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger return 500e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger 501e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger # If the we have no handler for this path, give em' the 404 502e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger self.send_error(404) 503e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger 504e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger 505e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenbergerdef run_server(expectations_manager, port=8080): 506e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger # It's important to parse the results file so that we can make a set of 507e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger # images that the web page might request. 508e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger skpdiff_records = expectations_manager.skpdiff_records 509e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger image_set = get_image_set_from_skpdiff(skpdiff_records) 51058190644c30e1c4aa8e527f3503c58f841e0fcf3Derek Sollenberger 51158190644c30e1c4aa8e527f3503c58f841e0fcf3Derek Sollenberger # Do not bind to interfaces other than localhost because the server will 51258190644c30e1c4aa8e527f3503c58f841e0fcf3Derek Sollenberger # attempt to serve files relative to the root directory as a last resort 51358190644c30e1c4aa8e527f3503c58f841e0fcf3Derek Sollenberger # before 404ing. This means all of your files can be accessed from this 51458190644c30e1c4aa8e527f3503c58f841e0fcf3Derek Sollenberger # server, so DO NOT let this server listen to anything but localhost. 515e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger server_address = ('127.0.0.1', port) 51658190644c30e1c4aa8e527f3503c58f841e0fcf3Derek Sollenberger http_server = BaseHTTPServer.HTTPServer(server_address, SkPDiffHandler) 517e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger http_server.image_set = image_set 518e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger http_server.expectations_manager = expectations_manager 519e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger print('Navigate thine browser to: http://{}:{}/'.format(*server_address)) 52058190644c30e1c4aa8e527f3503c58f841e0fcf3Derek Sollenberger http_server.serve_forever() 52158190644c30e1c4aa8e527f3503c58f841e0fcf3Derek Sollenberger 522e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger 523e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenbergerdef main(): 524e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger parser = argparse.ArgumentParser() 525e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger parser.add_argument('--port', '-p', metavar='PORT', 526e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger type=int, 527e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger default=8080, 528e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger help='port to bind the server to; ' + 529e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger 'defaults to %(default)s', 530e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger ) 531e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger 532e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger parser.add_argument('--expectations-dir', metavar='EXPECTATIONS_DIR', 533e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger default=DEFAULT_GM_EXPECTATIONS_DIR, 534e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger help='path to the gm expectations; ' + 535e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger 'defaults to %(default)s' 536e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger ) 537e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger 538e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger parser.add_argument('--expected', 539e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger metavar='EXPECTATIONS_FILE_NAME', 540e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger default='expected-results.json', 541e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger help='the file name of the expectations JSON; ' + 542e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger 'defaults to %(default)s' 543e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger ) 544e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger 545e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger parser.add_argument('--updated', 546e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger metavar='UPDATED_FILE_NAME', 547e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger default='updated-results.json', 548e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger help='the file name of the updated expectations JSON;' + 549e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger ' defaults to %(default)s' 550e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger ) 551e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger 552e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger parser.add_argument('--skpdiff-path', metavar='SKPDIFF_PATH', 553e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger default=None, 554e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger help='the path to the skpdiff binary to use; ' + 555e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger 'defaults to out/Release/skpdiff or out/Default/skpdiff' 556e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger ) 557e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger 558e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger args = vars(parser.parse_args()) # Convert args into a python dict 559e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger 560e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger # Make sure we have access to an skpdiff binary 561e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger skpdiff_path = get_skpdiff_path(args['skpdiff_path']) 562e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger if skpdiff_path is None: 563e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger sys.exit(1) 564e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger 565e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger # Print out the paths of things for easier debugging 566e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger print('script dir :', SCRIPT_DIR) 567e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger print('tools dir :', TOOLS_DIR) 568e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger print('root dir :', SKIA_ROOT_DIR) 569e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger print('expectations dir :', args['expectations_dir']) 570e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger print('skpdiff path :', skpdiff_path) 571e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger 572e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger expectations_manager = ExpectationsManager(args['expectations_dir'], 573e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger args['expected'], 574e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger args['updated'], 575e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger skpdiff_path) 576e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger 577e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger run_server(expectations_manager, port=args['port']) 578e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger 57958190644c30e1c4aa8e527f3503c58f841e0fcf3Derek Sollenbergerif __name__ == '__main__': 58058190644c30e1c4aa8e527f3503c58f841e0fcf3Derek Sollenberger main() 581