From: commit-bot@chromium.org Date: Thu, 13 Mar 2014 14:56:29 +0000 (+0000) Subject: rebaseline_server: generate JSON that can be viewed without a live server X-Git-Tag: accepted/tizen/5.0/unified/20181102.025319~8677 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=7498d95bde16eeaa4b20643fb930b6a3be7face1;p=platform%2Fupstream%2FlibSkiaSharp.git rebaseline_server: generate JSON that can be viewed without a live server BUG=skia:1919 NOTREECHECKS=True NOTRY=True R=rmistry@google.com Author: epoger@google.com Review URL: https://codereview.chromium.org/195943004 git-svn-id: http://skia.googlecode.com/svn/trunk@13783 2bbb7eff-a529-9590-31e7-b0007b416f81 --- diff --git a/gm/rebaseline_server/results.py b/gm/rebaseline_server/results.py index 8ff24ec..5c33e30 100755 --- a/gm/rebaseline_server/results.py +++ b/gm/rebaseline_server/results.py @@ -28,6 +28,7 @@ import time # so any dirs that are already in the PYTHONPATH will be preferred. PARENT_DIRECTORY = os.path.dirname(os.path.realpath(__file__)) GM_DIRECTORY = os.path.dirname(PARENT_DIRECTORY) +TRUNK_DIRECTORY = os.path.dirname(GM_DIRECTORY) if GM_DIRECTORY not in sys.path: sys.path.append(GM_DIRECTORY) import gm_json @@ -44,8 +45,16 @@ KEY__EXTRACOLUMN__BUILDER = 'builder' KEY__EXTRACOLUMN__CONFIG = 'config' KEY__EXTRACOLUMN__RESULT_TYPE = 'resultType' KEY__EXTRACOLUMN__TEST = 'test' +KEY__HEADER = 'header' +KEY__HEADER__DATAHASH = 'dataHash' +KEY__HEADER__IS_EDITABLE = 'isEditable' +KEY__HEADER__IS_EXPORTED = 'isExported' +KEY__HEADER__IS_STILL_LOADING = 'resultsStillLoading' KEY__HEADER__RESULTS_ALL = 'all' KEY__HEADER__RESULTS_FAILURES = 'failures' +KEY__HEADER__TIME_NEXT_UPDATE_AVAILABLE = 'timeNextUpdateAvailable' +KEY__HEADER__TIME_UPDATED = 'timeUpdated' +KEY__HEADER__TYPE = 'type' KEY__NEW_IMAGE_URL = 'newImageUrl' KEY__RESULT_TYPE__FAILED = gm_json.JSONKEY_ACTUALRESULTS_FAILED KEY__RESULT_TYPE__FAILUREIGNORED = gm_json.JSONKEY_ACTUALRESULTS_FAILUREIGNORED @@ -63,6 +72,11 @@ IMAGE_FILENAME_FORMATTER = '%s_%s.png' # pass in (testname, config) IMAGEPAIR_SET_DESCRIPTIONS = ('expected image', 'actual image') +DEFAULT_ACTUALS_DIR = '.gm-actuals' +DEFAULT_EXPECTATIONS_DIR = os.path.join(TRUNK_DIRECTORY, 'expectations', 'gm') +DEFAULT_GENERATED_IMAGES_ROOT = os.path.join(PARENT_DIRECTORY, 'static', + 'generated-images') + class Results(object): """ Loads actual and expected GM results into an ImagePairSet. @@ -74,7 +88,9 @@ class Results(object): are immutable. If you want to update the results based on updated JSON file contents, you will need to create a new Results object.""" - def __init__(self, actuals_root, expected_root, generated_images_root): + def __init__(self, actuals_root=DEFAULT_ACTUALS_DIR, + expected_root=DEFAULT_EXPECTATIONS_DIR, + generated_images_root=DEFAULT_GENERATED_IMAGES_ROOT): """ Args: actuals_root: root directory containing all actual-results.json files @@ -150,16 +166,55 @@ class Results(object): builder_expectations[image_name] = new_expectations Results._write_dicts_to_root(expected_builder_dicts, self._expected_root) - def get_results_of_type(self, type): - """Return results of some/all tests (depending on 'type' parameter). + def get_results_of_type(self, results_type): + """Return results of some/all tests (depending on 'results_type' parameter). Args: - type: string describing which types of results to include; must be one - of the RESULTS_* constants + results_type: string describing which types of results to include; must + be one of the RESULTS_* constants Results are returned in a dictionary as output by ImagePairSet.as_dict(). """ - return self._results[type] + return self._results[results_type] + + def get_packaged_results_of_type(self, results_type, reload_seconds=None, + is_editable=False, is_exported=True): + """ Package the results of some/all tests as a complete response_dict. + + Args: + results_type: string indicating which set of results to return; + must be one of the RESULTS_* constants + reload_seconds: if specified, note that new results may be available once + these results are reload_seconds old + is_editable: whether clients are allowed to submit new baselines + is_exported: whether these results are being made available to other + network hosts + """ + response_dict = self._results[results_type] + time_updated = self.get_timestamp() + response_dict[KEY__HEADER] = { + # Timestamps: + # 1. when this data was last updated + # 2. when the caller should check back for new data (if ever) + KEY__HEADER__TIME_UPDATED: time_updated, + KEY__HEADER__TIME_NEXT_UPDATE_AVAILABLE: ( + (time_updated+reload_seconds) if reload_seconds else None), + + # The type we passed to get_results_of_type() + KEY__HEADER__TYPE: results_type, + + # Hash of dataset, which the client must return with any edits-- + # this ensures that the edits were made to a particular dataset. + KEY__HEADER__DATAHASH: str(hash(repr( + response_dict[imagepairset.KEY__IMAGEPAIRS]))), + + # Whether the server will accept edits back. + KEY__HEADER__IS_EDITABLE: is_editable, + + # Whether the service is accessible from other hosts. + KEY__HEADER__IS_EXPORTED: is_exported, + } + return response_dict @staticmethod def _ignore_builder(builder): @@ -415,23 +470,31 @@ def main(): level=logging.INFO) parser = argparse.ArgumentParser() parser.add_argument( - '--actuals', required=True, + '--actuals', default=DEFAULT_ACTUALS_DIR, help='Directory containing all actual-result JSON files') parser.add_argument( - '--expectations', required=True, - help='Directory containing all expected-result JSON files') + '--expectations', default=DEFAULT_EXPECTATIONS_DIR, + help='Directory containing all expected-result JSON files; defaults to ' + '\'%(default)s\' .') parser.add_argument( '--outfile', required=True, - help='File to write result summary into, in JSON format') + help='File to write result summary into, in JSON format.') + parser.add_argument( + '--results', default=KEY__HEADER__RESULTS_FAILURES, + help='Which result types to include. Defaults to \'%(default)s\'; ' + 'must be one of ' + + str([KEY__HEADER__RESULTS_FAILURES, KEY__HEADER__RESULTS_ALL])) parser.add_argument( - '--workdir', default='.workdir', - help='Directory within which to download images and generate diffs') + '--workdir', default=DEFAULT_GENERATED_IMAGES_ROOT, + help='Directory within which to download images and generate diffs; ' + 'defaults to \'%(default)s\' .') args = parser.parse_args() results = Results(actuals_root=args.actuals, expected_root=args.expectations, generated_images_root=args.workdir) - gm_json.WriteToFile(results.get_results_of_type(KEY__HEADER__RESULTS_ALL), - args.outfile) + gm_json.WriteToFile( + results.get_packaged_results_of_type(results_type=args.results), + args.outfile) if __name__ == '__main__': diff --git a/gm/rebaseline_server/server.py b/gm/rebaseline_server/server.py index fc090d2..19b0035 100755 --- a/gm/rebaseline_server/server.py +++ b/gm/rebaseline_server/server.py @@ -40,13 +40,14 @@ if TOOLS_DIRECTORY not in sys.path: import svn # Imports from local dir +# +# Note: we import results under a different name, to avoid confusion with the +# Server.results() property. See discussion at +# https://codereview.chromium.org/195943004/diff/1/gm/rebaseline_server/server.py#newcode44 import imagepairset -import results +import results as results_mod PATHSPLIT_RE = re.compile('/([^/]+)/(.+)') -EXPECTATIONS_DIR = os.path.join(TRUNK_DIRECTORY, 'expectations', 'gm') -GENERATED_IMAGES_ROOT = os.path.join(PARENT_DIRECTORY, 'static', - 'generated-images') # 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 @@ -64,16 +65,8 @@ MIME_TYPE_MAP = {'': 'application/octet-stream', KEY__EDITS__MODIFICATIONS = 'modifications' KEY__EDITS__OLD_RESULTS_HASH = 'oldResultsHash' KEY__EDITS__OLD_RESULTS_TYPE = 'oldResultsType' -KEY__HEADER = 'header' -KEY__HEADER__DATAHASH = 'dataHash' -KEY__HEADER__IS_EDITABLE = 'isEditable' -KEY__HEADER__IS_EXPORTED = 'isExported' -KEY__HEADER__IS_STILL_LOADING = 'resultsStillLoading' -KEY__HEADER__TIME_NEXT_UPDATE_AVAILABLE = 'timeNextUpdateAvailable' -KEY__HEADER__TIME_UPDATED = 'timeUpdated' -KEY__HEADER__TYPE = 'type' - -DEFAULT_ACTUALS_DIR = '.gm-actuals' + +DEFAULT_ACTUALS_DIR = results_mod.DEFAULT_ACTUALS_DIR DEFAULT_ACTUALS_REPO_REVISION = 'HEAD' DEFAULT_ACTUALS_REPO_URL = 'http://skia-autogen.googlecode.com/svn/gm-actual' DEFAULT_PORT = 8888 @@ -201,8 +194,8 @@ class Server(object): return self._reload_seconds def update_results(self, invalidate=False): - """ Create or update self._results, based on the expectations in - EXPECTATIONS_DIR and the latest actuals from skia-autogen. + """ Create or update self._results, based on the latest expectations and + actuals. We hold self.results_rlock while we do this, to guarantee that no other thread attempts to update either self._results or the underlying files at @@ -236,13 +229,10 @@ class Server(object): if self._reload_seconds: logging.info( 'Updating expected GM results in %s by syncing Skia repo ...' % - EXPECTATIONS_DIR) + results_mod.DEFAULT_EXPECTATIONS_DIR) _run_command(['gclient', 'sync'], TRUNK_DIRECTORY) - self._results = results.Results( - actuals_root=self._actuals_dir, - expected_root=EXPECTATIONS_DIR, - generated_images_root=GENERATED_IMAGES_ROOT) + self._results = results_mod.Results(actuals_root=self._actuals_dir) def _result_loader(self, reload_seconds=0): """ Call self.update_results(), either once or periodically. @@ -315,14 +305,14 @@ class HTTPRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler): self.send_error(404) raise - def do_GET_results(self, type): + def do_GET_results(self, results_type): """ Handle a GET request for GM results. Args: - type: string indicating which set of results to return; - must be one of the results.RESULTS_* constants + results_type: string indicating which set of results to return; + must be one of the results_mod.RESULTS_* constants """ - logging.debug('do_GET_results: sending results of type "%s"' % type) + logging.debug('do_GET_results: sending results of type "%s"' % results_type) # Since we must make multiple calls to the Results object, grab a # reference to it in case it is updated to point at a new Results # object within another thread. @@ -333,60 +323,21 @@ class HTTPRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler): # the handler's .server instance variable. results_obj = _SERVER.results if results_obj: - response_dict = self.package_results(results_obj, type) + response_dict = results_obj.get_packaged_results_of_type( + results_type=results_type, reload_seconds=_SERVER.reload_seconds, + is_editable=_SERVER.is_editable, is_exported=_SERVER.is_exported) else: now = int(time.time()) response_dict = { - KEY__HEADER: { - KEY__HEADER__IS_STILL_LOADING: True, - KEY__HEADER__TIME_UPDATED: now, - KEY__HEADER__TIME_NEXT_UPDATE_AVAILABLE: ( + results_mod.KEY__HEADER: { + results_mod.KEY__HEADER__IS_STILL_LOADING: True, + results_mod.KEY__HEADER__TIME_UPDATED: now, + results_mod.KEY__HEADER__TIME_NEXT_UPDATE_AVAILABLE: ( now + RELOAD_INTERVAL_UNTIL_READY), }, } self.send_json_dict(response_dict) - def package_results(self, results_obj, type): - """ Given a nonempty "results" object, package it as a response_dict - as needed within do_GET_results. - - Args: - results_obj: nonempty "results" object - type: string indicating which set of results to return; - must be one of the results.RESULTS_* constants - """ - response_dict = results_obj.get_results_of_type(type) - time_updated = results_obj.get_timestamp() - response_dict[KEY__HEADER] = { - # Timestamps: - # 1. when this data was last updated - # 2. when the caller should check back for new data (if ever) - # - # We only return these timestamps if the --reload argument was passed; - # otherwise, we have no idea when the expectations were last updated - # (we allow the user to maintain her own expectations as she sees fit). - KEY__HEADER__TIME_UPDATED: - time_updated if _SERVER.reload_seconds else None, - KEY__HEADER__TIME_NEXT_UPDATE_AVAILABLE: - (time_updated+_SERVER.reload_seconds) if _SERVER.reload_seconds - else None, - - # The type we passed to get_results_of_type() - KEY__HEADER__TYPE: type, - - # Hash of dataset, which the client must return with any edits-- - # this ensures that the edits were made to a particular dataset. - KEY__HEADER__DATAHASH: str(hash(repr( - response_dict[imagepairset.KEY__IMAGEPAIRS]))), - - # Whether the server will accept edits back. - KEY__HEADER__IS_EDITABLE: _SERVER.is_editable, - - # Whether the service is accessible from other hosts. - KEY__HEADER__IS_EXPORTED: _SERVER.is_exported, - } - return response_dict - def do_GET_static(self, path): """ Handle a GET request for a file under the 'static' directory. Only allow serving of files within the 'static' directory that is a @@ -441,7 +392,7 @@ class HTTPRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler): # client and server apply # modifications to the same base) KEY__EDITS__MODIFICATIONS: [ - # as needed by results.edit_expectations() + # as needed by results_mod.edit_expectations() ... ], } diff --git a/gm/rebaseline_server/static/constants.js b/gm/rebaseline_server/static/constants.js index 00c1852..f795b6f 100644 --- a/gm/rebaseline_server/static/constants.js +++ b/gm/rebaseline_server/static/constants.js @@ -45,8 +45,16 @@ module.constant('constants', (function() { KEY__EXTRACOLUMN__CONFIG: 'config', KEY__EXTRACOLUMN__RESULT_TYPE: 'resultType', KEY__EXTRACOLUMN__TEST: 'test', + KEY__HEADER: 'header', + KEY__HEADER__DATAHASH: 'dataHash', + KEY__HEADER__IS_EDITABLE: 'isEditable', + KEY__HEADER__IS_EXPORTED: 'isExported', + KEY__HEADER__IS_STILL_LOADING: 'resultsStillLoading', KEY__HEADER__RESULTS_ALL: 'all', KEY__HEADER__RESULTS_FAILURES: 'failures', + KEY__HEADER__TIME_NEXT_UPDATE_AVAILABLE: 'timeNextUpdateAvailable', + KEY__HEADER__TIME_UPDATED: 'timeUpdated', + KEY__HEADER__TYPE: 'type', KEY__NEW_IMAGE_URL: 'newImageUrl', KEY__RESULT_TYPE__FAILED: 'failed', KEY__RESULT_TYPE__FAILUREIGNORED: 'failure-ignored', @@ -57,13 +65,5 @@ module.constant('constants', (function() { KEY__EDITS__MODIFICATIONS: 'modifications', KEY__EDITS__OLD_RESULTS_HASH: 'oldResultsHash', KEY__EDITS__OLD_RESULTS_TYPE: 'oldResultsType', - KEY__HEADER: 'header', - KEY__HEADER__DATAHASH: 'dataHash', - KEY__HEADER__IS_EDITABLE: 'isEditable', - KEY__HEADER__IS_EXPORTED: 'isExported', - KEY__HEADER__IS_STILL_LOADING: 'resultsStillLoading', - KEY__HEADER__TIME_NEXT_UPDATE_AVAILABLE: 'timeNextUpdateAvailable', - KEY__HEADER__TIME_UPDATED: 'timeUpdated', - KEY__HEADER__TYPE: 'type', } })()) diff --git a/gm/rebaseline_server/static/index.html b/gm/rebaseline_server/static/index.html index 39991e0..df9bb0e 100644 --- a/gm/rebaseline_server/static/index.html +++ b/gm/rebaseline_server/static/index.html @@ -9,13 +9,13 @@ Here are links to the result pages: