mirror of
https://github.com/servo/servo.git
synced 2025-09-05 20:48:22 +01:00
Update web-platform-tests to revision ddfc95cf0493ae147a4f6a4d7be8eff1a0c23098
This commit is contained in:
parent
1f6a864ab5
commit
7e6290451f
832 changed files with 16026 additions and 2649 deletions
|
@ -132,11 +132,6 @@ def get_sha1():
|
|||
return git("rev-parse", "HEAD").strip()
|
||||
|
||||
|
||||
def install_wptrunner():
|
||||
"""Install wptrunner."""
|
||||
call("pip", "install", wptrunner_root)
|
||||
|
||||
|
||||
def deepen_checkout(user):
|
||||
"""Convert from a shallow checkout to a full one"""
|
||||
fetch_args = [user, "+refs/heads/*:refs/remotes/origin/*"]
|
||||
|
|
|
@ -16,6 +16,7 @@ fi
|
|||
|
||||
if [[ $(./wpt test-jobs --includes wptrunner_unittest; echo $?) -eq 0 ]]; then
|
||||
if [ $TOXENV == "py27" ] || [ $TOXENV == "pypy" ]; then
|
||||
TOXENV="$TOXENV,py27-flake8"
|
||||
cd tools/wptrunner
|
||||
tox
|
||||
fi
|
||||
|
|
|
@ -70,8 +70,8 @@ class XMLParser(object):
|
|||
def _end(self, tag):
|
||||
return self._target.end(_fixname(tag))
|
||||
|
||||
def _external(self, context, base, systemId, publicId):
|
||||
if publicId in {
|
||||
def _external(self, context, base, system_id, public_id):
|
||||
if public_id in {
|
||||
"-//W3C//DTD XHTML 1.0 Transitional//EN",
|
||||
"-//W3C//DTD XHTML 1.1//EN",
|
||||
"-//W3C//DTD XHTML 1.0 Strict//EN",
|
||||
|
|
|
@ -2,7 +2,7 @@ import json
|
|||
import os
|
||||
import re
|
||||
from collections import defaultdict
|
||||
from six import iteritems, itervalues, viewkeys
|
||||
from six import iteritems, itervalues, viewkeys, string_types
|
||||
|
||||
from .item import ManualTest, WebdriverSpecTest, Stub, RefTestNode, RefTest, TestharnessTest, SupportFile, ConformanceCheckerTest, VisualTest
|
||||
from .log import get_logger
|
||||
|
@ -20,14 +20,6 @@ class ManifestVersionMismatch(ManifestError):
|
|||
pass
|
||||
|
||||
|
||||
def sourcefile_items(args):
|
||||
tests_root, url_base, rel_path, status = args
|
||||
source_file = SourceFile(tests_root,
|
||||
rel_path,
|
||||
url_base)
|
||||
return rel_path, source_file.manifest_items()
|
||||
|
||||
|
||||
class Manifest(object):
|
||||
def __init__(self, url_base="/"):
|
||||
assert url_base is not None
|
||||
|
@ -221,7 +213,7 @@ def load(tests_root, manifest):
|
|||
logger = get_logger()
|
||||
|
||||
# "manifest" is a path or file-like object.
|
||||
if isinstance(manifest, basestring):
|
||||
if isinstance(manifest, string_types):
|
||||
if os.path.exists(manifest):
|
||||
logger.debug("Opening manifest at %s" % manifest)
|
||||
else:
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
# flake8: noqa
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
import argparse
|
||||
|
|
|
@ -1,9 +1,3 @@
|
|||
def get_logger(name="ssl"):
|
||||
logger = structured.get_default_logger(name)
|
||||
if logger is None:
|
||||
logger = structured.structuredlog.StructuredLogger(name)
|
||||
return logger
|
||||
|
||||
class NoSSLEnvironment(object):
|
||||
ssl_enabled = False
|
||||
|
||||
|
|
|
@ -3,8 +3,12 @@ envlist = py27,py36,pypy
|
|||
skipsdist=True
|
||||
|
||||
[testenv]
|
||||
# flake8 versions should be kept in sync across tools/tox.ini, tools/wpt/tox.ini, and tools/wptrunner/tox.ini
|
||||
deps =
|
||||
flake8
|
||||
flake8==3.5.0
|
||||
pycodestyle==2.3.1
|
||||
pyflakes==1.6.0
|
||||
pep8-naming==0.4.1
|
||||
pytest
|
||||
pytest-cov
|
||||
mock
|
||||
|
@ -19,6 +23,28 @@ passenv =
|
|||
HYPOTHESIS_PROFILE
|
||||
|
||||
[flake8]
|
||||
ignore = E128,E129,E221,E226,E231,E251,E265,E302,E303,E305,E402,E901,F401,F821,F841
|
||||
# flake8 config should be kept in sync across tools/tox.ini, tools/wpt/tox.ini, and tools/wptrunner/tox.ini
|
||||
select = E,W,F,N
|
||||
# E128: continuation line under-indented for visual indent
|
||||
# E129: visually indented line with same indent as next logical line
|
||||
# E221: multiple spaces before operator
|
||||
# E226: missing whitespace around arithmetic operator
|
||||
# E231: missing whitespace after ‘,’, ‘;’, or ‘:’
|
||||
# E251: unexpected spaces around keyword / parameter equals
|
||||
# E265: block comment should start with ‘# ‘
|
||||
# E302: expected 2 blank lines, found 0
|
||||
# E303: too many blank lines (3)
|
||||
# E305: expected 2 blank lines after end of function or class
|
||||
# E402: module level import not at top of file
|
||||
# E731: do not assign a lambda expression, use a def
|
||||
# E901: SyntaxError or IndentationError
|
||||
# W601: .has_key() is deprecated, use ‘in’
|
||||
# F401: module imported but unused
|
||||
# F403: ‘from module import *’ used; unable to detect undefined names
|
||||
# F405: name may be undefined, or defined from star imports: module
|
||||
# F841: local variable name is assigned to but never used
|
||||
# N801: class names should use CapWords convention
|
||||
# N802: function name should be lowercase
|
||||
ignore = E128,E129,E221,E226,E231,E251,E265,E302,E303,E305,E402,E731,E901,W601,F401,F403,F405,F841,N801,N802
|
||||
max-line-length = 141
|
||||
exclude = .tox,html5lib,third_party/py,third_party/pytest,third_party/funcsigs,third_party/attrs,third_party/pluggy/,pywebsocket,six,_venv,webencodings,wptserve/docs,wptserve/tests/functional/docroot/,wpt,wptrunner
|
||||
|
|
|
@ -4,6 +4,8 @@ import error
|
|||
import protocol
|
||||
import transport
|
||||
|
||||
from six import string_types
|
||||
|
||||
from mozlog import get_default_logger
|
||||
|
||||
logger = get_default_logger()
|
||||
|
@ -313,7 +315,7 @@ class Cookies(object):
|
|||
cookie = {"name": name,
|
||||
"value": None}
|
||||
|
||||
if isinstance(name, (str, unicode)):
|
||||
if isinstance(name, string_types):
|
||||
cookie["value"] = value
|
||||
elif hasattr(value, "value"):
|
||||
cookie["value"] = value.value
|
||||
|
|
|
@ -16,7 +16,7 @@ class Encoder(json.JSONEncoder):
|
|||
return [self.default(x) for x in obj]
|
||||
elif isinstance(obj, webdriver.Element):
|
||||
return {webdriver.Element.identifier: obj.id}
|
||||
return super(ProtocolEncoder, self).default(obj)
|
||||
return super(Encoder, self).default(obj)
|
||||
|
||||
|
||||
class Decoder(json.JSONDecoder):
|
||||
|
|
|
@ -1,18 +0,0 @@
|
|||
class ServoExtensionCommands(object):
|
||||
def __init__(self, session):
|
||||
self.session = session
|
||||
|
||||
@command
|
||||
def get_prefs(self, *prefs):
|
||||
body = {"prefs": list(prefs)}
|
||||
return self.session.send_command("POST", "servo/prefs/get", body)
|
||||
|
||||
@command
|
||||
def set_prefs(self, prefs):
|
||||
body = {"prefs": prefs}
|
||||
return self.session.send_command("POST", "servo/prefs/set", body)
|
||||
|
||||
@command
|
||||
def reset_prefs(self, *prefs):
|
||||
body = {"prefs": list(prefs)}
|
||||
return self.session.send_command("POST", "servo/prefs/reset", body)
|
|
@ -4,6 +4,7 @@ import urlparse
|
|||
|
||||
import error
|
||||
|
||||
from six import text_type
|
||||
|
||||
"""Implements HTTP transport for the WebDriver wire protocol."""
|
||||
|
||||
|
@ -135,7 +136,7 @@ class HTTPWireProtocol(object):
|
|||
except ValueError:
|
||||
raise ValueError("Failed to encode request body as JSON:\n"
|
||||
"%s" % json.dumps(body, indent=2))
|
||||
if isinstance(payload, unicode):
|
||||
if isinstance(payload, text_type):
|
||||
payload = body.encode("utf-8")
|
||||
|
||||
if headers is None:
|
||||
|
|
|
@ -4,9 +4,12 @@ import platform
|
|||
import re
|
||||
import shutil
|
||||
import stat
|
||||
import subprocess
|
||||
import sys
|
||||
from abc import ABCMeta, abstractmethod
|
||||
from ConfigParser import RawConfigParser
|
||||
from distutils.spawn import find_executable
|
||||
from io import BytesIO
|
||||
|
||||
from utils import call, get, untar, unzip
|
||||
|
||||
|
@ -278,6 +281,61 @@ class Chrome(Browser):
|
|||
logger.critical("dbus not running and can't be started")
|
||||
sys.exit(1)
|
||||
|
||||
class ChromeAndroid(Browser):
|
||||
"""Chrome-specific interface for android.
|
||||
|
||||
Includes installation, webdriver installation, and wptrunner setup methods.
|
||||
"""
|
||||
|
||||
product = "chrome_android"
|
||||
requirements = "requirements_chrome_android.txt"
|
||||
|
||||
def install(self, dest=None):
|
||||
raise NotImplementedError
|
||||
|
||||
def platform_string(self):
|
||||
raise NotImplementedError
|
||||
|
||||
def find_webdriver(self):
|
||||
return find_executable("chromedriver")
|
||||
|
||||
def install_webdriver(self, dest=None):
|
||||
"""Install latest Webdriver."""
|
||||
if dest is None:
|
||||
dest = os.pwd
|
||||
latest = get("http://chromedriver.storage.googleapis.com/LATEST_RELEASE").text.strip()
|
||||
url = "http://chromedriver.storage.googleapis.com/%s/chromedriver_%s.zip" % (latest,
|
||||
self.platform_string())
|
||||
unzip(get(url).raw, dest)
|
||||
|
||||
path = find_executable("chromedriver", dest)
|
||||
st = os.stat(path)
|
||||
os.chmod(path, st.st_mode | stat.S_IEXEC)
|
||||
return path
|
||||
|
||||
def version(self, root):
|
||||
raise NotImplementedError
|
||||
|
||||
def prepare_environment(self):
|
||||
# https://bugs.chromium.org/p/chromium/issues/detail?id=713947
|
||||
logger.debug("DBUS_SESSION_BUS_ADDRESS %s" % os.environ.get("DBUS_SESSION_BUS_ADDRESS"))
|
||||
if "DBUS_SESSION_BUS_ADDRESS" not in os.environ:
|
||||
if find_executable("dbus-launch"):
|
||||
logger.debug("Attempting to start dbus")
|
||||
dbus_conf = subprocess.check_output(["dbus-launch"])
|
||||
logger.debug(dbus_conf)
|
||||
|
||||
# From dbus-launch(1):
|
||||
#
|
||||
# > When dbus-launch prints bus information to standard output,
|
||||
# > by default it is in a simple key-value pairs format.
|
||||
for line in dbus_conf.strip().split("\n"):
|
||||
key, _, value = line.partition("=")
|
||||
os.environ[key] = value
|
||||
else:
|
||||
logger.critical("dbus not running and can't be started")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
class Opera(Browser):
|
||||
"""Opera-specific interface.
|
||||
|
|
|
@ -39,8 +39,3 @@ def install(name, component, destination):
|
|||
subclass = getattr(browser, name.title())
|
||||
sys.stdout.write('Now installing %s %s...\n' % (name, component))
|
||||
getattr(subclass(), method)(dest=destination)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
args = parser.parse_args()
|
||||
run(None, **vars(args))
|
||||
|
|
|
@ -38,18 +38,3 @@ def table(headings, data, log):
|
|||
for row in data:
|
||||
log("|%s|" % "|".join(" %s" % row[i].ljust(max_widths[i] - 1) for i in cols))
|
||||
log("")
|
||||
|
||||
|
||||
def err_string(results_dict, iterations):
|
||||
"""Create and return string with errors from test run."""
|
||||
rv = []
|
||||
total_results = sum(results_dict.values())
|
||||
for key, value in sorted(results_dict.items()):
|
||||
rv.append("%s%s" %
|
||||
(key, ": %s/%s" % (value, iterations) if value != iterations else ""))
|
||||
if total_results < iterations:
|
||||
rv.append("MISSING: %s/%s" % (iterations - total_results, iterations))
|
||||
rv = ", ".join(rv)
|
||||
if is_inconsistent(results_dict, iterations):
|
||||
rv = "**%s**" % rv
|
||||
return rv
|
||||
|
|
|
@ -223,6 +223,28 @@ class Chrome(BrowserSetup):
|
|||
else:
|
||||
raise WptrunError("Unable to locate or install chromedriver binary")
|
||||
|
||||
class ChromeAndroid(BrowserSetup):
|
||||
name = "chrome_android"
|
||||
browser_cls = browser.ChromeAndroid
|
||||
|
||||
def setup_kwargs(self, kwargs):
|
||||
if kwargs["webdriver_binary"] is None:
|
||||
webdriver_binary = self.browser.find_webdriver()
|
||||
|
||||
if webdriver_binary is None:
|
||||
install = self.prompt_install("chromedriver")
|
||||
|
||||
if install:
|
||||
print("Downloading chromedriver")
|
||||
webdriver_binary = self.browser.install_webdriver(dest=self.venv.bin_path)
|
||||
else:
|
||||
print("Using webdriver binary %s" % webdriver_binary)
|
||||
|
||||
if webdriver_binary:
|
||||
kwargs["webdriver_binary"] = webdriver_binary
|
||||
else:
|
||||
raise WptrunError("Unable to locate or install chromedriver binary")
|
||||
|
||||
|
||||
class Opera(BrowserSetup):
|
||||
name = "opera"
|
||||
|
@ -321,6 +343,7 @@ class Servo(BrowserSetup):
|
|||
product_setup = {
|
||||
"firefox": Firefox,
|
||||
"chrome": Chrome,
|
||||
"chrome_android": ChromeAndroid,
|
||||
"edge": Edge,
|
||||
"ie": InternetExplorer,
|
||||
"servo": Servo,
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
from tools.wpt import stability
|
||||
|
||||
def test_is_inconsistent():
|
||||
assert stability.is_inconsistent({"PASS": 10}, 10) is False
|
||||
assert stability.is_inconsistent({"PASS": 9}, 10) is True
|
||||
assert stability.is_inconsistent({"PASS": 9, "FAIL": 1}, 10) is True
|
||||
assert stability.is_inconsistent({"PASS": 8, "FAIL": 1}, 10) is True
|
|
@ -10,7 +10,8 @@ import pytest
|
|||
from tools.wpt import wpt
|
||||
|
||||
|
||||
# Tests currently don't work on Windows for path reasons
|
||||
pytestmark = pytest.mark.skipif(os.name == "nt",
|
||||
reason="Tests currently don't work on Windows for path reasons")
|
||||
|
||||
def test_missing():
|
||||
with pytest.raises(SystemExit):
|
||||
|
@ -25,6 +26,9 @@ def test_help():
|
|||
assert excinfo.value.code == 0
|
||||
|
||||
|
||||
@pytest.mark.slow
|
||||
@pytest.mark.system_dependent
|
||||
@pytest.mark.remote_network
|
||||
def test_run_firefox():
|
||||
# TODO: It seems like there's a bug in argparse that makes this argument order required
|
||||
# should try to work around that
|
||||
|
@ -44,6 +48,8 @@ def test_run_firefox():
|
|||
del os.environ["MOZ_HEADLESS"]
|
||||
|
||||
|
||||
@pytest.mark.slow
|
||||
@pytest.mark.system_dependent
|
||||
def test_run_chrome():
|
||||
with pytest.raises(SystemExit) as excinfo:
|
||||
wpt.main(argv=["run", "--yes", "--no-pause", "--binary-arg", "headless",
|
||||
|
@ -52,6 +58,8 @@ def test_run_chrome():
|
|||
assert excinfo.value.code == 0
|
||||
|
||||
|
||||
@pytest.mark.slow
|
||||
@pytest.mark.remote_network
|
||||
def test_install_chromedriver():
|
||||
chromedriver_path = os.path.join(wpt.localpaths.repo_root, "_venv", "bin", "chromedriver")
|
||||
if os.path.exists(chromedriver_path):
|
||||
|
@ -63,6 +71,8 @@ def test_install_chromedriver():
|
|||
os.unlink(chromedriver_path)
|
||||
|
||||
|
||||
@pytest.mark.slow
|
||||
@pytest.mark.remote_network
|
||||
def test_install_firefox():
|
||||
fx_path = os.path.join(wpt.localpaths.repo_root, "_venv", "firefox")
|
||||
if os.path.exists(fx_path):
|
||||
|
@ -109,6 +119,8 @@ def test_files_changed_ignore_rules():
|
|||
assert compile_ignore_rule("foobar/baz/**").pattern == "^foobar/baz/.*$"
|
||||
|
||||
|
||||
@pytest.mark.slow # this updates the manifest
|
||||
@pytest.mark.system_dependent
|
||||
def test_tests_affected(capsys):
|
||||
# This doesn't really work properly for random commits because we test the files in
|
||||
# the current working directory for references to the changed files, not the ones at
|
||||
|
@ -121,6 +133,8 @@ def test_tests_affected(capsys):
|
|||
assert "html/browsers/offline/appcache/workers/appcache-worker.html" in out
|
||||
|
||||
|
||||
@pytest.mark.slow
|
||||
@pytest.mark.system_dependent
|
||||
def test_serve():
|
||||
def test():
|
||||
s = socket.socket()
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
[tox]
|
||||
envlist = py27
|
||||
envlist = py27,py27-flake8
|
||||
skipsdist=True
|
||||
|
||||
[testenv]
|
||||
deps =
|
||||
flake8
|
||||
pytest
|
||||
pytest-cov
|
||||
hypothesis
|
||||
|
@ -13,9 +12,41 @@ deps =
|
|||
-r{toxinidir}/../wptrunner/requirements_firefox.txt
|
||||
|
||||
commands =
|
||||
pytest --cov
|
||||
flake8
|
||||
pytest --cov {posargs}
|
||||
|
||||
[testenv:py27-flake8]
|
||||
# flake8 versions should be kept in sync across tools/tox.ini, tools/wpt/tox.ini, and tools/wptrunner/tox.ini
|
||||
deps =
|
||||
flake8==3.5.0
|
||||
pycodestyle==2.3.1
|
||||
pyflakes==1.6.0
|
||||
pep8-naming==0.4.1
|
||||
|
||||
commands =
|
||||
flake8 {posargs}
|
||||
|
||||
[flake8]
|
||||
ignore = E128,E129,E221,E226,E231,E251,E265,E302,E303,E305,E402,E901,F401,F821,F841
|
||||
# flake8 config should be kept in sync across tools/tox.ini, tools/wpt/tox.ini, and tools/wptrunner/tox.ini
|
||||
select = E,W,F,N
|
||||
# E128: continuation line under-indented for visual indent
|
||||
# E129: visually indented line with same indent as next logical line
|
||||
# E221: multiple spaces before operator
|
||||
# E226: missing whitespace around arithmetic operator
|
||||
# E231: missing whitespace after ‘,’, ‘;’, or ‘:’
|
||||
# E251: unexpected spaces around keyword / parameter equals
|
||||
# E265: block comment should start with ‘# ‘
|
||||
# E302: expected 2 blank lines, found 0
|
||||
# E303: too many blank lines (3)
|
||||
# E305: expected 2 blank lines after end of function or class
|
||||
# E402: module level import not at top of file
|
||||
# E731: do not assign a lambda expression, use a def
|
||||
# E901: SyntaxError or IndentationError
|
||||
# W601: .has_key() is deprecated, use ‘in’
|
||||
# F401: module imported but unused
|
||||
# F403: ‘from module import *’ used; unable to detect undefined names
|
||||
# F405: name may be undefined, or defined from star imports: module
|
||||
# F841: local variable name is assigned to but never used
|
||||
# N801: class names should use CapWords convention
|
||||
# N802: function name should be lowercase
|
||||
ignore = E128,E129,E221,E226,E231,E251,E265,E302,E303,E305,E402,E731,E901,W601,F401,F403,F405,F841,N801,N802
|
||||
max-line-length = 141
|
||||
|
|
|
@ -27,7 +27,7 @@ class Kwargs(dict):
|
|||
value = value()
|
||||
if not value:
|
||||
if err_fn is not None:
|
||||
return err_fn(kwargs, "Failed to find %s" % desc)
|
||||
return err_fn(self, "Failed to find %s" % desc)
|
||||
else:
|
||||
return
|
||||
self[name] = value
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import os
|
||||
import shutil
|
||||
import sys
|
||||
import logging
|
||||
from distutils.spawn import find_executable
|
||||
|
@ -21,7 +22,7 @@ class Virtualenv(object):
|
|||
def create(self):
|
||||
if os.path.exists(self.path):
|
||||
shutil.rmtree(self.path)
|
||||
call(self.virtualenv, self.path)
|
||||
call(self.virtualenv, self.path, "-p", sys.executable)
|
||||
|
||||
@property
|
||||
def bin_path(self):
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
exclude MANIFEST.in
|
||||
include requirements.txt
|
||||
include wptrunner/browsers/b2g_setup/*
|
||||
include wptrunner.default.ini
|
||||
include wptrunner/testharness_runner.html
|
||||
include wptrunner/*.js
|
||||
|
|
|
@ -23,7 +23,7 @@ The ``wptrunner`` command takes multiple options, of which the
|
|||
following are most significant:
|
||||
|
||||
``--product`` (defaults to `firefox`)
|
||||
The product to test against: `b2g`, `chrome`, `firefox`, or `servo`.
|
||||
The product to test against: `chrome`, `firefox`, or `servo`.
|
||||
|
||||
``--binary`` (required if product is `firefox` or `servo`)
|
||||
The path to a binary file for the product (browser) to test against.
|
||||
|
|
|
@ -186,22 +186,22 @@ htmlhelp_basename = 'wptrunnerdoc'
|
|||
# -- Options for LaTeX output ---------------------------------------------
|
||||
|
||||
latex_elements = {
|
||||
# The paper size ('letterpaper' or 'a4paper').
|
||||
#'papersize': 'letterpaper',
|
||||
# The paper size ('letterpaper' or 'a4paper').
|
||||
#'papersize': 'letterpaper',
|
||||
|
||||
# The font size ('10pt', '11pt' or '12pt').
|
||||
#'pointsize': '10pt',
|
||||
# The font size ('10pt', '11pt' or '12pt').
|
||||
#'pointsize': '10pt',
|
||||
|
||||
# Additional stuff for the LaTeX preamble.
|
||||
#'preamble': '',
|
||||
# Additional stuff for the LaTeX preamble.
|
||||
#'preamble': '',
|
||||
}
|
||||
|
||||
# Grouping the document tree into LaTeX files. List of tuples
|
||||
# (source start file, target name, title,
|
||||
# author, documentclass [howto, manual, or own class]).
|
||||
latex_documents = [
|
||||
('index', 'wptrunner.tex', u'wptrunner Documentation',
|
||||
u'James Graham', 'manual'),
|
||||
('index', 'wptrunner.tex', u'wptrunner Documentation',
|
||||
u'James Graham', 'manual'),
|
||||
]
|
||||
|
||||
# The name of an image file (relative to this directory) to place at the top of
|
||||
|
@ -244,9 +244,9 @@ man_pages = [
|
|||
# (source start file, target name, title, author,
|
||||
# dir menu entry, description, category)
|
||||
texinfo_documents = [
|
||||
('index', 'wptrunner', u'wptrunner Documentation',
|
||||
u'James Graham', 'wptrunner', 'One line description of project.',
|
||||
'Miscellaneous'),
|
||||
('index', 'wptrunner', u'wptrunner Documentation',
|
||||
u'James Graham', 'wptrunner', 'One line description of project.',
|
||||
'Miscellaneous'),
|
||||
]
|
||||
|
||||
# Documents to append as an appendix to all manuals.
|
||||
|
|
|
@ -54,7 +54,7 @@ A test run is started using the ``wptrunner`` command. The command
|
|||
takes multiple options, of which the following are most significant:
|
||||
|
||||
``--product`` (defaults to `firefox`)
|
||||
The product to test against: `b2g`, `chrome`, `firefox`, or `servo`.
|
||||
The product to test against: `chrome`, `firefox`, or `servo`.
|
||||
|
||||
``--binary`` (required if product is `firefox` or `servo`)
|
||||
The path to a binary file for the product (browser) to test against.
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
mozprocess >= 0.19
|
||||
selenium >= 2.41.0
|
|
@ -53,13 +53,12 @@ setup(name=PACKAGE_NAME,
|
|||
"config.json",
|
||||
"wptrunner.default.ini",
|
||||
"browsers/server-locations.txt",
|
||||
"browsers/b2g_setup/*",
|
||||
"browsers/sauce_setup/*",
|
||||
"prefs/*"]},
|
||||
include_package_data=True,
|
||||
data_files=[("requirements", requirements_files)],
|
||||
install_requires=deps
|
||||
)
|
||||
)
|
||||
|
||||
if "install" in sys.argv:
|
||||
path = os.path.relpath(os.path.join(sys.prefix, "requirements"), os.curdir)
|
||||
|
|
|
@ -156,7 +156,8 @@ def main():
|
|||
run(config, args)
|
||||
except Exception:
|
||||
if args.pdb:
|
||||
import pdb, traceback
|
||||
import pdb
|
||||
import traceback
|
||||
print traceback.format_exc()
|
||||
pdb.post_mortem()
|
||||
else:
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
xfail_strict=true
|
||||
|
||||
[tox]
|
||||
envlist = {py27,pypy}-{base,b2g,chrome,firefox,servo}
|
||||
envlist = {py27,pypy}-{base,chrome,firefox,servo},py27-flake8
|
||||
|
||||
[testenv]
|
||||
deps =
|
||||
|
@ -15,3 +15,40 @@ deps =
|
|||
servo: -r{toxinidir}/requirements_servo.txt
|
||||
|
||||
commands = pytest --cov
|
||||
|
||||
[testenv:py27-flake8]
|
||||
# flake8 versions should be kept in sync across tools/tox.ini, tools/wpt/tox.ini, and tools/wptrunner/tox.ini
|
||||
deps =
|
||||
flake8==3.5.0
|
||||
pycodestyle==2.3.1
|
||||
pyflakes==1.6.0
|
||||
pep8-naming==0.4.1
|
||||
|
||||
commands =
|
||||
flake8
|
||||
|
||||
[flake8]
|
||||
# flake8 config should be kept in sync across tools/tox.ini, tools/wpt/tox.ini, and tools/wptrunner/tox.ini
|
||||
select = E,W,F,N
|
||||
# E128: continuation line under-indented for visual indent
|
||||
# E129: visually indented line with same indent as next logical line
|
||||
# E221: multiple spaces before operator
|
||||
# E226: missing whitespace around arithmetic operator
|
||||
# E231: missing whitespace after ‘,’, ‘;’, or ‘:’
|
||||
# E251: unexpected spaces around keyword / parameter equals
|
||||
# E265: block comment should start with ‘# ‘
|
||||
# E302: expected 2 blank lines, found 0
|
||||
# E303: too many blank lines (3)
|
||||
# E305: expected 2 blank lines after end of function or class
|
||||
# E402: module level import not at top of file
|
||||
# E731: do not assign a lambda expression, use a def
|
||||
# E901: SyntaxError or IndentationError
|
||||
# W601: .has_key() is deprecated, use ‘in’
|
||||
# F401: module imported but unused
|
||||
# F403: ‘from module import *’ used; unable to detect undefined names
|
||||
# F405: name may be undefined, or defined from star imports: module
|
||||
# F841: local variable name is assigned to but never used
|
||||
# N801: class names should use CapWords convention
|
||||
# N802: function name should be lowercase
|
||||
ignore = E128,E129,E221,E226,E231,E251,E265,E302,E303,E305,E402,E731,E901,W601,F401,F403,F405,F841,N801,N802
|
||||
max-line-length = 141
|
||||
|
|
|
@ -23,6 +23,7 @@ module global scope.
|
|||
"""
|
||||
|
||||
product_list = ["chrome",
|
||||
"chrome_android",
|
||||
"edge",
|
||||
"firefox",
|
||||
"ie",
|
||||
|
|
Binary file not shown.
|
@ -0,0 +1,98 @@
|
|||
from .base import Browser, ExecutorBrowser, require_arg
|
||||
from ..webdriver_server import ChromeDriverServer
|
||||
from ..executors import executor_kwargs as base_executor_kwargs
|
||||
from ..executors.executorselenium import (SeleniumTestharnessExecutor,
|
||||
SeleniumRefTestExecutor)
|
||||
from ..executors.executorchrome import ChromeDriverWdspecExecutor
|
||||
|
||||
|
||||
__wptrunner__ = {"product": "chrome_android",
|
||||
"check_args": "check_args",
|
||||
"browser": "ChromeAndroidBrowser",
|
||||
"executor": {"testharness": "SeleniumTestharnessExecutor",
|
||||
"reftest": "SeleniumRefTestExecutor",
|
||||
"wdspec": "ChromeDriverWdspecExecutor"},
|
||||
"browser_kwargs": "browser_kwargs",
|
||||
"executor_kwargs": "executor_kwargs",
|
||||
"env_extras": "env_extras",
|
||||
"env_options": "env_options"}
|
||||
|
||||
|
||||
def check_args(**kwargs):
|
||||
require_arg(kwargs, "webdriver_binary")
|
||||
|
||||
|
||||
def browser_kwargs(test_type, run_info_data, **kwargs):
|
||||
return {"binary": kwargs["binary"],
|
||||
"webdriver_binary": kwargs["webdriver_binary"],
|
||||
"webdriver_args": kwargs.get("webdriver_args")}
|
||||
|
||||
|
||||
def executor_kwargs(test_type, server_config, cache_manager, run_info_data,
|
||||
**kwargs):
|
||||
from selenium.webdriver import DesiredCapabilities
|
||||
|
||||
executor_kwargs = base_executor_kwargs(test_type, server_config,
|
||||
cache_manager, **kwargs)
|
||||
executor_kwargs["close_after_done"] = True
|
||||
capabilities = dict(DesiredCapabilities.CHROME.items())
|
||||
capabilities["chromeOptions"] = {}
|
||||
# required to start on mobile
|
||||
capabilities["chromeOptions"]["androidPackage"] = "com.android.chrome"
|
||||
|
||||
for (kwarg, capability) in [("binary", "binary"), ("binary_args", "args")]:
|
||||
if kwargs[kwarg] is not None:
|
||||
capabilities["chromeOptions"][capability] = kwargs[kwarg]
|
||||
if test_type == "testharness":
|
||||
capabilities["useAutomationExtension"] = False
|
||||
capabilities["excludeSwitches"] = ["enable-automation"]
|
||||
if test_type == "wdspec":
|
||||
capabilities["chromeOptions"]["w3c"] = True
|
||||
executor_kwargs["capabilities"] = capabilities
|
||||
return executor_kwargs
|
||||
|
||||
|
||||
def env_extras(**kwargs):
|
||||
return []
|
||||
|
||||
|
||||
def env_options():
|
||||
return {"host": "web-platform.test",
|
||||
"bind_hostname": "true"}
|
||||
|
||||
|
||||
class ChromeAndroidBrowser(Browser):
|
||||
"""Chrome is backed by chromedriver, which is supplied through
|
||||
``wptrunner.webdriver.ChromeDriverServer``.
|
||||
"""
|
||||
|
||||
def __init__(self, logger, binary, webdriver_binary="chromedriver",
|
||||
webdriver_args=None):
|
||||
"""Creates a new representation of Chrome. The `binary` argument gives
|
||||
the browser binary to use for testing."""
|
||||
Browser.__init__(self, logger)
|
||||
self.binary = binary
|
||||
self.server = ChromeDriverServer(self.logger,
|
||||
binary=webdriver_binary,
|
||||
args=webdriver_args)
|
||||
|
||||
def start(self, **kwargs):
|
||||
self.server.start(block=False)
|
||||
|
||||
def stop(self, force=False):
|
||||
self.server.stop(force=force)
|
||||
|
||||
def pid(self):
|
||||
return self.server.pid
|
||||
|
||||
def is_alive(self):
|
||||
# TODO(ato): This only indicates the driver is alive,
|
||||
# and doesn't say anything about whether a browser session
|
||||
# is active.
|
||||
return self.server.is_alive()
|
||||
|
||||
def cleanup(self):
|
||||
self.stop()
|
||||
|
||||
def executor_browser(self):
|
||||
return ExecutorBrowser, {"webdriver_url": self.server.url}
|
|
@ -96,15 +96,15 @@ def executor_kwargs(test_type, server_config, cache_manager, run_info_data,
|
|||
executor_kwargs["reftest_internal"] = kwargs["reftest_internal"]
|
||||
executor_kwargs["reftest_screenshot"] = kwargs["reftest_screenshot"]
|
||||
if test_type == "wdspec":
|
||||
fxOptions = {}
|
||||
options = {}
|
||||
if kwargs["binary"]:
|
||||
fxOptions["binary"] = kwargs["binary"]
|
||||
options["binary"] = kwargs["binary"]
|
||||
if kwargs["binary_args"]:
|
||||
fxOptions["args"] = kwargs["binary_args"]
|
||||
fxOptions["prefs"] = {
|
||||
options["args"] = kwargs["binary_args"]
|
||||
options["prefs"] = {
|
||||
"network.dns.localDomains": ",".join(hostnames)
|
||||
}
|
||||
capabilities["moz:firefoxOptions"] = fxOptions
|
||||
capabilities["moz:firefoxOptions"] = options
|
||||
if kwargs["certutil_binary"] is None:
|
||||
capabilities["acceptInsecureCerts"] = True
|
||||
if capabilities:
|
||||
|
@ -364,7 +364,7 @@ class FirefoxBrowser(Browser):
|
|||
|
||||
env[env_var] = (os.path.pathsep.join([certutil_dir, env[env_var]])
|
||||
if env_var in env else certutil_dir).encode(
|
||||
sys.getfilesystemencoding() or 'utf-8', 'replace')
|
||||
sys.getfilesystemencoding() or 'utf-8', 'replace')
|
||||
|
||||
def certutil(*args):
|
||||
cmd = [self.certutil_binary] + list(args)
|
||||
|
|
|
@ -28,10 +28,10 @@ def executor_kwargs(test_type, server_config, cache_manager, run_info_data,
|
|||
**kwargs):
|
||||
from selenium.webdriver import DesiredCapabilities
|
||||
|
||||
ieOptions = {}
|
||||
ieOptions["requireWindowFocus"] = True
|
||||
options = {}
|
||||
options["requireWindowFocus"] = True
|
||||
capabilities = {}
|
||||
capabilities["se:ieOptions"] = ieOptions
|
||||
capabilities["se:ieOptions"] = options
|
||||
executor_kwargs = base_executor_kwargs(test_type, server_config,
|
||||
cache_manager, **kwargs)
|
||||
executor_kwargs["close_after_done"] = True
|
||||
|
@ -51,7 +51,7 @@ class InternetExplorerBrowser(Browser):
|
|||
|
||||
def __init__(self, logger, webdriver_binary, webdriver_args=None):
|
||||
Browser.__init__(self, logger)
|
||||
self.server = InterentExplorerDriverServer(self.logger,
|
||||
self.server = InternetExplorerDriverServer(self.logger,
|
||||
binary=webdriver_binary,
|
||||
args=webdriver_args)
|
||||
self.webdriver_host = "localhost"
|
||||
|
|
|
@ -61,7 +61,7 @@ def get_ssl_kwargs(**kwargs):
|
|||
elif kwargs["ssl_type"] == "pregenerated":
|
||||
args = {"host_key_path": kwargs["host_key_path"],
|
||||
"host_cert_path": kwargs["host_cert_path"],
|
||||
"ca_cert_path": kwargs["ca_cert_path"]}
|
||||
"ca_cert_path": kwargs["ca_cert_path"]}
|
||||
else:
|
||||
args = {}
|
||||
return args
|
||||
|
|
|
@ -13,7 +13,7 @@ here = os.path.split(__file__)[0]
|
|||
|
||||
# Extra timeout to use after internal test timeout at which the harness
|
||||
# should force a timeout
|
||||
extra_timeout = 5 # seconds
|
||||
extra_timeout = 5 # seconds
|
||||
|
||||
|
||||
def executor_kwargs(test_type, server_config, cache_manager, **kwargs):
|
||||
|
@ -67,8 +67,8 @@ class TestharnessResultConverter(object):
|
|||
(result_url, test.url))
|
||||
harness_result = test.result_cls(self.harness_codes[status], message)
|
||||
return (harness_result,
|
||||
[test.subtest_result_cls(name, self.test_codes[status], message, stack)
|
||||
for name, status, message, stack in subtest_results])
|
||||
[test.subtest_result_cls(st_name, self.test_codes[st_status], st_message, st_stack)
|
||||
for st_name, st_status, st_message, st_stack in subtest_results])
|
||||
|
||||
|
||||
testharness_result_converter = TestharnessResultConverter()
|
||||
|
@ -124,7 +124,7 @@ class TestExecutor(object):
|
|||
self.debug_info = debug_info
|
||||
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
|
||||
def logger(self):
|
||||
|
|
|
@ -304,7 +304,7 @@ class ExecuteAsyncScriptRun(object):
|
|||
self.result_flag = threading.Event()
|
||||
|
||||
def run(self):
|
||||
index = self.url.rfind("/storage/");
|
||||
index = self.url.rfind("/storage/")
|
||||
if index != -1:
|
||||
# Clear storage
|
||||
self.protocol.clear_origin(self.url)
|
||||
|
@ -503,7 +503,7 @@ class MarionetteRefTestExecutor(RefTestExecutor):
|
|||
assert viewport_size is None
|
||||
assert dpi is None
|
||||
|
||||
timeout = self.timeout_multiplier * test.timeout if self.debug_info is None else None
|
||||
timeout = self.timeout_multiplier * test.timeout if self.debug_info is None else None
|
||||
|
||||
test_url = self.test_url(test)
|
||||
|
||||
|
|
|
@ -51,7 +51,7 @@ class SeleniumProtocol(Protocol):
|
|||
self.webdriver = webdriver.Remote(command_executor=RemoteConnection(self.url.strip("/"),
|
||||
resolve_ip=False),
|
||||
desired_capabilities=self.capabilities)
|
||||
except:
|
||||
except Exception:
|
||||
self.logger.warning(
|
||||
"Connecting to Selenium failed:\n%s" % traceback.format_exc())
|
||||
else:
|
||||
|
@ -64,7 +64,7 @@ class SeleniumProtocol(Protocol):
|
|||
else:
|
||||
try:
|
||||
self.after_connect()
|
||||
except:
|
||||
except Exception:
|
||||
print >> sys.stderr, traceback.format_exc()
|
||||
self.logger.warning(
|
||||
"Failed to connect to navigate initial page")
|
||||
|
@ -76,7 +76,7 @@ class SeleniumProtocol(Protocol):
|
|||
self.logger.debug("Hanging up on Selenium session")
|
||||
try:
|
||||
self.webdriver.quit()
|
||||
except:
|
||||
except Exception:
|
||||
pass
|
||||
del self.webdriver
|
||||
|
||||
|
@ -103,7 +103,7 @@ class SeleniumProtocol(Protocol):
|
|||
def wait(self):
|
||||
while True:
|
||||
try:
|
||||
self.webdriver.execute_async_script("");
|
||||
self.webdriver.execute_async_script("")
|
||||
except exceptions.TimeoutException:
|
||||
pass
|
||||
except (socket.timeout, exceptions.NoSuchWindowException,
|
||||
|
@ -220,7 +220,7 @@ class SeleniumTestharnessExecutor(TestharnessExecutor):
|
|||
win_s = webdriver.execute_script("return window['%s'];" % self.window_id)
|
||||
win_obj = json.loads(win_s)
|
||||
test_window = win_obj["window-fcc6-11e5-b4f8-330a88ab9d7f"]
|
||||
except:
|
||||
except Exception:
|
||||
after = webdriver.window_handles
|
||||
if len(after) == 2:
|
||||
test_window = next(iter(set(after) - set([parent])))
|
||||
|
|
|
@ -28,7 +28,7 @@ from .executormarionette import WdspecRun
|
|||
pytestrunner = None
|
||||
webdriver = None
|
||||
|
||||
extra_timeout = 5 # seconds
|
||||
extra_timeout = 5 # seconds
|
||||
|
||||
hosts_text = """127.0.0.1 web-platform.test
|
||||
127.0.0.1 www.web-platform.test
|
||||
|
|
|
@ -43,7 +43,7 @@ class ServoWebDriverProtocol(Protocol):
|
|||
self.session = webdriver.Session(self.host, self.port,
|
||||
extension=webdriver.servo.ServoCommandExtensions)
|
||||
self.session.start()
|
||||
except:
|
||||
except Exception:
|
||||
self.logger.warning(
|
||||
"Connecting with WebDriver failed:\n%s" % traceback.format_exc())
|
||||
else:
|
||||
|
@ -60,7 +60,7 @@ class ServoWebDriverProtocol(Protocol):
|
|||
self.logger.debug("Hanging up on WebDriver session")
|
||||
try:
|
||||
self.session.end()
|
||||
except:
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def is_alive(self):
|
||||
|
|
|
@ -247,8 +247,8 @@ class TestNode(ManifestItem):
|
|||
boolean_properties=self.root.boolean_properties)
|
||||
except ConditionError as e:
|
||||
if stability is not None:
|
||||
self.set("disabled", stability or "unstable", e.cond.children[0])
|
||||
self.new_disabled = True
|
||||
self.set("disabled", stability or "unstable", e.cond.children[0])
|
||||
self.new_disabled = True
|
||||
else:
|
||||
print "Conflicting test results for %s, cannot update" % self.root.test_path
|
||||
return
|
||||
|
|
|
@ -355,7 +355,7 @@ def load_expected(test_manifest, metadata_path, test_path, tests, property_order
|
|||
|
||||
# Remove expected data for tests that no longer exist
|
||||
for test in expected_manifest.iterchildren():
|
||||
if not test.id in tests_by_id:
|
||||
if test.id not in tests_by_id:
|
||||
test.remove()
|
||||
|
||||
# Add tests that don't have expected data
|
||||
|
|
|
@ -274,6 +274,6 @@ def check_stability(logger, repeat_loop=10, repeat_restart=5, chaos_mode=True, m
|
|||
write_summary(logger, step_results, "FAIL")
|
||||
return 1
|
||||
|
||||
step_results.append((desc, "PASS"))
|
||||
step_results.append((desc, "PASS"))
|
||||
|
||||
write_summary(logger, step_results, "PASS")
|
||||
|
|
|
@ -93,7 +93,7 @@ class EqualTimeChunker(TestChunker):
|
|||
for i, (test_type, test_path, tests) in enumerate(manifest_items):
|
||||
test_dir = tuple(os.path.split(test_path)[0].split(os.path.sep)[:3])
|
||||
|
||||
if not test_dir in by_dir:
|
||||
if test_dir not in by_dir:
|
||||
by_dir[test_dir] = PathData(test_dir)
|
||||
|
||||
data = by_dir[test_dir]
|
||||
|
@ -261,7 +261,7 @@ class EqualTimeChunker(TestChunker):
|
|||
return self.paths.popleft()
|
||||
|
||||
@property
|
||||
def badness(self_):
|
||||
def badness(self_): # noqa: N805
|
||||
"""Badness metric for this chunk"""
|
||||
return self._badness(self_.time)
|
||||
|
||||
|
@ -587,6 +587,7 @@ class TestSource(object):
|
|||
self.current_metadata = None
|
||||
|
||||
@abstractmethod
|
||||
# noqa: N805
|
||||
#@classmethod (doesn't compose with @abstractmethod)
|
||||
def make_queue(cls, tests, **kwargs):
|
||||
pass
|
||||
|
|
|
@ -195,7 +195,7 @@ class BrowserManager(object):
|
|||
self.logger.debug("Starting browser with settings %r" % self.browser_settings)
|
||||
self.browser.start(**self.browser_settings)
|
||||
self.browser_pid = self.browser.pid()
|
||||
except:
|
||||
except Exception:
|
||||
self.logger.warning("Failure during init %s" % traceback.format_exc())
|
||||
if self.init_timer is not None:
|
||||
self.init_timer.cancel()
|
||||
|
@ -566,7 +566,7 @@ class TestRunnerManager(threading.Thread):
|
|||
expected = test.expected()
|
||||
status = file_result.status if file_result.status != "EXTERNAL-TIMEOUT" else "TIMEOUT"
|
||||
|
||||
if file_result.status in ("TIMEOUT", "EXTERNAL-TIMEOUT"):
|
||||
if file_result.status in ("TIMEOUT", "EXTERNAL-TIMEOUT"):
|
||||
if self.browser.check_for_crashes():
|
||||
status = "CRASH"
|
||||
|
||||
|
@ -585,8 +585,8 @@ class TestRunnerManager(threading.Thread):
|
|||
|
||||
restart_before_next = (test.restart_after or
|
||||
file_result.status in ("CRASH", "EXTERNAL-TIMEOUT") or
|
||||
((subtest_unexpected or is_unexpected)
|
||||
and self.restart_on_unexpected))
|
||||
((subtest_unexpected or is_unexpected) and
|
||||
self.restart_on_unexpected))
|
||||
|
||||
if (self.pause_after_test or
|
||||
(self.pause_on_unexpected and (subtest_unexpected or is_unexpected))):
|
||||
|
@ -689,7 +689,7 @@ class TestRunnerManager(threading.Thread):
|
|||
break
|
||||
else:
|
||||
if cmd == "log":
|
||||
self.log(*data)
|
||||
self.log(*data)
|
||||
else:
|
||||
self.logger.warning("%r: %r" % (cmd, data))
|
||||
while True:
|
||||
|
|
|
@ -42,15 +42,13 @@ class HostsTest(unittest.TestCase):
|
|||
192.168.1.1 another_host another_alias
|
||||
""","""127.0.0.1 localhost alias
|
||||
192.168.1.1 another_host another_alias
|
||||
"""
|
||||
)
|
||||
""")
|
||||
|
||||
def test_multiple_same_name(self):
|
||||
# The semantics are that we overwrite earlier entries with the same name
|
||||
self.do_test("""127.0.0.1 \tlocalhost alias
|
||||
192.168.1.1 localhost another_alias""","""192.168.1.1 localhost another_alias
|
||||
"""
|
||||
)
|
||||
""")
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
|
|
@ -44,4 +44,3 @@ def main():
|
|||
assert structuredlog.get_default_logger() is not None
|
||||
success = run_update(logger, **args)
|
||||
sys.exit(0 if success else 1)
|
||||
|
||||
|
|
|
@ -109,7 +109,7 @@ class UpdateCheckout(Step):
|
|||
state.sync["branch"],
|
||||
state.local_branch)
|
||||
sync_path = os.path.abspath(sync_tree.root)
|
||||
if not sync_path in sys.path:
|
||||
if sync_path not in sys.path:
|
||||
from update import setup_paths
|
||||
setup_paths(sync_path)
|
||||
|
||||
|
|
|
@ -75,7 +75,7 @@ class HgTree(object):
|
|||
kwargs["repo"] = path
|
||||
try:
|
||||
hg("root", **kwargs)
|
||||
except:
|
||||
except Exception:
|
||||
return False
|
||||
return True
|
||||
|
||||
|
@ -155,7 +155,7 @@ class GitTree(object):
|
|||
kwargs["repo"] = path
|
||||
try:
|
||||
git("rev-parse", "--show-toplevel", **kwargs)
|
||||
except:
|
||||
except Exception:
|
||||
return False
|
||||
return True
|
||||
|
||||
|
@ -305,8 +305,8 @@ class GitTree(object):
|
|||
|
||||
def paths(self):
|
||||
"""List paths in the tree"""
|
||||
repo_paths = [self.root] + [os.path.join(self.root, path)
|
||||
for path in self.submodules()]
|
||||
repo_paths = [self.root] + [os.path.join(self.root, path)
|
||||
for path in self.submodules()]
|
||||
|
||||
rv = []
|
||||
|
||||
|
|
|
@ -15,7 +15,7 @@ def vcs(bin_name):
|
|||
repo = kwargs.pop("repo", None)
|
||||
log_error = kwargs.pop("log_error", True)
|
||||
if kwargs:
|
||||
raise TypeError, kwargs
|
||||
raise TypeError(kwargs)
|
||||
|
||||
args = list(args)
|
||||
|
||||
|
|
|
@ -72,7 +72,7 @@ class WebDriverServer(object):
|
|||
"Waiting for server to become accessible: %s" % self.url)
|
||||
try:
|
||||
wait_for_service((self.host, self.port))
|
||||
except:
|
||||
except Exception:
|
||||
self.logger.error(
|
||||
"WebDriver HTTP server was not accessible "
|
||||
"within the timeout:\n%s" % traceback.format_exc())
|
||||
|
@ -125,8 +125,6 @@ class SeleniumServer(WebDriverServer):
|
|||
|
||||
|
||||
class ChromeDriverServer(WebDriverServer):
|
||||
default_base_path = "/"
|
||||
|
||||
def __init__(self, logger, binary="chromedriver", port=None,
|
||||
base_path="", args=None):
|
||||
WebDriverServer.__init__(
|
||||
|
@ -138,8 +136,6 @@ class ChromeDriverServer(WebDriverServer):
|
|||
cmd_arg("url-base", self.base_path) if self.base_path else ""] + self._args
|
||||
|
||||
class EdgeDriverServer(WebDriverServer):
|
||||
default_base_path = "/"
|
||||
|
||||
def __init__(self, logger, binary="microsoftwebdriver.exe", port=None,
|
||||
base_path="", args=None):
|
||||
WebDriverServer.__init__(
|
||||
|
@ -147,8 +143,7 @@ class EdgeDriverServer(WebDriverServer):
|
|||
|
||||
def make_command(self):
|
||||
return [self.binary,
|
||||
cmd_arg("port", str(self.port)),
|
||||
cmd_arg("url-base", self.base_path) if self.base_path else ""] + self._args
|
||||
"--port=%s" % str(self.port)] + self._args
|
||||
|
||||
class OperaDriverServer(ChromeDriverServer):
|
||||
def __init__(self, logger, binary="operadriver", port=None,
|
||||
|
@ -157,17 +152,6 @@ class OperaDriverServer(ChromeDriverServer):
|
|||
self, logger, binary, port=port, base_path=base_path, args=args)
|
||||
|
||||
|
||||
class EdgeDriverServer(WebDriverServer):
|
||||
def __init__(self, logger, binary="MicrosoftWebDriver.exe", port=None,
|
||||
base_path="", host="localhost", args=None):
|
||||
WebDriverServer.__init__(
|
||||
self, logger, binary, host=host, port=port, args=args)
|
||||
|
||||
def make_command(self):
|
||||
return [self.binary,
|
||||
"--port=%s" % str(self.port)] + self._args
|
||||
|
||||
|
||||
class InternetExplorerDriverServer(WebDriverServer):
|
||||
def __init__(self, logger, binary="IEDriverServer.exe", port=None,
|
||||
base_path="", host="localhost", args=None):
|
||||
|
|
|
@ -28,7 +28,7 @@ def require_arg(kwargs, name, value_func=None):
|
|||
if value_func is None:
|
||||
value_func = lambda x: x is not None
|
||||
|
||||
if not name in kwargs or not value_func(kwargs[name]):
|
||||
if name not in kwargs or not value_func(kwargs[name]):
|
||||
print >> sys.stderr, "Missing required argument %s" % name
|
||||
sys.exit(1)
|
||||
|
||||
|
@ -97,7 +97,8 @@ scheme host and port.""")
|
|||
test_selection_group.add_argument("--skip-timeout", action="store_true",
|
||||
help="Skip tests that are expected to time out")
|
||||
test_selection_group.add_argument("--tag", action="append", dest="tags",
|
||||
help="Labels applied to tests to include in the run. Labels starting dir: are equivalent to top-level directories.")
|
||||
help="Labels applied to tests to include in the run. "
|
||||
"Labels starting dir: are equivalent to top-level directories.")
|
||||
|
||||
debugging_group = parser.add_argument_group("Debugging")
|
||||
debugging_group.add_argument('--debugger', const="__default__", nargs="?",
|
||||
|
@ -479,7 +480,8 @@ def create_parser_update(product_choices=None):
|
|||
help="Don't create a VCS commit containing the changes.")
|
||||
parser.add_argument("--sync", dest="sync", action="store_true", default=False,
|
||||
help="Sync the tests with the latest from upstream (implies --patch)")
|
||||
parser.add_argument("--ignore-existing", action="store_true", help="When updating test results only consider results from the logfiles provided, not existing expectations.")
|
||||
parser.add_argument("--ignore-existing", action="store_true",
|
||||
help="When updating test results only consider results from the logfiles provided, not existing expectations.")
|
||||
parser.add_argument("--stability", nargs="?", action="store", const="unstable", default=None,
|
||||
help=("Reason for disabling tests. When updating test results, disable tests that have "
|
||||
"inconsistent results across many runs with the given reason."))
|
||||
|
|
|
@ -211,15 +211,15 @@ class TokenizerTest(unittest.TestCase):
|
|||
""")
|
||||
|
||||
def test_atom_1(self):
|
||||
self.compare(r"""key: @True
|
||||
self.compare(r"""key: @True
|
||||
""")
|
||||
|
||||
def test_atom_2(self):
|
||||
self.compare(r"""key: @False
|
||||
self.compare(r"""key: @False
|
||||
""")
|
||||
|
||||
def test_atom_3(self):
|
||||
self.compare(r"""key: @Reset
|
||||
self.compare(r"""key: @Reset
|
||||
""")
|
||||
|
||||
def test_atom_4(self):
|
||||
|
|
|
@ -145,8 +145,7 @@ class TokenizerTest(unittest.TestCase):
|
|||
(token_types.string, r"\nb")])
|
||||
|
||||
def test_list_0(self):
|
||||
self.compare(
|
||||
"""
|
||||
self.compare("""
|
||||
key: []""",
|
||||
[(token_types.string, "key"),
|
||||
(token_types.separator, ":"),
|
||||
|
@ -154,8 +153,7 @@ key: []""",
|
|||
(token_types.list_end, "]")])
|
||||
|
||||
def test_list_1(self):
|
||||
self.compare(
|
||||
"""
|
||||
self.compare("""
|
||||
key: [a, "b"]""",
|
||||
[(token_types.string, "key"),
|
||||
(token_types.separator, ":"),
|
||||
|
@ -165,8 +163,7 @@ key: [a, "b"]""",
|
|||
(token_types.list_end, "]")])
|
||||
|
||||
def test_list_2(self):
|
||||
self.compare(
|
||||
"""
|
||||
self.compare("""
|
||||
key: [a,
|
||||
b]""",
|
||||
[(token_types.string, "key"),
|
||||
|
@ -177,8 +174,7 @@ key: [a,
|
|||
(token_types.list_end, "]")])
|
||||
|
||||
def test_list_3(self):
|
||||
self.compare(
|
||||
"""
|
||||
self.compare("""
|
||||
key: [a, #b]
|
||||
c]""",
|
||||
[(token_types.string, "key"),
|
||||
|
@ -199,18 +195,16 @@ key: [a, #b]
|
|||
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, "]")])
|
||||
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):
|
||||
self.compare(
|
||||
"""
|
||||
self.compare("""
|
||||
key:
|
||||
if cond == 1: value""",
|
||||
[(token_types.string, "key"),
|
||||
|
@ -224,8 +218,7 @@ key:
|
|||
(token_types.string, "value")])
|
||||
|
||||
def test_expr_1(self):
|
||||
self.compare(
|
||||
"""
|
||||
self.compare("""
|
||||
key:
|
||||
if cond == 1: value1
|
||||
value2""",
|
||||
|
@ -241,8 +234,7 @@ key:
|
|||
(token_types.string, "value2")])
|
||||
|
||||
def test_expr_2(self):
|
||||
self.compare(
|
||||
"""
|
||||
self.compare("""
|
||||
key:
|
||||
if cond=="1": value""",
|
||||
[(token_types.string, "key"),
|
||||
|
@ -256,8 +248,7 @@ key:
|
|||
(token_types.string, "value")])
|
||||
|
||||
def test_expr_3(self):
|
||||
self.compare(
|
||||
"""
|
||||
self.compare("""
|
||||
key:
|
||||
if cond==1.1: value""",
|
||||
[(token_types.string, "key"),
|
||||
|
@ -271,8 +262,7 @@ key:
|
|||
(token_types.string, "value")])
|
||||
|
||||
def test_expr_4(self):
|
||||
self.compare(
|
||||
"""
|
||||
self.compare("""
|
||||
key:
|
||||
if cond==1.1 and cond2 == "a": value""",
|
||||
[(token_types.string, "key"),
|
||||
|
@ -290,8 +280,7 @@ key:
|
|||
(token_types.string, "value")])
|
||||
|
||||
def test_expr_5(self):
|
||||
self.compare(
|
||||
"""
|
||||
self.compare("""
|
||||
key:
|
||||
if (cond==1.1 ): value""",
|
||||
[(token_types.string, "key"),
|
||||
|
@ -307,8 +296,7 @@ key:
|
|||
(token_types.string, "value")])
|
||||
|
||||
def test_expr_6(self):
|
||||
self.compare(
|
||||
"""
|
||||
self.compare("""
|
||||
key:
|
||||
if "\\ttest": value""",
|
||||
[(token_types.string, "key"),
|
||||
|
@ -322,27 +310,26 @@ key:
|
|||
def test_expr_7(self):
|
||||
with self.assertRaises(parser.ParseError):
|
||||
self.tokenize(
|
||||
"""
|
||||
"""
|
||||
key:
|
||||
if 1A: value""")
|
||||
|
||||
def test_expr_8(self):
|
||||
with self.assertRaises(parser.ParseError):
|
||||
self.tokenize(
|
||||
"""
|
||||
"""
|
||||
key:
|
||||
if 1a: value""")
|
||||
|
||||
def test_expr_9(self):
|
||||
with self.assertRaises(parser.ParseError):
|
||||
self.tokenize(
|
||||
"""
|
||||
"""
|
||||
key:
|
||||
if 1.1.1: value""")
|
||||
|
||||
def test_expr_10(self):
|
||||
self.compare(
|
||||
"""
|
||||
self.compare("""
|
||||
key:
|
||||
if 1.: value""",
|
||||
[(token_types.string, "key"),
|
||||
|
|
|
@ -307,7 +307,8 @@ def main():
|
|||
return start(**kwargs)
|
||||
except Exception:
|
||||
if kwargs["pdb"]:
|
||||
import pdb, traceback
|
||||
import pdb
|
||||
import traceback
|
||||
print traceback.format_exc()
|
||||
pdb.post_mortem()
|
||||
else:
|
||||
|
|
|
@ -356,7 +356,7 @@ class ReftestTest(Test):
|
|||
return node
|
||||
|
||||
def update_metadata(self, metadata):
|
||||
if not "url_count" in metadata:
|
||||
if "url_count" not in metadata:
|
||||
metadata["url_count"] = defaultdict(int)
|
||||
for reference, _ in self.references:
|
||||
# We assume a naive implementation in which a url with multiple
|
||||
|
|
|
@ -12,8 +12,8 @@ This would serve bytes 1 to 199, inclusive, of foo.txt with the HTTP status
|
|||
code 404.
|
||||
|
||||
.. note::
|
||||
Pipes are only applied to static files, and will not work if applied to
|
||||
other types of handlers, such as Python File Handlers.
|
||||
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.
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
import os
|
||||
import unittest
|
||||
import time
|
||||
import json
|
||||
|
||||
import pytest
|
||||
|
||||
wptserve = pytest.importorskip("wptserve")
|
||||
from .base import TestUsingServer, doc_root
|
||||
|
||||
|
||||
|
@ -82,5 +84,72 @@ class TestTrickle(TestUsingServer):
|
|||
self.assertEqual(resp.info()["Pragma"], "no-cache")
|
||||
self.assertEqual(resp.info()["Expires"], "0")
|
||||
|
||||
class TestPipesWithVariousHandlers(TestUsingServer):
|
||||
def test_with_python_file_handler(self):
|
||||
resp = self.request("/test_string.py", query="pipe=slice(null,2)")
|
||||
self.assertEqual(resp.read(), "PA")
|
||||
|
||||
def test_with_python_func_handler(self):
|
||||
@wptserve.handlers.handler
|
||||
def handler(request, response):
|
||||
return "PASS"
|
||||
route = ("GET", "/test/test_pipes_1/", handler)
|
||||
self.server.router.register(*route)
|
||||
resp = self.request(route[1], query="pipe=slice(null,2)")
|
||||
self.assertEqual(resp.read(), "PA")
|
||||
|
||||
def test_with_python_func_handler_using_response_writer(self):
|
||||
@wptserve.handlers.handler
|
||||
def handler(request, response):
|
||||
response.writer.write_content("PASS")
|
||||
route = ("GET", "/test/test_pipes_1/", handler)
|
||||
self.server.router.register(*route)
|
||||
resp = self.request(route[1], query="pipe=slice(null,2)")
|
||||
# slice has not been applied to the response, because response.writer was used.
|
||||
self.assertEqual(resp.read(), "PASS")
|
||||
|
||||
def test_header_pipe_with_python_func_using_response_writer(self):
|
||||
@wptserve.handlers.handler
|
||||
def handler(request, response):
|
||||
response.writer.write_content("CONTENT")
|
||||
route = ("GET", "/test/test_pipes_1/", handler)
|
||||
self.server.router.register(*route)
|
||||
resp = self.request(route[1], query="pipe=header(X-TEST,FAIL)")
|
||||
# header pipe was ignored, because response.writer was used.
|
||||
self.assertFalse(resp.info().get("X-TEST"))
|
||||
self.assertEqual(resp.read(), "CONTENT")
|
||||
|
||||
def test_with_json_handler(self):
|
||||
@wptserve.handlers.json_handler
|
||||
def handler(request, response):
|
||||
return json.dumps({'data': 'PASS'})
|
||||
route = ("GET", "/test/test_pipes_2/", handler)
|
||||
self.server.router.register(*route)
|
||||
resp = self.request(route[1], query="pipe=slice(null,2)")
|
||||
self.assertEqual(resp.read(), '"{')
|
||||
|
||||
def test_slice_with_as_is_handler(self):
|
||||
resp = self.request("/test.asis", query="pipe=slice(null,2)")
|
||||
self.assertEqual(202, resp.getcode())
|
||||
self.assertEqual("Giraffe", resp.msg)
|
||||
self.assertEqual("PASS", resp.info()["X-Test"])
|
||||
# slice has not been applied to the response, because response.writer was used.
|
||||
self.assertEqual("Content", resp.read())
|
||||
|
||||
def test_headers_with_as_is_handler(self):
|
||||
resp = self.request("/test.asis", query="pipe=header(X-TEST,FAIL)")
|
||||
self.assertEqual(202, resp.getcode())
|
||||
self.assertEqual("Giraffe", resp.msg)
|
||||
# header pipe was ignored.
|
||||
self.assertEqual("PASS", resp.info()["X-TEST"])
|
||||
self.assertEqual("Content", resp.read())
|
||||
|
||||
def test_trickle_with_as_is_handler(self):
|
||||
t0 = time.time()
|
||||
resp = self.request("/test.asis", query="pipe=trickle(1:d2:5:d1:r2)")
|
||||
t1 = time.time()
|
||||
self.assertTrue('Content' in resp.read())
|
||||
self.assertGreater(6, t1-t0)
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
|
|
@ -237,6 +237,7 @@ class PythonScriptHandler(object):
|
|||
if "main" in environ:
|
||||
handler = FunctionHandler(environ["main"])
|
||||
handler(request, response)
|
||||
wrap_pipeline(path, request, response)
|
||||
else:
|
||||
raise HTTPException(500, "No main function in script %s" % path)
|
||||
except IOError:
|
||||
|
@ -267,6 +268,7 @@ class FunctionHandler(object):
|
|||
else:
|
||||
content = rv
|
||||
response.content = content
|
||||
wrap_pipeline('', request, response)
|
||||
|
||||
|
||||
#The generic name here is so that this can be used as a decorator
|
||||
|
@ -309,6 +311,7 @@ class AsIsHandler(object):
|
|||
try:
|
||||
with open(path) as f:
|
||||
response.writer.write_content(f.read())
|
||||
wrap_pipeline(path, request, response)
|
||||
response.close_connection = True
|
||||
except IOError:
|
||||
raise HTTPException(404)
|
||||
|
|
|
@ -6,6 +6,7 @@ import types
|
|||
import uuid
|
||||
from cStringIO import StringIO
|
||||
|
||||
from six import text_type
|
||||
|
||||
def resolve_content(response):
|
||||
return b"".join(item for item in response.iter_content(read_file=True))
|
||||
|
@ -276,18 +277,18 @@ def slice(request, response, start, end=None):
|
|||
|
||||
|
||||
class ReplacementTokenizer(object):
|
||||
def ident(scanner, token):
|
||||
def ident(self, token):
|
||||
return ("ident", token)
|
||||
|
||||
def index(scanner, token):
|
||||
def index(self, token):
|
||||
token = token[1:-1]
|
||||
try:
|
||||
token = int(token)
|
||||
except ValueError:
|
||||
token = unicode(token, "utf8")
|
||||
token = token.decode('utf8')
|
||||
return ("index", token)
|
||||
|
||||
def var(scanner, token):
|
||||
def var(self, token):
|
||||
token = token[:-1]
|
||||
return ("var", token)
|
||||
|
||||
|
@ -425,7 +426,7 @@ def template(request, content, escape_type="html"):
|
|||
|
||||
#Should possibly support escaping for other contexts e.g. script
|
||||
#TODO: read the encoding of the response
|
||||
return escape_func(unicode(value)).encode("utf-8")
|
||||
return escape_func(text_type(value)).encode("utf-8")
|
||||
|
||||
template_regexp = re.compile(r"{{([^}]*)}}")
|
||||
new_content = template_regexp.sub(config_replacement, content)
|
||||
|
|
|
@ -9,6 +9,8 @@ import socket
|
|||
from .constants import response_codes
|
||||
from .logger import get_logger
|
||||
|
||||
from six import string_types, binary_type, text_type
|
||||
|
||||
missing = object()
|
||||
|
||||
class Response(object):
|
||||
|
@ -400,7 +402,7 @@ class ResponseWriter(object):
|
|||
if name.lower() not in self._headers_seen:
|
||||
self.write_header(name, f())
|
||||
|
||||
if (type(self._response.content) in (str, unicode) and
|
||||
if (isinstance(self._response.content, string_types) and
|
||||
"content-length" not in self._headers_seen):
|
||||
#Would be nice to avoid double-encoding here
|
||||
self.write_header("Content-Length", len(self.encode(self._response.content)))
|
||||
|
@ -457,9 +459,9 @@ class ResponseWriter(object):
|
|||
|
||||
def encode(self, data):
|
||||
"""Convert unicode to bytes according to response.encoding."""
|
||||
if isinstance(data, str):
|
||||
if isinstance(data, binary_type):
|
||||
return data
|
||||
elif isinstance(data, unicode):
|
||||
elif isinstance(data, text_type):
|
||||
return data.encode(self._response.encoding)
|
||||
else:
|
||||
raise ValueError
|
||||
|
|
|
@ -110,14 +110,15 @@ class WebTestServer(ThreadingMixIn, BaseHTTPServer.HTTPServer):
|
|||
# Ensure that we don't hang on shutdown waiting for requests
|
||||
daemon_threads = True
|
||||
|
||||
def __init__(self, server_address, RequestHandlerClass, router, rewriter, bind_hostname,
|
||||
def __init__(self, server_address, request_handler_cls,
|
||||
router, rewriter, bind_hostname,
|
||||
config=None, use_ssl=False, key_file=None, certificate=None,
|
||||
encrypt_after_connect=False, latency=None, **kwargs):
|
||||
"""Server for HTTP(s) Requests
|
||||
|
||||
:param server_address: tuple of (server_name, port)
|
||||
|
||||
:param RequestHandlerClass: BaseHTTPRequestHandler-like class to use for
|
||||
:param request_handler_cls: BaseHTTPRequestHandler-like class to use for
|
||||
handling requests.
|
||||
|
||||
:param router: Router instance to use for matching requests to handler
|
||||
|
@ -161,7 +162,7 @@ class WebTestServer(ThreadingMixIn, BaseHTTPServer.HTTPServer):
|
|||
hostname_port = ("",server_address[1])
|
||||
|
||||
#super doesn't work here because BaseHTTPServer.HTTPServer is old-style
|
||||
BaseHTTPServer.HTTPServer.__init__(self, hostname_port, RequestHandlerClass, **kwargs)
|
||||
BaseHTTPServer.HTTPServer.__init__(self, hostname_port, request_handler_cls, **kwargs)
|
||||
|
||||
if config is not None:
|
||||
Server.config = config
|
||||
|
|
|
@ -2,7 +2,8 @@ import base64
|
|||
import json
|
||||
import os
|
||||
import uuid
|
||||
from multiprocessing.managers import BaseManager, DictProxy
|
||||
import threading
|
||||
from multiprocessing.managers import AcquirerProxy, BaseManager, DictProxy
|
||||
|
||||
class ServerDictManager(BaseManager):
|
||||
shared_data = {}
|
||||
|
@ -13,11 +14,13 @@ def _get_shared():
|
|||
ServerDictManager.register("get_dict",
|
||||
callable=_get_shared,
|
||||
proxytype=DictProxy)
|
||||
ServerDictManager.register('Lock', threading.Lock, AcquirerProxy)
|
||||
|
||||
class ClientDictManager(BaseManager):
|
||||
pass
|
||||
|
||||
ClientDictManager.register("get_dict")
|
||||
ClientDictManager.register("Lock")
|
||||
|
||||
class StashServer(object):
|
||||
def __init__(self, address=None, authkey=None):
|
||||
|
@ -53,6 +56,22 @@ def start_server(address=None, authkey=None):
|
|||
return (manager, manager._address, manager._authkey)
|
||||
|
||||
|
||||
class LockWrapper(object):
|
||||
def __init__(self, lock):
|
||||
self.lock = lock
|
||||
|
||||
def acquire(self):
|
||||
self.lock.acquire()
|
||||
|
||||
def release(self):
|
||||
self.lock.release()
|
||||
|
||||
def __enter__(self):
|
||||
self.acquire()
|
||||
|
||||
def __exit__(self, *args, **kwargs):
|
||||
self.release()
|
||||
|
||||
#TODO: Consider expiring values after some fixed time for long-running
|
||||
#servers
|
||||
|
||||
|
@ -81,21 +100,23 @@ class Stash(object):
|
|||
"""
|
||||
|
||||
_proxy = None
|
||||
lock = None
|
||||
|
||||
def __init__(self, default_path, address=None, authkey=None):
|
||||
self.default_path = default_path
|
||||
self.data = self._get_proxy(address, authkey)
|
||||
self._get_proxy(address, authkey)
|
||||
self.data = Stash._proxy
|
||||
|
||||
def _get_proxy(self, address=None, authkey=None):
|
||||
if address is None and authkey is None:
|
||||
Stash._proxy = {}
|
||||
Stash.lock = threading.Lock()
|
||||
|
||||
if Stash._proxy is None:
|
||||
manager = ClientDictManager(address, authkey)
|
||||
manager.connect()
|
||||
Stash._proxy = manager.get_dict()
|
||||
|
||||
return Stash._proxy
|
||||
Stash.lock = LockWrapper(manager.Lock())
|
||||
|
||||
def _wrap_key(self, key, path):
|
||||
if path is None:
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue