mirror of
https://github.com/servo/servo.git
synced 2025-08-04 05:00:08 +01:00
Update to latest wptrunner (036c9931).
This commit is contained in:
parent
88d9c1b257
commit
f837e575fe
33 changed files with 709 additions and 229 deletions
|
@ -1,5 +1,4 @@
|
||||||
html5lib >= 0.99
|
html5lib >= 0.99
|
||||||
mozinfo >= 0.7
|
mozinfo >= 0.7
|
||||||
mozlog >= 2.8
|
mozlog >= 2.8
|
||||||
# Unfortunately, just for gdb flags
|
mozdebug >= 0.1
|
||||||
mozrunner >= 6.1
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
marionette_client >= 0.7.10
|
marionette_driver >= 0.4
|
||||||
mozprofile >= 0.21
|
mozprofile >= 0.21
|
||||||
mozprocess >= 0.19
|
mozprocess >= 0.19
|
||||||
mozcrash >= 0.13
|
mozcrash >= 0.13
|
||||||
|
mozrunner >= 6.7
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
[test_pref_set.html]
|
||||||
|
prefs: ["browser.display.foreground_color:#00FF00",
|
||||||
|
"browser.display.background_color:#000000"]
|
|
@ -139,7 +139,7 @@ def get_parser():
|
||||||
help="Specific product to include in test run")
|
help="Specific product to include in test run")
|
||||||
parser.add_argument("--pdb", action="store_true",
|
parser.add_argument("--pdb", action="store_true",
|
||||||
help="Invoke pdb on uncaught exception")
|
help="Invoke pdb on uncaught exception")
|
||||||
parser.add_argument("test", nargs="*", type=wptcommandline.slash_prefixed,
|
parser.add_argument("test", nargs="*",
|
||||||
help="Specific tests to include in test run")
|
help="Specific tests to include in test run")
|
||||||
return parser
|
return parser
|
||||||
|
|
||||||
|
|
10
tests/wpt/harness/test/testdata/testharness/firefox/test_pref_set.html
vendored
Normal file
10
tests/wpt/harness/test/testdata/testharness/firefox/test_pref_set.html
vendored
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
<!doctype html>
|
||||||
|
<title>Example https test</title>
|
||||||
|
<script src="/resources/testharness.js"></script>
|
||||||
|
<script src="/resources/testharnessreport.js"></script>
|
||||||
|
<p>Test requires the pref browser.display.foreground_color to be set to #00FF00</p>
|
||||||
|
<script>
|
||||||
|
test(function() {
|
||||||
|
assert_equals(getComputedStyle(document.body).color, "rgb(0, 255, 0)");
|
||||||
|
}, "Test that pref was set");
|
||||||
|
</script>
|
|
@ -20,6 +20,7 @@ from mozprofile import FirefoxProfile, Preferences
|
||||||
from .base import get_free_port, BrowserError, Browser, ExecutorBrowser
|
from .base import get_free_port, BrowserError, Browser, ExecutorBrowser
|
||||||
from ..executors.executormarionette import MarionetteTestharnessExecutor
|
from ..executors.executormarionette import MarionetteTestharnessExecutor
|
||||||
from ..hosts import HostsFile, HostsLine
|
from ..hosts import HostsFile, HostsLine
|
||||||
|
from ..environment import hostnames
|
||||||
|
|
||||||
here = os.path.split(__file__)[0]
|
here = os.path.split(__file__)[0]
|
||||||
|
|
||||||
|
@ -115,13 +116,6 @@ class B2GBrowser(Browser):
|
||||||
self.logger.debug("Device runner started")
|
self.logger.debug("Device runner started")
|
||||||
|
|
||||||
def setup_hosts(self):
|
def setup_hosts(self):
|
||||||
hostnames = ["web-platform.test",
|
|
||||||
"www.web-platform.test",
|
|
||||||
"www1.web-platform.test",
|
|
||||||
"www2.web-platform.test",
|
|
||||||
"xn--n8j6ds53lwwkrqhv28a.web-platform.test",
|
|
||||||
"xn--lve-6lad.web-platform.test"]
|
|
||||||
|
|
||||||
host_ip = moznetwork.get_ip()
|
host_ip = moznetwork.get_ip()
|
||||||
|
|
||||||
temp_dir = tempfile.mkdtemp()
|
temp_dir = tempfile.mkdtemp()
|
||||||
|
|
Binary file not shown.
|
@ -41,6 +41,18 @@ def get_free_port(start_port, exclude=None):
|
||||||
finally:
|
finally:
|
||||||
s.close()
|
s.close()
|
||||||
|
|
||||||
|
def browser_command(binary, args, debug_info):
|
||||||
|
if debug_info:
|
||||||
|
if debug_info.requiresEscapedArgs:
|
||||||
|
args = [item.replace("&", "\\&") for item in args]
|
||||||
|
debug_args = [debug_info.path] + debug_info.args
|
||||||
|
else:
|
||||||
|
debug_args = []
|
||||||
|
|
||||||
|
command = [binary] + args
|
||||||
|
|
||||||
|
return debug_args, command
|
||||||
|
|
||||||
|
|
||||||
class BrowserError(Exception):
|
class BrowserError(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
|
@ -12,9 +12,10 @@ from mozprofile.permissions import ServerLocations
|
||||||
from mozrunner import FirefoxRunner
|
from mozrunner import FirefoxRunner
|
||||||
from mozcrash import mozcrash
|
from mozcrash import mozcrash
|
||||||
|
|
||||||
from .base import get_free_port, Browser, ExecutorBrowser, require_arg, cmd_arg
|
from .base import get_free_port, Browser, ExecutorBrowser, require_arg, cmd_arg, browser_command
|
||||||
from ..executors import executor_kwargs as base_executor_kwargs
|
from ..executors import executor_kwargs as base_executor_kwargs
|
||||||
from ..executors.executormarionette import MarionetteTestharnessExecutor, MarionetteRefTestExecutor
|
from ..executors.executormarionette import MarionetteTestharnessExecutor, MarionetteRefTestExecutor
|
||||||
|
from ..environment import hostnames
|
||||||
|
|
||||||
here = os.path.join(os.path.split(__file__)[0])
|
here = os.path.join(os.path.split(__file__)[0])
|
||||||
|
|
||||||
|
@ -37,8 +38,7 @@ def check_args(**kwargs):
|
||||||
def browser_kwargs(**kwargs):
|
def browser_kwargs(**kwargs):
|
||||||
return {"binary": kwargs["binary"],
|
return {"binary": kwargs["binary"],
|
||||||
"prefs_root": kwargs["prefs_root"],
|
"prefs_root": kwargs["prefs_root"],
|
||||||
"debug_args": kwargs["debug_args"],
|
"debug_info": kwargs["debug_info"],
|
||||||
"interactive": kwargs["interactive"],
|
|
||||||
"symbols_path": kwargs["symbols_path"],
|
"symbols_path": kwargs["symbols_path"],
|
||||||
"stackwalk_binary": kwargs["stackwalk_binary"],
|
"stackwalk_binary": kwargs["stackwalk_binary"],
|
||||||
"certutil_binary": kwargs["certutil_binary"],
|
"certutil_binary": kwargs["certutil_binary"],
|
||||||
|
@ -57,13 +57,13 @@ def env_options():
|
||||||
"external_host": "web-platform.test",
|
"external_host": "web-platform.test",
|
||||||
"bind_hostname": "false",
|
"bind_hostname": "false",
|
||||||
"certificate_domain": "web-platform.test",
|
"certificate_domain": "web-platform.test",
|
||||||
"encrypt_after_connect": True}
|
"supports_debugger": True}
|
||||||
|
|
||||||
|
|
||||||
class FirefoxBrowser(Browser):
|
class FirefoxBrowser(Browser):
|
||||||
used_ports = set()
|
used_ports = set()
|
||||||
|
|
||||||
def __init__(self, logger, binary, prefs_root, debug_args=None, interactive=None,
|
def __init__(self, logger, binary, prefs_root, debug_info=None,
|
||||||
symbols_path=None, stackwalk_binary=None, certutil_binary=None,
|
symbols_path=None, stackwalk_binary=None, certutil_binary=None,
|
||||||
ca_certificate_path=None):
|
ca_certificate_path=None):
|
||||||
Browser.__init__(self, logger)
|
Browser.__init__(self, logger)
|
||||||
|
@ -72,8 +72,7 @@ class FirefoxBrowser(Browser):
|
||||||
self.marionette_port = None
|
self.marionette_port = None
|
||||||
self.used_ports.add(self.marionette_port)
|
self.used_ports.add(self.marionette_port)
|
||||||
self.runner = None
|
self.runner = None
|
||||||
self.debug_args = debug_args
|
self.debug_info = debug_info
|
||||||
self.interactive = interactive
|
|
||||||
self.profile = None
|
self.profile = None
|
||||||
self.symbols_path = symbols_path
|
self.symbols_path = symbols_path
|
||||||
self.stackwalk_binary = stackwalk_binary
|
self.stackwalk_binary = stackwalk_binary
|
||||||
|
@ -84,38 +83,35 @@ class FirefoxBrowser(Browser):
|
||||||
self.marionette_port = get_free_port(2828, exclude=self.used_ports)
|
self.marionette_port = get_free_port(2828, exclude=self.used_ports)
|
||||||
|
|
||||||
env = os.environ.copy()
|
env = os.environ.copy()
|
||||||
env["MOZ_CRASHREPORTER"] = "1"
|
|
||||||
env["MOZ_CRASHREPORTER_SHUTDOWN"] = "1"
|
|
||||||
env["MOZ_CRASHREPORTER_NO_REPORT"] = "1"
|
|
||||||
env["MOZ_DISABLE_NONLOCAL_CONNECTIONS"] = "1"
|
env["MOZ_DISABLE_NONLOCAL_CONNECTIONS"] = "1"
|
||||||
|
|
||||||
locations = ServerLocations(filename=os.path.join(here, "server-locations.txt"))
|
locations = ServerLocations(filename=os.path.join(here, "server-locations.txt"))
|
||||||
|
|
||||||
preferences = self.load_prefs()
|
preferences = self.load_prefs()
|
||||||
|
|
||||||
ports = {"http": "8000",
|
|
||||||
"https": "8443",
|
|
||||||
"ws": "8888"}
|
|
||||||
|
|
||||||
self.profile = FirefoxProfile(locations=locations,
|
self.profile = FirefoxProfile(locations=locations,
|
||||||
proxy=ports,
|
|
||||||
preferences=preferences)
|
preferences=preferences)
|
||||||
self.profile.set_preferences({"marionette.defaultPrefs.enabled": True,
|
self.profile.set_preferences({"marionette.defaultPrefs.enabled": True,
|
||||||
"marionette.defaultPrefs.port": self.marionette_port,
|
"marionette.defaultPrefs.port": self.marionette_port,
|
||||||
"dom.disable_open_during_load": False})
|
"dom.disable_open_during_load": False,
|
||||||
|
"network.dns.localDomains": ",".join(hostnames)})
|
||||||
|
|
||||||
if self.ca_certificate_path is not None:
|
if self.ca_certificate_path is not None:
|
||||||
self.setup_ssl()
|
self.setup_ssl()
|
||||||
|
|
||||||
|
debug_args, cmd = browser_command(self.binary, [cmd_arg("marionette"), "about:blank"],
|
||||||
|
self.debug_info)
|
||||||
|
|
||||||
self.runner = FirefoxRunner(profile=self.profile,
|
self.runner = FirefoxRunner(profile=self.profile,
|
||||||
binary=self.binary,
|
binary=cmd[0],
|
||||||
cmdargs=[cmd_arg("marionette"), "about:blank"],
|
cmdargs=cmd[1:],
|
||||||
env=env,
|
env=env,
|
||||||
process_class=ProcessHandler,
|
process_class=ProcessHandler,
|
||||||
process_args={"processOutputLine": [self.on_output]})
|
process_args={"processOutputLine": [self.on_output]})
|
||||||
|
|
||||||
self.logger.debug("Starting Firefox")
|
self.logger.debug("Starting Firefox")
|
||||||
self.runner.start(debug_args=self.debug_args, interactive=self.interactive)
|
|
||||||
|
self.runner.start(debug_args=debug_args, interactive=self.debug_info and self.debug_info.interactive)
|
||||||
self.logger.debug("Firefox Started")
|
self.logger.debug("Firefox Started")
|
||||||
|
|
||||||
def load_prefs(self):
|
def load_prefs(self):
|
||||||
|
|
|
@ -26,8 +26,7 @@ def check_args(**kwargs):
|
||||||
|
|
||||||
def browser_kwargs(**kwargs):
|
def browser_kwargs(**kwargs):
|
||||||
return {"binary": kwargs["binary"],
|
return {"binary": kwargs["binary"],
|
||||||
"debug_args": kwargs["debug_args"],
|
"debug_info": kwargs["debug_info"]}
|
||||||
"interactive": kwargs["interactive"]}
|
|
||||||
|
|
||||||
|
|
||||||
def executor_kwargs(test_type, server_config, cache_manager, **kwargs):
|
def executor_kwargs(test_type, server_config, cache_manager, **kwargs):
|
||||||
|
@ -39,17 +38,16 @@ def executor_kwargs(test_type, server_config, cache_manager, **kwargs):
|
||||||
def env_options():
|
def env_options():
|
||||||
return {"host": "localhost",
|
return {"host": "localhost",
|
||||||
"bind_hostname": "true",
|
"bind_hostname": "true",
|
||||||
"testharnessreport": "testharnessreport-servo.js"}
|
"testharnessreport": "testharnessreport-servo.js",
|
||||||
|
"supports_debugger": True}
|
||||||
|
|
||||||
|
|
||||||
class ServoBrowser(NullBrowser):
|
class ServoBrowser(NullBrowser):
|
||||||
def __init__(self, logger, binary, debug_args=None, interactive=False):
|
def __init__(self, logger, binary, debug_info=None):
|
||||||
NullBrowser.__init__(self, logger)
|
NullBrowser.__init__(self, logger)
|
||||||
self.binary = binary
|
self.binary = binary
|
||||||
self.debug_args = debug_args
|
self.debug_info = debug_info
|
||||||
self.interactive = interactive
|
|
||||||
|
|
||||||
def executor_browser(self):
|
def executor_browser(self):
|
||||||
return ExecutorBrowser, {"binary": self.binary,
|
return ExecutorBrowser, {"binary": self.binary,
|
||||||
"debug_args": self.debug_args,
|
"debug_info": self.debug_info}
|
||||||
"interactive": self.interactive}
|
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
import multiprocessing
|
import multiprocessing
|
||||||
|
import signal
|
||||||
import socket
|
import socket
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
|
@ -18,6 +19,15 @@ here = os.path.split(__file__)[0]
|
||||||
serve = None
|
serve = None
|
||||||
sslutils = None
|
sslutils = None
|
||||||
|
|
||||||
|
|
||||||
|
hostnames = ["web-platform.test",
|
||||||
|
"www.web-platform.test",
|
||||||
|
"www1.web-platform.test",
|
||||||
|
"www2.web-platform.test",
|
||||||
|
"xn--n8j6ds53lwwkrqhv28a.web-platform.test",
|
||||||
|
"xn--lve-6lad.web-platform.test"]
|
||||||
|
|
||||||
|
|
||||||
def do_delayed_imports(logger, test_paths):
|
def do_delayed_imports(logger, test_paths):
|
||||||
global serve, sslutils
|
global serve, sslutils
|
||||||
|
|
||||||
|
@ -90,7 +100,7 @@ class StaticHandler(object):
|
||||||
|
|
||||||
|
|
||||||
class TestEnvironment(object):
|
class TestEnvironment(object):
|
||||||
def __init__(self, test_paths, ssl_env, pause_after_test, options):
|
def __init__(self, test_paths, ssl_env, pause_after_test, debug_info, options):
|
||||||
"""Context manager that owns the test environment i.e. the http and
|
"""Context manager that owns the test environment i.e. the http and
|
||||||
websockets servers"""
|
websockets servers"""
|
||||||
self.test_paths = test_paths
|
self.test_paths = test_paths
|
||||||
|
@ -100,11 +110,13 @@ class TestEnvironment(object):
|
||||||
self.external_config = None
|
self.external_config = None
|
||||||
self.pause_after_test = pause_after_test
|
self.pause_after_test = pause_after_test
|
||||||
self.test_server_port = options.pop("test_server_port", True)
|
self.test_server_port = options.pop("test_server_port", True)
|
||||||
|
self.debug_info = debug_info
|
||||||
self.options = options if options is not None else {}
|
self.options = options if options is not None else {}
|
||||||
|
|
||||||
self.cache_manager = multiprocessing.Manager()
|
self.cache_manager = multiprocessing.Manager()
|
||||||
self.routes = self.get_routes()
|
self.routes = self.get_routes()
|
||||||
|
|
||||||
|
|
||||||
def __enter__(self):
|
def __enter__(self):
|
||||||
self.ssl_env.__enter__()
|
self.ssl_env.__enter__()
|
||||||
self.cache_manager.__enter__()
|
self.cache_manager.__enter__()
|
||||||
|
@ -113,9 +125,12 @@ class TestEnvironment(object):
|
||||||
serve.set_computed_defaults(self.config)
|
serve.set_computed_defaults(self.config)
|
||||||
self.external_config, self.servers = serve.start(self.config, self.ssl_env,
|
self.external_config, self.servers = serve.start(self.config, self.ssl_env,
|
||||||
self.routes)
|
self.routes)
|
||||||
|
if self.options.get("supports_debugger") and self.debug_info and self.debug_info.interactive:
|
||||||
|
self.ignore_interrupts()
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||||
|
self.process_interrupts()
|
||||||
self.cache_manager.__exit__(exc_type, exc_val, exc_tb)
|
self.cache_manager.__exit__(exc_type, exc_val, exc_tb)
|
||||||
self.ssl_env.__exit__(exc_type, exc_val, exc_tb)
|
self.ssl_env.__exit__(exc_type, exc_val, exc_tb)
|
||||||
|
|
||||||
|
@ -123,6 +138,12 @@ class TestEnvironment(object):
|
||||||
for port, server in servers:
|
for port, server in servers:
|
||||||
server.kill()
|
server.kill()
|
||||||
|
|
||||||
|
def ignore_interrupts(self):
|
||||||
|
signal.signal(signal.SIGINT, signal.SIG_IGN)
|
||||||
|
|
||||||
|
def process_interrupts(self):
|
||||||
|
signal.signal(signal.SIGINT, signal.SIG_DFL)
|
||||||
|
|
||||||
def load_config(self):
|
def load_config(self):
|
||||||
default_config_path = os.path.join(serve_path(self.test_paths), "config.default.json")
|
default_config_path = os.path.join(serve_path(self.test_paths), "config.default.json")
|
||||||
local_config_path = os.path.join(here, "config.json")
|
local_config_path = os.path.join(here, "config.json")
|
||||||
|
|
|
@ -21,7 +21,7 @@ def executor_kwargs(test_type, server_config, cache_manager, **kwargs):
|
||||||
|
|
||||||
executor_kwargs = {"server_config": server_config,
|
executor_kwargs = {"server_config": server_config,
|
||||||
"timeout_multiplier": timeout_multiplier,
|
"timeout_multiplier": timeout_multiplier,
|
||||||
"debug_args": kwargs["debug_args"]}
|
"debug_info": kwargs["debug_info"]}
|
||||||
|
|
||||||
if test_type == "reftest":
|
if test_type == "reftest":
|
||||||
executor_kwargs["screenshot_cache"] = cache_manager.dict()
|
executor_kwargs["screenshot_cache"] = cache_manager.dict()
|
||||||
|
@ -81,7 +81,7 @@ class TestExecutor(object):
|
||||||
convert_result = None
|
convert_result = None
|
||||||
|
|
||||||
def __init__(self, browser, server_config, timeout_multiplier=1,
|
def __init__(self, browser, server_config, timeout_multiplier=1,
|
||||||
debug_args=None):
|
debug_info=None):
|
||||||
"""Abstract Base class for object that actually executes the tests in a
|
"""Abstract Base class for object that actually executes the tests in a
|
||||||
specific browser. Typically there will be a different TestExecutor
|
specific browser. Typically there will be a different TestExecutor
|
||||||
subclass for each test type and method of executing tests.
|
subclass for each test type and method of executing tests.
|
||||||
|
@ -97,8 +97,9 @@ class TestExecutor(object):
|
||||||
self.browser = browser
|
self.browser = browser
|
||||||
self.server_config = server_config
|
self.server_config = server_config
|
||||||
self.timeout_multiplier = timeout_multiplier
|
self.timeout_multiplier = timeout_multiplier
|
||||||
self.debug_args = debug_args
|
self.debug_info = debug_info
|
||||||
self.last_protocol = "http"
|
self.last_environment = {"protocol": "http",
|
||||||
|
"prefs": []}
|
||||||
self.protocol = None # This must be set in subclasses
|
self.protocol = None # This must be set in subclasses
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@ -123,9 +124,8 @@ class TestExecutor(object):
|
||||||
"""Run a particular test.
|
"""Run a particular test.
|
||||||
|
|
||||||
:param test: The test to run"""
|
:param test: The test to run"""
|
||||||
|
if test.environment != self.last_environment:
|
||||||
if test.protocol != self.last_protocol:
|
self.on_environment_change(test.environment)
|
||||||
self.on_protocol_change(test.protocol)
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
result = self.do_test(test)
|
result = self.do_test(test)
|
||||||
|
@ -138,7 +138,7 @@ class TestExecutor(object):
|
||||||
if result[0].status == "ERROR":
|
if result[0].status == "ERROR":
|
||||||
self.logger.debug(result[0].message)
|
self.logger.debug(result[0].message)
|
||||||
|
|
||||||
self.last_protocol = test.protocol
|
self.last_environment = test.environment
|
||||||
|
|
||||||
self.runner.send_message("test_ended", test, result)
|
self.runner.send_message("test_ended", test, result)
|
||||||
|
|
||||||
|
@ -149,17 +149,17 @@ class TestExecutor(object):
|
||||||
self.server_config["ports"][protocol][0])
|
self.server_config["ports"][protocol][0])
|
||||||
|
|
||||||
def test_url(self, test):
|
def test_url(self, test):
|
||||||
return urlparse.urljoin(self.server_url(test.protocol), test.url)
|
return urlparse.urljoin(self.server_url(test.environment["protocol"]), test.url)
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def do_test(self, test):
|
def do_test(self, test):
|
||||||
"""Test-type and protocol specific implmentation of running a
|
"""Test-type and protocol specific implementation of running a
|
||||||
specific test.
|
specific test.
|
||||||
|
|
||||||
:param test: The test to run."""
|
:param test: The test to run."""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def on_protocol_change(self, new_protocol):
|
def on_environment_change(self, new_environment):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def result_from_exception(self, test, e):
|
def result_from_exception(self, test, e):
|
||||||
|
@ -182,10 +182,10 @@ class RefTestExecutor(TestExecutor):
|
||||||
convert_result = reftest_result_converter
|
convert_result = reftest_result_converter
|
||||||
|
|
||||||
def __init__(self, browser, server_config, timeout_multiplier=1, screenshot_cache=None,
|
def __init__(self, browser, server_config, timeout_multiplier=1, screenshot_cache=None,
|
||||||
debug_args=None):
|
debug_info=None):
|
||||||
TestExecutor.__init__(self, browser, server_config,
|
TestExecutor.__init__(self, browser, server_config,
|
||||||
timeout_multiplier=timeout_multiplier,
|
timeout_multiplier=timeout_multiplier,
|
||||||
debug_args=debug_args)
|
debug_info=debug_info)
|
||||||
|
|
||||||
self.screenshot_cache = screenshot_cache
|
self.screenshot_cache = screenshot_cache
|
||||||
|
|
||||||
|
|
|
@ -62,7 +62,7 @@ class MarionetteProtocol(Protocol):
|
||||||
while True:
|
while True:
|
||||||
success = self.marionette.wait_for_port(60)
|
success = self.marionette.wait_for_port(60)
|
||||||
#When running in a debugger wait indefinitely for firefox to start
|
#When running in a debugger wait indefinitely for firefox to start
|
||||||
if success or self.executor.debug_args is None:
|
if success or self.executor.debug_info is None:
|
||||||
break
|
break
|
||||||
|
|
||||||
session_started = False
|
session_started = False
|
||||||
|
@ -131,12 +131,80 @@ class MarionetteProtocol(Protocol):
|
||||||
self.marionette.execute_async_script("");
|
self.marionette.execute_async_script("");
|
||||||
except errors.ScriptTimeoutException:
|
except errors.ScriptTimeoutException:
|
||||||
pass
|
pass
|
||||||
except (socket.timeout, errors.InvalidResponseException, IOError):
|
except (socket.timeout, IOError):
|
||||||
break
|
break
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.error(traceback.format_exc(e))
|
self.logger.error(traceback.format_exc(e))
|
||||||
break
|
break
|
||||||
|
|
||||||
|
def on_environment_change(self, old_environment, new_environment):
|
||||||
|
#Unset all the old prefs
|
||||||
|
for name, _ in old_environment.get("prefs", []):
|
||||||
|
value = self.executor.original_pref_values[name]
|
||||||
|
if value is None:
|
||||||
|
self.clear_user_pref(name)
|
||||||
|
else:
|
||||||
|
self.set_pref(name, value)
|
||||||
|
|
||||||
|
for name, value in new_environment.get("prefs", []):
|
||||||
|
self.executor.original_pref_values[name] = self.get_pref(name)
|
||||||
|
self.set_pref(name, value)
|
||||||
|
|
||||||
|
def set_pref(self, name, value):
|
||||||
|
self.logger.info("Setting pref %s (%s)" % (name, value))
|
||||||
|
self.marionette.set_context(self.marionette.CONTEXT_CHROME)
|
||||||
|
script = """
|
||||||
|
let prefInterface = Components.classes["@mozilla.org/preferences-service;1"]
|
||||||
|
.getService(Components.interfaces.nsIPrefBranch);
|
||||||
|
let pref = '%s';
|
||||||
|
let value = '%s';
|
||||||
|
let type = prefInterface.getPrefType(pref);
|
||||||
|
switch(type) {
|
||||||
|
case prefInterface.PREF_STRING:
|
||||||
|
prefInterface.setCharPref(pref, value);
|
||||||
|
break;
|
||||||
|
case prefInterface.PREF_BOOL:
|
||||||
|
prefInterface.setBoolPref(pref, value);
|
||||||
|
break;
|
||||||
|
case prefInterface.PREF_INT:
|
||||||
|
prefInterface.setIntPref(pref, value);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
""" % (name, value)
|
||||||
|
self.marionette.execute_script(script)
|
||||||
|
self.marionette.set_context(self.marionette.CONTEXT_CONTENT)
|
||||||
|
|
||||||
|
def clear_user_pref(self, name):
|
||||||
|
self.logger.info("Clearing pref %s" % (name))
|
||||||
|
self.marionette.set_context(self.marionette.CONTEXT_CHROME)
|
||||||
|
script = """
|
||||||
|
let prefInterface = Components.classes["@mozilla.org/preferences-service;1"]
|
||||||
|
.getService(Components.interfaces.nsIPrefBranch);
|
||||||
|
let pref = '%s';
|
||||||
|
prefInterface.clearUserPref(pref);
|
||||||
|
""" % name
|
||||||
|
self.marionette.execute_script(script)
|
||||||
|
self.marionette.set_context(self.marionette.CONTEXT_CONTENT)
|
||||||
|
|
||||||
|
def get_pref(self, name):
|
||||||
|
self.marionette.set_context(self.marionette.CONTEXT_CHROME)
|
||||||
|
self.marionette.execute_script("""
|
||||||
|
let prefInterface = Components.classes["@mozilla.org/preferences-service;1"]
|
||||||
|
.getService(Components.interfaces.nsIPrefBranch);
|
||||||
|
let pref = '%s';
|
||||||
|
let type = prefInterface.getPrefType(pref);
|
||||||
|
switch(type) {
|
||||||
|
case prefInterface.PREF_STRING:
|
||||||
|
return prefInterface.getCharPref(pref);
|
||||||
|
case prefInterface.PREF_BOOL:
|
||||||
|
return prefInterface.getBoolPref(pref);
|
||||||
|
case prefInterface.PREF_INT:
|
||||||
|
return prefInterface.getIntPref(pref);
|
||||||
|
case prefInterface.PREF_INVALID:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
""" % (name))
|
||||||
|
self.marionette.set_context(self.marionette.CONTEXT_CONTENT)
|
||||||
|
|
||||||
class MarionetteRun(object):
|
class MarionetteRun(object):
|
||||||
def __init__(self, logger, func, marionette, url, timeout):
|
def __init__(self, logger, func, marionette, url, timeout):
|
||||||
|
@ -159,7 +227,7 @@ class MarionetteRun(object):
|
||||||
# make that possible. It also seems to time out immediately if the
|
# make that possible. It also seems to time out immediately if the
|
||||||
# timeout is set too high. This works at least.
|
# timeout is set too high. This works at least.
|
||||||
self.marionette.set_script_timeout(2**31 - 1)
|
self.marionette.set_script_timeout(2**31 - 1)
|
||||||
except (IOError, errors.InvalidResponseException):
|
except IOError:
|
||||||
self.logger.error("Lost marionette connection before starting test")
|
self.logger.error("Lost marionette connection before starting test")
|
||||||
return Stop
|
return Stop
|
||||||
|
|
||||||
|
@ -185,7 +253,7 @@ class MarionetteRun(object):
|
||||||
except errors.ScriptTimeoutException:
|
except errors.ScriptTimeoutException:
|
||||||
self.logger.debug("Got a marionette timeout")
|
self.logger.debug("Got a marionette timeout")
|
||||||
self.result = False, ("EXTERNAL-TIMEOUT", None)
|
self.result = False, ("EXTERNAL-TIMEOUT", None)
|
||||||
except (socket.timeout, errors.InvalidResponseException, IOError):
|
except (socket.timeout, IOError):
|
||||||
# This can happen on a crash
|
# This can happen on a crash
|
||||||
# Also, should check after the test if the firefox process is still running
|
# Also, should check after the test if the firefox process is still running
|
||||||
# and otherwise ignore any other result and set it to crash
|
# and otherwise ignore any other result and set it to crash
|
||||||
|
@ -203,28 +271,33 @@ class MarionetteRun(object):
|
||||||
|
|
||||||
class MarionetteTestharnessExecutor(TestharnessExecutor):
|
class MarionetteTestharnessExecutor(TestharnessExecutor):
|
||||||
def __init__(self, browser, server_config, timeout_multiplier=1, close_after_done=True,
|
def __init__(self, browser, server_config, timeout_multiplier=1, close_after_done=True,
|
||||||
debug_args=None):
|
debug_info=None):
|
||||||
"""Marionette-based executor for testharness.js tests"""
|
"""Marionette-based executor for testharness.js tests"""
|
||||||
TestharnessExecutor.__init__(self, browser, server_config,
|
TestharnessExecutor.__init__(self, browser, server_config,
|
||||||
timeout_multiplier=timeout_multiplier,
|
timeout_multiplier=timeout_multiplier,
|
||||||
debug_args=debug_args)
|
debug_info=debug_info)
|
||||||
|
|
||||||
self.protocol = MarionetteProtocol(self, browser)
|
self.protocol = MarionetteProtocol(self, browser)
|
||||||
self.script = open(os.path.join(here, "testharness_marionette.js")).read()
|
self.script = open(os.path.join(here, "testharness_marionette.js")).read()
|
||||||
self.close_after_done = close_after_done
|
self.close_after_done = close_after_done
|
||||||
self.window_id = str(uuid.uuid4())
|
self.window_id = str(uuid.uuid4())
|
||||||
|
|
||||||
|
self.original_pref_values = {}
|
||||||
|
|
||||||
if marionette is None:
|
if marionette is None:
|
||||||
do_delayed_imports()
|
do_delayed_imports()
|
||||||
|
|
||||||
def is_alive(self):
|
def is_alive(self):
|
||||||
return self.protocol.is_alive()
|
return self.protocol.is_alive()
|
||||||
|
|
||||||
def on_protocol_change(self, new_protocol):
|
def on_environment_change(self, new_environment):
|
||||||
self.protocol.load_runner(new_protocol)
|
self.protocol.on_environment_change(self.last_environment, new_environment)
|
||||||
|
|
||||||
|
if new_environment["protocol"] != self.last_environment["protocol"]:
|
||||||
|
self.protocol.load_runner(new_environment["protocol"])
|
||||||
|
|
||||||
def do_test(self, test):
|
def do_test(self, test):
|
||||||
timeout = (test.timeout * self.timeout_multiplier if self.debug_args is None
|
timeout = (test.timeout * self.timeout_multiplier if self.debug_info is None
|
||||||
else None)
|
else None)
|
||||||
|
|
||||||
success, data = MarionetteRun(self.logger,
|
success, data = MarionetteRun(self.logger,
|
||||||
|
@ -258,18 +331,19 @@ class MarionetteTestharnessExecutor(TestharnessExecutor):
|
||||||
|
|
||||||
class MarionetteRefTestExecutor(RefTestExecutor):
|
class MarionetteRefTestExecutor(RefTestExecutor):
|
||||||
def __init__(self, browser, server_config, timeout_multiplier=1,
|
def __init__(self, browser, server_config, timeout_multiplier=1,
|
||||||
screenshot_cache=None, close_after_done=True, debug_args=None):
|
screenshot_cache=None, close_after_done=True, debug_info=None):
|
||||||
"""Marionette-based executor for reftests"""
|
"""Marionette-based executor for reftests"""
|
||||||
RefTestExecutor.__init__(self,
|
RefTestExecutor.__init__(self,
|
||||||
browser,
|
browser,
|
||||||
server_config,
|
server_config,
|
||||||
screenshot_cache=screenshot_cache,
|
screenshot_cache=screenshot_cache,
|
||||||
timeout_multiplier=timeout_multiplier,
|
timeout_multiplier=timeout_multiplier,
|
||||||
debug_args=debug_args)
|
debug_info=debug_info)
|
||||||
self.protocol = MarionetteProtocol(self, browser)
|
self.protocol = MarionetteProtocol(self, browser)
|
||||||
self.implementation = RefTestImplementation(self)
|
self.implementation = RefTestImplementation(self)
|
||||||
self.close_after_done = close_after_done
|
self.close_after_done = close_after_done
|
||||||
self.has_window = False
|
self.has_window = False
|
||||||
|
self.original_pref_values = {}
|
||||||
|
|
||||||
with open(os.path.join(here, "reftest.js")) as f:
|
with open(os.path.join(here, "reftest.js")) as f:
|
||||||
self.script = f.read()
|
self.script = f.read()
|
||||||
|
@ -279,6 +353,9 @@ class MarionetteRefTestExecutor(RefTestExecutor):
|
||||||
def is_alive(self):
|
def is_alive(self):
|
||||||
return self.protocol.is_alive()
|
return self.protocol.is_alive()
|
||||||
|
|
||||||
|
def on_environment_change(self, new_environment):
|
||||||
|
self.protocol.on_environment_change(self.last_environment, new_environment)
|
||||||
|
|
||||||
def do_test(self, test):
|
def do_test(self, test):
|
||||||
if self.close_after_done and self.has_window:
|
if self.close_after_done and self.has_window:
|
||||||
self.protocol.marionette.close()
|
self.protocol.marionette.close()
|
||||||
|
@ -296,7 +373,7 @@ class MarionetteRefTestExecutor(RefTestExecutor):
|
||||||
return self.convert_result(test, result)
|
return self.convert_result(test, result)
|
||||||
|
|
||||||
def screenshot(self, test):
|
def screenshot(self, test):
|
||||||
timeout = test.timeout if self.debug_args is None else None
|
timeout = self.timeout_multiplier * test.timeout if self.debug_info is None else None
|
||||||
|
|
||||||
test_url = self.test_url(test)
|
test_url = self.test_url(test)
|
||||||
|
|
||||||
|
|
|
@ -165,11 +165,11 @@ class SeleniumRun(object):
|
||||||
|
|
||||||
class SeleniumTestharnessExecutor(TestharnessExecutor):
|
class SeleniumTestharnessExecutor(TestharnessExecutor):
|
||||||
def __init__(self, browser, server_config, timeout_multiplier=1,
|
def __init__(self, browser, server_config, timeout_multiplier=1,
|
||||||
close_after_done=True, capabilities=None, debug_args=None):
|
close_after_done=True, capabilities=None, debug_info=None):
|
||||||
"""Selenium-based executor for testharness.js tests"""
|
"""Selenium-based executor for testharness.js tests"""
|
||||||
TestharnessExecutor.__init__(self, browser, server_config,
|
TestharnessExecutor.__init__(self, browser, server_config,
|
||||||
timeout_multiplier=timeout_multiplier,
|
timeout_multiplier=timeout_multiplier,
|
||||||
debug_args=debug_args)
|
debug_info=debug_info)
|
||||||
self.protocol = SeleniumProtocol(self, browser, capabilities)
|
self.protocol = SeleniumProtocol(self, browser, capabilities)
|
||||||
with open(os.path.join(here, "testharness_webdriver.js")) as f:
|
with open(os.path.join(here, "testharness_webdriver.js")) as f:
|
||||||
self.script = f.read()
|
self.script = f.read()
|
||||||
|
@ -206,14 +206,14 @@ class SeleniumTestharnessExecutor(TestharnessExecutor):
|
||||||
class SeleniumRefTestExecutor(RefTestExecutor):
|
class SeleniumRefTestExecutor(RefTestExecutor):
|
||||||
def __init__(self, browser, server_config, timeout_multiplier=1,
|
def __init__(self, browser, server_config, timeout_multiplier=1,
|
||||||
screenshot_cache=None, close_after_done=True,
|
screenshot_cache=None, close_after_done=True,
|
||||||
debug_args=None, capabilities=None):
|
debug_info=None, capabilities=None):
|
||||||
"""Selenium WebDriver-based executor for reftests"""
|
"""Selenium WebDriver-based executor for reftests"""
|
||||||
RefTestExecutor.__init__(self,
|
RefTestExecutor.__init__(self,
|
||||||
browser,
|
browser,
|
||||||
server_config,
|
server_config,
|
||||||
screenshot_cache=screenshot_cache,
|
screenshot_cache=screenshot_cache,
|
||||||
timeout_multiplier=timeout_multiplier,
|
timeout_multiplier=timeout_multiplier,
|
||||||
debug_args=debug_args)
|
debug_info=debug_info)
|
||||||
self.protocol = SeleniumProtocol(self, browser,
|
self.protocol = SeleniumProtocol(self, browser,
|
||||||
capabilities=capabilities)
|
capabilities=capabilities)
|
||||||
self.implementation = RefTestImplementation(self)
|
self.implementation = RefTestImplementation(self)
|
||||||
|
|
|
@ -21,6 +21,7 @@ from .base import (ExecutorException,
|
||||||
testharness_result_converter,
|
testharness_result_converter,
|
||||||
reftest_result_converter)
|
reftest_result_converter)
|
||||||
from .process import ProcessTestExecutor
|
from .process import ProcessTestExecutor
|
||||||
|
from ..browsers.base import browser_command
|
||||||
|
|
||||||
hosts_text = """127.0.0.1 web-platform.test
|
hosts_text = """127.0.0.1 web-platform.test
|
||||||
127.0.0.1 www.web-platform.test
|
127.0.0.1 www.web-platform.test
|
||||||
|
@ -39,11 +40,11 @@ def make_hosts_file():
|
||||||
class ServoTestharnessExecutor(ProcessTestExecutor):
|
class ServoTestharnessExecutor(ProcessTestExecutor):
|
||||||
convert_result = testharness_result_converter
|
convert_result = testharness_result_converter
|
||||||
|
|
||||||
def __init__(self, browser, server_config, timeout_multiplier=1, debug_args=None,
|
def __init__(self, browser, server_config, timeout_multiplier=1, debug_info=None,
|
||||||
pause_after_test=False):
|
pause_after_test=False):
|
||||||
ProcessTestExecutor.__init__(self, browser, server_config,
|
ProcessTestExecutor.__init__(self, browser, server_config,
|
||||||
timeout_multiplier=timeout_multiplier,
|
timeout_multiplier=timeout_multiplier,
|
||||||
debug_args=debug_args)
|
debug_info=debug_info)
|
||||||
self.pause_after_test = pause_after_test
|
self.pause_after_test = pause_after_test
|
||||||
self.result_data = None
|
self.result_data = None
|
||||||
self.result_flag = None
|
self.result_flag = None
|
||||||
|
@ -61,40 +62,48 @@ class ServoTestharnessExecutor(ProcessTestExecutor):
|
||||||
self.result_data = None
|
self.result_data = None
|
||||||
self.result_flag = threading.Event()
|
self.result_flag = threading.Event()
|
||||||
|
|
||||||
self.command = [self.binary, "--cpu", "--hard-fail", "-z", self.test_url(test)]
|
debug_args, command = browser_command(self.binary, ["--cpu", "--hard-fail", "-z", self.test_url(test)],
|
||||||
|
self.debug_info)
|
||||||
|
|
||||||
|
self.command = command
|
||||||
|
|
||||||
if self.pause_after_test:
|
if self.pause_after_test:
|
||||||
self.command.remove("-z")
|
self.command.remove("-z")
|
||||||
|
|
||||||
if self.debug_args:
|
self.command = debug_args + self.command
|
||||||
self.command = list(self.debug_args) + self.command
|
|
||||||
|
|
||||||
env = os.environ.copy()
|
env = os.environ.copy()
|
||||||
env["HOST_FILE"] = self.hosts_path
|
env["HOST_FILE"] = self.hosts_path
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if not self.interactive:
|
||||||
self.proc = ProcessHandler(self.command,
|
self.proc = ProcessHandler(self.command,
|
||||||
processOutputLine=[self.on_output],
|
processOutputLine=[self.on_output],
|
||||||
onFinish=self.on_finish,
|
onFinish=self.on_finish,
|
||||||
env=env)
|
env=env,
|
||||||
|
storeOutput=False)
|
||||||
|
self.proc.run()
|
||||||
|
else:
|
||||||
|
self.proc = subprocess.Popen(self.command, env=env)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self.proc.run()
|
|
||||||
|
|
||||||
timeout = test.timeout * self.timeout_multiplier
|
timeout = test.timeout * self.timeout_multiplier
|
||||||
|
|
||||||
# Now wait to get the output we expect, or until we reach the timeout
|
# Now wait to get the output we expect, or until we reach the timeout
|
||||||
if self.debug_args is None and not self.pause_after_test:
|
if not self.interactive and not self.pause_after_test:
|
||||||
wait_timeout = timeout + 5
|
wait_timeout = timeout + 5
|
||||||
|
self.result_flag.wait(wait_timeout)
|
||||||
else:
|
else:
|
||||||
wait_timeout = None
|
wait_timeout = None
|
||||||
self.result_flag.wait(wait_timeout)
|
self.proc.wait()
|
||||||
|
|
||||||
proc_is_running = True
|
proc_is_running = True
|
||||||
if self.result_flag.is_set() and self.result_data is not None:
|
if self.result_flag.is_set() and self.result_data is not None:
|
||||||
self.result_data["test"] = test.url
|
self.result_data["test"] = test.url
|
||||||
result = self.convert_result(test, self.result_data)
|
result = self.convert_result(test, self.result_data)
|
||||||
else:
|
else:
|
||||||
if self.proc.proc.poll() is not None:
|
if self.proc.poll() is not None:
|
||||||
result = (test.result_cls("CRASH", None), [])
|
result = (test.result_cls("CRASH", None), [])
|
||||||
proc_is_running = False
|
proc_is_running = False
|
||||||
else:
|
else:
|
||||||
|
@ -150,13 +159,13 @@ class ServoRefTestExecutor(ProcessTestExecutor):
|
||||||
convert_result = reftest_result_converter
|
convert_result = reftest_result_converter
|
||||||
|
|
||||||
def __init__(self, browser, server_config, binary=None, timeout_multiplier=1,
|
def __init__(self, browser, server_config, binary=None, timeout_multiplier=1,
|
||||||
screenshot_cache=None, debug_args=None, pause_after_test=False):
|
screenshot_cache=None, debug_info=None, pause_after_test=False):
|
||||||
|
|
||||||
ProcessTestExecutor.__init__(self,
|
ProcessTestExecutor.__init__(self,
|
||||||
browser,
|
browser,
|
||||||
server_config,
|
server_config,
|
||||||
timeout_multiplier=timeout_multiplier,
|
timeout_multiplier=timeout_multiplier,
|
||||||
debug_args=debug_args)
|
debug_info=debug_info)
|
||||||
|
|
||||||
self.protocol = Protocol(self, browser)
|
self.protocol = Protocol(self, browser)
|
||||||
self.screenshot_cache = screenshot_cache
|
self.screenshot_cache = screenshot_cache
|
||||||
|
|
|
@ -9,7 +9,8 @@ class ProcessTestExecutor(TestExecutor):
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
TestExecutor.__init__(self, *args, **kwargs)
|
TestExecutor.__init__(self, *args, **kwargs)
|
||||||
self.binary = self.browser.binary
|
self.binary = self.browser.binary
|
||||||
self.interactive = self.browser.interactive
|
self.interactive = (False if self.debug_info is None
|
||||||
|
else self.debug_info.interactive)
|
||||||
|
|
||||||
def setup(self, runner):
|
def setup(self, runner):
|
||||||
self.runner = runner
|
self.runner = runner
|
||||||
|
|
|
@ -107,6 +107,15 @@ class TestNode(ManifestItem):
|
||||||
except KeyError:
|
except KeyError:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
def prefs(self):
|
||||||
|
try:
|
||||||
|
prefs = self.get("prefs")
|
||||||
|
if type(prefs) in (str, unicode):
|
||||||
|
prefs = [prefs]
|
||||||
|
return [item.split(":", 1) for item in prefs]
|
||||||
|
except KeyError:
|
||||||
|
return []
|
||||||
|
|
||||||
def append(self, node):
|
def append(self, node):
|
||||||
"""Add a subtest to the current test
|
"""Add a subtest to the current test
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,7 @@ representing the file and each subnode representing a subdirectory that should
|
||||||
be included or excluded.
|
be included or excluded.
|
||||||
"""
|
"""
|
||||||
import os
|
import os
|
||||||
|
import urlparse
|
||||||
|
|
||||||
from wptmanifest.node import DataNode
|
from wptmanifest.node import DataNode
|
||||||
from wptmanifest.backends import conditional
|
from wptmanifest.backends import conditional
|
||||||
|
@ -42,7 +43,7 @@ class IncludeManifest(ManifestItem):
|
||||||
this object.
|
this object.
|
||||||
|
|
||||||
:param test: The test object"""
|
:param test: The test object"""
|
||||||
path_components = self._get_path_components(test)
|
path_components = self._get_components(test.url)
|
||||||
return self._include(test, path_components)
|
return self._include(test, path_components)
|
||||||
|
|
||||||
def _include(self, test, path_components):
|
def _include(self, test, path_components):
|
||||||
|
@ -64,21 +65,41 @@ class IncludeManifest(ManifestItem):
|
||||||
# Include by default
|
# Include by default
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def _get_path_components(self, test):
|
def _get_components(self, url):
|
||||||
test_url = test.url
|
rv = []
|
||||||
assert test_url[0] == "/"
|
url_parts = urlparse.urlsplit(url)
|
||||||
return [item for item in reversed(test_url.split("/")) if item]
|
variant = ""
|
||||||
|
if url_parts.query:
|
||||||
|
variant += "?" + url_parts.query
|
||||||
|
if url_parts.fragment:
|
||||||
|
variant += "#" + url_parts.fragment
|
||||||
|
if variant:
|
||||||
|
rv.append(variant)
|
||||||
|
rv.extend([item for item in reversed(url_parts.path.split("/")) if item])
|
||||||
|
return rv
|
||||||
|
|
||||||
def _add_rule(self, test_manifests, url, direction):
|
def _add_rule(self, test_manifests, url, direction):
|
||||||
maybe_path = os.path.abspath(os.path.join(os.curdir, url))
|
maybe_path = os.path.join(os.path.abspath(os.curdir), url)
|
||||||
|
rest, last = os.path.split(maybe_path)
|
||||||
|
variant = ""
|
||||||
|
if "#" in last:
|
||||||
|
last, fragment = last.rsplit("#", 1)
|
||||||
|
variant += "#" + fragment
|
||||||
|
if "?" in last:
|
||||||
|
last, query = last.rsplit("?", 1)
|
||||||
|
variant += "?" + query
|
||||||
|
|
||||||
|
maybe_path = os.path.join(rest, last)
|
||||||
|
|
||||||
if os.path.exists(maybe_path):
|
if os.path.exists(maybe_path):
|
||||||
for manifest, data in test_manifests.iteritems():
|
for manifest, data in test_manifests.iteritems():
|
||||||
rel_path = os.path.relpath(maybe_path, data["tests_path"])
|
rel_path = os.path.relpath(maybe_path, data["tests_path"])
|
||||||
if ".." not in rel_path.split(os.sep):
|
if ".." not in rel_path.split(os.sep):
|
||||||
url = rel_path
|
url = data["url_base"] + rel_path.replace(os.path.sep, "/") + variant
|
||||||
|
break
|
||||||
|
|
||||||
assert direction in ("include", "exclude")
|
assert direction in ("include", "exclude")
|
||||||
components = [item for item in reversed(url.split("/")) if item]
|
components = self._get_components(url)
|
||||||
|
|
||||||
node = self
|
node = self
|
||||||
while components:
|
while components:
|
||||||
|
|
|
@ -186,7 +186,7 @@ def write_new_expected(metadata_path, expected_map):
|
||||||
if not os.path.exists(dir):
|
if not os.path.exists(dir):
|
||||||
os.makedirs(dir)
|
os.makedirs(dir)
|
||||||
with open(path, "w") as f:
|
with open(path, "w") as f:
|
||||||
f.write(manifest_str.encode("utf8"))
|
f.write(manifest_str)
|
||||||
|
|
||||||
|
|
||||||
class ExpectedUpdater(object):
|
class ExpectedUpdater(object):
|
||||||
|
|
|
@ -190,8 +190,6 @@ class EqualTimeChunker(TestChunker):
|
||||||
|
|
||||||
class TestFilter(object):
|
class TestFilter(object):
|
||||||
def __init__(self, test_manifests, include=None, exclude=None, manifest_path=None):
|
def __init__(self, test_manifests, include=None, exclude=None, manifest_path=None):
|
||||||
test_manifests = test_manifests
|
|
||||||
|
|
||||||
if manifest_path is not None and include is None:
|
if manifest_path is not None and include is None:
|
||||||
self.manifest = manifestinclude.get_manifest(manifest_path)
|
self.manifest = manifestinclude.get_manifest(manifest_path)
|
||||||
else:
|
else:
|
||||||
|
@ -355,7 +353,7 @@ class TestLoader(object):
|
||||||
|
|
||||||
for test_path, test_type, test in self.iter_tests():
|
for test_path, test_type, test in self.iter_tests():
|
||||||
enabled = not test.disabled()
|
enabled = not test.disabled()
|
||||||
if not self.include_https and test.protocol == "https":
|
if not self.include_https and test.environment["protocol"] == "https":
|
||||||
enabled = False
|
enabled = False
|
||||||
key = "enabled" if enabled else "disabled"
|
key = "enabled" if enabled else "disabled"
|
||||||
tests[key][test_type].append(test)
|
tests[key][test_type].append(test)
|
||||||
|
|
|
@ -168,7 +168,7 @@ class TestRunnerManager(threading.Thread):
|
||||||
|
|
||||||
def __init__(self, suite_name, test_queue, test_source_cls, browser_cls, browser_kwargs,
|
def __init__(self, suite_name, test_queue, test_source_cls, browser_cls, browser_kwargs,
|
||||||
executor_cls, executor_kwargs, stop_flag, pause_after_test=False,
|
executor_cls, executor_kwargs, stop_flag, pause_after_test=False,
|
||||||
pause_on_unexpected=False, debug_args=None):
|
pause_on_unexpected=False, debug_info=None):
|
||||||
"""Thread that owns a single TestRunner process and any processes required
|
"""Thread that owns a single TestRunner process and any processes required
|
||||||
by the TestRunner (e.g. the Firefox binary).
|
by the TestRunner (e.g. the Firefox binary).
|
||||||
|
|
||||||
|
@ -206,7 +206,7 @@ class TestRunnerManager(threading.Thread):
|
||||||
|
|
||||||
self.pause_after_test = pause_after_test
|
self.pause_after_test = pause_after_test
|
||||||
self.pause_on_unexpected = pause_on_unexpected
|
self.pause_on_unexpected = pause_on_unexpected
|
||||||
self.debug_args = debug_args
|
self.debug_info = debug_info
|
||||||
|
|
||||||
self.manager_number = next_manager_number()
|
self.manager_number = next_manager_number()
|
||||||
|
|
||||||
|
@ -333,7 +333,7 @@ class TestRunnerManager(threading.Thread):
|
||||||
with self.init_lock:
|
with self.init_lock:
|
||||||
# Guard against problems initialising the browser or the browser
|
# Guard against problems initialising the browser or the browser
|
||||||
# remote control method
|
# remote control method
|
||||||
if self.debug_args is None:
|
if self.debug_info is None:
|
||||||
self.init_timer = threading.Timer(self.browser.init_timeout, init_failed)
|
self.init_timer = threading.Timer(self.browser.init_timeout, init_failed)
|
||||||
|
|
||||||
test_queue = self.test_source.get_queue()
|
test_queue = self.test_source.get_queue()
|
||||||
|
@ -560,7 +560,6 @@ class TestQueue(object):
|
||||||
self.test_type = test_type
|
self.test_type = test_type
|
||||||
self.tests = tests
|
self.tests = tests
|
||||||
self.kwargs = kwargs
|
self.kwargs = kwargs
|
||||||
self.queue = None
|
|
||||||
|
|
||||||
def __enter__(self):
|
def __enter__(self):
|
||||||
if not self.tests[self.test_type]:
|
if not self.tests[self.test_type]:
|
||||||
|
@ -590,7 +589,7 @@ class ManagerGroup(object):
|
||||||
executor_cls, executor_kwargs,
|
executor_cls, executor_kwargs,
|
||||||
pause_after_test=False,
|
pause_after_test=False,
|
||||||
pause_on_unexpected=False,
|
pause_on_unexpected=False,
|
||||||
debug_args=None):
|
debug_info=None):
|
||||||
"""Main thread object that owns all the TestManager threads."""
|
"""Main thread object that owns all the TestManager threads."""
|
||||||
self.suite_name = suite_name
|
self.suite_name = suite_name
|
||||||
self.size = size
|
self.size = size
|
||||||
|
@ -602,7 +601,7 @@ class ManagerGroup(object):
|
||||||
self.executor_kwargs = executor_kwargs
|
self.executor_kwargs = executor_kwargs
|
||||||
self.pause_after_test = pause_after_test
|
self.pause_after_test = pause_after_test
|
||||||
self.pause_on_unexpected = pause_on_unexpected
|
self.pause_on_unexpected = pause_on_unexpected
|
||||||
self.debug_args = debug_args
|
self.debug_info = debug_info
|
||||||
|
|
||||||
self.pool = set()
|
self.pool = set()
|
||||||
# Event that is polled by threads so that they can gracefully exit in the face
|
# Event that is polled by threads so that they can gracefully exit in the face
|
||||||
|
@ -640,7 +639,7 @@ class ManagerGroup(object):
|
||||||
self.stop_flag,
|
self.stop_flag,
|
||||||
self.pause_after_test,
|
self.pause_after_test,
|
||||||
self.pause_on_unexpected,
|
self.pause_on_unexpected,
|
||||||
self.debug_args)
|
self.debug_info)
|
||||||
manager.start()
|
manager.start()
|
||||||
self.pool.add(manager)
|
self.pool.add(manager)
|
||||||
self.wait()
|
self.wait()
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
|
import sys
|
||||||
import uuid
|
import uuid
|
||||||
|
|
||||||
from .. import testloader
|
from .. import testloader
|
||||||
|
@ -93,6 +94,14 @@ class UpdateCheckout(Step):
|
||||||
sync_tree.update(state.sync["remote_url"],
|
sync_tree.update(state.sync["remote_url"],
|
||||||
state.sync["branch"],
|
state.sync["branch"],
|
||||||
state.local_branch)
|
state.local_branch)
|
||||||
|
sync_path = os.path.abspath(sync_tree.root)
|
||||||
|
if not sync_path in sys.path:
|
||||||
|
from update import setup_paths
|
||||||
|
setup_paths(sync_path)
|
||||||
|
|
||||||
|
def restore(self, state):
|
||||||
|
assert os.path.abspath(state.sync_tree.root) in sys.path
|
||||||
|
Step.restore(self, state)
|
||||||
|
|
||||||
|
|
||||||
class GetSyncTargetCommit(Step):
|
class GetSyncTargetCommit(Step):
|
||||||
|
|
|
@ -14,7 +14,7 @@ from base import Step, StepRunner, exit_clean, exit_unclean
|
||||||
from state import State
|
from state import State
|
||||||
|
|
||||||
def setup_paths(sync_path):
|
def setup_paths(sync_path):
|
||||||
sys.path.insert(0, sync_path)
|
sys.path.insert(0, os.path.abspath(sync_path))
|
||||||
from tools import localpaths
|
from tools import localpaths
|
||||||
|
|
||||||
class LoadConfig(Step):
|
class LoadConfig(Step):
|
||||||
|
@ -117,6 +117,8 @@ class WPTUpdate(object):
|
||||||
if not kwargs["sync"]:
|
if not kwargs["sync"]:
|
||||||
setup_paths(self.serve_root)
|
setup_paths(self.serve_root)
|
||||||
else:
|
else:
|
||||||
|
if os.path.exists(kwargs["sync_path"]):
|
||||||
|
# If the sync path doesn't exist we defer this until it does
|
||||||
setup_paths(kwargs["sync_path"])
|
setup_paths(kwargs["sync_path"])
|
||||||
|
|
||||||
self.state = State(logger)
|
self.state = State(logger)
|
||||||
|
|
|
@ -25,12 +25,6 @@ def url_or_path(path):
|
||||||
else:
|
else:
|
||||||
return abs_path(path)
|
return abs_path(path)
|
||||||
|
|
||||||
def slash_prefixed(url):
|
|
||||||
if not url.startswith("/"):
|
|
||||||
url = "/" + url
|
|
||||||
return url
|
|
||||||
|
|
||||||
|
|
||||||
def require_arg(kwargs, name, value_func=None):
|
def require_arg(kwargs, name, value_func=None):
|
||||||
if value_func is None:
|
if value_func is None:
|
||||||
value_func = lambda x: x is not None
|
value_func = lambda x: x is not None
|
||||||
|
@ -97,15 +91,15 @@ def create_parser(product_choices=None):
|
||||||
nargs="*", default=["testharness", "reftest"],
|
nargs="*", default=["testharness", "reftest"],
|
||||||
choices=["testharness", "reftest"],
|
choices=["testharness", "reftest"],
|
||||||
help="Test types to run")
|
help="Test types to run")
|
||||||
test_selection_group.add_argument("--include", action="append", type=slash_prefixed,
|
test_selection_group.add_argument("--include", action="append",
|
||||||
help="URL prefix to include")
|
help="URL prefix to include")
|
||||||
test_selection_group.add_argument("--exclude", action="append", type=slash_prefixed,
|
test_selection_group.add_argument("--exclude", action="append",
|
||||||
help="URL prefix to exclude")
|
help="URL prefix to exclude")
|
||||||
test_selection_group.add_argument("--include-manifest", type=abs_path,
|
test_selection_group.add_argument("--include-manifest", type=abs_path,
|
||||||
help="Path to manifest listing tests to include")
|
help="Path to manifest listing tests to include")
|
||||||
|
|
||||||
debugging_group = parser.add_argument_group("Debugging")
|
debugging_group = parser.add_argument_group("Debugging")
|
||||||
debugging_group.add_argument('--debugger',
|
debugging_group.add_argument('--debugger', const="__default__", nargs="?",
|
||||||
help="run under a debugger, e.g. gdb or valgrind")
|
help="run under a debugger, e.g. gdb or valgrind")
|
||||||
debugging_group.add_argument('--debugger-args', help="arguments to the debugger")
|
debugging_group.add_argument('--debugger-args', help="arguments to the debugger")
|
||||||
|
|
||||||
|
@ -233,8 +227,6 @@ def exe_path(name):
|
||||||
|
|
||||||
|
|
||||||
def check_args(kwargs):
|
def check_args(kwargs):
|
||||||
from mozrunner import debugger_arguments
|
|
||||||
|
|
||||||
set_from_config(kwargs)
|
set_from_config(kwargs)
|
||||||
|
|
||||||
for test_paths in kwargs["test_paths"].itervalues():
|
for test_paths in kwargs["test_paths"].itervalues():
|
||||||
|
@ -278,16 +270,18 @@ def check_args(kwargs):
|
||||||
kwargs["processes"] = 1
|
kwargs["processes"] = 1
|
||||||
|
|
||||||
if kwargs["debugger"] is not None:
|
if kwargs["debugger"] is not None:
|
||||||
debug_args, interactive = debugger_arguments(kwargs["debugger"],
|
import mozdebug
|
||||||
|
if kwargs["debugger"] == "__default__":
|
||||||
|
kwargs["debugger"] = mozdebug.get_default_debugger_name()
|
||||||
|
debug_info = mozdebug.get_debugger_info(kwargs["debugger"],
|
||||||
kwargs["debugger_args"])
|
kwargs["debugger_args"])
|
||||||
if interactive:
|
if debug_info.interactive:
|
||||||
require_arg(kwargs, "processes", lambda x: x == 1)
|
if kwargs["processes"] != 1:
|
||||||
|
kwargs["processes"] = 1
|
||||||
kwargs["no_capture_stdio"] = True
|
kwargs["no_capture_stdio"] = True
|
||||||
kwargs["interactive"] = interactive
|
kwargs["debug_info"] = debug_info
|
||||||
kwargs["debug_args"] = debug_args
|
|
||||||
else:
|
else:
|
||||||
kwargs["interactive"] = False
|
kwargs["debug_info"] = None
|
||||||
kwargs["debug_args"] = None
|
|
||||||
|
|
||||||
if kwargs["binary"] is not None:
|
if kwargs["binary"] is not None:
|
||||||
if not os.path.exists(kwargs["binary"]):
|
if not os.path.exists(kwargs["binary"]):
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
|
|
||||||
import operator
|
import operator
|
||||||
|
|
||||||
from ..node import NodeVisitor, DataNode, ConditionalNode, KeyValueNode, ValueNode
|
from ..node import NodeVisitor, DataNode, ConditionalNode, KeyValueNode, ListNode, ValueNode
|
||||||
from ..parser import parse
|
from ..parser import parse
|
||||||
|
|
||||||
|
|
||||||
|
@ -17,13 +17,16 @@ class ConditionalValue(object):
|
||||||
self.condition_node = self.node.children[0]
|
self.condition_node = self.node.children[0]
|
||||||
self.value_node = self.node.children[1]
|
self.value_node = self.node.children[1]
|
||||||
else:
|
else:
|
||||||
assert isinstance(node, ValueNode)
|
assert isinstance(node, (ValueNode, ListNode))
|
||||||
self.condition_node = None
|
self.condition_node = None
|
||||||
self.value_node = self.node
|
self.value_node = self.node
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def value(self):
|
def value(self):
|
||||||
|
if isinstance(self.value_node, ValueNode):
|
||||||
return self.value_node.data
|
return self.value_node.data
|
||||||
|
else:
|
||||||
|
return [item.data for item in self.value_node.children]
|
||||||
|
|
||||||
@value.setter
|
@value.setter
|
||||||
def value(self, value):
|
def value(self, value):
|
||||||
|
@ -106,6 +109,9 @@ class Compiler(NodeVisitor):
|
||||||
|
|
||||||
self.output_node._add_key_value(node, key_values)
|
self.output_node._add_key_value(node, key_values)
|
||||||
|
|
||||||
|
def visit_ListNode(self, node):
|
||||||
|
return (lambda x:True, [self.visit(child) for child in node.children])
|
||||||
|
|
||||||
def visit_ValueNode(self, node):
|
def visit_ValueNode(self, node):
|
||||||
return (lambda x: True, node.data)
|
return (lambda x: True, node.data)
|
||||||
|
|
||||||
|
|
|
@ -68,6 +68,9 @@ class Compiler(NodeVisitor):
|
||||||
def visit_ValueNode(self, node):
|
def visit_ValueNode(self, node):
|
||||||
return node.data
|
return node.data
|
||||||
|
|
||||||
|
def visit_ListNode(self, node):
|
||||||
|
return [self.visit(child) for child in node.children]
|
||||||
|
|
||||||
def visit_ConditionalNode(self, node):
|
def visit_ConditionalNode(self, node):
|
||||||
assert len(node.children) == 2
|
assert len(node.children) == 2
|
||||||
if self.visit(node.children[0]):
|
if self.visit(node.children[0]):
|
||||||
|
|
|
@ -82,6 +82,12 @@ class KeyValueNode(Node):
|
||||||
self.children.append(other)
|
self.children.append(other)
|
||||||
|
|
||||||
|
|
||||||
|
class ListNode(Node):
|
||||||
|
def append(self, other):
|
||||||
|
other.parent = self
|
||||||
|
self.children.append(other)
|
||||||
|
|
||||||
|
|
||||||
class ValueNode(Node):
|
class ValueNode(Node):
|
||||||
def append(self, other):
|
def append(self, other):
|
||||||
raise TypeError
|
raise TypeError
|
||||||
|
|
|
@ -23,7 +23,12 @@ from node import *
|
||||||
|
|
||||||
|
|
||||||
class ParseError(Exception):
|
class ParseError(Exception):
|
||||||
pass
|
def __init__(self, filename, line, detail):
|
||||||
|
self.line = line
|
||||||
|
self.filename = filename
|
||||||
|
self.detail = detail
|
||||||
|
self.message = "%s: %s line %s" % (self.detail, self.filename, self.line)
|
||||||
|
Exception.__init__(self, self.message)
|
||||||
|
|
||||||
eol = object
|
eol = object
|
||||||
group_start = object
|
group_start = object
|
||||||
|
@ -41,7 +46,7 @@ operators = ["==", "!=", "not", "and", "or"]
|
||||||
|
|
||||||
|
|
||||||
def decode(byte_str):
|
def decode(byte_str):
|
||||||
return byte_str.decode("string_escape").decode("utf8")
|
return byte_str.decode("utf8")
|
||||||
|
|
||||||
|
|
||||||
def precedence(operator_node):
|
def precedence(operator_node):
|
||||||
|
@ -50,7 +55,7 @@ def precedence(operator_node):
|
||||||
|
|
||||||
class TokenTypes(object):
|
class TokenTypes(object):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
for type in ["group_start", "group_end", "paren", "separator", "ident", "string", "number", "eof"]:
|
for type in ["group_start", "group_end", "paren", "list_start", "list_end", "separator", "ident", "string", "number", "eof"]:
|
||||||
setattr(self, type, type)
|
setattr(self, type, type)
|
||||||
|
|
||||||
token_types = TokenTypes()
|
token_types = TokenTypes()
|
||||||
|
@ -70,18 +75,27 @@ class Tokenizer(object):
|
||||||
self.reset()
|
self.reset()
|
||||||
if type(stream) in types.StringTypes:
|
if type(stream) in types.StringTypes:
|
||||||
stream = StringIO(stream)
|
stream = StringIO(stream)
|
||||||
|
if not hasattr(stream, "name"):
|
||||||
|
self.filename = ""
|
||||||
|
else:
|
||||||
|
self.filename = stream.name
|
||||||
|
|
||||||
|
self.next_line_state = self.line_start_state
|
||||||
for i, line in enumerate(stream):
|
for i, line in enumerate(stream):
|
||||||
self.state = self.line_start_state
|
self.state = self.next_line_state
|
||||||
|
assert self.state is not None
|
||||||
|
states = []
|
||||||
|
self.next_line_state = None
|
||||||
self.line_number = i + 1
|
self.line_number = i + 1
|
||||||
self.index = 0
|
self.index = 0
|
||||||
self.line = line.rstrip()
|
self.line = line.rstrip()
|
||||||
if self.line:
|
|
||||||
while self.state != self.eol_state:
|
while self.state != self.eol_state:
|
||||||
|
states.append(self.state)
|
||||||
tokens = self.state()
|
tokens = self.state()
|
||||||
if tokens:
|
if tokens:
|
||||||
for token in tokens:
|
for token in tokens:
|
||||||
yield token
|
yield token
|
||||||
|
self.state()
|
||||||
while True:
|
while True:
|
||||||
yield (token_types.eof, None)
|
yield (token_types.eof, None)
|
||||||
|
|
||||||
|
@ -102,11 +116,14 @@ class Tokenizer(object):
|
||||||
self.consume()
|
self.consume()
|
||||||
|
|
||||||
def eol_state(self):
|
def eol_state(self):
|
||||||
pass
|
if self.next_line_state is None:
|
||||||
|
self.next_line_state = self.line_start_state
|
||||||
|
|
||||||
def line_start_state(self):
|
def line_start_state(self):
|
||||||
self.skip_whitespace()
|
self.skip_whitespace()
|
||||||
assert self.char() != eol
|
if self.char() == eol:
|
||||||
|
self.state = self.eol_state
|
||||||
|
return
|
||||||
if self.index > self.indent_levels[-1]:
|
if self.index > self.indent_levels[-1]:
|
||||||
self.indent_levels.append(self.index)
|
self.indent_levels.append(self.index)
|
||||||
yield (token_types.group_start, None)
|
yield (token_types.group_start, None)
|
||||||
|
@ -119,7 +136,7 @@ class Tokenizer(object):
|
||||||
# it must always be a heading or key next so we go back to data_line_state
|
# it must always be a heading or key next so we go back to data_line_state
|
||||||
self.next_state = self.data_line_state
|
self.next_state = self.data_line_state
|
||||||
if self.index != self.indent_levels[-1]:
|
if self.index != self.indent_levels[-1]:
|
||||||
raise ParseError("Unexpected indent")
|
raise ParseError(self.filename, self.line_number, "Unexpected indent")
|
||||||
|
|
||||||
self.state = self.next_state
|
self.state = self.next_state
|
||||||
|
|
||||||
|
@ -132,57 +149,44 @@ class Tokenizer(object):
|
||||||
self.state = self.key_state
|
self.state = self.key_state
|
||||||
|
|
||||||
def heading_state(self):
|
def heading_state(self):
|
||||||
index_0 = self.index
|
rv = ""
|
||||||
skip_indexes = []
|
|
||||||
while True:
|
while True:
|
||||||
c = self.char()
|
c = self.char()
|
||||||
if c == "\\":
|
if c == "\\":
|
||||||
self.consume()
|
rv += self.consume_escape()
|
||||||
c = self.char()
|
|
||||||
if c == eol:
|
|
||||||
raise ParseError("Unexpected EOL in heading")
|
|
||||||
elif c == "]":
|
|
||||||
skip_indexes.append(self.index - 1)
|
|
||||||
self.consume()
|
|
||||||
elif c == "]":
|
elif c == "]":
|
||||||
break
|
break
|
||||||
elif c == eol:
|
elif c == eol:
|
||||||
raise ParseError("EOL in heading")
|
raise ParseError(self.filename, self.line_number, "EOL in heading")
|
||||||
else:
|
else:
|
||||||
|
rv += c
|
||||||
self.consume()
|
self.consume()
|
||||||
|
|
||||||
self.state = self.line_end_state
|
yield (token_types.string, decode(rv))
|
||||||
index_1 = self.index
|
|
||||||
parts = []
|
|
||||||
min_index = index_0
|
|
||||||
for index in skip_indexes:
|
|
||||||
parts.append(self.line[min_index:index])
|
|
||||||
min_index = index + 1
|
|
||||||
parts.append(self.line[min_index:index_1])
|
|
||||||
yield (token_types.string, decode("".join(parts)))
|
|
||||||
yield (token_types.paren, "]")
|
yield (token_types.paren, "]")
|
||||||
self.consume()
|
self.consume()
|
||||||
self.state = self.line_end_state
|
self.state = self.line_end_state
|
||||||
self.next_state = self.data_line_state
|
self.next_state = self.data_line_state
|
||||||
|
|
||||||
def key_state(self):
|
def key_state(self):
|
||||||
index_0 = self.index
|
rv = ""
|
||||||
while True:
|
while True:
|
||||||
c = self.char()
|
c = self.char()
|
||||||
if c == " ":
|
if c == " ":
|
||||||
index_1 = self.index
|
|
||||||
self.skip_whitespace()
|
self.skip_whitespace()
|
||||||
if self.char() != ":":
|
if self.char() != ":":
|
||||||
raise ParseError("Space in key name")
|
raise ParseError(self.filename, self.line_number, "Space in key name")
|
||||||
break
|
break
|
||||||
elif c == ":":
|
elif c == ":":
|
||||||
index_1 = self.index
|
|
||||||
break
|
break
|
||||||
elif c == eol:
|
elif c == eol:
|
||||||
raise ParseError("EOL in key name (missing ':'?)")
|
raise ParseError(self.filename, self.line_number, "EOL in key name (missing ':'?)")
|
||||||
|
elif c == "\\":
|
||||||
|
rv += self.consume_escape()
|
||||||
else:
|
else:
|
||||||
|
rv += c
|
||||||
self.consume()
|
self.consume()
|
||||||
yield (token_types.string, decode(self.line[index_0:index_1]))
|
yield (token_types.string, decode(rv))
|
||||||
yield (token_types.separator, ":")
|
yield (token_types.separator, ":")
|
||||||
self.consume()
|
self.consume()
|
||||||
self.state = self.after_key_state
|
self.state = self.after_key_state
|
||||||
|
@ -196,39 +200,115 @@ class Tokenizer(object):
|
||||||
elif c == eol:
|
elif c == eol:
|
||||||
self.next_state = self.expr_or_value_state
|
self.next_state = self.expr_or_value_state
|
||||||
self.state = self.eol_state
|
self.state = self.eol_state
|
||||||
|
elif c == "[":
|
||||||
|
self.state = self.list_start_state
|
||||||
else:
|
else:
|
||||||
self.state = self.value_state
|
self.state = self.value_state
|
||||||
|
|
||||||
def value_state(self):
|
def list_start_state(self):
|
||||||
|
yield (token_types.list_start, "[")
|
||||||
|
self.consume()
|
||||||
|
self.state = self.list_value_start_state
|
||||||
|
|
||||||
|
def list_value_start_state(self):
|
||||||
self.skip_whitespace()
|
self.skip_whitespace()
|
||||||
index_0 = self.index
|
if self.char() == "]":
|
||||||
if self.char() in ("'", '"'):
|
self.state = self.list_end_state
|
||||||
|
elif self.char() in ("'", '"'):
|
||||||
quote_char = self.char()
|
quote_char = self.char()
|
||||||
self.consume()
|
self.consume()
|
||||||
yield (token_types.string, decode(self.read_string(quote_char)))
|
yield (token_types.string, self.consume_string(quote_char))
|
||||||
|
self.skip_whitespace()
|
||||||
|
if self.char() == "]":
|
||||||
|
self.state = self.list_end_state
|
||||||
|
elif self.char() != ",":
|
||||||
|
raise ParseError(self.filename, self.line_number, "Junk after quoted string")
|
||||||
|
self.consume()
|
||||||
|
elif self.char() == "#":
|
||||||
|
self.state = self.comment_state
|
||||||
|
self.next_line_state = self.list_value_start_state
|
||||||
|
elif self.char() == eol:
|
||||||
|
self.next_line_state = self.list_value_start_state
|
||||||
|
self.state = self.eol_state
|
||||||
|
elif self.char() == ",":
|
||||||
|
raise ParseError(self.filename, self.line_number, "List item started with separator")
|
||||||
else:
|
else:
|
||||||
index_1 = self.index
|
self.state = self.list_value_state
|
||||||
|
|
||||||
|
def list_value_state(self):
|
||||||
|
rv = ""
|
||||||
|
spaces = 0
|
||||||
while True:
|
while True:
|
||||||
c = self.char()
|
c = self.char()
|
||||||
if c == "\\":
|
if c == "\\":
|
||||||
|
escape = self.consume_escape()
|
||||||
|
rv += escape
|
||||||
|
elif c == eol:
|
||||||
|
raise ParseError(self.filename, self.line_number, "EOL in list value")
|
||||||
|
elif c == "#":
|
||||||
|
raise ParseError(self.filename, self.line_number, "EOL in list value (comment)")
|
||||||
|
elif c == ",":
|
||||||
|
self.state = self.list_value_start_state
|
||||||
self.consume()
|
self.consume()
|
||||||
if self.char() == eol:
|
break
|
||||||
raise ParseError("EOL in character escape")
|
elif c == " ":
|
||||||
|
spaces += 1
|
||||||
|
self.consume()
|
||||||
|
elif c == "]":
|
||||||
|
self.state = self.list_end_state
|
||||||
|
self.consume()
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
rv += " " * spaces
|
||||||
|
spaces = 0
|
||||||
|
rv += c
|
||||||
|
self.consume()
|
||||||
|
|
||||||
|
if rv:
|
||||||
|
yield (token_types.string, decode(rv))
|
||||||
|
|
||||||
|
def list_end_state(self):
|
||||||
|
self.consume()
|
||||||
|
yield (token_types.list_end, "]")
|
||||||
|
self.state = self.line_end_state
|
||||||
|
|
||||||
|
def value_state(self):
|
||||||
|
self.skip_whitespace()
|
||||||
|
if self.char() in ("'", '"'):
|
||||||
|
quote_char = self.char()
|
||||||
|
self.consume()
|
||||||
|
yield (token_types.string, self.consume_string(quote_char))
|
||||||
|
if self.char() == "#":
|
||||||
|
self.state = self.comment_state
|
||||||
|
else:
|
||||||
|
self.state = self.line_end_state
|
||||||
|
else:
|
||||||
|
rv = ""
|
||||||
|
spaces = 0
|
||||||
|
while True:
|
||||||
|
c = self.char()
|
||||||
|
if c == "\\":
|
||||||
|
rv += self.consume_escape()
|
||||||
elif c == "#":
|
elif c == "#":
|
||||||
self.state = self.comment_state
|
self.state = self.comment_state
|
||||||
break
|
break
|
||||||
elif c == " ":
|
elif c == " ":
|
||||||
# prevent whitespace before comments from being included in the value
|
# prevent whitespace before comments from being included in the value
|
||||||
pass
|
spaces += 1
|
||||||
|
self.consume()
|
||||||
elif c == eol:
|
elif c == eol:
|
||||||
|
self.state = self.line_end_state
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
index_1 = self.index
|
rv += " " * spaces
|
||||||
|
spaces = 0
|
||||||
|
rv += c
|
||||||
self.consume()
|
self.consume()
|
||||||
yield (token_types.string, decode(self.line[index_0:index_1 + 1]))
|
yield (token_types.string, decode(rv))
|
||||||
self.state = self.line_end_state
|
|
||||||
|
|
||||||
def comment_state(self):
|
def comment_state(self):
|
||||||
|
while self.char() is not eol:
|
||||||
|
self.consume()
|
||||||
self.state = self.eol_state
|
self.state = self.eol_state
|
||||||
|
|
||||||
def line_end_state(self):
|
def line_end_state(self):
|
||||||
|
@ -239,26 +319,24 @@ class Tokenizer(object):
|
||||||
elif c == eol:
|
elif c == eol:
|
||||||
self.state = self.eol_state
|
self.state = self.eol_state
|
||||||
else:
|
else:
|
||||||
raise ParseError("Junk before EOL c")
|
raise ParseError(self.filename, self.line_number, "Junk before EOL %s" % c)
|
||||||
|
|
||||||
def read_string(self, quote_char):
|
def consume_string(self, quote_char):
|
||||||
index_0 = self.index
|
rv = ""
|
||||||
while True:
|
while True:
|
||||||
c = self.char()
|
c = self.char()
|
||||||
if c == "\\":
|
if c == "\\":
|
||||||
self.consume()
|
rv += self.consume_escape()
|
||||||
if self.char == eol:
|
|
||||||
raise ParseError("EOL following quote")
|
|
||||||
self.consume()
|
|
||||||
elif c == quote_char:
|
elif c == quote_char:
|
||||||
|
self.consume()
|
||||||
break
|
break
|
||||||
elif c == eol:
|
elif c == eol:
|
||||||
raise ParseError("EOL in quoted string")
|
raise ParseError(self.filename, self.line_number, "EOL in quoted string")
|
||||||
else:
|
else:
|
||||||
|
rv += c
|
||||||
self.consume()
|
self.consume()
|
||||||
rv = self.line[index_0:self.index]
|
|
||||||
self.consume()
|
return decode(rv)
|
||||||
return rv
|
|
||||||
|
|
||||||
def expr_or_value_state(self):
|
def expr_or_value_state(self):
|
||||||
if self.peek(3) == "if ":
|
if self.peek(3) == "if ":
|
||||||
|
@ -270,12 +348,12 @@ class Tokenizer(object):
|
||||||
self.skip_whitespace()
|
self.skip_whitespace()
|
||||||
c = self.char()
|
c = self.char()
|
||||||
if c == eol:
|
if c == eol:
|
||||||
raise ParseError("EOL in expression")
|
raise ParseError(self.filename, self.line_number, "EOL in expression")
|
||||||
elif c in "'\"":
|
elif c in "'\"":
|
||||||
self.consume()
|
self.consume()
|
||||||
yield (token_types.string, decode(self.read_string(c)))
|
yield (token_types.string, self.consume_string(c))
|
||||||
elif c == "#":
|
elif c == "#":
|
||||||
raise ParseError("Comment before end of expression")
|
raise ParseError(self.filename, self.line_number, "Comment before end of expression")
|
||||||
elif c == ":":
|
elif c == ":":
|
||||||
yield (token_types.separator, c)
|
yield (token_types.separator, c)
|
||||||
self.consume()
|
self.consume()
|
||||||
|
@ -315,7 +393,7 @@ class Tokenizer(object):
|
||||||
self.consume()
|
self.consume()
|
||||||
elif c == ".":
|
elif c == ".":
|
||||||
if seen_dot:
|
if seen_dot:
|
||||||
raise ParseError("Invalid number")
|
raise ParseError(self.filename, self.line_number, "Invalid number")
|
||||||
self.consume()
|
self.consume()
|
||||||
seen_dot = True
|
seen_dot = True
|
||||||
elif c in parens:
|
elif c in parens:
|
||||||
|
@ -327,7 +405,7 @@ class Tokenizer(object):
|
||||||
elif c == ":":
|
elif c == ":":
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
raise ParseError("Invalid character in number")
|
raise ParseError(self.filename, self.line_number, "Invalid character in number")
|
||||||
|
|
||||||
self.state = self.expr_state
|
self.state = self.expr_state
|
||||||
yield (token_types.number, self.line[index_0:self.index])
|
yield (token_types.number, self.line[index_0:self.index])
|
||||||
|
@ -353,6 +431,44 @@ class Tokenizer(object):
|
||||||
self.state = self.expr_state
|
self.state = self.expr_state
|
||||||
yield (token_types.ident, self.line[index_0:self.index])
|
yield (token_types.ident, self.line[index_0:self.index])
|
||||||
|
|
||||||
|
def consume_escape(self):
|
||||||
|
assert self.char() == "\\"
|
||||||
|
self.consume()
|
||||||
|
c = self.char()
|
||||||
|
self.consume()
|
||||||
|
if c == "x":
|
||||||
|
return self.decode_escape(2)
|
||||||
|
elif c == "u":
|
||||||
|
return self.decode_escape(4)
|
||||||
|
elif c == "U":
|
||||||
|
return self.decode_escape(6)
|
||||||
|
elif c in ["a", "b", "f", "n", "r", "t", "v"]:
|
||||||
|
return eval("'\%s'" % c)
|
||||||
|
elif c is eol:
|
||||||
|
raise ParseError(self.filename, self.line_number, "EOL in escape")
|
||||||
|
else:
|
||||||
|
return c
|
||||||
|
|
||||||
|
def decode_escape(self, length):
|
||||||
|
value = 0
|
||||||
|
for i in xrange(length):
|
||||||
|
c = self.char()
|
||||||
|
value *= 16
|
||||||
|
value += self.escape_value(c)
|
||||||
|
self.consume()
|
||||||
|
|
||||||
|
return unichr(value).encode("utf8")
|
||||||
|
|
||||||
|
def escape_value(self, c):
|
||||||
|
if '0' <= c <= '9':
|
||||||
|
return ord(c) - ord('0')
|
||||||
|
elif 'a' <= c <= 'f':
|
||||||
|
return ord(c) - ord('a') + 10
|
||||||
|
elif 'A' <= c <= 'F':
|
||||||
|
return ord(c) - ord('A') + 10
|
||||||
|
else:
|
||||||
|
raise ParseError(self.filename, self.line_number, "Invalid character escape")
|
||||||
|
|
||||||
|
|
||||||
class Parser(object):
|
class Parser(object):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
@ -417,7 +533,10 @@ class Parser(object):
|
||||||
self.expect(token_types.group_end)
|
self.expect(token_types.group_end)
|
||||||
|
|
||||||
def value_block(self):
|
def value_block(self):
|
||||||
if self.token[0] == token_types.string:
|
if self.token[0] == token_types.list_start:
|
||||||
|
self.consume()
|
||||||
|
self.list_value()
|
||||||
|
elif self.token[0] == token_types.string:
|
||||||
self.value()
|
self.value()
|
||||||
elif self.token[0] == token_types.group_start:
|
elif self.token[0] == token_types.group_start:
|
||||||
self.consume()
|
self.consume()
|
||||||
|
@ -428,6 +547,13 @@ class Parser(object):
|
||||||
else:
|
else:
|
||||||
raise ParseError
|
raise ParseError
|
||||||
|
|
||||||
|
def list_value(self):
|
||||||
|
self.tree.append(ListNode())
|
||||||
|
while self.token[0] == token_types.string:
|
||||||
|
self.value()
|
||||||
|
self.expect(token_types.list_end)
|
||||||
|
self.tree.pop()
|
||||||
|
|
||||||
def expression_values(self):
|
def expression_values(self):
|
||||||
while self.token == (token_types.ident, "if"):
|
while self.token == (token_types.ident, "if"):
|
||||||
self.consume()
|
self.consume()
|
||||||
|
@ -446,7 +572,7 @@ class Parser(object):
|
||||||
self.tree.pop()
|
self.tree.pop()
|
||||||
|
|
||||||
def expr_start(self):
|
def expr_start(self):
|
||||||
self.expr_builder = ExpressionBuilder()
|
self.expr_builder = ExpressionBuilder(self.tokenizer)
|
||||||
self.expr_builders.append(self.expr_builder)
|
self.expr_builders.append(self.expr_builder)
|
||||||
self.expr()
|
self.expr()
|
||||||
expression = self.expr_builder.finish()
|
expression = self.expr_builder.finish()
|
||||||
|
@ -486,14 +612,14 @@ class Parser(object):
|
||||||
self.expr_builder.push_operator(UnaryOperatorNode(self.token[1]))
|
self.expr_builder.push_operator(UnaryOperatorNode(self.token[1]))
|
||||||
self.consume()
|
self.consume()
|
||||||
else:
|
else:
|
||||||
raise ParseError()
|
raise ParseError(self.filename, self.tokenizer.line_number, "Expected unary operator")
|
||||||
|
|
||||||
def expr_bin_op(self):
|
def expr_bin_op(self):
|
||||||
if self.token[1] in binary_operators:
|
if self.token[1] in binary_operators:
|
||||||
self.expr_builder.push_operator(BinaryOperatorNode(self.token[1]))
|
self.expr_builder.push_operator(BinaryOperatorNode(self.token[1]))
|
||||||
self.consume()
|
self.consume()
|
||||||
else:
|
else:
|
||||||
raise ParseError()
|
raise ParseError(self.filename, self.tokenizer.line_number, "Expected binary operator")
|
||||||
|
|
||||||
def expr_value(self):
|
def expr_value(self):
|
||||||
node_type = {token_types.string: StringNode,
|
node_type = {token_types.string: StringNode,
|
||||||
|
@ -528,9 +654,10 @@ class Treebuilder(object):
|
||||||
|
|
||||||
|
|
||||||
class ExpressionBuilder(object):
|
class ExpressionBuilder(object):
|
||||||
def __init__(self):
|
def __init__(self, tokenizer):
|
||||||
self.operands = []
|
self.operands = []
|
||||||
self.operators = [None]
|
self.operators = [None]
|
||||||
|
self.tokenizer = tokenizer
|
||||||
|
|
||||||
def finish(self):
|
def finish(self):
|
||||||
while self.operators[-1] is not None:
|
while self.operators[-1] is not None:
|
||||||
|
@ -546,7 +673,8 @@ class ExpressionBuilder(object):
|
||||||
while self.operators[-1] is not None:
|
while self.operators[-1] is not None:
|
||||||
self.pop_operator()
|
self.pop_operator()
|
||||||
if not self.operators:
|
if not self.operators:
|
||||||
raise ParseError("Unbalanced parens")
|
raise ParseError(self.tokenizer.filename, self.tokenizer.line,
|
||||||
|
"Unbalanced parens")
|
||||||
|
|
||||||
assert self.operators.pop() is None
|
assert self.operators.pop() is None
|
||||||
|
|
||||||
|
|
|
@ -2,15 +2,25 @@
|
||||||
# License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
# License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||||
# You can obtain one at http://mozilla.org/MPL/2.0/.
|
# You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
from node import NodeVisitor, ValueNode, BinaryExpressionNode
|
from node import NodeVisitor, ValueNode, ListNode, BinaryExpressionNode
|
||||||
from parser import precedence
|
from parser import precedence
|
||||||
|
|
||||||
|
named_escapes = set(["\a", "\b", "\f", "\n", "\r", "\t", "\v"])
|
||||||
|
|
||||||
def escape(string, extras=""):
|
def escape(string, extras=""):
|
||||||
rv = string.encode("utf8").encode("string_escape")
|
rv = ""
|
||||||
for extra in extras:
|
for c in string:
|
||||||
rv = rv.replace(extra, "\\" + extra)
|
if c in named_escapes:
|
||||||
return rv
|
rv += c.encode("unicode_escape")
|
||||||
|
elif c == "\\":
|
||||||
|
rv += "\\\\"
|
||||||
|
elif c < '\x20':
|
||||||
|
rv += "\\x%02x" % ord(c)
|
||||||
|
elif c in extras:
|
||||||
|
rv += "\\" + c
|
||||||
|
else:
|
||||||
|
rv += c
|
||||||
|
return rv.encode("utf8")
|
||||||
|
|
||||||
|
|
||||||
class ManifestSerializer(NodeVisitor):
|
class ManifestSerializer(NodeVisitor):
|
||||||
|
@ -42,34 +52,48 @@ class ManifestSerializer(NodeVisitor):
|
||||||
return rv
|
return rv
|
||||||
|
|
||||||
def visit_KeyValueNode(self, node):
|
def visit_KeyValueNode(self, node):
|
||||||
rv = [node.data + ":"]
|
rv = [escape(node.data, ":") + ":"]
|
||||||
indent = " " * self.indent
|
indent = " " * self.indent
|
||||||
|
|
||||||
if len(node.children) == 1 and isinstance(node.children[0], ValueNode):
|
if len(node.children) == 1 and isinstance(node.children[0], (ValueNode, ListNode)):
|
||||||
rv[0] += " %s" % escape(self.visit(node.children[0])[0])
|
rv[0] += " %s" % self.visit(node.children[0])[0]
|
||||||
else:
|
else:
|
||||||
for child in node.children:
|
for child in node.children:
|
||||||
rv.append(indent + self.visit(child)[0])
|
rv.append(indent + self.visit(child)[0])
|
||||||
|
|
||||||
return rv
|
return rv
|
||||||
|
|
||||||
|
def visit_ListNode(self, node):
|
||||||
|
rv = ["["]
|
||||||
|
rv.extend(", ".join(self.visit(child)[0] for child in node.children))
|
||||||
|
rv.append("]")
|
||||||
|
return ["".join(rv)]
|
||||||
|
|
||||||
def visit_ValueNode(self, node):
|
def visit_ValueNode(self, node):
|
||||||
return [escape(node.data)]
|
if "#" in node.data or (isinstance(node.parent, ListNode) and
|
||||||
|
("," in node.data or "]" in node.data)):
|
||||||
|
if "\"" in node.data:
|
||||||
|
quote = "'"
|
||||||
|
else:
|
||||||
|
quote = "\""
|
||||||
|
else:
|
||||||
|
quote = ""
|
||||||
|
return [quote + escape(node.data, extras=quote) + quote]
|
||||||
|
|
||||||
def visit_ConditionalNode(self, node):
|
def visit_ConditionalNode(self, node):
|
||||||
return ["if %s: %s" % tuple(self.visit(item)[0] for item in node.children)]
|
return ["if %s: %s" % tuple(self.visit(item)[0] for item in node.children)]
|
||||||
|
|
||||||
def visit_StringNode(self, node):
|
def visit_StringNode(self, node):
|
||||||
rv = ["\"%s\"" % node.data]
|
rv = ["\"%s\"" % escape(node.data, extras="\"")]
|
||||||
for child in node.children:
|
for child in node.children:
|
||||||
rv[0] += self.visit(child)[0]
|
rv[0] += self.visit(child)[0]
|
||||||
return rv
|
return rv
|
||||||
|
|
||||||
def visit_NumberNode(self, node):
|
def visit_NumberNode(self, node):
|
||||||
return [node.data]
|
return [str(node.data)]
|
||||||
|
|
||||||
def visit_VariableNode(self, node):
|
def visit_VariableNode(self, node):
|
||||||
rv = node.data
|
rv = escape(node.data)
|
||||||
for child in node.children:
|
for child in node.children:
|
||||||
rv += self.visit(child)
|
rv += self.visit(child)
|
||||||
return [rv]
|
return [rv]
|
||||||
|
@ -100,10 +124,10 @@ class ManifestSerializer(NodeVisitor):
|
||||||
return [" ".join(children)]
|
return [" ".join(children)]
|
||||||
|
|
||||||
def visit_UnaryOperatorNode(self, node):
|
def visit_UnaryOperatorNode(self, node):
|
||||||
return [node.data]
|
return [str(node.data)]
|
||||||
|
|
||||||
def visit_BinaryOperatorNode(self, node):
|
def visit_BinaryOperatorNode(self, node):
|
||||||
return [node.data]
|
return [str(node.data)]
|
||||||
|
|
||||||
|
|
||||||
def serialize(tree, *args, **kwargs):
|
def serialize(tree, *args, **kwargs):
|
||||||
|
|
|
@ -15,12 +15,12 @@ class TokenizerTest(unittest.TestCase):
|
||||||
self.parser = parser.Parser()
|
self.parser = parser.Parser()
|
||||||
|
|
||||||
def serialize(self, input_str):
|
def serialize(self, input_str):
|
||||||
return self.serializer.serialize(self.parser.parse(StringIO(input_str)))
|
return self.serializer.serialize(self.parser.parse(input_str))
|
||||||
|
|
||||||
def compare(self, input_str, expected=None):
|
def compare(self, input_str, expected=None):
|
||||||
if expected is None:
|
if expected is None:
|
||||||
expected = input_str
|
expected = input_str
|
||||||
|
expected = expected.encode("utf8")
|
||||||
actual = self.serialize(input_str)
|
actual = self.serialize(input_str)
|
||||||
self.assertEquals(actual, expected)
|
self.assertEquals(actual, expected)
|
||||||
|
|
||||||
|
@ -114,11 +114,98 @@ class TokenizerTest(unittest.TestCase):
|
||||||
|
|
||||||
[Heading 2]
|
[Heading 2]
|
||||||
other_key: other_value
|
other_key: other_value
|
||||||
"""
|
""")
|
||||||
)
|
|
||||||
|
|
||||||
def test_11(self):
|
def test_11(self):
|
||||||
self.compare("""key:
|
self.compare("""key:
|
||||||
if not a and b and c and d: true
|
if not a and b and c and d: true
|
||||||
"""
|
""")
|
||||||
)
|
|
||||||
|
def test_12(self):
|
||||||
|
self.compare("""[Heading 1]
|
||||||
|
key: [a:1, b:2]
|
||||||
|
""")
|
||||||
|
|
||||||
|
def test_13(self):
|
||||||
|
self.compare("""key: [a:1, "b:#"]
|
||||||
|
""")
|
||||||
|
|
||||||
|
def test_14(self):
|
||||||
|
self.compare("""key: [","]
|
||||||
|
""")
|
||||||
|
|
||||||
|
def test_15(self):
|
||||||
|
self.compare("""key: ,
|
||||||
|
""")
|
||||||
|
|
||||||
|
def test_16(self):
|
||||||
|
self.compare("""key: ["]", b]
|
||||||
|
""")
|
||||||
|
|
||||||
|
def test_17(self):
|
||||||
|
self.compare("""key: ]
|
||||||
|
""")
|
||||||
|
|
||||||
|
def test_18(self):
|
||||||
|
self.compare("""key: \]
|
||||||
|
""", """key: ]
|
||||||
|
""")
|
||||||
|
|
||||||
|
def test_escape_0(self):
|
||||||
|
self.compare(r"""k\t\:y: \a\b\f\n\r\t\v""",
|
||||||
|
r"""k\t\:y: \x07\x08\x0c\n\r\t\x0b
|
||||||
|
""")
|
||||||
|
|
||||||
|
def test_escape_1(self):
|
||||||
|
self.compare(r"""k\x00: \x12A\x45""",
|
||||||
|
r"""k\x00: \x12AE
|
||||||
|
""")
|
||||||
|
|
||||||
|
def test_escape_2(self):
|
||||||
|
self.compare(r"""k\u0045y: \u1234A\uABc6""",
|
||||||
|
u"""kEy: \u1234A\uabc6
|
||||||
|
""")
|
||||||
|
|
||||||
|
def test_escape_3(self):
|
||||||
|
self.compare(r"""k\u0045y: \u1234A\uABc6""",
|
||||||
|
u"""kEy: \u1234A\uabc6
|
||||||
|
""")
|
||||||
|
|
||||||
|
def test_escape_4(self):
|
||||||
|
self.compare(r"""key: '\u1234A\uABc6'""",
|
||||||
|
u"""key: \u1234A\uabc6
|
||||||
|
""")
|
||||||
|
|
||||||
|
def test_escape_5(self):
|
||||||
|
self.compare(r"""key: [\u1234A\uABc6]""",
|
||||||
|
u"""key: [\u1234A\uabc6]
|
||||||
|
""")
|
||||||
|
|
||||||
|
def test_escape_6(self):
|
||||||
|
self.compare(r"""key: [\u1234A\uABc6\,]""",
|
||||||
|
u"""key: ["\u1234A\uabc6,"]
|
||||||
|
""")
|
||||||
|
|
||||||
|
def test_escape_7(self):
|
||||||
|
self.compare(r"""key: [\,\]\#]""",
|
||||||
|
r"""key: [",]#"]
|
||||||
|
""")
|
||||||
|
|
||||||
|
def test_escape_8(self):
|
||||||
|
self.compare(r"""key: \#""",
|
||||||
|
r"""key: "#"
|
||||||
|
""")
|
||||||
|
|
||||||
|
def test_escape_9(self):
|
||||||
|
self.compare(r"""key: \U10FFFFabc""",
|
||||||
|
u"""key: \U0010FFFFabc
|
||||||
|
""")
|
||||||
|
|
||||||
|
def test_escape_10(self):
|
||||||
|
self.compare(r"""key: \u10FFab""",
|
||||||
|
u"""key: \u10FFab
|
||||||
|
""")
|
||||||
|
|
||||||
|
def test_escape_11(self):
|
||||||
|
self.compare(r"""key: \\ab
|
||||||
|
""")
|
||||||
|
|
|
@ -65,7 +65,7 @@ class TokenizerTest(unittest.TestCase):
|
||||||
(token_types.paren, "]")])
|
(token_types.paren, "]")])
|
||||||
|
|
||||||
def test_heading_6(self):
|
def test_heading_6(self):
|
||||||
self.compare("""[Heading \\ttext]""",
|
self.compare(r"""[Heading \ttext]""",
|
||||||
[(token_types.paren, "["),
|
[(token_types.paren, "["),
|
||||||
(token_types.string, "Heading \ttext"),
|
(token_types.string, "Heading \ttext"),
|
||||||
(token_types.paren, "]")])
|
(token_types.paren, "]")])
|
||||||
|
@ -142,6 +142,76 @@ class TokenizerTest(unittest.TestCase):
|
||||||
with self.assertRaises(parser.ParseError):
|
with self.assertRaises(parser.ParseError):
|
||||||
self.tokenize("""key: 'value' abc""")
|
self.tokenize("""key: 'value' abc""")
|
||||||
|
|
||||||
|
def test_key_14(self):
|
||||||
|
self.compare(r"""key: \\nb""",
|
||||||
|
[(token_types.string, "key"),
|
||||||
|
(token_types.separator, ":"),
|
||||||
|
(token_types.string, r"\nb")])
|
||||||
|
|
||||||
|
def test_list_0(self):
|
||||||
|
self.compare(
|
||||||
|
"""
|
||||||
|
key: []""",
|
||||||
|
[(token_types.string, "key"),
|
||||||
|
(token_types.separator, ":"),
|
||||||
|
(token_types.list_start, "["),
|
||||||
|
(token_types.list_end, "]")])
|
||||||
|
|
||||||
|
def test_list_1(self):
|
||||||
|
self.compare(
|
||||||
|
"""
|
||||||
|
key: [a, "b"]""",
|
||||||
|
[(token_types.string, "key"),
|
||||||
|
(token_types.separator, ":"),
|
||||||
|
(token_types.list_start, "["),
|
||||||
|
(token_types.string, "a"),
|
||||||
|
(token_types.string, "b"),
|
||||||
|
(token_types.list_end, "]")])
|
||||||
|
|
||||||
|
def test_list_2(self):
|
||||||
|
self.compare(
|
||||||
|
"""
|
||||||
|
key: [a,
|
||||||
|
b]""",
|
||||||
|
[(token_types.string, "key"),
|
||||||
|
(token_types.separator, ":"),
|
||||||
|
(token_types.list_start, "["),
|
||||||
|
(token_types.string, "a"),
|
||||||
|
(token_types.string, "b"),
|
||||||
|
(token_types.list_end, "]")])
|
||||||
|
|
||||||
|
def test_list_3(self):
|
||||||
|
self.compare(
|
||||||
|
"""
|
||||||
|
key: [a, #b]
|
||||||
|
c]""",
|
||||||
|
[(token_types.string, "key"),
|
||||||
|
(token_types.separator, ":"),
|
||||||
|
(token_types.list_start, "["),
|
||||||
|
(token_types.string, "a"),
|
||||||
|
(token_types.string, "c"),
|
||||||
|
(token_types.list_end, "]")])
|
||||||
|
|
||||||
|
def test_list_4(self):
|
||||||
|
with self.assertRaises(parser.ParseError):
|
||||||
|
self.tokenize("""key: [a #b]
|
||||||
|
c]""")
|
||||||
|
|
||||||
|
def test_list_5(self):
|
||||||
|
with self.assertRaises(parser.ParseError):
|
||||||
|
self.tokenize("""key: [a \\
|
||||||
|
c]""")
|
||||||
|
|
||||||
|
def test_list_6(self):
|
||||||
|
self.compare(
|
||||||
|
"""key: [a , b]""",
|
||||||
|
[(token_types.string, "key"),
|
||||||
|
(token_types.separator, ":"),
|
||||||
|
(token_types.list_start, "["),
|
||||||
|
(token_types.string, "a"),
|
||||||
|
(token_types.string, "b"),
|
||||||
|
(token_types.list_end, "]")])
|
||||||
|
|
||||||
def test_expr_0(self):
|
def test_expr_0(self):
|
||||||
self.compare(
|
self.compare(
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -134,6 +134,7 @@ def run_tests(config, test_paths, product, **kwargs):
|
||||||
with env.TestEnvironment(test_paths,
|
with env.TestEnvironment(test_paths,
|
||||||
ssl_env,
|
ssl_env,
|
||||||
kwargs["pause_after_test"],
|
kwargs["pause_after_test"],
|
||||||
|
kwargs["debug_info"],
|
||||||
env_options) as test_environment:
|
env_options) as test_environment:
|
||||||
try:
|
try:
|
||||||
test_environment.ensure_started()
|
test_environment.ensure_started()
|
||||||
|
@ -180,7 +181,7 @@ def run_tests(config, test_paths, product, **kwargs):
|
||||||
executor_kwargs,
|
executor_kwargs,
|
||||||
kwargs["pause_after_test"],
|
kwargs["pause_after_test"],
|
||||||
kwargs["pause_on_unexpected"],
|
kwargs["pause_on_unexpected"],
|
||||||
kwargs["debug_args"]) as manager_group:
|
kwargs["debug_info"]) as manager_group:
|
||||||
try:
|
try:
|
||||||
manager_group.run(test_type, test_loader.tests)
|
manager_group.run(test_type, test_loader.tests)
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
|
|
|
@ -90,7 +90,11 @@ class Test(object):
|
||||||
self._expected_metadata = expected_metadata
|
self._expected_metadata = expected_metadata
|
||||||
self.timeout = timeout
|
self.timeout = timeout
|
||||||
self.path = path
|
self.path = path
|
||||||
self.protocol = protocol
|
if expected_metadata:
|
||||||
|
prefs = expected_metadata.prefs()
|
||||||
|
else:
|
||||||
|
prefs = []
|
||||||
|
self.environment = {"protocol": protocol, "prefs": prefs}
|
||||||
|
|
||||||
def __eq__(self, other):
|
def __eq__(self, other):
|
||||||
return self.id == other.id
|
return self.id == other.id
|
||||||
|
@ -102,7 +106,7 @@ class Test(object):
|
||||||
expected_metadata,
|
expected_metadata,
|
||||||
timeout=timeout,
|
timeout=timeout,
|
||||||
path=manifest_item.path,
|
path=manifest_item.path,
|
||||||
protocol="https" if manifest_item.https else "http")
|
protocol="https" if hasattr(manifest_item, "https") and manifest_item.https else "http")
|
||||||
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@ -165,14 +169,12 @@ class ReftestTest(Test):
|
||||||
result_cls = ReftestResult
|
result_cls = ReftestResult
|
||||||
|
|
||||||
def __init__(self, url, expected, references, timeout=DEFAULT_TIMEOUT, path=None, protocol="http"):
|
def __init__(self, url, expected, references, timeout=DEFAULT_TIMEOUT, path=None, protocol="http"):
|
||||||
self.url = url
|
Test.__init__(self, url, expected, timeout, path, protocol)
|
||||||
|
|
||||||
for _, ref_type in references:
|
for _, ref_type in references:
|
||||||
if ref_type not in ("==", "!="):
|
if ref_type not in ("==", "!="):
|
||||||
raise ValueError
|
raise ValueError
|
||||||
self._expected_metadata = expected
|
|
||||||
self.timeout = timeout
|
|
||||||
self.path = path
|
|
||||||
self.protocol = protocol
|
|
||||||
self.references = references
|
self.references = references
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
@ -196,7 +198,7 @@ class ReftestTest(Test):
|
||||||
[],
|
[],
|
||||||
timeout=timeout,
|
timeout=timeout,
|
||||||
path=manifest_test.path,
|
path=manifest_test.path,
|
||||||
protocol="https" if manifest_test.https else "http")
|
protocol="https" if hasattr(manifest_test, "https") and manifest_test.https else "http")
|
||||||
|
|
||||||
nodes[url] = node
|
nodes[url] = node
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue