summaryrefslogtreecommitdiffstats
path: root/third_party/libwebrtc/build/util/lib/common
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
commit36d22d82aa202bb199967e9512281e9a53db42c9 (patch)
tree105e8c98ddea1c1e4784a60a5a6410fa416be2de /third_party/libwebrtc/build/util/lib/common
parentInitial commit. (diff)
downloadfirefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.tar.xz
firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.zip
Adding upstream version 115.7.0esr.upstream/115.7.0esrupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'third_party/libwebrtc/build/util/lib/common')
-rw-r--r--third_party/libwebrtc/build/util/lib/common/PRESUBMIT.py19
-rw-r--r--third_party/libwebrtc/build/util/lib/common/__init__.py0
-rw-r--r--third_party/libwebrtc/build/util/lib/common/chrome_test_server_spawner.py503
-rw-r--r--third_party/libwebrtc/build/util/lib/common/perf_result_data_type.py20
-rw-r--r--third_party/libwebrtc/build/util/lib/common/perf_tests_results_helper.py202
-rw-r--r--third_party/libwebrtc/build/util/lib/common/unittest_util.py155
-rwxr-xr-xthird_party/libwebrtc/build/util/lib/common/unittest_util_test.py65
-rw-r--r--third_party/libwebrtc/build/util/lib/common/util.py151
8 files changed, 1115 insertions, 0 deletions
diff --git a/third_party/libwebrtc/build/util/lib/common/PRESUBMIT.py b/third_party/libwebrtc/build/util/lib/common/PRESUBMIT.py
new file mode 100644
index 0000000000..7f280e5f5e
--- /dev/null
+++ b/third_party/libwebrtc/build/util/lib/common/PRESUBMIT.py
@@ -0,0 +1,19 @@
+# Copyright 2015 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.
+
+
+USE_PYTHON3 = True
+
+
+def _RunTests(input_api, output_api):
+ return (input_api.canned_checks.RunUnitTestsInDirectory(
+ input_api, output_api, '.', files_to_check=[r'.+_test.py$']))
+
+
+def CheckChangeOnUpload(input_api, output_api):
+ return _RunTests(input_api, output_api)
+
+
+def CheckChangeOnCommit(input_api, output_api):
+ return _RunTests(input_api, output_api)
diff --git a/third_party/libwebrtc/build/util/lib/common/__init__.py b/third_party/libwebrtc/build/util/lib/common/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/third_party/libwebrtc/build/util/lib/common/__init__.py
diff --git a/third_party/libwebrtc/build/util/lib/common/chrome_test_server_spawner.py b/third_party/libwebrtc/build/util/lib/common/chrome_test_server_spawner.py
new file mode 100644
index 0000000000..bec81558d1
--- /dev/null
+++ b/third_party/libwebrtc/build/util/lib/common/chrome_test_server_spawner.py
@@ -0,0 +1,503 @@
+# Copyright 2017 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.
+
+"""A "Test Server Spawner" that handles killing/stopping per-test test servers.
+
+It's used to accept requests from the device to spawn and kill instances of the
+chrome test server on the host.
+"""
+# pylint: disable=W0702
+
+import json
+import logging
+import os
+import select
+import struct
+import subprocess
+import sys
+import threading
+import time
+
+from six.moves import BaseHTTPServer, urllib
+
+
+SERVER_TYPES = {
+ 'http': '',
+ 'ftp': '-f',
+ 'ws': '--websocket',
+}
+
+
+_DIR_SOURCE_ROOT = os.path.abspath(
+ os.path.join(os.path.dirname(__file__), os.pardir, os.pardir, os.pardir,
+ os.pardir))
+
+
+_logger = logging.getLogger(__name__)
+
+
+# Path that are needed to import necessary modules when launching a testserver.
+os.environ['PYTHONPATH'] = os.environ.get('PYTHONPATH', '') + (':%s:%s:%s'
+ % (os.path.join(_DIR_SOURCE_ROOT, 'third_party'),
+ os.path.join(_DIR_SOURCE_ROOT, 'third_party', 'tlslite'),
+ os.path.join(_DIR_SOURCE_ROOT, 'net', 'tools', 'testserver')))
+
+
+# The timeout (in seconds) of starting up the Python test server.
+_TEST_SERVER_STARTUP_TIMEOUT = 10
+
+
+def _GetServerTypeCommandLine(server_type):
+ """Returns the command-line by the given server type.
+
+ Args:
+ server_type: the server type to be used (e.g. 'http').
+
+ Returns:
+ A string containing the command-line argument.
+ """
+ if server_type not in SERVER_TYPES:
+ raise NotImplementedError('Unknown server type: %s' % server_type)
+ return SERVER_TYPES[server_type]
+
+
+class PortForwarder:
+ def Map(self, port_pairs):
+ pass
+
+ def GetDevicePortForHostPort(self, host_port):
+ """Returns the device port that corresponds to a given host port."""
+ return host_port
+
+ def WaitHostPortAvailable(self, port):
+ """Returns True if |port| is available."""
+ return True
+
+ def WaitPortNotAvailable(self, port):
+ """Returns True if |port| is not available."""
+ return True
+
+ def WaitDevicePortReady(self, port):
+ """Returns whether the provided port is used."""
+ return True
+
+ def Unmap(self, device_port):
+ """Unmaps specified port"""
+ pass
+
+
+class TestServerThread(threading.Thread):
+ """A thread to run the test server in a separate process."""
+
+ def __init__(self, ready_event, arguments, port_forwarder):
+ """Initialize TestServerThread with the following argument.
+
+ Args:
+ ready_event: event which will be set when the test server is ready.
+ arguments: dictionary of arguments to run the test server.
+ device: An instance of DeviceUtils.
+ tool: instance of runtime error detection tool.
+ """
+ threading.Thread.__init__(self)
+ self.wait_event = threading.Event()
+ self.stop_event = threading.Event()
+ self.ready_event = ready_event
+ self.ready_event.clear()
+ self.arguments = arguments
+ self.port_forwarder = port_forwarder
+ self.test_server_process = None
+ self.is_ready = False
+ self.host_port = self.arguments['port']
+ self.host_ocsp_port = 0
+ assert isinstance(self.host_port, int)
+ # The forwarder device port now is dynamically allocated.
+ self.forwarder_device_port = 0
+ self.forwarder_ocsp_device_port = 0
+ # Anonymous pipe in order to get port info from test server.
+ self.pipe_in = None
+ self.pipe_out = None
+ self.process = None
+ self.command_line = []
+
+ def _WaitToStartAndGetPortFromTestServer(self):
+ """Waits for the Python test server to start and gets the port it is using.
+
+ The port information is passed by the Python test server with a pipe given
+ by self.pipe_out. It is written as a result to |self.host_port|.
+
+ Returns:
+ Whether the port used by the test server was successfully fetched.
+ """
+ assert self.host_port == 0 and self.pipe_out and self.pipe_in
+ (in_fds, _, _) = select.select([self.pipe_in, ], [], [],
+ _TEST_SERVER_STARTUP_TIMEOUT)
+ if len(in_fds) == 0:
+ _logger.error('Failed to wait to the Python test server to be started.')
+ return False
+ # First read the data length as an unsigned 4-byte value. This
+ # is _not_ using network byte ordering since the Python test server packs
+ # size as native byte order and all Chromium platforms so far are
+ # configured to use little-endian.
+ # TODO(jnd): Change the Python test server and local_test_server_*.cc to
+ # use a unified byte order (either big-endian or little-endian).
+ data_length = os.read(self.pipe_in, struct.calcsize('=L'))
+ if data_length:
+ (data_length,) = struct.unpack('=L', data_length)
+ assert data_length
+ if not data_length:
+ _logger.error('Failed to get length of server data.')
+ return False
+ server_data_json = os.read(self.pipe_in, data_length)
+ if not server_data_json:
+ _logger.error('Failed to get server data.')
+ return False
+ _logger.info('Got port json data: %s', server_data_json)
+
+ parsed_server_data = None
+ try:
+ parsed_server_data = json.loads(server_data_json)
+ except ValueError:
+ pass
+
+ if not isinstance(parsed_server_data, dict):
+ _logger.error('Failed to parse server_data: %s' % server_data_json)
+ return False
+
+ if not isinstance(parsed_server_data.get('port'), int):
+ _logger.error('Failed to get port information from the server data.')
+ return False
+
+ self.host_port = parsed_server_data['port']
+ self.host_ocsp_port = parsed_server_data.get('ocsp_port', 0)
+
+ return self.port_forwarder.WaitPortNotAvailable(self.host_port)
+
+ def _GenerateCommandLineArguments(self):
+ """Generates the command line to run the test server.
+
+ Note that all options are processed by following the definitions in
+ testserver.py.
+ """
+ if self.command_line:
+ return
+
+ args_copy = dict(self.arguments)
+
+ # Translate the server type.
+ type_cmd = _GetServerTypeCommandLine(args_copy.pop('server-type'))
+ if type_cmd:
+ self.command_line.append(type_cmd)
+
+ # Use a pipe to get the port given by the instance of Python test server
+ # if the test does not specify the port.
+ assert self.host_port == args_copy['port']
+ if self.host_port == 0:
+ (self.pipe_in, self.pipe_out) = os.pipe()
+ self.command_line.append('--startup-pipe=%d' % self.pipe_out)
+
+ # Pass the remaining arguments as-is.
+ for key, values in args_copy.iteritems():
+ if not isinstance(values, list):
+ values = [values]
+ for value in values:
+ if value is None:
+ self.command_line.append('--%s' % key)
+ else:
+ self.command_line.append('--%s=%s' % (key, value))
+
+ def _CloseUnnecessaryFDsForTestServerProcess(self):
+ # This is required to avoid subtle deadlocks that could be caused by the
+ # test server child process inheriting undesirable file descriptors such as
+ # file lock file descriptors. Note stdin, stdout, and stderr (0-2) are left
+ # alone and redirected with subprocess.Popen. It is important to leave those
+ # fds filled, or the test server will accidentally open other fds at those
+ # numbers.
+ for fd in xrange(3, 1024):
+ if fd != self.pipe_out:
+ try:
+ os.close(fd)
+ except:
+ pass
+
+ def run(self):
+ _logger.info('Start running the thread!')
+ self.wait_event.clear()
+ self._GenerateCommandLineArguments()
+ # TODO(crbug.com/941669): When this script is ported to Python 3, replace
+ # 'vpython3' below with sys.executable. The call to
+ # vpython3 -vpython-tool install below can also be removed.
+ command = [
+ 'vpython3',
+ os.path.join(_DIR_SOURCE_ROOT, 'net', 'tools', 'testserver',
+ 'testserver.py')
+ ] + self.command_line
+ _logger.info('Running: %s', command)
+
+ # Disable PYTHONUNBUFFERED because it has a bad interaction with the
+ # testserver. Remove once this interaction is fixed.
+ unbuf = os.environ.pop('PYTHONUNBUFFERED', None)
+
+ # Pass _DIR_SOURCE_ROOT as the child's working directory so that relative
+ # paths in the arguments are resolved correctly. devnull can be replaced
+ # with subprocess.DEVNULL in Python 3.
+ with open(os.devnull, 'r+b') as devnull:
+ # _WaitToStartAndGetPortFromTestServer has a short timeout. If the
+ # vpython3 cache is not initialized, launching the test server can take
+ # some time. Prewarm the cache before running the server.
+ subprocess.check_call(
+ [
+ 'vpython3', '-vpython-spec',
+ os.path.join(_DIR_SOURCE_ROOT, '.vpython3'), '-vpython-tool',
+ 'install'
+ ],
+ preexec_fn=self._CloseUnnecessaryFDsForTestServerProcess,
+ stdin=devnull,
+ stdout=None,
+ stderr=None,
+ cwd=_DIR_SOURCE_ROOT)
+
+ self.process = subprocess.Popen(
+ command,
+ preexec_fn=self._CloseUnnecessaryFDsForTestServerProcess,
+ stdin=devnull,
+ # Preserve stdout and stderr from the test server.
+ stdout=None,
+ stderr=None,
+ cwd=_DIR_SOURCE_ROOT)
+ if unbuf:
+ os.environ['PYTHONUNBUFFERED'] = unbuf
+ if self.process:
+ if self.pipe_out:
+ self.is_ready = self._WaitToStartAndGetPortFromTestServer()
+ else:
+ self.is_ready = self.port_forwarder.WaitPortNotAvailable(self.host_port)
+
+ if self.is_ready:
+ port_map = [(0, self.host_port)]
+ if self.host_ocsp_port:
+ port_map.extend([(0, self.host_ocsp_port)])
+ self.port_forwarder.Map(port_map)
+
+ self.forwarder_device_port = \
+ self.port_forwarder.GetDevicePortForHostPort(self.host_port)
+ if self.host_ocsp_port:
+ self.forwarder_ocsp_device_port = \
+ self.port_forwarder.GetDevicePortForHostPort(self.host_ocsp_port)
+
+ # Check whether the forwarder is ready on the device.
+ self.is_ready = self.forwarder_device_port and \
+ self.port_forwarder.WaitDevicePortReady(self.forwarder_device_port)
+
+ # Wake up the request handler thread.
+ self.ready_event.set()
+ # Keep thread running until Stop() gets called.
+ self.stop_event.wait()
+ if self.process.poll() is None:
+ self.process.kill()
+ # Wait for process to actually terminate.
+ # (crbug.com/946475)
+ self.process.wait()
+
+ self.port_forwarder.Unmap(self.forwarder_device_port)
+ self.process = None
+ self.is_ready = False
+ if self.pipe_out:
+ os.close(self.pipe_in)
+ os.close(self.pipe_out)
+ self.pipe_in = None
+ self.pipe_out = None
+ _logger.info('Test-server has died.')
+ self.wait_event.set()
+
+ def Stop(self):
+ """Blocks until the loop has finished.
+
+ Note that this must be called in another thread.
+ """
+ if not self.process:
+ return
+ self.stop_event.set()
+ self.wait_event.wait()
+
+
+class SpawningServerRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
+ """A handler used to process http GET/POST request."""
+
+ def _SendResponse(self, response_code, response_reason, additional_headers,
+ contents):
+ """Generates a response sent to the client from the provided parameters.
+
+ Args:
+ response_code: number of the response status.
+ response_reason: string of reason description of the response.
+ additional_headers: dict of additional headers. Each key is the name of
+ the header, each value is the content of the header.
+ contents: string of the contents we want to send to client.
+ """
+ self.send_response(response_code, response_reason)
+ self.send_header('Content-Type', 'text/html')
+ # Specify the content-length as without it the http(s) response will not
+ # be completed properly (and the browser keeps expecting data).
+ self.send_header('Content-Length', len(contents))
+ for header_name in additional_headers:
+ self.send_header(header_name, additional_headers[header_name])
+ self.end_headers()
+ self.wfile.write(contents)
+ self.wfile.flush()
+
+ def _StartTestServer(self):
+ """Starts the test server thread."""
+ _logger.info('Handling request to spawn a test server.')
+ content_type = self.headers.getheader('content-type')
+ if content_type != 'application/json':
+ raise Exception('Bad content-type for start request.')
+ content_length = self.headers.getheader('content-length')
+ if not content_length:
+ content_length = 0
+ try:
+ content_length = int(content_length)
+ except:
+ raise Exception('Bad content-length for start request.')
+ _logger.info(content_length)
+ test_server_argument_json = self.rfile.read(content_length)
+ _logger.info(test_server_argument_json)
+
+ if len(self.server.test_servers) >= self.server.max_instances:
+ self._SendResponse(400, 'Invalid request', {},
+ 'Too many test servers running')
+ return
+
+ ready_event = threading.Event()
+ new_server = TestServerThread(ready_event,
+ json.loads(test_server_argument_json),
+ self.server.port_forwarder)
+ new_server.setDaemon(True)
+ new_server.start()
+ ready_event.wait()
+ if new_server.is_ready:
+ response = {'port': new_server.forwarder_device_port,
+ 'message': 'started'};
+ if new_server.forwarder_ocsp_device_port:
+ response['ocsp_port'] = new_server.forwarder_ocsp_device_port
+ self._SendResponse(200, 'OK', {}, json.dumps(response))
+ _logger.info('Test server is running on port %d forwarded to %d.' %
+ (new_server.forwarder_device_port, new_server.host_port))
+ port = new_server.forwarder_device_port
+ assert port not in self.server.test_servers
+ self.server.test_servers[port] = new_server
+ else:
+ new_server.Stop()
+ self._SendResponse(500, 'Test Server Error.', {}, '')
+ _logger.info('Encounter problem during starting a test server.')
+
+ def _KillTestServer(self, params):
+ """Stops the test server instance."""
+ try:
+ port = int(params['port'][0])
+ except ValueError:
+ port = None
+ if port == None or port <= 0:
+ self._SendResponse(400, 'Invalid request.', {}, 'port must be specified')
+ return
+
+ if port not in self.server.test_servers:
+ self._SendResponse(400, 'Invalid request.', {},
+ "testserver isn't running on port %d" % port)
+ return
+
+ server = self.server.test_servers.pop(port)
+
+ _logger.info('Handling request to kill a test server on port: %d.', port)
+ server.Stop()
+
+ # Make sure the status of test server is correct before sending response.
+ if self.server.port_forwarder.WaitHostPortAvailable(port):
+ self._SendResponse(200, 'OK', {}, 'killed')
+ _logger.info('Test server on port %d is killed', port)
+ else:
+ # We expect the port to be free, but nothing stops the system from
+ # binding something else to that port, so don't throw error.
+ # (crbug.com/946475)
+ self._SendResponse(200, 'OK', {}, '')
+ _logger.warn('Port %s is not free after killing test server.' % port)
+
+ def log_message(self, format, *args):
+ # Suppress the default HTTP logging behavior if the logging level is higher
+ # than INFO.
+ if _logger.getEffectiveLevel() <= logging.INFO:
+ pass
+
+ def do_POST(self):
+ parsed_path = urllib.parse.urlparse(self.path)
+ action = parsed_path.path
+ _logger.info('Action for POST method is: %s.', action)
+ if action == '/start':
+ self._StartTestServer()
+ else:
+ self._SendResponse(400, 'Unknown request.', {}, '')
+ _logger.info('Encounter unknown request: %s.', action)
+
+ def do_GET(self):
+ parsed_path = urllib.parse.urlparse(self.path)
+ action = parsed_path.path
+ params = urllib.parse.parse_qs(parsed_path.query, keep_blank_values=1)
+ _logger.info('Action for GET method is: %s.', action)
+ for param in params:
+ _logger.info('%s=%s', param, params[param][0])
+ if action == '/kill':
+ self._KillTestServer(params)
+ elif action == '/ping':
+ # The ping handler is used to check whether the spawner server is ready
+ # to serve the requests. We don't need to test the status of the test
+ # server when handling ping request.
+ self._SendResponse(200, 'OK', {}, 'ready')
+ _logger.info('Handled ping request and sent response.')
+ else:
+ self._SendResponse(400, 'Unknown request', {}, '')
+ _logger.info('Encounter unknown request: %s.', action)
+
+
+class SpawningServer(object):
+ """The class used to start/stop a http server."""
+
+ def __init__(self, test_server_spawner_port, port_forwarder, max_instances):
+ self.server = BaseHTTPServer.HTTPServer(('', test_server_spawner_port),
+ SpawningServerRequestHandler)
+ self.server_port = self.server.server_port
+ _logger.info('Started test server spawner on port: %d.', self.server_port)
+
+ self.server.port_forwarder = port_forwarder
+ self.server.test_servers = {}
+ self.server.max_instances = max_instances
+
+ def _Listen(self):
+ _logger.info('Starting test server spawner.')
+ self.server.serve_forever()
+
+ def Start(self):
+ """Starts the test server spawner."""
+ listener_thread = threading.Thread(target=self._Listen)
+ listener_thread.setDaemon(True)
+ listener_thread.start()
+
+ def Stop(self):
+ """Stops the test server spawner.
+
+ Also cleans the server state.
+ """
+ self.CleanupState()
+ self.server.shutdown()
+
+ def CleanupState(self):
+ """Cleans up the spawning server state.
+
+ This should be called if the test server spawner is reused,
+ to avoid sharing the test server instance.
+ """
+ if self.server.test_servers:
+ _logger.warning('Not all test servers were stopped.')
+ for port in self.server.test_servers:
+ _logger.warning('Stopping test server on port %d' % port)
+ self.server.test_servers[port].Stop()
+ self.server.test_servers = {}
diff --git a/third_party/libwebrtc/build/util/lib/common/perf_result_data_type.py b/third_party/libwebrtc/build/util/lib/common/perf_result_data_type.py
new file mode 100644
index 0000000000..67b550a46c
--- /dev/null
+++ b/third_party/libwebrtc/build/util/lib/common/perf_result_data_type.py
@@ -0,0 +1,20 @@
+# Copyright 2013 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.
+
+DEFAULT = 'default'
+UNIMPORTANT = 'unimportant'
+HISTOGRAM = 'histogram'
+UNIMPORTANT_HISTOGRAM = 'unimportant-histogram'
+INFORMATIONAL = 'informational'
+
+ALL_TYPES = [DEFAULT, UNIMPORTANT, HISTOGRAM, UNIMPORTANT_HISTOGRAM,
+ INFORMATIONAL]
+
+
+def IsValidType(datatype):
+ return datatype in ALL_TYPES
+
+
+def IsHistogram(datatype):
+ return (datatype == HISTOGRAM or datatype == UNIMPORTANT_HISTOGRAM)
diff --git a/third_party/libwebrtc/build/util/lib/common/perf_tests_results_helper.py b/third_party/libwebrtc/build/util/lib/common/perf_tests_results_helper.py
new file mode 100644
index 0000000000..153886dce5
--- /dev/null
+++ b/third_party/libwebrtc/build/util/lib/common/perf_tests_results_helper.py
@@ -0,0 +1,202 @@
+# Copyright 2013 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.
+
+from __future__ import print_function
+
+import re
+import sys
+
+import json
+import logging
+import math
+
+import perf_result_data_type
+
+
+# Mapping from result type to test output
+RESULT_TYPES = {perf_result_data_type.UNIMPORTANT: 'RESULT ',
+ perf_result_data_type.DEFAULT: '*RESULT ',
+ perf_result_data_type.INFORMATIONAL: '',
+ perf_result_data_type.UNIMPORTANT_HISTOGRAM: 'HISTOGRAM ',
+ perf_result_data_type.HISTOGRAM: '*HISTOGRAM '}
+
+
+def _EscapePerfResult(s):
+ """Escapes |s| for use in a perf result."""
+ return re.sub('[\:|=/#&,]', '_', s)
+
+
+def FlattenList(values):
+ """Returns a simple list without sub-lists."""
+ ret = []
+ for entry in values:
+ if isinstance(entry, list):
+ ret.extend(FlattenList(entry))
+ else:
+ ret.append(entry)
+ return ret
+
+
+def GeomMeanAndStdDevFromHistogram(histogram_json):
+ histogram = json.loads(histogram_json)
+ # Handle empty histograms gracefully.
+ if not 'buckets' in histogram:
+ return 0.0, 0.0
+ count = 0
+ sum_of_logs = 0
+ for bucket in histogram['buckets']:
+ if 'high' in bucket:
+ bucket['mean'] = (bucket['low'] + bucket['high']) / 2.0
+ else:
+ bucket['mean'] = bucket['low']
+ if bucket['mean'] > 0:
+ sum_of_logs += math.log(bucket['mean']) * bucket['count']
+ count += bucket['count']
+
+ if count == 0:
+ return 0.0, 0.0
+
+ sum_of_squares = 0
+ geom_mean = math.exp(sum_of_logs / count)
+ for bucket in histogram['buckets']:
+ if bucket['mean'] > 0:
+ sum_of_squares += (bucket['mean'] - geom_mean) ** 2 * bucket['count']
+ return geom_mean, math.sqrt(sum_of_squares / count)
+
+
+def _ValueToString(v):
+ # Special case for floats so we don't print using scientific notation.
+ if isinstance(v, float):
+ return '%f' % v
+ else:
+ return str(v)
+
+
+def _MeanAndStdDevFromList(values):
+ avg = None
+ sd = None
+ if len(values) > 1:
+ try:
+ value = '[%s]' % ','.join([_ValueToString(v) for v in values])
+ avg = sum([float(v) for v in values]) / len(values)
+ sqdiffs = [(float(v) - avg) ** 2 for v in values]
+ variance = sum(sqdiffs) / (len(values) - 1)
+ sd = math.sqrt(variance)
+ except ValueError:
+ value = ', '.join(values)
+ else:
+ value = values[0]
+ return value, avg, sd
+
+
+def PrintPages(page_list):
+ """Prints list of pages to stdout in the format required by perf tests."""
+ print('Pages: [%s]' % ','.join([_EscapePerfResult(p) for p in page_list]))
+
+
+def PrintPerfResult(measurement, trace, values, units,
+ result_type=perf_result_data_type.DEFAULT,
+ print_to_stdout=True):
+ """Prints numerical data to stdout in the format required by perf tests.
+
+ The string args may be empty but they must not contain any colons (:) or
+ equals signs (=).
+ This is parsed by the buildbot using:
+ http://src.chromium.org/viewvc/chrome/trunk/tools/build/scripts/slave/process_log_utils.py
+
+ Args:
+ measurement: A description of the quantity being measured, e.g. "vm_peak".
+ On the dashboard, this maps to a particular graph. Mandatory.
+ trace: A description of the particular data point, e.g. "reference".
+ On the dashboard, this maps to a particular "line" in the graph.
+ Mandatory.
+ values: A list of numeric measured values. An N-dimensional list will be
+ flattened and treated as a simple list.
+ units: A description of the units of measure, e.g. "bytes".
+ result_type: Accepts values of perf_result_data_type.ALL_TYPES.
+ print_to_stdout: If True, prints the output in stdout instead of returning
+ the output to caller.
+
+ Returns:
+ String of the formated perf result.
+ """
+ assert perf_result_data_type.IsValidType(result_type), \
+ 'result type: %s is invalid' % result_type
+
+ trace_name = _EscapePerfResult(trace)
+
+ if (result_type == perf_result_data_type.UNIMPORTANT or
+ result_type == perf_result_data_type.DEFAULT or
+ result_type == perf_result_data_type.INFORMATIONAL):
+ assert isinstance(values, list)
+ assert '/' not in measurement
+ flattened_values = FlattenList(values)
+ assert len(flattened_values)
+ value, avg, sd = _MeanAndStdDevFromList(flattened_values)
+ output = '%s%s: %s%s%s %s' % (
+ RESULT_TYPES[result_type],
+ _EscapePerfResult(measurement),
+ trace_name,
+ # Do not show equal sign if the trace is empty. Usually it happens when
+ # measurement is enough clear to describe the result.
+ '= ' if trace_name else '',
+ value,
+ units)
+ else:
+ assert perf_result_data_type.IsHistogram(result_type)
+ assert isinstance(values, list)
+ # The histograms can only be printed individually, there's no computation
+ # across different histograms.
+ assert len(values) == 1
+ value = values[0]
+ output = '%s%s: %s= %s %s' % (
+ RESULT_TYPES[result_type],
+ _EscapePerfResult(measurement),
+ trace_name,
+ value,
+ units)
+ avg, sd = GeomMeanAndStdDevFromHistogram(value)
+
+ if avg:
+ output += '\nAvg %s: %f%s' % (measurement, avg, units)
+ if sd:
+ output += '\nSd %s: %f%s' % (measurement, sd, units)
+ if print_to_stdout:
+ print(output)
+ sys.stdout.flush()
+ return output
+
+
+def ReportPerfResult(chart_data, graph_title, trace_title, value, units,
+ improvement_direction='down', important=True):
+ """Outputs test results in correct format.
+
+ If chart_data is None, it outputs data in old format. If chart_data is a
+ dictionary, formats in chartjson format. If any other format defaults to
+ old format.
+
+ Args:
+ chart_data: A dictionary corresponding to perf results in the chartjson
+ format.
+ graph_title: A string containing the name of the chart to add the result
+ to.
+ trace_title: A string containing the name of the trace within the chart
+ to add the result to.
+ value: The value of the result being reported.
+ units: The units of the value being reported.
+ improvement_direction: A string denoting whether higher or lower is
+ better for the result. Either 'up' or 'down'.
+ important: A boolean denoting whether the result is important or not.
+ """
+ if chart_data and isinstance(chart_data, dict):
+ chart_data['charts'].setdefault(graph_title, {})
+ chart_data['charts'][graph_title][trace_title] = {
+ 'type': 'scalar',
+ 'value': value,
+ 'units': units,
+ 'improvement_direction': improvement_direction,
+ 'important': important
+ }
+ else:
+ PrintPerfResult(graph_title, trace_title, [value], units)
diff --git a/third_party/libwebrtc/build/util/lib/common/unittest_util.py b/third_party/libwebrtc/build/util/lib/common/unittest_util.py
new file mode 100644
index 0000000000..d6ff7f6c22
--- /dev/null
+++ b/third_party/libwebrtc/build/util/lib/common/unittest_util.py
@@ -0,0 +1,155 @@
+# Copyright 2013 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.
+
+"""Utilities for dealing with the python unittest module."""
+
+import fnmatch
+import re
+import sys
+import unittest
+
+
+class _TextTestResult(unittest._TextTestResult):
+ """A test result class that can print formatted text results to a stream.
+
+ Results printed in conformance with gtest output format, like:
+ [ RUN ] autofill.AutofillTest.testAutofillInvalid: "test desc."
+ [ OK ] autofill.AutofillTest.testAutofillInvalid
+ [ RUN ] autofill.AutofillTest.testFillProfile: "test desc."
+ [ OK ] autofill.AutofillTest.testFillProfile
+ [ RUN ] autofill.AutofillTest.testFillProfileCrazyCharacters: "Test."
+ [ OK ] autofill.AutofillTest.testFillProfileCrazyCharacters
+ """
+ def __init__(self, stream, descriptions, verbosity):
+ unittest._TextTestResult.__init__(self, stream, descriptions, verbosity)
+ self._fails = set()
+
+ def _GetTestURI(self, test):
+ return '%s.%s.%s' % (test.__class__.__module__,
+ test.__class__.__name__,
+ test._testMethodName)
+
+ def getDescription(self, test):
+ return '%s: "%s"' % (self._GetTestURI(test), test.shortDescription())
+
+ def startTest(self, test):
+ unittest.TestResult.startTest(self, test)
+ self.stream.writeln('[ RUN ] %s' % self.getDescription(test))
+
+ def addSuccess(self, test):
+ unittest.TestResult.addSuccess(self, test)
+ self.stream.writeln('[ OK ] %s' % self._GetTestURI(test))
+
+ def addError(self, test, err):
+ unittest.TestResult.addError(self, test, err)
+ self.stream.writeln('[ ERROR ] %s' % self._GetTestURI(test))
+ self._fails.add(self._GetTestURI(test))
+
+ def addFailure(self, test, err):
+ unittest.TestResult.addFailure(self, test, err)
+ self.stream.writeln('[ FAILED ] %s' % self._GetTestURI(test))
+ self._fails.add(self._GetTestURI(test))
+
+ def getRetestFilter(self):
+ return ':'.join(self._fails)
+
+
+class TextTestRunner(unittest.TextTestRunner):
+ """Test Runner for displaying test results in textual format.
+
+ Results are displayed in conformance with google test output.
+ """
+
+ def __init__(self, verbosity=1):
+ unittest.TextTestRunner.__init__(self, stream=sys.stderr,
+ verbosity=verbosity)
+
+ def _makeResult(self):
+ return _TextTestResult(self.stream, self.descriptions, self.verbosity)
+
+
+def GetTestsFromSuite(suite):
+ """Returns all the tests from a given test suite."""
+ tests = []
+ for x in suite:
+ if isinstance(x, unittest.TestSuite):
+ tests += GetTestsFromSuite(x)
+ else:
+ tests += [x]
+ return tests
+
+
+def GetTestNamesFromSuite(suite):
+ """Returns a list of every test name in the given suite."""
+ return map(lambda x: GetTestName(x), GetTestsFromSuite(suite))
+
+
+def GetTestName(test):
+ """Gets the test name of the given unittest test."""
+ return '.'.join([test.__class__.__module__,
+ test.__class__.__name__,
+ test._testMethodName])
+
+
+def FilterTestSuite(suite, gtest_filter):
+ """Returns a new filtered tests suite based on the given gtest filter.
+
+ See https://github.com/google/googletest/blob/master/docs/advanced.md
+ for gtest_filter specification.
+ """
+ return unittest.TestSuite(FilterTests(GetTestsFromSuite(suite), gtest_filter))
+
+
+def FilterTests(all_tests, gtest_filter):
+ """Filter a list of tests based on the given gtest filter.
+
+ Args:
+ all_tests: List of tests (unittest.TestSuite)
+ gtest_filter: Filter to apply.
+
+ Returns:
+ Filtered subset of the given list of tests.
+ """
+ test_names = [GetTestName(test) for test in all_tests]
+ filtered_names = FilterTestNames(test_names, gtest_filter)
+ return [test for test in all_tests if GetTestName(test) in filtered_names]
+
+
+def FilterTestNames(all_tests, gtest_filter):
+ """Filter a list of test names based on the given gtest filter.
+
+ See https://github.com/google/googletest/blob/master/docs/advanced.md
+ for gtest_filter specification.
+
+ Args:
+ all_tests: List of test names.
+ gtest_filter: Filter to apply.
+
+ Returns:
+ Filtered subset of the given list of test names.
+ """
+ pattern_groups = gtest_filter.split('-')
+ positive_patterns = ['*']
+ if pattern_groups[0]:
+ positive_patterns = pattern_groups[0].split(':')
+ negative_patterns = []
+ if len(pattern_groups) > 1:
+ negative_patterns = pattern_groups[1].split(':')
+
+ neg_pats = None
+ if negative_patterns:
+ neg_pats = re.compile('|'.join(fnmatch.translate(p) for p in
+ negative_patterns))
+
+ tests = []
+ test_set = set()
+ for pattern in positive_patterns:
+ pattern_tests = [
+ test for test in all_tests
+ if (fnmatch.fnmatch(test, pattern)
+ and not (neg_pats and neg_pats.match(test))
+ and test not in test_set)]
+ tests.extend(pattern_tests)
+ test_set.update(pattern_tests)
+ return tests
diff --git a/third_party/libwebrtc/build/util/lib/common/unittest_util_test.py b/third_party/libwebrtc/build/util/lib/common/unittest_util_test.py
new file mode 100755
index 0000000000..1514c9b6d4
--- /dev/null
+++ b/third_party/libwebrtc/build/util/lib/common/unittest_util_test.py
@@ -0,0 +1,65 @@
+#!/usr/bin/env python
+# Copyright 2015 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.
+
+# pylint: disable=protected-access
+
+import logging
+import sys
+import unittest
+import unittest_util
+
+
+class FilterTestNamesTest(unittest.TestCase):
+
+ possible_list = ["Foo.One",
+ "Foo.Two",
+ "Foo.Three",
+ "Bar.One",
+ "Bar.Two",
+ "Bar.Three",
+ "Quux.One",
+ "Quux.Two",
+ "Quux.Three"]
+
+ def testMatchAll(self):
+ x = unittest_util.FilterTestNames(self.possible_list, "*")
+ self.assertEquals(x, self.possible_list)
+
+ def testMatchPartial(self):
+ x = unittest_util.FilterTestNames(self.possible_list, "Foo.*")
+ self.assertEquals(x, ["Foo.One", "Foo.Two", "Foo.Three"])
+
+ def testMatchFull(self):
+ x = unittest_util.FilterTestNames(self.possible_list, "Foo.Two")
+ self.assertEquals(x, ["Foo.Two"])
+
+ def testMatchTwo(self):
+ x = unittest_util.FilterTestNames(self.possible_list, "Bar.*:Foo.*")
+ self.assertEquals(x, ["Bar.One",
+ "Bar.Two",
+ "Bar.Three",
+ "Foo.One",
+ "Foo.Two",
+ "Foo.Three"])
+
+ def testMatchWithNegative(self):
+ x = unittest_util.FilterTestNames(self.possible_list, "Bar.*:Foo.*-*.Three")
+ self.assertEquals(x, ["Bar.One",
+ "Bar.Two",
+ "Foo.One",
+ "Foo.Two"])
+
+ def testMatchOverlapping(self):
+ x = unittest_util.FilterTestNames(self.possible_list, "Bar.*:*.Two")
+ self.assertEquals(x, ["Bar.One",
+ "Bar.Two",
+ "Bar.Three",
+ "Foo.Two",
+ "Quux.Two"])
+
+
+if __name__ == '__main__':
+ logging.getLogger().setLevel(logging.DEBUG)
+ unittest.main(verbosity=2)
diff --git a/third_party/libwebrtc/build/util/lib/common/util.py b/third_party/libwebrtc/build/util/lib/common/util.py
new file mode 100644
index 0000000000..a415b1f534
--- /dev/null
+++ b/third_party/libwebrtc/build/util/lib/common/util.py
@@ -0,0 +1,151 @@
+# Copyright 2013 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.
+
+"""Generic utilities for all python scripts."""
+
+import atexit
+import httplib
+import os
+import signal
+import stat
+import subprocess
+import sys
+import tempfile
+import urlparse
+
+
+def GetPlatformName():
+ """Return a string to be used in paths for the platform."""
+ if IsWindows():
+ return 'win'
+ if IsMac():
+ return 'mac'
+ if IsLinux():
+ return 'linux'
+ raise NotImplementedError('Unknown platform "%s".' % sys.platform)
+
+
+def IsWindows():
+ return sys.platform == 'cygwin' or sys.platform.startswith('win')
+
+
+def IsLinux():
+ return sys.platform.startswith('linux')
+
+
+def IsMac():
+ return sys.platform.startswith('darwin')
+
+
+def _DeleteDir(path):
+ """Deletes a directory recursively, which must exist."""
+ # Don't use shutil.rmtree because it can't delete read-only files on Win.
+ for root, dirs, files in os.walk(path, topdown=False):
+ for name in files:
+ filename = os.path.join(root, name)
+ os.chmod(filename, stat.S_IWRITE)
+ os.remove(filename)
+ for name in dirs:
+ os.rmdir(os.path.join(root, name))
+ os.rmdir(path)
+
+
+def Delete(path):
+ """Deletes the given file or directory (recursively), which must exist."""
+ if os.path.isdir(path):
+ _DeleteDir(path)
+ else:
+ os.remove(path)
+
+
+def MaybeDelete(path):
+ """Deletes the given file or directory (recurisvely), if it exists."""
+ if os.path.exists(path):
+ Delete(path)
+
+
+def MakeTempDir(parent_dir=None):
+ """Creates a temporary directory and returns an absolute path to it.
+
+ The temporary directory is automatically deleted when the python interpreter
+ exits normally.
+
+ Args:
+ parent_dir: the directory to create the temp dir in. If None, the system
+ temp dir is used.
+
+ Returns:
+ The absolute path to the temporary directory.
+ """
+ path = tempfile.mkdtemp(dir=parent_dir)
+ atexit.register(MaybeDelete, path)
+ return path
+
+
+def Unzip(zip_path, output_dir):
+ """Unzips the given zip file using a system installed unzip tool.
+
+ Args:
+ zip_path: zip file to unzip.
+ output_dir: directory to unzip the contents of the zip file. The directory
+ must exist.
+
+ Raises:
+ RuntimeError if the unzip operation fails.
+ """
+ if IsWindows():
+ unzip_cmd = ['C:\\Program Files\\7-Zip\\7z.exe', 'x', '-y']
+ else:
+ unzip_cmd = ['unzip', '-o']
+ unzip_cmd += [zip_path]
+ if RunCommand(unzip_cmd, output_dir) != 0:
+ raise RuntimeError('Unable to unzip %s to %s' % (zip_path, output_dir))
+
+
+def Kill(pid):
+ """Terminate the given pid."""
+ if IsWindows():
+ subprocess.call(['taskkill.exe', '/T', '/F', '/PID', str(pid)])
+ else:
+ os.kill(pid, signal.SIGTERM)
+
+
+def RunCommand(cmd, cwd=None):
+ """Runs the given command and returns the exit code.
+
+ Args:
+ cmd: list of command arguments.
+ cwd: working directory to execute the command, or None if the current
+ working directory should be used.
+
+ Returns:
+ The exit code of the command.
+ """
+ process = subprocess.Popen(cmd, cwd=cwd)
+ process.wait()
+ return process.returncode
+
+
+def DoesUrlExist(url):
+ """Determines whether a resource exists at the given URL.
+
+ Args:
+ url: URL to be verified.
+
+ Returns:
+ True if url exists, otherwise False.
+ """
+ parsed = urlparse.urlparse(url)
+ try:
+ conn = httplib.HTTPConnection(parsed.netloc)
+ conn.request('HEAD', parsed.path)
+ response = conn.getresponse()
+ except (socket.gaierror, socket.error):
+ return False
+ finally:
+ conn.close()
+ # Follow both permanent (301) and temporary (302) redirects.
+ if response.status == 302 or response.status == 301:
+ return DoesUrlExist(response.getheader('location'))
+ return response.status == 200