Update web-platform-tests to revision b9d4748d6a7f9b21bd420486955b44349aa005ea

This commit is contained in:
WPT Sync Bot 2019-05-30 10:23:26 +00:00
parent 0a9a222356
commit 28ed236c3a
76 changed files with 2158 additions and 581 deletions

View file

@ -153,7 +153,7 @@ class MtimeCache(CacheFile):
def __init__(self, cache_root, tests_root, manifest_path, rebuild=False):
self.manifest_path = manifest_path
super(MtimeCache, self).__init__(cache_root, tests_root, rebuild=False)
super(MtimeCache, self).__init__(cache_root, tests_root, rebuild)
def updated(self, rel_path, stat):
"""Return a boolean indicating whether the file changed since the cache was last updated.

View file

@ -179,7 +179,11 @@ class Firefox(Browser):
binary = find_executable("firefox", os.path.join(path, "firefox"))
elif self.platform == "win":
import mozinstall
binary = mozinstall.get_binary(path, "firefox")
try:
binary = mozinstall.get_binary(path, "firefox")
except mozinstall.InvalidBinary:
# ignore the case where we fail to get a binary
pass
elif self.platform == "macos":
binary = find_executable("firefox", os.path.join(path, self.application_name.get(channel, "Firefox Nightly.app"),
"Contents", "MacOS"))

View file

@ -125,6 +125,11 @@ def check_environ(product):
else:
hosts_path = "/etc/hosts"
if os.path.abspath(os.curdir) == wpt_root:
wpt_path = "wpt"
else:
wpt_path = os.path.join(wpt_root, "wpt")
with open(hosts_path, "r") as f:
for line in f:
line = line.split("#", 1)[0].strip()
@ -136,13 +141,14 @@ def check_environ(product):
if is_windows:
message = """Missing hosts file configuration. Run
python wpt make-hosts-file | Out-File %s -Encoding ascii -Append
python %s make-hosts-file | Out-File %s -Encoding ascii -Append
in PowerShell with Administrator privileges.""" % hosts_path
in PowerShell with Administrator privileges.""" % (wpt_path, hosts_path)
else:
message = """Missing hosts file configuration. Run
./wpt make-hosts-file | sudo tee -a %s""" % hosts_path
%s make-hosts-file | sudo tee -a %s""" % ("./wpt" if wpt_path == "wpt" else wpt_path,
hosts_path)
raise WptrunError(message)

View file

@ -85,7 +85,7 @@ class Virtualenv(object):
def install(self, *requirements):
try:
self.working_set.require(*requirements)
except pkg_resources.ResolutionError:
except Exception:
pass
else:
return
@ -98,7 +98,7 @@ class Virtualenv(object):
with open(requirements_path) as f:
try:
self.working_set.require(f.read())
except pkg_resources.ResolutionError:
except Exception:
pass
else:
return

View file

@ -3,7 +3,7 @@ from six.moves.urllib.parse import urljoin
from collections import deque
from wptmanifest.backends import static
from wptmanifest.backends.static import ManifestItem
from wptmanifest.backends.base import ManifestItem
import expected
@ -198,7 +198,7 @@ def fuzzy_prop(node):
class ExpectedManifest(ManifestItem):
def __init__(self, name, test_path, url_base):
def __init__(self, node, test_path, url_base):
"""Object representing all the tests in a particular manifest
:param name: Name of the AST Node associated with this object.
@ -207,13 +207,14 @@ class ExpectedManifest(ManifestItem):
:param test_path: Path of the test file associated with this manifest.
:param url_base: Base url for serving the tests in this manifest
"""
name = node.data
if name is not None:
raise ValueError("ExpectedManifest should represent the root node")
if test_path is None:
raise ValueError("ExpectedManifest requires a test path")
if url_base is None:
raise ValueError("ExpectedManifest requires a base url")
ManifestItem.__init__(self, name)
ManifestItem.__init__(self, node)
self.child_map = {}
self.test_path = test_path
self.url_base = url_base
@ -339,12 +340,12 @@ class DirectoryManifest(ManifestItem):
class TestNode(ManifestItem):
def __init__(self, name):
def __init__(self, node, **kwargs):
"""Tree node associated with a particular test in a manifest
:param name: name of the test"""
assert name is not None
ManifestItem.__init__(self, name)
assert node.data is not None
ManifestItem.__init__(self, node, **kwargs)
self.updated_expected = []
self.new_expected = []
self.subtests = {}
@ -431,12 +432,6 @@ class TestNode(ManifestItem):
class SubtestNode(TestNode):
def __init__(self, name):
"""Tree node associated with a particular subtest in a manifest
:param name: name of the subtest"""
TestNode.__init__(self, name)
@property
def is_empty(self):
if self._data:

View file

@ -7,9 +7,7 @@ from six.moves.queue import Empty
from collections import namedtuple
from multiprocessing import Process, current_process, Queue
from mozlog import structuredlog
import wptlogging
from mozlog import structuredlog, capture
# Special value used as a sentinal in various commands
Stop = object()
@ -142,7 +140,7 @@ def start_runner(runner_command_queue, runner_result_queue,
logger = MessageLogger(send_message)
with wptlogging.CaptureIO(logger, capture_stdio):
with capture.CaptureIO(logger, capture_stdio):
try:
browser = executor_browser_cls(**executor_browser_kwargs)
executor = executor_cls(browser, **executor_kwargs)

View file

@ -1,12 +1,9 @@
import logging
import sys
import threading
from six import StringIO
from multiprocessing import Queue
from mozlog import commandline, stdadapter, set_default_logger
from mozlog.structuredlog import StructuredLogger
def setup(args, defaults):
logger = args.pop('log', None)
if logger:
@ -49,86 +46,3 @@ class LogLevelRewriter(object):
data = data.copy()
data["level"] = self.to_level
return self.inner(data)
class LogThread(threading.Thread):
def __init__(self, queue, logger, level):
self.queue = queue
self.log_func = getattr(logger, level)
threading.Thread.__init__(self, name="Thread-Log")
self.daemon = True
def run(self):
while True:
try:
msg = self.queue.get()
except (EOFError, IOError):
break
if msg is None:
break
else:
self.log_func(msg)
class LoggingWrapper(StringIO):
"""Wrapper for file like objects to redirect output to logger
instead"""
def __init__(self, queue, prefix=None):
StringIO.__init__(self)
self.queue = queue
self.prefix = prefix
def write(self, data):
if isinstance(data, str):
try:
data = data.decode("utf8")
except UnicodeDecodeError:
data = data.encode("string_escape").decode("ascii")
if data.endswith("\n"):
data = data[:-1]
if data.endswith("\r"):
data = data[:-1]
if not data:
return
if self.prefix is not None:
data = "%s: %s" % (self.prefix, data)
self.queue.put(data)
def flush(self):
pass
class CaptureIO(object):
def __init__(self, logger, do_capture):
self.logger = logger
self.do_capture = do_capture
self.logging_queue = None
self.logging_thread = None
self.original_stdio = None
def __enter__(self):
if self.do_capture:
self.original_stdio = (sys.stdout, sys.stderr)
self.logging_queue = Queue()
self.logging_thread = LogThread(self.logging_queue, self.logger, "info")
sys.stdout = LoggingWrapper(self.logging_queue, prefix="STDOUT")
sys.stderr = LoggingWrapper(self.logging_queue, prefix="STDERR")
self.logging_thread.start()
def __exit__(self, *args, **kwargs):
if self.do_capture:
sys.stdout, sys.stderr = self.original_stdio
if self.logging_queue is not None:
self.logger.info("Closing logging queue")
self.logging_queue.put(None)
if self.logging_thread is not None:
self.logging_thread.join(10)
while not self.logging_queue.empty():
try:
self.logger.warning("Dropping log message: %r", self.logging_queue.get())
except Exception:
pass
self.logging_queue.close()
self.logger.info("queue closed")

View file

@ -0,0 +1,222 @@
import abc
from ..node import NodeVisitor
from ..parser import parse
class Compiler(NodeVisitor):
__metaclass__ = abc.ABCMeta
def compile(self, tree, data_cls_getter=None, **kwargs):
self._kwargs = kwargs
return self._compile(tree, data_cls_getter, **kwargs)
def _compile(self, tree, data_cls_getter=None, **kwargs):
"""Compile a raw AST into a form where conditional expressions
are represented by ConditionalValue objects that can be evaluated
at runtime.
tree - The root node of the wptmanifest AST to compile
data_cls_getter - A function taking two parameters; the previous
output node and the current ast node and returning
the class of the output node to use for the current
ast node
"""
if data_cls_getter is None:
self.data_cls_getter = lambda x, y: ManifestItem
else:
self.data_cls_getter = data_cls_getter
self.tree = tree
self.output_node = self._initial_output_node(tree, **kwargs)
self.visit(tree)
if hasattr(self.output_node, "set_defaults"):
self.output_node.set_defaults()
assert self.output_node is not None
return self.output_node
def _initial_output_node(self, node, **kwargs):
return self.data_cls_getter(None, None)(node, **kwargs)
def visit_DataNode(self, node):
if node != self.tree:
output_parent = self.output_node
self.output_node = self.data_cls_getter(self.output_node, node)(node, **self._kwargs)
else:
output_parent = None
assert self.output_node is not None
for child in node.children:
self.visit(child)
if output_parent is not None:
# Append to the parent *after* processing all the node data
output_parent.append(self.output_node)
self.output_node = self.output_node.parent
assert self.output_node is not None
@abc.abstractmethod
def visit_KeyValueNode(self, node):
pass
def visit_ListNode(self, node):
return [self.visit(child) for child in node.children]
def visit_ValueNode(self, node):
return node.data
def visit_AtomNode(self, node):
return node.data
@abc.abstractmethod
def visit_ConditionalNode(self, node):
pass
def visit_StringNode(self, node):
indexes = [self.visit(child) for child in node.children]
def value(x):
rv = node.data
for index in indexes:
rv = rv[index(x)]
return rv
return value
def visit_NumberNode(self, node):
if "." in node.data:
return float(node.data)
else:
return int(node.data)
def visit_VariableNode(self, node):
indexes = [self.visit(child) for child in node.children]
def value(x):
data = x[node.data]
for index in indexes:
data = data[index(x)]
return data
return value
def visit_IndexNode(self, node):
assert len(node.children) == 1
return self.visit(node.children[0])
@abc.abstractmethod
def visit_UnaryExpressionNode(self, node):
pass
@abc.abstractmethod
def visit_BinaryExpressionNode(self, node):
pass
@abc.abstractmethod
def visit_UnaryOperatorNode(self, node):
pass
@abc.abstractmethod
def visit_BinaryOperatorNode(self, node):
pass
class ManifestItem(object):
def __init__(self, node, **kwargs):
self.parent = None
self.node = node
self.children = []
self._data = {}
def __repr__(self):
return "<%s %s>" % (self.__class__, self.node.data)
def __str__(self):
rv = [repr(self)]
for item in self.children:
rv.extend(" %s" % line for line in str(item).split("\n"))
return "\n".join(rv)
def set_defaults(self):
pass
@property
def is_empty(self):
if self._data:
return False
return all(child.is_empty for child in self.children)
@property
def root(self):
node = self
while node.parent is not None:
node = node.parent
return node
@property
def name(self):
return self.node.data
def get(self, key):
for node in [self, self.root]:
if key in node._data:
return node._data[key]
raise KeyError
def set(self, name, value):
self._data[name] = value
def remove(self):
if self.parent:
self.parent.children.remove(self)
self.parent = None
def iterchildren(self, name=None):
for item in self.children:
if item.name == name or name is None:
yield item
def has_key(self, key):
for node in [self, self.root]:
if key in node._data:
return True
return False
def _flatten(self):
rv = {}
for node in [self, self.root]:
for name, value in node._data.iteritems():
if name not in rv:
rv[name] = value
return rv
def iteritems(self):
for item in self._flatten().iteritems():
yield item
def iterkeys(self):
for item in self._flatten().iterkeys():
yield item
def itervalues(self):
for item in self._flatten().itervalues():
yield item
def append(self, child):
child.parent = self
self.children.append(child)
return child
def compile_ast(compiler, ast, data_cls_getter=None, **kwargs):
return compiler().compile(ast,
data_cls_getter=data_cls_getter,
**kwargs)
def compile(compiler, stream, data_cls_getter=None, **kwargs):
return compile_ast(compiler,
parse(stream),
data_cls_getter=data_cls_getter,
**kwargs)

View file

@ -1,10 +1,10 @@
import operator
from ..node import NodeVisitor
from . import base
from ..parser import parse
class Compiler(NodeVisitor):
class Compiler(base.Compiler):
"""Compiler backend that evaluates conditional expressions
to give static output"""
@ -26,29 +26,7 @@ class Compiler(NodeVisitor):
self._kwargs = kwargs
self.expr_data = expr_data
if data_cls_getter is None:
self.data_cls_getter = lambda x, y: ManifestItem
else:
self.data_cls_getter = data_cls_getter
self.output_node = None
self.visit(tree)
return self.output_node
def visit_DataNode(self, node):
output_parent = self.output_node
if self.output_node is None:
assert node.parent is None
self.output_node = self.data_cls_getter(None, None)(None, **self._kwargs)
else:
self.output_node = self.data_cls_getter(self.output_node, node)(node.data)
for child in node.children:
self.visit(child)
if output_parent is not None:
output_parent.append(self.output_node)
self.output_node = self.output_node.parent
return self._compile(tree, data_cls_getter, **kwargs)
def visit_KeyValueNode(self, node):
key_name = node.data
@ -61,15 +39,6 @@ class Compiler(NodeVisitor):
if key_value is not None:
self.output_node.set(key_name, key_value)
def visit_ValueNode(self, node):
return node.data
def visit_AtomNode(self, node):
return node.data
def visit_ListNode(self, node):
return [self.visit(child) for child in node.children]
def visit_ConditionalNode(self, node):
assert len(node.children) == 2
if self.visit(node.children[0]):
@ -81,12 +50,6 @@ class Compiler(NodeVisitor):
value = self.visit(child)(value)
return value
def visit_NumberNode(self, node):
if "." in node.data:
return float(node.data)
else:
return int(node.data)
def visit_VariableNode(self, node):
value = self.expr_data[node.data]
for child in node.children:
@ -123,92 +86,6 @@ class Compiler(NodeVisitor):
"!=": operator.ne}[node.data]
class ManifestItem(object):
def __init__(self, name, **kwargs):
self.parent = None
self.name = name
self.children = []
self._data = {}
def __repr__(self):
return "<static.ManifestItem %s>" % (self.name)
def __str__(self):
rv = [repr(self)]
for item in self.children:
rv.extend(" %s" % line for line in str(item).split("\n"))
return "\n".join(rv)
def set_defaults(self):
pass
@property
def is_empty(self):
if self._data:
return False
return all(child.is_empty for child in self.children)
@property
def root(self):
node = self
while node.parent is not None:
node = node.parent
return node
def has_key(self, key):
for node in [self, self.root]:
if key in node._data:
return True
return False
def get(self, key):
for node in [self, self.root]:
if key in node._data:
return node._data[key]
raise KeyError
def set(self, name, value):
self._data[name] = value
def remove(self):
if self.parent:
self.parent._remove_child(self)
def _remove_child(self, child):
self.children.remove(child)
child.parent = None
def iterchildren(self, name=None):
for item in self.children:
if item.name == name or name is None:
yield item
def _flatten(self):
rv = {}
for node in [self, self.root]:
for name, value in node._data.iteritems():
if name not in rv:
rv[name] = value
return rv
def iteritems(self):
for item in self._flatten().iteritems():
yield item
def iterkeys(self):
for item in self._flatten().iterkeys():
yield item
def itervalues(self):
for item in self._flatten().itervalues():
yield item
def append(self, child):
child.parent = self
self.children.append(child)
return child
def compile_ast(ast, expr_data, data_cls_getter=None, **kwargs):
return Compiler().compile(ast,
expr_data,

View file

@ -12,6 +12,7 @@ import testloader
import wptcommandline
import wptlogging
import wpttest
from mozlog import capture
from font import FontInstaller
from testrunner import ManagerGroup
from browsers.base import NullBrowser
@ -132,7 +133,7 @@ def get_pause_after_test(test_loader, **kwargs):
def run_tests(config, test_paths, product, **kwargs):
with wptlogging.CaptureIO(logger, not kwargs["no_capture_stdio"]):
with capture.CaptureIO(logger, not kwargs["no_capture_stdio"]):
env.do_delayed_imports(logger, test_paths)
product = products.load_product(config, product, load_cls=True)

View file

@ -71,16 +71,13 @@ decorator::
Python File Handlers
--------------------
Python file handlers are designed to provide a vaguely PHP-like interface
where each resource corresponds to a particular python file on the
filesystem. Typically this is hooked up to a route like ``("*",
"*.py", python_file_handler)``, meaning that any .py file will be
treated as a handler file (note that this makes python files unsafe in
much the same way that .php files are when using PHP).
Python file handlers are Python files which the server executes in response to
requests made to the corresponding URL. This is hooked up to a route like
``("*", "*.py", python_file_handler)``, meaning that any .py file will be
treated as a handler file (note that this makes it easy to write unsafe
handlers, particularly when running the server in a web-exposed setting).
Unlike PHP, the python files don't work by outputting text to stdout
from the global scope. Instead they must define a single function
`main` with the signature::
The Python files must define a single function `main` with the signature::
main(request, response)

View file

@ -1,163 +1,6 @@
Pipes
======
Pipes are functions that may be used when serving files to alter parts
of the response. These are invoked by adding a pipe= query parameter
taking a | separated list of pipe functions and parameters. The pipe
functions are applied to the response from left to right. For example::
GET /sample.txt?pipe=slice(1,200)|status(404).
This would serve bytes 1 to 199, inclusive, of foo.txt with the HTTP status
code 404.
.. note::
If you write directly to the response socket using ResponseWriter,
or when using the asis handler, only the trickle pipe will affect the response.
There are several built-in pipe functions, and it is possible to add
more using the `@pipe` decorator on a function, if required.
.. note::
Because of the way pipes compose, using some pipe functions prevents the
content-length of the response from being known in advance. In these cases
the server will close the connection to indicate the end of the response,
preventing the use of HTTP 1.1 keepalive.
Built-In Pipes
--------------
sub
~~~
Used to substitute variables from the server environment, or from the
request into the response.
Substitutions are marked in a file using a block delimited by `{{`
and `}}`. Inside the block the following variables are available:
`{{host}}`
The host name of the server excluding any subdomain part.
`{{domains[]}}`
The domain name of a particular subdomain
e.g. `{{domains[www]}}` for the `www` subdomain.
`{{ports[][]}}`
The port number of servers, by protocol
e.g. `{{ports[http][0]}}` for the first (and, depending on setup,
possibly only) http server
`{{headers[]}}`
The HTTP headers in the request
e.g. `{{headers[X-Test]}}` for a hypothetical `X-Test` header.
`{{header_or_default(header, default)}}`
The value of an HTTP header, or a default value if it is absent.
e.g. `{{header_or_default(X-Test, test-header-absent)}}`
`{{GET[]}}`
The query parameters for the request
e.g. `{{GET[id]}}` for an id parameter sent with the request.
So, for example, to write a JavaScript file called `xhr.js` that
depends on the host name of the server, without hardcoding, one might
write::
var server_url = http://{{host}}:{{ports[http][0]}}/path/to/resource;
//Create the actual XHR and so on
The file would then be included as:
<script src="xhr.js?pipe=sub"></script>
This pipe can also be enabled by using a filename `*.sub.ext`, e.g. the file above could be called `xhr.sub.js`.
status
~~~~~~
Used to set the HTTP status of the response, for example::
example.js?pipe=status(410)
headers
~~~~~~~
Used to add or replace http headers in the response. Takes two or
three arguments; the header name, the header value and whether to
append the header rather than replace an existing header (default:
False). So, for example, a request for::
example.html?pipe=header(Content-Type,text/plain)
causes example.html to be returned with a text/plain content type
whereas::
example.html?pipe=header(Content-Type,text/plain,True)
Will cause example.html to be returned with both text/html and
text/plain content-type headers.
slice
~~~~~
Used to send only part of a response body. Takes the start and,
optionally, end bytes as arguments, although either can be null to
indicate the start or end of the file, respectively. So for example::
example.txt?pipe=slice(10,20)
Would result in a response with a body containing 10 bytes of
example.txt including byte 10 but excluding byte 20.
::
example.txt?pipe=slice(10)
Would cause all bytes from byte 10 of example.txt to be sent, but::
example.txt?pipe=slice(null,20)
Would send the first 20 bytes of example.txt.
trickle
~~~~~~~
.. note::
Using this function will force a connection close.
Used to send the body of a response in chunks with delays. Takes a
single argument that is a microsyntax consisting of colon-separated
commands. There are three types of commands:
* Bare numbers represent a number of bytes to send
* Numbers prefixed `d` indicate a delay in seconds
* Numbers prefixed `r` must only appear at the end of the command, and
indicate that the preceding N items must be repeated until there is
no more content to send. The number of items to repeat must be even.
In the absence of a repetition command, the entire remainder of the content is
sent at once when the command list is exhausted. So for example::
example.txt?pipe=trickle(d1)
causes a 1s delay before sending the entirety of example.txt.
::
example.txt?pipe=trickle(100:d1)
causes 100 bytes of example.txt to be sent, followed by a 1s delay,
and then the remainder of the file to be sent. On the other hand::
example.txt?pipe=trickle(100:d1:r2)
Will cause the file to be sent in 100 byte chunks separated by a 1s
delay until the whole content has been sent.
:mod:`Interface <pipes>`
------------------------