| Index: gm/rebaseline_server/server.py
|
| ===================================================================
|
| --- gm/rebaseline_server/server.py (revision 11913)
|
| +++ gm/rebaseline_server/server.py (working copy)
|
| @@ -60,6 +60,9 @@
|
| DEFAULT_EXPECTATIONS_DIR = os.path.join(TRUNK_DIRECTORY, 'expectations', 'gm')
|
| DEFAULT_PORT = 8888
|
|
|
| +_HTTP_HEADER_CONTENT_LENGTH = 'Content-Length'
|
| +_HTTP_HEADER_CONTENT_TYPE = 'Content-Type'
|
| +
|
| _SERVER = None # This gets filled in by main()
|
|
|
| class Server(object):
|
| @@ -103,54 +106,60 @@
|
| results. """
|
| return self._reload_seconds
|
|
|
| - def _update_results(self):
|
| + def update_results(self):
|
| """ Create or update self.results, based on the expectations in
|
| self._expectations_dir and the latest actuals from skia-autogen.
|
| """
|
| - logging.info('Updating actual GM results in %s from SVN repo %s ...' % (
|
| - self._actuals_dir, ACTUALS_SVN_REPO))
|
| - actuals_repo = svn.Svn(self._actuals_dir)
|
| - if not os.path.isdir(self._actuals_dir):
|
| - os.makedirs(self._actuals_dir)
|
| - actuals_repo.Checkout(ACTUALS_SVN_REPO, '.')
|
| - else:
|
| - actuals_repo.Update('.')
|
| -
|
| - # We only update the expectations dir if the server was run with a nonzero
|
| - # --reload argument; otherwise, we expect the user to maintain her own
|
| - # expectations as she sees fit.
|
| - #
|
| - # TODO(epoger): Use git instead of svn to check out expectations, since
|
| - # the Skia repo is moving to git.
|
| - if self._reload_seconds:
|
| - logging.info('Updating expected GM results in %s from SVN repo %s ...' % (
|
| - self._expectations_dir, EXPECTATIONS_SVN_REPO))
|
| - expectations_repo = svn.Svn(self._expectations_dir)
|
| - if not os.path.isdir(self._expectations_dir):
|
| - os.makedirs(self._expectations_dir)
|
| - expectations_repo.Checkout(EXPECTATIONS_SVN_REPO, '.')
|
| + with self.results_lock:
|
| + # self.results_lock prevents us from updating the actual GM results
|
| + # in multiple threads simultaneously
|
| + logging.info('Updating actual GM results in %s from SVN repo %s ...' % (
|
| + self._actuals_dir, ACTUALS_SVN_REPO))
|
| + actuals_repo = svn.Svn(self._actuals_dir)
|
| + if not os.path.isdir(self._actuals_dir):
|
| + os.makedirs(self._actuals_dir)
|
| + actuals_repo.Checkout(ACTUALS_SVN_REPO, '.')
|
| else:
|
| - expectations_repo.Update('.')
|
| + actuals_repo.Update('.')
|
|
|
| - logging.info(
|
| - 'Parsing results from actuals in %s and expectations in %s ...' % (
|
| - self._actuals_dir, self._expectations_dir))
|
| - self.results = results.Results(
|
| - actuals_root=self._actuals_dir,
|
| - expected_root=self._expectations_dir)
|
| + # We only update the expectations dir if the server was run with a
|
| + # nonzero --reload argument; otherwise, we expect the user to maintain
|
| + # her own expectations as she sees fit.
|
| + #
|
| + # self.results_lock prevents us from updating the expected GM results
|
| + # in multiple threads simultaneously
|
| + #
|
| + # TODO(epoger): Use git instead of svn to check out expectations, since
|
| + # the Skia repo is moving to git.
|
| + if self._reload_seconds:
|
| + logging.info(
|
| + 'Updating expected GM results in %s from SVN repo %s ...' % (
|
| + self._expectations_dir, EXPECTATIONS_SVN_REPO))
|
| + expectations_repo = svn.Svn(self._expectations_dir)
|
| + if not os.path.isdir(self._expectations_dir):
|
| + os.makedirs(self._expectations_dir)
|
| + expectations_repo.Checkout(EXPECTATIONS_SVN_REPO, '.')
|
| + else:
|
| + expectations_repo.Update('.')
|
|
|
| + logging.info(
|
| + 'Parsing results from actuals in %s and expectations in %s ...' % (
|
| + self._actuals_dir, self._expectations_dir))
|
| + self.results = results.Results(
|
| + actuals_root=self._actuals_dir,
|
| + expected_root=self._expectations_dir)
|
| +
|
| def _result_reloader(self):
|
| """ If --reload argument was specified, reload results at the appropriate
|
| interval.
|
| """
|
| while self._reload_seconds:
|
| time.sleep(self._reload_seconds)
|
| - with self.results_lock:
|
| - self._update_results()
|
| + self.update_results()
|
|
|
| def run(self):
|
| - self._update_results()
|
| self.results_lock = thread.allocate_lock()
|
| + self.update_results()
|
| thread.start_new_thread(self._result_reloader, ())
|
|
|
| if self._export:
|
| @@ -224,6 +233,9 @@
|
| (time_updated+_SERVER.reload_seconds()) if _SERVER.reload_seconds()
|
| else None),
|
|
|
| + # The type we passed to get_results_of_type()
|
| + 'type': type,
|
| +
|
| # Hash of testData, which the client must return with any edits--
|
| # this ensures that the edits were made to a particular dataset.
|
| 'dataHash': str(hash(repr(response_dict['testData']))),
|
| @@ -261,6 +273,76 @@
|
| % (full_path, static_dir))
|
| self.send_error(404)
|
|
|
| + def do_POST(self):
|
| + """ Handles all POST requests, forwarding them to the appropriate
|
| + do_POST_* dispatcher. """
|
| + # All requests must be of this form:
|
| + # /dispatcher
|
| + # where 'dispatcher' indicates which do_POST_* dispatcher to run.
|
| + normpath = posixpath.normpath(self.path)
|
| + dispatchers = {
|
| + '/edits': self.do_POST_edits,
|
| + }
|
| + try:
|
| + dispatcher = dispatchers[normpath]
|
| + dispatcher()
|
| + self.send_response(200)
|
| + except:
|
| + self.send_error(404)
|
| + raise
|
| +
|
| + def do_POST_edits(self):
|
| + """ Handle a POST request with modifications to GM expectations, in this
|
| + format:
|
| +
|
| + {
|
| + 'oldResultsType': 'all', # type of results that the client loaded
|
| + # and then made modifications to
|
| + 'oldResultsHash': 39850913, # hash of results when the client loaded them
|
| + # (ensures that the client and server apply
|
| + # modifications to the same base)
|
| + 'modifications': [
|
| + {
|
| + 'builder': 'Test-Android-Nexus10-MaliT604-Arm7-Debug',
|
| + 'test': 'strokerect',
|
| + 'config': 'gpu',
|
| + 'expectedHashType': 'bitmap-64bitMD5',
|
| + 'expectedHashDigest': '1707359671708613629',
|
| + },
|
| + ...
|
| + ],
|
| + }
|
| +
|
| + Raises an Exception if there were any problems.
|
| + """
|
| + if not _SERVER.is_editable():
|
| + raise Exception('this server is not running in --editable mode')
|
| +
|
| + content_type = self.headers[_HTTP_HEADER_CONTENT_TYPE]
|
| + if content_type != 'application/json;charset=UTF-8':
|
| + raise Exception('unsupported %s [%s]' % (
|
| + _HTTP_HEADER_CONTENT_TYPE, content_type))
|
| +
|
| + content_length = int(self.headers[_HTTP_HEADER_CONTENT_LENGTH])
|
| + json_data = self.rfile.read(content_length)
|
| + data = json.loads(json_data)
|
| + logging.debug('do_POST_edits: received new GM expectations data [%s]' %
|
| + data)
|
| +
|
| + with _SERVER.results_lock:
|
| + oldResultsType = data['oldResultsType']
|
| + oldResults = _SERVER.results.get_results_of_type(oldResultsType)
|
| + oldResultsHash = str(hash(repr(oldResults['testData'])))
|
| + if oldResultsHash != data['oldResultsHash']:
|
| + raise Exception('results of type "%s" changed while the client was '
|
| + 'making modifications. The client should reload the '
|
| + 'results and submit the modifications again.' %
|
| + oldResultsType)
|
| + _SERVER.results.edit_expectations(data['modifications'])
|
| +
|
| + # Now that the edits have been committed, update results to reflect them.
|
| + _SERVER.update_results()
|
| +
|
| def redirect_to(self, url):
|
| """ Redirect the HTTP client to a different url.
|
|
|
| @@ -318,8 +400,7 @@
|
| 'exist, it will be created. Defaults to %(default)s'),
|
| default=DEFAULT_ACTUALS_DIR)
|
| parser.add_argument('--editable', action='store_true',
|
| - help=('TODO(epoger): NOT YET IMPLEMENTED. '
|
| - 'Allow HTTP clients to submit new baselines.'))
|
| + help=('Allow HTTP clients to submit new baselines.'))
|
| parser.add_argument('--expectations-dir',
|
| help=('Directory under which to find GM expectations; '
|
| 'defaults to %(default)s'),
|
|
|