Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(437)

Unified Diff: tools/skpdiff/skpdiff_server.py

Issue 1502173003: When was SkPDiff last used? (Closed) Base URL: https://skia.googlesource.com/skia.git@master
Patch Set: Created 5 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « tools/skpdiff/skpdiff_main.cpp ('k') | tools/skpdiff/skpdiff_util.h » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: tools/skpdiff/skpdiff_server.py
diff --git a/tools/skpdiff/skpdiff_server.py b/tools/skpdiff/skpdiff_server.py
deleted file mode 100755
index 15ff8a9dab5c8803c15be7d3c9a353ba3a34b2ec..0000000000000000000000000000000000000000
--- a/tools/skpdiff/skpdiff_server.py
+++ /dev/null
@@ -1,580 +0,0 @@
-#!/usr/bin/python
-# -*- coding: utf-8 -*-
-
-from __future__ import print_function
-import argparse
-import BaseHTTPServer
-import json
-import os
-import os.path
-import re
-import subprocess
-import sys
-import tempfile
-import urllib2
-
-# Grab the script path because that is where all the static assets are
-SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
-
-# Find the tools directory for python imports
-TOOLS_DIR = os.path.dirname(SCRIPT_DIR)
-
-# Find the root of the skia trunk for finding skpdiff binary
-SKIA_ROOT_DIR = os.path.dirname(TOOLS_DIR)
-
-# Find the default location of gm expectations
-DEFAULT_GM_EXPECTATIONS_DIR = os.path.join(SKIA_ROOT_DIR, 'expectations', 'gm')
-
-# Imports from within Skia
-if TOOLS_DIR not in sys.path:
- sys.path.append(TOOLS_DIR)
-GM_DIR = os.path.join(SKIA_ROOT_DIR, 'gm')
-if GM_DIR not in sys.path:
- sys.path.append(GM_DIR)
-import gm_json
-import jsondiff
-
-# A simple dictionary of file name extensions to MIME types. The empty string
-# entry is used as the default when no extension was given or if the extension
-# has no entry in this dictionary.
-MIME_TYPE_MAP = {'': 'application/octet-stream',
- 'html': 'text/html',
- 'css': 'text/css',
- 'png': 'image/png',
- 'js': 'application/javascript',
- 'json': 'application/json'
- }
-
-
-IMAGE_FILENAME_RE = re.compile(gm_json.IMAGE_FILENAME_PATTERN)
-
-SKPDIFF_INVOKE_FORMAT = '{} --jsonp=false -o {} -f {} {}'
-
-
-def get_skpdiff_path(user_path=None):
- """Find the skpdiff binary.
-
- @param user_path If none, searches in Release and Debug out directories of
- the skia root. If set, checks that the path is a real file and
- returns it.
- """
- skpdiff_path = None
- possible_paths = []
-
- # Use the user given path, or try out some good default paths.
- if user_path:
- possible_paths.append(user_path)
- else:
- possible_paths.append(os.path.join(SKIA_ROOT_DIR, 'out',
- 'Release', 'skpdiff'))
- possible_paths.append(os.path.join(SKIA_ROOT_DIR, 'out',
- 'Release', 'skpdiff.exe'))
- possible_paths.append(os.path.join(SKIA_ROOT_DIR, 'out',
- 'Debug', 'skpdiff'))
- possible_paths.append(os.path.join(SKIA_ROOT_DIR, 'out',
- 'Debug', 'skpdiff.exe'))
- # Use the first path that actually points to the binary
- for possible_path in possible_paths:
- if os.path.isfile(possible_path):
- skpdiff_path = possible_path
- break
-
- # If skpdiff was not found, print out diagnostic info for the user.
- if skpdiff_path is None:
- print('Could not find skpdiff binary. Either build it into the ' +
- 'default directory, or specify the path on the command line.')
- print('skpdiff paths tried:')
- for possible_path in possible_paths:
- print(' ', possible_path)
- return skpdiff_path
-
-
-def download_file(url, output_path):
- """Download the file at url and place it in output_path"""
- reader = urllib2.urlopen(url)
- with open(output_path, 'wb') as writer:
- writer.write(reader.read())
-
-
-def download_gm_image(image_name, image_path, hash_val):
- """Download the gm result into the given path.
-
- @param image_name The GM file name, for example imageblur_gpu.png.
- @param image_path Path to place the image.
- @param hash_val The hash value of the image.
- """
- if hash_val is None:
- return
-
- # Separate the test name from a image name
- image_match = IMAGE_FILENAME_RE.match(image_name)
- test_name = image_match.group(1)
-
- # Calculate the URL of the requested image
- image_url = gm_json.CreateGmActualUrl(
- test_name, gm_json.JSONKEY_HASHTYPE_BITMAP_64BITMD5, hash_val)
-
- # Download the image as requested
- download_file(image_url, image_path)
-
-
-def get_image_set_from_skpdiff(skpdiff_records):
- """Get the set of all images references in the given records.
-
- @param skpdiff_records An array of records, which are dictionary objects.
- """
- expected_set = frozenset([r['baselinePath'] for r in skpdiff_records])
- actual_set = frozenset([r['testPath'] for r in skpdiff_records])
- return expected_set | actual_set
-
-
-def set_expected_hash_in_json(expected_results_json, image_name, hash_value):
- """Set the expected hash for the object extracted from
- expected-results.json. Note that this only work with bitmap-64bitMD5 hash
- types.
-
- @param expected_results_json The Python dictionary with the results to
- modify.
- @param image_name The name of the image to set the hash of.
- @param hash_value The hash to set for the image.
- """
- expected_results = expected_results_json[gm_json.JSONKEY_EXPECTEDRESULTS]
-
- if image_name in expected_results:
- expected_results[image_name][gm_json.JSONKEY_EXPECTEDRESULTS_ALLOWEDDIGESTS][0][1] = hash_value
- else:
- expected_results[image_name] = {
- gm_json.JSONKEY_EXPECTEDRESULTS_ALLOWEDDIGESTS:
- [
- [
- gm_json.JSONKEY_HASHTYPE_BITMAP_64BITMD5,
- hash_value
- ]
- ]
- }
-
-
-def get_head_version(path):
- """Get the version of the file at the given path stored inside the HEAD of
- the git repository. It is returned as a string.
-
- @param path The path of the file whose HEAD is returned. It is assumed the
- path is inside a git repo rooted at SKIA_ROOT_DIR.
- """
-
- # git-show will not work with absolute paths. This ensures we give it a path
- # relative to the skia root. This path also has to use forward slashes, even
- # on windows.
- git_path = os.path.relpath(path, SKIA_ROOT_DIR).replace('\\', '/')
- git_show_proc = subprocess.Popen(['git', 'show', 'HEAD:' + git_path],
- stdout=subprocess.PIPE)
-
- # When invoked outside a shell, git will output the last committed version
- # of the file directly to stdout.
- git_version_content, _ = git_show_proc.communicate()
- return git_version_content
-
-
-class GMInstance:
- """Information about a GM test result on a specific device:
- - device_name = the name of the device that rendered it
- - image_name = the GM test name and config
- - expected_hash = the current expected hash value
- - actual_hash = the actual hash value
- - is_rebaselined = True if actual_hash is what is currently in the expected
- results file, False otherwise.
- """
- def __init__(self,
- device_name, image_name,
- expected_hash, actual_hash,
- is_rebaselined):
- self.device_name = device_name
- self.image_name = image_name
- self.expected_hash = expected_hash
- self.actual_hash = actual_hash
- self.is_rebaselined = is_rebaselined
-
-
-class ExpectationsManager:
- def __init__(self, expectations_dir, expected_name, updated_name,
- skpdiff_path):
- """
- @param expectations_dir The directory to traverse for results files.
- This should resemble expectations/gm in the Skia trunk.
- @param expected_name The name of the expected result files. These
- are in the format of expected-results.json.
- @param updated_name The name of the updated expected result files.
- Normally this matches --expectations-filename-output for the
- rebaseline.py tool.
- @param skpdiff_path The path used to execute the skpdiff command.
- """
- self._expectations_dir = expectations_dir
- self._expected_name = expected_name
- self._updated_name = updated_name
- self._skpdiff_path = skpdiff_path
- self._generate_gm_comparison()
-
- def _generate_gm_comparison(self):
- """Generate all the data needed to compare GMs:
- - determine which GMs changed
- - download the changed images
- - compare them with skpdiff
- """
-
- # Get the expectations and compare them with actual hashes
- self._get_expectations()
-
-
- # Create a temporary file tree that makes sense for skpdiff to operate
- # on. We take the realpath of the new temp directory because some OSs
- # (*cough* osx) put the temp directory behind a symlink that gets
- # resolved later down the pipeline and breaks the image map.
- image_output_dir = os.path.realpath(tempfile.mkdtemp('skpdiff'))
- expected_image_dir = os.path.join(image_output_dir, 'expected')
- actual_image_dir = os.path.join(image_output_dir, 'actual')
- os.mkdir(expected_image_dir)
- os.mkdir(actual_image_dir)
-
- # Download expected and actual images that differed into the temporary
- # file tree.
- self._download_expectation_images(expected_image_dir, actual_image_dir)
-
- # Invoke skpdiff with our downloaded images and place its results in the
- # temporary directory.
- self._skpdiff_output_path = os.path.join(image_output_dir,
- 'skpdiff_output.json')
- skpdiff_cmd = SKPDIFF_INVOKE_FORMAT.format(self._skpdiff_path,
- self._skpdiff_output_path,
- expected_image_dir,
- actual_image_dir)
- os.system(skpdiff_cmd)
- self._load_skpdiff_output()
-
-
- def _get_expectations(self):
- """Fills self._expectations with GMInstance objects for each test whose
- expectation is different between the following two files:
- - the local filesystem's updated results file
- - git's head version of the expected results file
- """
- differ = jsondiff.GMDiffer()
- self._expectations = []
- for root, dirs, files in os.walk(self._expectations_dir):
- for expectation_file in files:
- # There are many files in the expectations directory. We only
- # care about expected results.
- if expectation_file != self._expected_name:
- continue
-
- # Get the name of the results file, and be sure there is an
- # updated result to compare against. If there is not, there is
- # no point in diffing this device.
- expected_file_path = os.path.join(root, self._expected_name)
- updated_file_path = os.path.join(root, self._updated_name)
- if not os.path.isfile(updated_file_path):
- continue
-
- # Always get the expected results from git because we may have
- # changed them in a previous instance of the server.
- expected_contents = get_head_version(expected_file_path)
- updated_contents = None
- with open(updated_file_path, 'rb') as updated_file:
- updated_contents = updated_file.read()
-
- # Read the expected results on disk to determine what we've
- # already rebaselined.
- commited_contents = None
- with open(expected_file_path, 'rb') as expected_file:
- commited_contents = expected_file.read()
-
- # Find all expectations that did not match.
- expected_diff = differ.GenerateDiffDictFromStrings(
- expected_contents,
- updated_contents)
-
- # Generate a set of images that have already been rebaselined
- # onto disk.
- rebaselined_diff = differ.GenerateDiffDictFromStrings(
- expected_contents,
- commited_contents)
-
- rebaselined_set = set(rebaselined_diff.keys())
-
- # The name of the device corresponds to the name of the folder
- # we are in.
- device_name = os.path.basename(root)
-
- # Store old and new versions of the expectation for each GM
- for image_name, hashes in expected_diff.iteritems():
- self._expectations.append(
- GMInstance(device_name, image_name,
- hashes['old'], hashes['new'],
- image_name in rebaselined_set))
-
- def _load_skpdiff_output(self):
- """Loads the results of skpdiff and annotates them with whether they
- have already been rebaselined or not. The resulting data is store in
- self.skpdiff_records."""
- self.skpdiff_records = None
- with open(self._skpdiff_output_path, 'rb') as skpdiff_output_file:
- self.skpdiff_records = json.load(skpdiff_output_file)['records']
- for record in self.skpdiff_records:
- record['isRebaselined'] = self.image_map[record['baselinePath']][1].is_rebaselined
-
-
- def _download_expectation_images(self, expected_image_dir, actual_image_dir):
- """Download the expected and actual images for the _expectations array.
-
- @param expected_image_dir The directory to download expected images
- into.
- @param actual_image_dir The directory to download actual images into.
- """
- image_map = {}
-
- # Look through expectations and download their images.
- for expectation in self._expectations:
- # Build appropriate paths to download the images into.
- expected_image_path = os.path.join(expected_image_dir,
- expectation.device_name + '-' +
- expectation.image_name)
-
- actual_image_path = os.path.join(actual_image_dir,
- expectation.device_name + '-' +
- expectation.image_name)
-
- print('Downloading %s for device %s' % (
- expectation.image_name, expectation.device_name))
-
- # Download images
- download_gm_image(expectation.image_name,
- expected_image_path,
- expectation.expected_hash)
-
- download_gm_image(expectation.image_name,
- actual_image_path,
- expectation.actual_hash)
-
- # Annotate the expectations with where the images were downloaded
- # to.
- expectation.expected_image_path = expected_image_path
- expectation.actual_image_path = actual_image_path
-
- # Map the image paths back to the expectations.
- image_map[expected_image_path] = (False, expectation)
- image_map[actual_image_path] = (True, expectation)
-
- self.image_map = image_map
-
- def _set_expected_hash(self, device_name, image_name, hash_value):
- """Set the expected hash for the image of the given device. This always
- writes directly to the expected results file of the given device
-
- @param device_name The name of the device to write the hash to.
- @param image_name The name of the image whose hash to set.
- @param hash_value The value of the hash to set.
- """
-
- # Retrieve the expected results file as it is in the working tree
- json_path = os.path.join(self._expectations_dir, device_name,
- self._expected_name)
- expectations = gm_json.LoadFromFile(json_path)
-
- # Set the specified hash.
- set_expected_hash_in_json(expectations, image_name, hash_value)
-
- # Write it out to disk using gm_json to keep the formatting consistent.
- gm_json.WriteToFile(expectations, json_path)
-
- def commit_rebaselines(self, rebaselines):
- """Sets the expected results file to use the hashes of the images in
- the rebaselines list. If a expected result image is not in rebaselines
- at all, the old hash will be used.
-
- @param rebaselines A list of image paths to use the hash of.
- """
- # Reset all expectations to their old hashes because some of them may
- # have been set to the new hash by a previous call to this function.
- for expectation in self._expectations:
- expectation.is_rebaselined = False
- self._set_expected_hash(expectation.device_name,
- expectation.image_name,
- expectation.expected_hash)
-
- # Take all the images to rebaseline
- for image_path in rebaselines:
- # Get the metadata about the image at the path.
- is_actual, expectation = self.image_map[image_path]
-
- expectation.is_rebaselined = is_actual
- expectation_hash = expectation.actual_hash if is_actual else\
- expectation.expected_hash
-
- # Write out that image's hash directly to the expected results file.
- self._set_expected_hash(expectation.device_name,
- expectation.image_name,
- expectation_hash)
-
- self._load_skpdiff_output()
-
-
-class SkPDiffHandler(BaseHTTPServer.BaseHTTPRequestHandler):
- def send_file(self, file_path):
- # Grab the extension if there is one
- extension = os.path.splitext(file_path)[1]
- if len(extension) >= 1:
- extension = extension[1:]
-
- # Determine the MIME type of the file from its extension
- mime_type = MIME_TYPE_MAP.get(extension, MIME_TYPE_MAP[''])
-
- # Open the file and send it over HTTP
- if os.path.isfile(file_path):
- with open(file_path, 'rb') as sending_file:
- self.send_response(200)
- self.send_header('Content-type', mime_type)
- self.end_headers()
- self.wfile.write(sending_file.read())
- else:
- self.send_error(404)
-
- def serve_if_in_dir(self, dir_path, file_path):
- # Determine if the file exists relative to the given dir_path AND exists
- # under the dir_path. This is to prevent accidentally serving files
- # outside the directory intended using symlinks, or '../'.
- real_path = os.path.normpath(os.path.join(dir_path, file_path))
- if os.path.commonprefix([real_path, dir_path]) == dir_path:
- if os.path.isfile(real_path):
- self.send_file(real_path)
- return True
- return False
-
- def do_GET(self):
- # Simple rewrite rule of the root path to 'viewer.html'
- if self.path == '' or self.path == '/':
- self.path = '/viewer.html'
-
- # The [1:] chops off the leading '/'
- file_path = self.path[1:]
-
- # Handle skpdiff_output.json manually because it is was processed by the
- # server when it was started and does not exist as a file.
- if file_path == 'skpdiff_output.json':
- self.send_response(200)
- self.send_header('Content-type', MIME_TYPE_MAP['json'])
- self.end_headers()
-
- # Add JSONP padding to the JSON because the web page expects it. It
- # expects it because it was designed to run with or without a web
- # server. Without a web server, the only way to load JSON is with
- # JSONP.
- skpdiff_records = self.server.expectations_manager.skpdiff_records
- self.wfile.write('var SkPDiffRecords = ')
- json.dump({'records': skpdiff_records}, self.wfile)
- self.wfile.write(';')
- return
-
- # Attempt to send static asset files first.
- if self.serve_if_in_dir(SCRIPT_DIR, file_path):
- return
-
- # WARNING: Serving any file the user wants is incredibly insecure. Its
- # redeeming quality is that we only serve gm files on a white list.
- if self.path in self.server.image_set:
- self.send_file(self.path)
- return
-
- # If no file to send was found, just give the standard 404
- self.send_error(404)
-
- def do_POST(self):
- if self.path == '/commit_rebaselines':
- content_length = int(self.headers['Content-length'])
- request_data = json.loads(self.rfile.read(content_length))
- rebaselines = request_data['rebaselines']
- self.server.expectations_manager.commit_rebaselines(rebaselines)
- self.send_response(200)
- self.send_header('Content-type', 'application/json')
- self.end_headers()
- self.wfile.write('{"success":true}')
- return
-
- # If the we have no handler for this path, give em' the 404
- self.send_error(404)
-
-
-def run_server(expectations_manager, port=8080):
- # It's important to parse the results file so that we can make a set of
- # images that the web page might request.
- skpdiff_records = expectations_manager.skpdiff_records
- image_set = get_image_set_from_skpdiff(skpdiff_records)
-
- # Do not bind to interfaces other than localhost because the server will
- # attempt to serve files relative to the root directory as a last resort
- # before 404ing. This means all of your files can be accessed from this
- # server, so DO NOT let this server listen to anything but localhost.
- server_address = ('127.0.0.1', port)
- http_server = BaseHTTPServer.HTTPServer(server_address, SkPDiffHandler)
- http_server.image_set = image_set
- http_server.expectations_manager = expectations_manager
- print('Navigate thine browser to: http://{}:{}/'.format(*server_address))
- http_server.serve_forever()
-
-
-def main():
- parser = argparse.ArgumentParser()
- parser.add_argument('--port', '-p', metavar='PORT',
- type=int,
- default=8080,
- help='port to bind the server to; ' +
- 'defaults to %(default)s',
- )
-
- parser.add_argument('--expectations-dir', metavar='EXPECTATIONS_DIR',
- default=DEFAULT_GM_EXPECTATIONS_DIR,
- help='path to the gm expectations; ' +
- 'defaults to %(default)s'
- )
-
- parser.add_argument('--expected',
- metavar='EXPECTATIONS_FILE_NAME',
- default='expected-results.json',
- help='the file name of the expectations JSON; ' +
- 'defaults to %(default)s'
- )
-
- parser.add_argument('--updated',
- metavar='UPDATED_FILE_NAME',
- default='updated-results.json',
- help='the file name of the updated expectations JSON;' +
- ' defaults to %(default)s'
- )
-
- parser.add_argument('--skpdiff-path', metavar='SKPDIFF_PATH',
- default=None,
- help='the path to the skpdiff binary to use; ' +
- 'defaults to out/Release/skpdiff or out/Default/skpdiff'
- )
-
- args = vars(parser.parse_args()) # Convert args into a python dict
-
- # Make sure we have access to an skpdiff binary
- skpdiff_path = get_skpdiff_path(args['skpdiff_path'])
- if skpdiff_path is None:
- sys.exit(1)
-
- # Print out the paths of things for easier debugging
- print('script dir :', SCRIPT_DIR)
- print('tools dir :', TOOLS_DIR)
- print('root dir :', SKIA_ROOT_DIR)
- print('expectations dir :', args['expectations_dir'])
- print('skpdiff path :', skpdiff_path)
-
- expectations_manager = ExpectationsManager(args['expectations_dir'],
- args['expected'],
- args['updated'],
- skpdiff_path)
-
- run_server(expectations_manager, port=args['port'])
-
-if __name__ == '__main__':
- main()
« no previous file with comments | « tools/skpdiff/skpdiff_main.cpp ('k') | tools/skpdiff/skpdiff_util.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698