| Index: chrome/test/kasko/py/kasko/crash_server.py
|
| diff --git a/chrome/test/kasko/py/kasko/crash_server.py b/chrome/test/kasko/py/kasko/crash_server.py
|
| new file mode 100755
|
| index 0000000000000000000000000000000000000000..625531be105da1a546230a1d11bb79c96ba282f3
|
| --- /dev/null
|
| +++ b/chrome/test/kasko/py/kasko/crash_server.py
|
| @@ -0,0 +1,148 @@
|
| +#!/usr/bin/env python
|
| +# Copyright 2016 The Chromium Authors. All rights reserved.
|
| +# Use of this source code is governed by a BSD-style license that can be
|
| +# found in the LICENSE file.
|
| +
|
| +"""Exceptions used by the Kasko integration test module."""
|
| +
|
| +import BaseHTTPServer
|
| +import cgi
|
| +import logging
|
| +import os
|
| +import socket
|
| +import threading
|
| +import time
|
| +import uuid
|
| +
|
| +
|
| +_LOGGER = logging.getLogger(os.path.basename(__file__))
|
| +
|
| +
|
| +class _StoppableHTTPServer(BaseHTTPServer.HTTPServer):
|
| + """An extension of BaseHTTPServer that uses timeouts and is interruptable."""
|
| +
|
| + def server_bind(self):
|
| + BaseHTTPServer.HTTPServer.server_bind(self)
|
| + self.socket.settimeout(1)
|
| + self.run_ = True
|
| +
|
| + def get_request(self):
|
| + while self.run_:
|
| + try:
|
| + sock, addr = self.socket.accept()
|
| + sock.settimeout(None)
|
| + return (sock, addr)
|
| + except socket.timeout:
|
| + pass
|
| +
|
| + def stop(self):
|
| + self.run_ = False
|
| +
|
| + def serve(self):
|
| + while self.run_:
|
| + self.handle_request()
|
| +
|
| +
|
| +class CrashServer(object):
|
| + """A simple crash server for testing."""
|
| +
|
| + def __init__(self):
|
| + self.server_ = None
|
| + self.lock_ = threading.Lock()
|
| + self.crashes_ = [] # Under lock_.
|
| +
|
| + def crash(self, index):
|
| + """Accessor for the list of crashes."""
|
| + with self.lock_:
|
| + if index >= len(self.crashes_):
|
| + return None
|
| + return self.crashes_[index]
|
| +
|
| + @property
|
| + def port(self):
|
| + """Returns the port associated with the server."""
|
| + if not self.server_:
|
| + return 0
|
| + return self.server_.server_port
|
| +
|
| + def start(self):
|
| + """Starts the server on another thread. Call from main thread only."""
|
| + page_handler = self.multipart_form_handler()
|
| + self.server_ = _StoppableHTTPServer(('127.0.0.1', 0), page_handler)
|
| + self.thread_ = self.server_thread()
|
| + self.thread_.start()
|
| +
|
| + def stop(self):
|
| + """Stops the running server. Call from main thread only."""
|
| + self.server_.stop()
|
| + self.thread_.join()
|
| + self.server_ = None
|
| + self.thread_ = None
|
| +
|
| + def wait_for_report(self, timeout):
|
| + """Waits until the server has received a crash report.
|
| +
|
| + Returns True if the a report has been received in the given time, or False
|
| + if a timeout occurred. Since Python condition variables have no notion of
|
| + timeout this is, sadly, a busy loop on the calling thread.
|
| + """
|
| + started = time.time()
|
| + elapsed = 0
|
| + while elapsed < timeout:
|
| + with self.lock_:
|
| + if len(self.crashes_):
|
| + return True
|
| + time.sleep(0.1)
|
| + elapsed = time.time() - started
|
| +
|
| + return False
|
| +
|
| +
|
| + def multipart_form_handler(crash_server):
|
| + """Returns a multi-part form handler class for use with a BaseHTTPServer."""
|
| +
|
| + class MultipartFormHandler(BaseHTTPServer.BaseHTTPRequestHandler):
|
| + """A multi-part form handler that processes crash reports.
|
| +
|
| + This class only handles multipart form POST messages, with all other
|
| + requests by default returning a '501 not implemented' error.
|
| + """
|
| +
|
| + def __init__(self, request, client_address, socket_server):
|
| + BaseHTTPServer.BaseHTTPRequestHandler.__init__(
|
| + self, request, client_address, socket_server)
|
| +
|
| + def log_message(self, format, *args):
|
| + _LOGGER.debug(format, *args)
|
| +
|
| + def do_POST(self):
|
| + """Handles POST messages contained multipart form data."""
|
| + content_type, parameters = cgi.parse_header(
|
| + self.headers.getheader('content-type'))
|
| + if content_type != 'multipart/form-data':
|
| + raise Exception('Unsupported Content-Type: ' + content_type)
|
| + post_multipart = cgi.parse_multipart(self.rfile, parameters)
|
| +
|
| + # Save the crash report.
|
| + report = dict(post_multipart.items())
|
| + report_id = str(uuid.uuid4())
|
| + report['report-id'] = [report_id]
|
| + with crash_server.lock_:
|
| + crash_server.crashes_.append(report)
|
| +
|
| + # Send the response.
|
| + self.send_response(200)
|
| + self.send_header("Content-Type", "text/plain")
|
| + self.end_headers()
|
| + self.wfile.write(report_id)
|
| +
|
| + return MultipartFormHandler
|
| +
|
| + def server_thread(crash_server):
|
| + """Returns a thread that hosts the webserver."""
|
| +
|
| + class ServerThread(threading.Thread):
|
| + def run(self):
|
| + crash_server.server_.serve()
|
| +
|
| + return ServerThread()
|
|
|