| Index: tools/telemetry/third_party/webpagereplay/replay.py
|
| diff --git a/tools/telemetry/third_party/webpagereplay/replay.py b/tools/telemetry/third_party/webpagereplay/replay.py
|
| deleted file mode 100755
|
| index 50762ce4fbd16ca138a4c3bdbfa0718f1b814696..0000000000000000000000000000000000000000
|
| --- a/tools/telemetry/third_party/webpagereplay/replay.py
|
| +++ /dev/null
|
| @@ -1,556 +0,0 @@
|
| -#!/usr/bin/env python
|
| -# Copyright 2010 Google Inc. All Rights Reserved.
|
| -#
|
| -# Licensed under the Apache License, Version 2.0 (the "License");
|
| -# you may not use this file except in compliance with the License.
|
| -# You may obtain a copy of the License at
|
| -#
|
| -# http://www.apache.org/licenses/LICENSE-2.0
|
| -#
|
| -# Unless required by applicable law or agreed to in writing, software
|
| -# distributed under the License is distributed on an "AS IS" BASIS,
|
| -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| -# See the License for the specific language governing permissions and
|
| -# limitations under the License.
|
| -
|
| -"""Replays web pages under simulated network conditions.
|
| -
|
| -Must be run as administrator (sudo).
|
| -
|
| -To record web pages:
|
| - 1. Start the program in record mode.
|
| - $ sudo ./replay.py --record archive.wpr
|
| - 2. Load the web pages you want to record in a web browser. It is important to
|
| - clear browser caches before this so that all subresources are requested
|
| - from the network.
|
| - 3. Kill the process to stop recording.
|
| -
|
| -To replay web pages:
|
| - 1. Start the program in replay mode with a previously recorded archive.
|
| - $ sudo ./replay.py archive.wpr
|
| - 2. Load recorded pages in a web browser. A 404 will be served for any pages or
|
| - resources not in the recorded archive.
|
| -
|
| -Network simulation examples:
|
| - # 128KByte/s uplink bandwidth, 4Mbps/s downlink bandwidth with 100ms RTT time
|
| - $ sudo ./replay.py --up 128KByte/s --down 4Mbit/s --delay_ms=100 archive.wpr
|
| -
|
| - # 1% packet loss rate
|
| - $ sudo ./replay.py --packet_loss_rate=0.01 archive.wpr
|
| -"""
|
| -
|
| -import json
|
| -import logging
|
| -import optparse
|
| -import os
|
| -import socket
|
| -import sys
|
| -import traceback
|
| -
|
| -import customhandlers
|
| -import dnsproxy
|
| -import httparchive
|
| -import httpclient
|
| -import httpproxy
|
| -import net_configs
|
| -import platformsettings
|
| -import rules_parser
|
| -import script_injector
|
| -import servermanager
|
| -import trafficshaper
|
| -
|
| -if sys.version < '2.6':
|
| - print 'Need Python 2.6 or greater.'
|
| - sys.exit(1)
|
| -
|
| -
|
| -def configure_logging(log_level_name, log_file_name=None):
|
| - """Configure logging level and format.
|
| -
|
| - Args:
|
| - log_level_name: 'debug', 'info', 'warning', 'error', or 'critical'.
|
| - log_file_name: a file name
|
| - """
|
| - if logging.root.handlers:
|
| - logging.critical('A logging method (e.g. "logging.warn(...)")'
|
| - ' was called before logging was configured.')
|
| - log_level = getattr(logging, log_level_name.upper())
|
| - log_format = (
|
| - '(%(levelname)s) %(asctime)s %(module)s.%(funcName)s:%(lineno)d '
|
| - '%(message)s')
|
| -
|
| -
|
| - logging.basicConfig(level=log_level, format=log_format)
|
| - logger = logging.getLogger()
|
| - if log_file_name:
|
| - fh = logging.FileHandler(log_file_name)
|
| - fh.setLevel(log_level)
|
| - fh.setFormatter(logging.Formatter(log_format))
|
| - logger.addHandler(fh)
|
| - system_handler = platformsettings.get_system_logging_handler()
|
| - if system_handler:
|
| - logger.addHandler(system_handler)
|
| -
|
| -
|
| -def AddDnsForward(server_manager, host):
|
| - """Forward DNS traffic."""
|
| - server_manager.Append(platformsettings.set_temporary_primary_nameserver, host)
|
| -
|
| -
|
| -def AddDnsProxy(server_manager, options, host, port, real_dns_lookup,
|
| - http_archive):
|
| - dns_filters = []
|
| - if options.dns_private_passthrough:
|
| - private_filter = dnsproxy.PrivateIpFilter(real_dns_lookup, http_archive)
|
| - dns_filters.append(private_filter)
|
| - server_manager.AppendRecordCallback(private_filter.InitializeArchiveHosts)
|
| - server_manager.AppendReplayCallback(private_filter.InitializeArchiveHosts)
|
| - if options.shaping_dns:
|
| - delay_filter = dnsproxy.DelayFilter(options.record, **options.shaping_dns)
|
| - dns_filters.append(delay_filter)
|
| - server_manager.AppendRecordCallback(delay_filter.SetRecordMode)
|
| - server_manager.AppendReplayCallback(delay_filter.SetReplayMode)
|
| - server_manager.Append(dnsproxy.DnsProxyServer, host, port,
|
| - dns_lookup=dnsproxy.ReplayDnsLookup(host, dns_filters))
|
| -
|
| -
|
| -def AddWebProxy(server_manager, options, host, real_dns_lookup, http_archive):
|
| - if options.rules_path:
|
| - with open(options.rules_path) as file_obj:
|
| - allowed_imports = [
|
| - name.strip() for name in options.allowed_rule_imports.split(',')]
|
| - rules = rules_parser.Rules(file_obj, allowed_imports)
|
| - logging.info('Parsed %s rules:\n%s', options.rules_path, rules)
|
| - else:
|
| - rules = rules_parser.Rules()
|
| - inject_script = script_injector.GetInjectScript(options.inject_scripts)
|
| - custom_handlers = customhandlers.CustomHandlers(options, http_archive)
|
| - custom_handlers.add_server_manager_handler(server_manager)
|
| - archive_fetch = httpclient.ControllableHttpArchiveFetch(
|
| - http_archive, real_dns_lookup,
|
| - inject_script,
|
| - options.diff_unknown_requests, options.record,
|
| - use_closest_match=options.use_closest_match,
|
| - scramble_images=options.scramble_images)
|
| - server_manager.AppendRecordCallback(archive_fetch.SetRecordMode)
|
| - server_manager.AppendReplayCallback(archive_fetch.SetReplayMode)
|
| - server_manager.Append(
|
| - httpproxy.HttpProxyServer,
|
| - archive_fetch, custom_handlers, rules,
|
| - host=host, port=options.port, use_delays=options.use_server_delay,
|
| - **options.shaping_http)
|
| - if options.ssl:
|
| - if options.should_generate_certs:
|
| - server_manager.Append(
|
| - httpproxy.HttpsProxyServer, archive_fetch, custom_handlers, rules,
|
| - options.https_root_ca_cert_path, host=host, port=options.ssl_port,
|
| - use_delays=options.use_server_delay, **options.shaping_http)
|
| - else:
|
| - server_manager.Append(
|
| - httpproxy.SingleCertHttpsProxyServer, archive_fetch,
|
| - custom_handlers, rules, options.https_root_ca_cert_path, host=host,
|
| - port=options.ssl_port, use_delays=options.use_server_delay,
|
| - **options.shaping_http)
|
| - if options.http_to_https_port:
|
| - server_manager.Append(
|
| - httpproxy.HttpToHttpsProxyServer,
|
| - archive_fetch, custom_handlers, rules,
|
| - host=host, port=options.http_to_https_port,
|
| - use_delays=options.use_server_delay,
|
| - **options.shaping_http)
|
| -
|
| -
|
| -def AddTrafficShaper(server_manager, options, host):
|
| - if options.shaping_dummynet:
|
| - server_manager.AppendTrafficShaper(
|
| - trafficshaper.TrafficShaper, host=host,
|
| - use_loopback=not options.server_mode and host == '127.0.0.1',
|
| - **options.shaping_dummynet)
|
| -
|
| -
|
| -class OptionsWrapper(object):
|
| - """Add checks, updates, and methods to option values.
|
| -
|
| - Example:
|
| - options, args = option_parser.parse_args()
|
| - options = OptionsWrapper(options, option_parser) # run checks and updates
|
| - if options.record and options.HasTrafficShaping():
|
| - [...]
|
| - """
|
| - _TRAFFICSHAPING_OPTIONS = {
|
| - 'down', 'up', 'delay_ms', 'packet_loss_rate', 'init_cwnd', 'net'}
|
| - _CONFLICTING_OPTIONS = (
|
| - ('record', ('down', 'up', 'delay_ms', 'packet_loss_rate', 'net',
|
| - 'spdy', 'use_server_delay')),
|
| - ('append', ('down', 'up', 'delay_ms', 'packet_loss_rate', 'net',
|
| - 'use_server_delay')), # same as --record
|
| - ('net', ('down', 'up', 'delay_ms')),
|
| - ('server', ('server_mode',)),
|
| - )
|
| -
|
| - def __init__(self, options, parser):
|
| - self._options = options
|
| - self._parser = parser
|
| - self._nondefaults = set([
|
| - name for name, value in parser.defaults.items()
|
| - if getattr(options, name) != value])
|
| - self._CheckConflicts()
|
| - self._CheckValidIp('host')
|
| - self._CheckFeatureSupport()
|
| - self._MassageValues()
|
| -
|
| - def _CheckConflicts(self):
|
| - """Give an error if mutually exclusive options are used."""
|
| - for option, bad_options in self._CONFLICTING_OPTIONS:
|
| - if option in self._nondefaults:
|
| - for bad_option in bad_options:
|
| - if bad_option in self._nondefaults:
|
| - self._parser.error('Option --%s cannot be used with --%s.' %
|
| - (bad_option, option))
|
| -
|
| - def _CheckValidIp(self, name):
|
| - """Give an error if option |name| is not a valid IPv4 address."""
|
| - value = getattr(self._options, name)
|
| - if value:
|
| - try:
|
| - socket.inet_aton(value)
|
| - except Exception:
|
| - self._parser.error('Option --%s must be a valid IPv4 address.' % name)
|
| -
|
| - def _CheckFeatureSupport(self):
|
| - if (self._options.should_generate_certs and
|
| - not platformsettings.HasSniSupport()):
|
| - self._parser.error('Option --should_generate_certs requires pyOpenSSL '
|
| - '0.13 or greater for SNI support.')
|
| -
|
| - def _ShapingKeywordArgs(self, shaping_key):
|
| - """Return the shaping keyword args for |shaping_key|.
|
| -
|
| - Args:
|
| - shaping_key: one of 'dummynet', 'dns', 'http'.
|
| - Returns:
|
| - {} # if shaping_key does not apply, or options have default values.
|
| - {k: v, ...}
|
| - """
|
| - kwargs = {}
|
| - def AddItemIfSet(d, kw_key, opt_key=None):
|
| - opt_key = opt_key or kw_key
|
| - if opt_key in self._nondefaults:
|
| - d[kw_key] = getattr(self, opt_key)
|
| - if ((self.shaping_type == 'proxy' and shaping_key in ('dns', 'http')) or
|
| - self.shaping_type == shaping_key):
|
| - AddItemIfSet(kwargs, 'delay_ms')
|
| - if shaping_key in ('dummynet', 'http'):
|
| - AddItemIfSet(kwargs, 'down_bandwidth', opt_key='down')
|
| - AddItemIfSet(kwargs, 'up_bandwidth', opt_key='up')
|
| - if shaping_key == 'dummynet':
|
| - AddItemIfSet(kwargs, 'packet_loss_rate')
|
| - AddItemIfSet(kwargs, 'init_cwnd')
|
| - elif self.shaping_type != 'none':
|
| - if 'packet_loss_rate' in self._nondefaults:
|
| - logging.warn('Shaping type, %s, ignores --packet_loss_rate=%s',
|
| - self.shaping_type, self.packet_loss_rate)
|
| - if 'init_cwnd' in self._nondefaults:
|
| - logging.warn('Shaping type, %s, ignores --init_cwnd=%s',
|
| - self.shaping_type, self.init_cwnd)
|
| - return kwargs
|
| -
|
| - def _MassageValues(self):
|
| - """Set options that depend on the values of other options."""
|
| - if self.append and not self.record:
|
| - self._options.record = True
|
| - if self.net:
|
| - self._options.down, self._options.up, self._options.delay_ms = \
|
| - net_configs.GetNetConfig(self.net)
|
| - self._nondefaults.update(['down', 'up', 'delay_ms'])
|
| - if not self.ssl:
|
| - self._options.https_root_ca_cert_path = None
|
| - self.shaping_dns = self._ShapingKeywordArgs('dns')
|
| - self.shaping_http = self._ShapingKeywordArgs('http')
|
| - self.shaping_dummynet = self._ShapingKeywordArgs('dummynet')
|
| -
|
| - def __getattr__(self, name):
|
| - """Make the original option values available."""
|
| - return getattr(self._options, name)
|
| -
|
| - def __repr__(self):
|
| - """Return a json representation of the original options dictionary."""
|
| - return json.dumps(self._options.__dict__)
|
| -
|
| - def IsRootRequired(self):
|
| - """Returns True iff the options require whole program root access."""
|
| - if self.server:
|
| - return True
|
| -
|
| - def IsPrivilegedPort(port):
|
| - return port and port < 1024
|
| -
|
| - if IsPrivilegedPort(self.port) or (self.ssl and
|
| - IsPrivilegedPort(self.ssl_port)):
|
| - return True
|
| -
|
| - if self.dns_forwarding:
|
| - if IsPrivilegedPort(self.dns_port):
|
| - return True
|
| - if not self.server_mode and self.host == '127.0.0.1':
|
| - return True
|
| -
|
| - return False
|
| -
|
| -
|
| -def replay(options, replay_filename):
|
| - if options.admin_check and options.IsRootRequired():
|
| - platformsettings.rerun_as_administrator()
|
| - configure_logging(options.log_level, options.log_file)
|
| - server_manager = servermanager.ServerManager(options.record)
|
| - if options.server:
|
| - AddDnsForward(server_manager, options.server)
|
| - else:
|
| - real_dns_lookup = dnsproxy.RealDnsLookup(
|
| - name_servers=[platformsettings.get_original_primary_nameserver()])
|
| - if options.record:
|
| - httparchive.HttpArchive.AssertWritable(replay_filename)
|
| - if options.append and os.path.exists(replay_filename):
|
| - http_archive = httparchive.HttpArchive.Load(replay_filename)
|
| - logging.info('Appending to %s (loaded %d existing responses)',
|
| - replay_filename, len(http_archive))
|
| - else:
|
| - http_archive = httparchive.HttpArchive()
|
| - else:
|
| - http_archive = httparchive.HttpArchive.Load(replay_filename)
|
| - logging.info('Loaded %d responses from %s',
|
| - len(http_archive), replay_filename)
|
| - server_manager.AppendRecordCallback(real_dns_lookup.ClearCache)
|
| - server_manager.AppendRecordCallback(http_archive.clear)
|
| -
|
| - ipfw_dns_host = None
|
| - if options.dns_forwarding or options.shaping_dummynet:
|
| - # compute the ip/host used for the DNS server and traffic shaping
|
| - ipfw_dns_host = options.host
|
| - if not ipfw_dns_host:
|
| - ipfw_dns_host = platformsettings.get_server_ip_address(
|
| - options.server_mode)
|
| -
|
| - if options.dns_forwarding:
|
| - if not options.server_mode and ipfw_dns_host == '127.0.0.1':
|
| - AddDnsForward(server_manager, ipfw_dns_host)
|
| - AddDnsProxy(server_manager, options, ipfw_dns_host, options.dns_port,
|
| - real_dns_lookup, http_archive)
|
| - if options.ssl and options.https_root_ca_cert_path is None:
|
| - options.https_root_ca_cert_path = os.path.join(os.path.dirname(__file__),
|
| - 'wpr_cert.pem')
|
| - http_proxy_address = options.host
|
| - if not http_proxy_address:
|
| - http_proxy_address = platformsettings.get_httpproxy_ip_address(
|
| - options.server_mode)
|
| - AddWebProxy(server_manager, options, http_proxy_address, real_dns_lookup,
|
| - http_archive)
|
| - AddTrafficShaper(server_manager, options, ipfw_dns_host)
|
| -
|
| - exit_status = 0
|
| - try:
|
| - server_manager.Run()
|
| - except KeyboardInterrupt:
|
| - logging.info('Shutting down.')
|
| - except (dnsproxy.DnsProxyException,
|
| - trafficshaper.TrafficShaperException,
|
| - platformsettings.NotAdministratorError,
|
| - platformsettings.DnsUpdateError) as e:
|
| - logging.critical('%s: %s', e.__class__.__name__, e)
|
| - exit_status = 1
|
| - except Exception:
|
| - logging.critical(traceback.format_exc())
|
| - exit_status = 2
|
| -
|
| - if options.record:
|
| - http_archive.Persist(replay_filename)
|
| - logging.info('Saved %d responses to %s', len(http_archive), replay_filename)
|
| - return exit_status
|
| -
|
| -
|
| -def GetOptionParser():
|
| - class PlainHelpFormatter(optparse.IndentedHelpFormatter):
|
| - def format_description(self, description):
|
| - if description:
|
| - return description + '\n'
|
| - else:
|
| - return ''
|
| - option_parser = optparse.OptionParser(
|
| - usage='%prog [options] replay_file',
|
| - formatter=PlainHelpFormatter(),
|
| - description=__doc__,
|
| - epilog='http://code.google.com/p/web-page-replay/')
|
| -
|
| - option_parser.add_option('-r', '--record', default=False,
|
| - action='store_true',
|
| - help='Download real responses and record them to replay_file')
|
| - option_parser.add_option('--append', default=False,
|
| - action='store_true',
|
| - help='Append responses to replay_file.')
|
| - option_parser.add_option('-l', '--log_level', default='debug',
|
| - action='store',
|
| - type='choice',
|
| - choices=('debug', 'info', 'warning', 'error', 'critical'),
|
| - help='Minimum verbosity level to log')
|
| - option_parser.add_option('-f', '--log_file', default=None,
|
| - action='store',
|
| - type='string',
|
| - help='Log file to use in addition to writting logs to stderr.')
|
| -
|
| - network_group = optparse.OptionGroup(option_parser,
|
| - 'Network Simulation Options',
|
| - 'These options configure the network simulation in replay mode')
|
| - network_group.add_option('-u', '--up', default='0',
|
| - action='store',
|
| - type='string',
|
| - help='Upload Bandwidth in [K|M]{bit/s|Byte/s}. Zero means unlimited.')
|
| - network_group.add_option('-d', '--down', default='0',
|
| - action='store',
|
| - type='string',
|
| - help='Download Bandwidth in [K|M]{bit/s|Byte/s}. Zero means unlimited.')
|
| - network_group.add_option('-m', '--delay_ms', default='0',
|
| - action='store',
|
| - type='string',
|
| - help='Propagation delay (latency) in milliseconds. Zero means no delay.')
|
| - network_group.add_option('-p', '--packet_loss_rate', default='0',
|
| - action='store',
|
| - type='string',
|
| - help='Packet loss rate in range [0..1]. Zero means no loss.')
|
| - network_group.add_option('-w', '--init_cwnd', default='0',
|
| - action='store',
|
| - type='string',
|
| - help='Set initial cwnd (linux only, requires kernel patch)')
|
| - network_group.add_option('--net', default=None,
|
| - action='store',
|
| - type='choice',
|
| - choices=net_configs.NET_CONFIG_NAMES,
|
| - help='Select a set of network options: %s.' % ', '.join(
|
| - net_configs.NET_CONFIG_NAMES))
|
| - network_group.add_option('--shaping_type', default='dummynet',
|
| - action='store',
|
| - choices=('dummynet', 'proxy'),
|
| - help='When shaping is configured (i.e. --up, --down, etc.) decides '
|
| - 'whether to use |dummynet| (default), or |proxy| servers.')
|
| - option_parser.add_option_group(network_group)
|
| -
|
| - harness_group = optparse.OptionGroup(option_parser,
|
| - 'Replay Harness Options',
|
| - 'These advanced options configure various aspects of the replay harness')
|
| - harness_group.add_option('-S', '--server', default=None,
|
| - action='store',
|
| - type='string',
|
| - help='IP address of host running "replay.py --server_mode". '
|
| - 'This only changes the primary DNS nameserver to use the given IP.')
|
| - harness_group.add_option('-M', '--server_mode', default=False,
|
| - action='store_true',
|
| - help='Run replay DNS & http proxies, and trafficshaping on --port '
|
| - 'without changing the primary DNS nameserver. '
|
| - 'Other hosts may connect to this using "replay.py --server" '
|
| - 'or by pointing their DNS to this server.')
|
| - harness_group.add_option('-i', '--inject_scripts', default='deterministic.js',
|
| - action='store',
|
| - dest='inject_scripts',
|
| - help='A comma separated list of JavaScript sources to inject in all '
|
| - 'pages. By default a script is injected that eliminates sources '
|
| - 'of entropy such as Date() and Math.random() deterministic. '
|
| - 'CAUTION: Without deterministic.js, many pages will not replay.')
|
| - harness_group.add_option('-D', '--no-diff_unknown_requests', default=True,
|
| - action='store_false',
|
| - dest='diff_unknown_requests',
|
| - help='During replay, do not show a diff of unknown requests against '
|
| - 'their nearest match in the archive.')
|
| - harness_group.add_option('-C', '--use_closest_match', default=False,
|
| - action='store_true',
|
| - dest='use_closest_match',
|
| - help='During replay, if a request is not found, serve the closest match'
|
| - 'in the archive instead of giving a 404.')
|
| - harness_group.add_option('-U', '--use_server_delay', default=False,
|
| - action='store_true',
|
| - dest='use_server_delay',
|
| - help='During replay, simulate server delay by delaying response time to'
|
| - 'requests.')
|
| - harness_group.add_option('-I', '--screenshot_dir', default=None,
|
| - action='store',
|
| - type='string',
|
| - help='Save PNG images of the loaded page in the given directory.')
|
| - harness_group.add_option('-P', '--no-dns_private_passthrough', default=True,
|
| - action='store_false',
|
| - dest='dns_private_passthrough',
|
| - help='Don\'t forward DNS requests that resolve to private network '
|
| - 'addresses. CAUTION: With this option important services like '
|
| - 'Kerberos will resolve to the HTTP proxy address.')
|
| - harness_group.add_option('-x', '--no-dns_forwarding', default=True,
|
| - action='store_false',
|
| - dest='dns_forwarding',
|
| - help='Don\'t forward DNS requests to the local replay server. '
|
| - 'CAUTION: With this option an external mechanism must be used to '
|
| - 'forward traffic to the replay server.')
|
| - harness_group.add_option('--host', default=None,
|
| - action='store',
|
| - type='str',
|
| - help='The IP address to bind all servers to. Defaults to 0.0.0.0 or '
|
| - '127.0.0.1, depending on --server_mode and platform.')
|
| - harness_group.add_option('-o', '--port', default=80,
|
| - action='store',
|
| - type='int',
|
| - help='Port number to listen on.')
|
| - harness_group.add_option('--ssl_port', default=443,
|
| - action='store',
|
| - type='int',
|
| - help='SSL port number to listen on.')
|
| - harness_group.add_option('--http_to_https_port', default=None,
|
| - action='store',
|
| - type='int',
|
| - help='Port on which WPR will listen for HTTP requests that it will send '
|
| - 'along as HTTPS requests.')
|
| - harness_group.add_option('--dns_port', default=53,
|
| - action='store',
|
| - type='int',
|
| - help='DNS port number to listen on.')
|
| - harness_group.add_option('-c', '--https_root_ca_cert_path', default=None,
|
| - action='store',
|
| - type='string',
|
| - help='Certificate file to use with SSL (gets auto-generated if needed).')
|
| - harness_group.add_option('--no-ssl', default=True,
|
| - action='store_false',
|
| - dest='ssl',
|
| - help='Do not setup an SSL proxy.')
|
| - option_parser.add_option_group(harness_group)
|
| - harness_group.add_option('--should_generate_certs', default=False,
|
| - action='store_true',
|
| - help='Use OpenSSL to generate certificate files for requested hosts.')
|
| - harness_group.add_option('--no-admin-check', default=True,
|
| - action='store_false',
|
| - dest='admin_check',
|
| - help='Do not check if administrator access is needed.')
|
| - harness_group.add_option('--scramble_images', default=False,
|
| - action='store_true',
|
| - dest='scramble_images',
|
| - help='Scramble image responses.')
|
| - harness_group.add_option('--rules_path', default=None,
|
| - action='store',
|
| - help='Path of file containing Python rules.')
|
| - harness_group.add_option('--allowed_rule_imports', default='rules',
|
| - action='store',
|
| - help='A comma-separate list of allowed rule imports, or \'*\' to allow'
|
| - ' all packages. Defaults to \'%default\'.')
|
| - return option_parser
|
| -
|
| -
|
| -def main():
|
| - option_parser = GetOptionParser()
|
| - options, args = option_parser.parse_args()
|
| - options = OptionsWrapper(options, option_parser)
|
| -
|
| - if options.server:
|
| - replay_filename = None
|
| - elif len(args) != 1:
|
| - option_parser.error('Must specify a replay_file')
|
| - else:
|
| - replay_filename = args[0]
|
| -
|
| - return replay(options, replay_filename)
|
| -
|
| -
|
| -if __name__ == '__main__':
|
| - sys.exit(main())
|
|
|