Get rid of httpbin

Fixes #2949
This commit is contained in:
Florian Bruhin 2017-09-19 10:35:54 +02:00
parent 40b26d7492
commit 55a4eb18f2
21 changed files with 266 additions and 154 deletions

View File

@ -4,7 +4,6 @@ hg+https://bitbucket.org/ned/coveragepy
git+https://github.com/micheles/decorator.git
git+https://github.com/pallets/flask.git
git+https://github.com/miracle2k/python-glob2.git
git+https://github.com/Runscope/httpbin.git
git+https://github.com/HypothesisWorks/hypothesis-python.git
git+https://github.com/pallets/itsdangerous.git
git+https://bitbucket.org/zzzeek/mako.git

View File

@ -3,7 +3,6 @@ cheroot
coverage
Flask
hunter
httpbin
hypothesis
pytest
pytest-bdd

View File

@ -34,7 +34,7 @@ from PyQt5.QtCore import PYQT_VERSION
pytest.register_assert_rewrite('end2end.fixtures')
from end2end.fixtures.webserver import httpbin, httpbin_after_test, ssl_server
from end2end.fixtures.webserver import server, server_after_test, ssl_server
from end2end.fixtures.quteprocess import (quteproc_process, quteproc,
quteproc_new)
from end2end.fixtures.testprocess import pytest_runtest_makereport

View File

@ -5,7 +5,7 @@
<title>Failing download when redirecting/aborting</title>
</head>
<body>
<a href="/custom/redirect-later?delay=1">redirect after 1s</a>
<a href="/redirect-later?delay=1">redirect after 1s</a>
<a href="/does-not-exist">404</a>
</body>
</html>

View File

@ -116,14 +116,14 @@ def pytest_runtest_makereport(item, call):
@bdd.given(bdd.parsers.parse("I set {opt} to {value}"))
def set_setting_given(quteproc, httpbin, opt, value):
def set_setting_given(quteproc, server, opt, value):
"""Set a qutebrowser setting.
This is available as "Given:" step so it can be used as "Background:".
"""
if value == '<empty>':
value = ''
value = value.replace('(port)', str(httpbin.port))
value = value.replace('(port)', str(server.port))
quteproc.set_setting(opt, value)
@ -174,7 +174,7 @@ def pdfjs_available():
@bdd.when(bdd.parsers.parse("I open {path}"))
def open_path(quteproc, httpbin, path):
def open_path(quteproc, server, path):
"""Open a URL.
- If used like "When I open ... in a new tab", the URL is opened in a new
@ -183,7 +183,7 @@ def open_path(quteproc, httpbin, path):
- With "... in a private window" it's opened in a new private window.
- With "... as a URL", it's opened according to new_instance_open_target.
"""
path = path.replace('(port)', str(httpbin.port))
path = path.replace('(port)', str(server.port))
new_tab = False
new_bg_tab = False
@ -227,16 +227,16 @@ def open_path(quteproc, httpbin, path):
@bdd.when(bdd.parsers.parse("I set {opt} to {value}"))
def set_setting(quteproc, httpbin, opt, value):
def set_setting(quteproc, server, opt, value):
"""Set a qutebrowser setting."""
if value == '<empty>':
value = ''
value = value.replace('(port)', str(httpbin.port))
value = value.replace('(port)', str(server.port))
quteproc.set_setting(opt, value)
@bdd.when(bdd.parsers.parse("I run {command}"))
def run_command(quteproc, httpbin, tmpdir, command):
def run_command(quteproc, server, tmpdir, command):
"""Run a qutebrowser command.
The suffix "with count ..." can be used to pass a count to the command.
@ -254,7 +254,7 @@ def run_command(quteproc, httpbin, tmpdir, command):
else:
invalid = False
command = command.replace('(port)', str(httpbin.port))
command = command.replace('(port)', str(server.port))
command = command.replace('(testdata)', utils.abs_datapath())
command = command.replace('(tmpdir)', str(tmpdir))
command = command.replace('(dirsep)', os.sep)
@ -264,9 +264,9 @@ def run_command(quteproc, httpbin, tmpdir, command):
@bdd.when(bdd.parsers.parse("I reload"))
def reload(qtbot, httpbin, quteproc, command):
def reload(qtbot, server, quteproc, command):
"""Reload and wait until a new request is received."""
with qtbot.waitSignal(httpbin.new_request):
with qtbot.waitSignal(server.new_request):
quteproc.send_cmd(':reload')
@ -294,10 +294,10 @@ def wait_in_log(quteproc, is_regex, pattern, do_skip):
@bdd.when(bdd.parsers.re(r'I wait for the (?P<category>error|message|warning) '
r'"(?P<message>.*)"'))
def wait_for_message(quteproc, httpbin, category, message):
def wait_for_message(quteproc, server, category, message):
"""Wait for a given statusbar message/error/warning."""
quteproc.log_summary('Waiting for {} "{}"'.format(category, message))
expect_message(quteproc, httpbin, category, message)
expect_message(quteproc, server, category, message)
@bdd.when(bdd.parsers.parse("I wait {delay}s"))
@ -328,8 +328,8 @@ def selection_not_supported(qapp):
@bdd.when(bdd.parsers.re(r'I put "(?P<content>.*)" into the '
r'(?P<what>primary selection|clipboard)'))
def fill_clipboard(quteproc, httpbin, what, content):
content = content.replace('(port)', str(httpbin.port))
def fill_clipboard(quteproc, server, what, content):
content = content.replace('(port)', str(server.port))
content = content.replace(r'\n', '\n')
quteproc.send_cmd(':debug-set-fake-clipboard "{}"'.format(content))
@ -337,8 +337,8 @@ def fill_clipboard(quteproc, httpbin, what, content):
@bdd.when(bdd.parsers.re(r'I put the following lines into the '
r'(?P<what>primary selection|clipboard):\n'
r'(?P<content>.+)$', flags=re.DOTALL))
def fill_clipboard_multiline(quteproc, httpbin, what, content):
fill_clipboard(quteproc, httpbin, what, textwrap.dedent(content))
def fill_clipboard_multiline(quteproc, server, what, content):
fill_clipboard(quteproc, server, what, textwrap.dedent(content))
@bdd.when(bdd.parsers.parse('I hint with args "{args}"'))
@ -395,28 +395,28 @@ def path_should_be_loaded(quteproc, path):
@bdd.then(bdd.parsers.parse("{path} should be requested"))
def path_should_be_requested(httpbin, path):
def path_should_be_requested(server, path):
"""Make sure the given path was loaded from the webserver."""
httpbin.wait_for(verb='GET', path='/' + path)
server.wait_for(verb='GET', path='/' + path)
@bdd.then(bdd.parsers.parse("The requests should be:\n{pages}"))
def list_of_requests(httpbin, pages):
def list_of_requests(server, pages):
"""Make sure the given requests were done from the webserver."""
expected_requests = [httpbin.ExpectedRequest('GET', '/' + path.strip())
expected_requests = [server.ExpectedRequest('GET', '/' + path.strip())
for path in pages.split('\n')]
actual_requests = httpbin.get_requests()
actual_requests = server.get_requests()
assert actual_requests == expected_requests
@bdd.then(bdd.parsers.parse("The unordered requests should be:\n{pages}"))
def list_of_requests_unordered(httpbin, pages):
def list_of_requests_unordered(server, pages):
"""Make sure the given requests were done (in no particular order)."""
expected_requests = [httpbin.ExpectedRequest('GET', '/' + path.strip())
expected_requests = [server.ExpectedRequest('GET', '/' + path.strip())
for path in pages.split('\n')]
actual_requests = httpbin.get_requests()
actual_requests = server.get_requests()
# Requests are not hashable, we need to convert to ExpectedRequests
actual_requests = [httpbin.ExpectedRequest.from_request(req)
actual_requests = [server.ExpectedRequest.from_request(req)
for req in actual_requests]
assert (collections.Counter(actual_requests) ==
collections.Counter(expected_requests))
@ -424,14 +424,14 @@ def list_of_requests_unordered(httpbin, pages):
@bdd.then(bdd.parsers.re(r'the (?P<category>error|message|warning) '
r'"(?P<message>.*)" should be shown'))
def expect_message(quteproc, httpbin, category, message):
def expect_message(quteproc, server, category, message):
"""Expect the given message in the qutebrowser log."""
category_to_loglevel = {
'message': logging.INFO,
'error': logging.ERROR,
'warning': logging.WARNING,
}
message = message.replace('(port)', str(httpbin.port))
message = message.replace('(port)', str(server.port))
quteproc.mark_expected(category='message',
loglevel=category_to_loglevel[category],
message=message)
@ -439,12 +439,12 @@ def expect_message(quteproc, httpbin, category, message):
@bdd.then(bdd.parsers.re(r'(?P<is_regex>regex )?"(?P<pattern>[^"]+)" should '
r'be logged'))
def should_be_logged(quteproc, httpbin, is_regex, pattern):
def should_be_logged(quteproc, server, is_regex, pattern):
"""Expect the given pattern on regex in the log."""
if is_regex:
pattern = re.compile(pattern)
else:
pattern = pattern.replace('(port)', str(httpbin.port))
pattern = pattern.replace('(port)', str(server.port))
line = quteproc.wait_for(message=pattern)
line.expected = True
@ -493,7 +493,7 @@ def no_crash():
def check_header(quteproc, header, value):
"""Check if a given header is set correctly.
This assumes we're on the httpbin header page.
This assumes we're on the server header page.
"""
content = quteproc.get_content()
data = json.loads(content)
@ -589,16 +589,16 @@ def check_open_tabs(quteproc, request, tabs):
@bdd.then(bdd.parsers.re(r'the (?P<what>primary selection|clipboard) should '
r'contain "(?P<content>.*)"'))
def clipboard_contains(quteproc, httpbin, what, content):
expected = content.replace('(port)', str(httpbin.port))
def clipboard_contains(quteproc, server, what, content):
expected = content.replace('(port)', str(server.port))
expected = expected.replace('\\n', '\n')
quteproc.wait_for(message='Setting fake {}: {}'.format(
what, json.dumps(expected)))
@bdd.then(bdd.parsers.parse('the clipboard should contain:\n{content}'))
def clipboard_contains_multiline(quteproc, httpbin, content):
expected = textwrap.dedent(content).replace('(port)', str(httpbin.port))
def clipboard_contains_multiline(quteproc, server, content):
expected = textwrap.dedent(content).replace('(port)', str(server.port))
quteproc.wait_for(message='Setting fake clipboard: {}'.format(
json.dumps(expected)))

View File

@ -292,7 +292,7 @@ Feature: Downloading things from a website.
Scenario: Opening a mhtml download directly
When I set downloads.location.prompt to true
And I open html
And I open /
And I run :download --mhtml
And I wait for the download prompt for "*"
And I directly open the download
@ -572,27 +572,27 @@ Feature: Downloading things from a website.
Scenario: Downloading with redirect to itself
When I set downloads.location.prompt to false
And I run :download http://localhost:(port)/custom/redirect-self
And I run :download http://localhost:(port)/redirect-self
And I wait until the download is finished
Then the downloaded file redirect-self should exist
Scenario: Downloading with absolute redirect
When I set downloads.location.prompt to false
And I run :download http://localhost:(port)/absolute-redirect/1
And I run :download http://localhost:(port)/absolute-redirect
And I wait until the download is finished
Then the downloaded file 1 should exist
Then the downloaded file absolute-redirect should exist
Scenario: Downloading with relative redirect
When I set downloads.location.prompt to false
And I run :download http://localhost:(port)/relative-redirect/1
And I run :download http://localhost:(port)/relative-redirect
And I wait until the download is finished
Then the downloaded file 1 should exist
Then the downloaded file relative-redirect should exist
## Other
Scenario: Download without a content-size
When I set downloads.location.prompt to false
When I run :download http://localhost:(port)/custom/content-size
When I run :download http://localhost:(port)/content-size
And I wait until the download is finished
Then the downloaded file content-size should exist
@ -604,13 +604,13 @@ Feature: Downloading things from a website.
Scenario: Downloading 20MB file
When I set downloads.location.prompt to false
And I run :download http://localhost:(port)/custom/twenty-mb
And I run :download http://localhost:(port)/twenty-mb
And I wait until the download is finished
Then the downloaded file twenty-mb should be 20971520 bytes big
Scenario: Downloading 20MB file with late prompt confirmation
When I set downloads.location.prompt to true
And I run :download http://localhost:(port)/custom/twenty-mb
And I run :download http://localhost:(port)/twenty-mb
And I wait 1s
And I run :prompt-accept
And I wait until the download is finished
@ -645,12 +645,6 @@ Feature: Downloading things from a website.
Then the downloaded file download.bin should exist
And the downloaded file download2.bin should not exist
Scenario: Downloading a file with unknown size
When I set downloads.location.prompt to false
And I open stream-bytes/1024 without waiting
And I wait until the download is finished
Then the downloaded file 1024 should exist
@qtwebengine_skip: We can't get the UA from the page there
Scenario: user-agent when using :download
When I open user-agent
@ -660,16 +654,14 @@ Feature: Downloading things from a website.
@qtwebengine_skip: We can't get the UA from the page there
Scenario: user-agent when using hints
When I set hints.mode to number
And I open /
When I open /
And I run :hint links download
And I press the keys "us" # user-agent
And I run :follow-hint 0
And I run :follow-hint a
And I wait until the download is finished
Then the downloaded file user-agent should contain Safari/
@qtwebengine_skip: Handled by QtWebEngine, not by us
Scenario: Downloading a "Internal server error" with disposition: inline (#2304)
When I set downloads.location.prompt to false
And I open custom/500-inline
And I open 500-inline
Then the error "Download error: *INTERNAL SERVER ERROR" should be shown

View File

@ -46,10 +46,10 @@ Feature: Page history
@qtwebengine_todo: Error page message is not implemented
Scenario: History with a 404
When I open status/404 without waiting
And I wait for "Error while loading http://localhost:*/status/404: NOT FOUND" in the log
When I open 404 without waiting
And I wait for "Error while loading http://localhost:*/404: NOT FOUND" in the log
Then the history should contain:
http://localhost:(port)/status/404 Error loading page: http://localhost:(port)/status/404
http://localhost:(port)/404 Error loading page: http://localhost:(port)/404
Scenario: History with invalid URL
When I run :tab-only

View File

@ -186,15 +186,15 @@ Feature: Various utility commands.
Given I have a fresh instance
# We can't use "When I open" because we don't want to wait for load
# finished
When I run :open http://localhost:(port)/custom/redirect-later?delay=-1
When I run :open http://localhost:(port)/redirect-later?delay=-1
And I wait for "emitting: cur_load_status_changed('loading') (tab *)" in the log
And I wait 1s
And I run :stop
And I open custom/redirect-later-continue in a new tab
And I open redirect-later-continue in a new tab
And I wait 1s
Then the unordered requests should be:
custom/redirect-later-continue
custom/redirect-later?delay=-1
redirect-later-continue
redirect-later?delay=-1
# no request on / because we stopped the redirect
Scenario: :stop with wrong count

View File

@ -25,7 +25,7 @@ bdd.scenarios('adblock.feature')
@bdd.when(bdd.parsers.parse('I set up "{lists}" as block lists'))
def set_up_blocking(quteproc, lists, httpbin):
url = 'http://localhost:{}/data/adblock/'.format(httpbin.port)
def set_up_blocking(quteproc, lists, server):
url = 'http://localhost:{}/data/adblock/'.format(server.port)
urls = [url + item.strip() for item in lists.split(',')]
quteproc.set_setting('content.host_blocking.lists', json.dumps(urls))

View File

@ -27,9 +27,9 @@ bdd.scenarios('editor.feature')
@bdd.when(bdd.parsers.parse('I set up a fake editor replacing "{text}" by '
'"{replacement}"'))
def set_up_editor_replacement(quteproc, httpbin, tmpdir, text, replacement):
def set_up_editor_replacement(quteproc, server, tmpdir, text, replacement):
"""Set up editor.command to a small python script doing a replacement."""
text = text.replace('(port)', str(httpbin.port))
text = text.replace('(port)', str(server.port))
script = tmpdir / 'script.py'
script.write(textwrap.dedent("""
import sys
@ -47,7 +47,7 @@ def set_up_editor_replacement(quteproc, httpbin, tmpdir, text, replacement):
@bdd.when(bdd.parsers.parse('I set up a fake editor returning "{text}"'))
def set_up_editor(quteproc, httpbin, tmpdir, text):
def set_up_editor(quteproc, server, tmpdir, text):
"""Set up editor.command to a small python script inserting a text."""
script = tmpdir / 'script.py'
script.write(textwrap.dedent("""

View File

@ -35,7 +35,7 @@ def turn_on_sql_history(quteproc):
@bdd.then(bdd.parsers.parse("the history should contain:\n{expected}"))
def check_history(quteproc, httpbin, tmpdir, expected):
def check_history(quteproc, server, tmpdir, expected):
path = tmpdir / 'history'
quteproc.send_cmd(':debug-dump-history "{}"'.format(path))
quteproc.wait_for(category='message', loglevel=logging.INFO,
@ -45,10 +45,10 @@ def check_history(quteproc, httpbin, tmpdir, expected):
# ignore access times, they will differ in each run
actual = '\n'.join(re.sub('^\\d+-?', '', line).strip() for line in f)
expected = expected.replace('(port)', str(httpbin.port))
expected = expected.replace('(port)', str(server.port))
assert actual == expected
@bdd.then("the history should be empty")
def check_history_empty(quteproc, httpbin, tmpdir):
check_history(quteproc, httpbin, tmpdir, '')
def check_history_empty(quteproc, server, tmpdir):
check_history(quteproc, server, tmpdir, '')

View File

@ -27,7 +27,7 @@ bdd.scenarios('private.feature')
def check_cookie(quteproc, name, value):
"""Check if a given cookie is set correctly.
This assumes we're on the httpbin cookies page.
This assumes we're on the server cookies page.
"""
content = quteproc.get_content()
data = json.loads(content)

View File

@ -34,13 +34,13 @@ def create_session_file(quteproc, name, contents):
@bdd.when(bdd.parsers.parse('I replace "{pattern}" by "{replacement}" in the '
'"{name}" session file'))
def session_replace(quteproc, httpbin, pattern, replacement, name):
def session_replace(quteproc, server, pattern, replacement, name):
# First wait until the session was actually saved
quteproc.wait_for(category='message', loglevel=logging.INFO,
message='Saved session {}.'.format(name))
filename = os.path.join(quteproc.basedir, 'data', 'sessions',
name + '.yml')
replacement = replacement.replace('(port)', str(httpbin.port)) # yo dawg
replacement = replacement.replace('(port)', str(server.port)) # yo dawg
with open(filename, 'r', encoding='utf-8') as f:
data = f.read()
with open(filename, 'w', encoding='utf-8') as f:

View File

@ -415,10 +415,10 @@ class QuteProc(testprocess.Process):
if any(path.startswith(scheme) for scheme in special_schemes):
return path
else:
httpbin = self.request.getfixturevalue('httpbin')
server = self.request.getfixturevalue('server')
return '{}://localhost:{}/{}'.format(
'https' if https else 'http',
httpbin.port if port is None else port,
server.port if port is None else port,
path if path != '/' else '')
def wait_for_js(self, message):
@ -778,7 +778,7 @@ def _xpath_escape(text):
@pytest.fixture(scope='module')
def quteproc_process(qapp, httpbin, request):
def quteproc_process(qapp, server, request):
"""Fixture for qutebrowser process which is started once per file."""
# Passing request so it has an initial config
proc = QuteProc(request)
@ -788,7 +788,7 @@ def quteproc_process(qapp, httpbin, request):
@pytest.fixture
def quteproc(quteproc_process, httpbin, request):
def quteproc(quteproc_process, server, request):
"""Per-test qutebrowser fixture which uses the per-file process."""
request.node._quteproc_log = quteproc_process.captured_log
quteproc_process.before_test()
@ -798,7 +798,7 @@ def quteproc(quteproc_process, httpbin, request):
@pytest.fixture
def quteproc_new(qapp, httpbin, request):
def quteproc_new(qapp, server, request):
"""Per-test qutebrowser process to test invocations."""
proc = QuteProc(request)
request.node._quteproc_log = proc.captured_log

View File

@ -66,23 +66,23 @@ class FakeRequest:
"""Fake for request."""
def __init__(self, node, config, httpbin):
def __init__(self, node, config, server):
self.node = node
self.config = config
self._httpbin = httpbin
self._server = server
def getfixturevalue(self, name):
assert name == 'httpbin'
return self._httpbin
assert name == 'server'
return self._server
@pytest.fixture
def request_mock(quteproc, monkeypatch, httpbin):
def request_mock(quteproc, monkeypatch, server):
"""Patch out a pytest request."""
fake_call = FakeRepCall()
fake_config = FakeConfig()
fake_node = FakeNode(fake_call)
fake_request = FakeRequest(fake_node, fake_config, httpbin)
fake_request = FakeRequest(fake_node, fake_config, server)
assert not hasattr(fake_request.node.rep_call, 'wasxfail')
monkeypatch.setattr(quteproc, 'request', fake_request)
return fake_request

View File

@ -17,7 +17,7 @@
# You should have received a copy of the GNU General Public License
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
"""Test the httpbin webserver used for tests."""
"""Test the server webserver used for tests."""
import json
import urllib.request
@ -27,14 +27,14 @@ import pytest
@pytest.mark.parametrize('path, content, expected', [
('/', '<title>httpbin(1): HTTP Client Testing Service</title>', True),
# https://github.com/Runscope/httpbin/issues/245
('/', 'qutebrowser test webserver', True),
# https://github.com/Runscope/server/issues/245
('/', 'www.google-analytics.com', False),
('/data/hello.txt', 'Hello World!', True),
])
def test_httpbin(httpbin, qtbot, path, content, expected):
with qtbot.waitSignal(httpbin.new_request, timeout=100):
url = 'http://localhost:{}{}'.format(httpbin.port, path)
def test_server(server, qtbot, path, content, expected):
with qtbot.waitSignal(server.new_request, timeout=100):
url = 'http://localhost:{}{}'.format(server.port, path)
try:
response = urllib.request.urlopen(url)
except urllib.error.HTTPError as e:
@ -47,7 +47,7 @@ def test_httpbin(httpbin, qtbot, path, content, expected):
data = response.read().decode('utf-8')
assert httpbin.get_requests() == [httpbin.ExpectedRequest('GET', path)]
assert server.get_requests() == [server.ExpectedRequest('GET', path)]
assert (content in data) == expected
@ -58,7 +58,7 @@ def test_httpbin(httpbin, qtbot, path, content, expected):
({'verb': 'GET', 'path': '/', 'status': 200}, 'GET', '/foo', False),
({'verb': 'POST', 'path': '/', 'status': 200}, 'GET', '/', False),
])
def test_expected_request(httpbin, line, verb, path, equal):
expected = httpbin.ExpectedRequest(verb, path)
request = httpbin.Request(json.dumps(line))
def test_expected_request(server, line, verb, path, equal):
expected = server.ExpectedRequest(verb, path)
request = server.Request(json.dumps(line))
assert (expected == request) == equal

View File

@ -90,7 +90,7 @@ def _render_log(data, threshold=100):
@pytest.hookimpl(hookwrapper=True)
def pytest_runtest_makereport(item, call):
"""Add qutebrowser/httpbin sections to captured output if a test failed."""
"""Add qutebrowser/server sections to captured output if a test failed."""
outcome = yield
if call.when not in ['call', 'teardown']:
return
@ -100,7 +100,7 @@ def pytest_runtest_makereport(item, call):
return
quteproc_log = getattr(item, '_quteproc_log', None)
httpbin_log = getattr(item, '_httpbin_log', None)
server_log = getattr(item, '_server_log', None)
if not hasattr(report.longrepr, 'addsection'):
# In some conditions (on macOS and Windows it seems), report.longrepr
@ -114,8 +114,8 @@ def pytest_runtest_makereport(item, call):
if quteproc_log is not None:
report.longrepr.addsection("qutebrowser output",
_render_log(quteproc_log))
if httpbin_log is not None:
report.longrepr.addsection("httpbin output", _render_log(httpbin_log))
if server_log is not None:
report.longrepr.addsection("server output", _render_log(server_log))
class Process(QObject):

View File

@ -17,7 +17,7 @@
# You should have received a copy of the GNU General Public License
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
"""Fixtures for the httpbin webserver."""
"""Fixtures for the server webserver."""
import re
import sys
@ -35,7 +35,7 @@ from qutebrowser.utils import utils
class Request(testprocess.Line):
"""A parsed line from the httpbin/flask log output.
"""A parsed line from the flask log output.
Attributes:
verb/path/status: Parsed from the log output.
@ -67,22 +67,20 @@ class Request(testprocess.Line):
'/favicon.ico': [http.client.NOT_FOUND],
'/does-not-exist': [http.client.NOT_FOUND],
'/does-not-exist-2': [http.client.NOT_FOUND],
'/status/404': [http.client.NOT_FOUND],
'/404': [http.client.NOT_FOUND],
'/custom/redirect-later': [http.client.FOUND],
'/custom/redirect-self': [http.client.FOUND],
'/redirect-later': [http.client.FOUND],
'/redirect-self': [http.client.FOUND],
'/redirect-to': [http.client.FOUND],
'/relative-redirect': [http.client.FOUND],
'/absolute-redirect': [http.client.FOUND],
'/cookies/set': [http.client.FOUND],
'/custom/500-inline': [http.client.INTERNAL_SERVER_ERROR],
'/500-inline': [http.client.INTERNAL_SERVER_ERROR],
}
for i in range(15):
path_to_statuses['/redirect/{}'.format(i)] = [http.client.FOUND]
path_to_statuses['/relative-redirect/{}'.format(i)] = [
http.client.FOUND]
path_to_statuses['/absolute-redirect/{}'.format(i)] = [
http.client.FOUND]
for suffix in ['', '1', '2', '3', '4', '5', '6']:
key = '/basic-auth/user{}/password{}'.format(suffix, suffix)
path_to_statuses[key] = [http.client.UNAUTHORIZED, http.client.OK]
@ -130,7 +128,7 @@ class ExpectedRequest:
class WebserverProcess(testprocess.Process):
"""Abstraction over a running HTTPbin server process.
"""Abstraction over a running Flask server process.
Reads the log from its stdout and parses it.
@ -186,31 +184,31 @@ class WebserverProcess(testprocess.Process):
@pytest.fixture(scope='session', autouse=True)
def httpbin(qapp):
"""Fixture for an httpbin object which ensures clean setup/teardown."""
httpbin = WebserverProcess('webserver_sub')
httpbin.start()
yield httpbin
httpbin.cleanup()
def server(qapp):
"""Fixture for an server object which ensures clean setup/teardown."""
server = WebserverProcess('webserver_sub')
server.start()
yield server
server.cleanup()
@pytest.fixture(autouse=True)
def httpbin_after_test(httpbin, request):
"""Fixture to clean httpbin request list after each test."""
request.node._httpbin_log = httpbin.captured_log
def server_after_test(server, request):
"""Fixture to clean server request list after each test."""
request.node._server_log = server.captured_log
yield
httpbin.after_test()
server.after_test()
@pytest.fixture
def ssl_server(request, qapp):
"""Fixture for a webserver with a self-signed SSL certificate.
This needs to be explicitly used in a test, and overwrites the httpbin log
This needs to be explicitly used in a test, and overwrites the server log
used in that test.
"""
server = WebserverProcess('webserver_sub_ssl')
request.node._httpbin_log = server.captured_log
request.node._server_log = server.captured_log
server.start()
yield server
server.after_test()

View File

@ -17,9 +17,13 @@
# You should have received a copy of the GNU General Public License
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
"""httpbin web server for end2end tests.
"""Web server for end2end tests.
This script gets called as a QProcess from end2end/conftest.py.
Some of the handlers here are inspired by the server project, but simplified
for qutebrowser's needs. Note that it probably doesn't handle e.g. multiple
parameters or headers with the same name properly.
"""
import sys
@ -29,15 +33,20 @@ import signal
import os
import threading
from httpbin.core import app
from httpbin.structures import CaseInsensitiveDict
import cheroot.wsgi
import flask
app = flask.Flask(__name__)
_redirect_later_event = None
@app.route('/')
def root():
"""Show simple text."""
return flask.Response(b'qutebrowser test webserver, '
b'<a href="/user-agent">user agent</a>')
@app.route('/data/<path:path>')
def send_data(path):
"""Send a given data file to qutebrowser.
@ -57,15 +66,14 @@ def send_data(path):
return flask.send_from_directory(data_dir, path)
@app.route('/custom/redirect-later')
@app.route('/redirect-later')
def redirect_later():
"""302 redirect to / after the given delay.
If delay is -1, wait until a request on redirect-later-continue is done.
"""
global _redirect_later_event
args = CaseInsensitiveDict(flask.request.args.items())
delay = int(args.get('delay', '1'))
delay = int(flask.request.args.get('delay', '1'))
if delay == -1:
_redirect_later_event = threading.Event()
ok = _redirect_later_event.wait(timeout=30 * 1000)
@ -77,7 +85,7 @@ def redirect_later():
return x
@app.route('/custom/redirect-later-continue')
@app.route('/redirect-later-continue')
def redirect_later_continue():
"""Continue a redirect-later request."""
if _redirect_later_event is None:
@ -87,7 +95,50 @@ def redirect_later_continue():
return flask.Response(b'Continued redirect.')
@app.route('/custom/content-size')
@app.route('/redirect-self')
def redirect_self():
"""302 Redirects to itself."""
return app.make_response(flask.redirect(flask.url_for('redirect_self')))
@app.route('/redirect/<int:n>')
def redirect_n_times(n):
"""302 Redirects n times."""
assert n > 0
return flask.redirect(flask.url_for('redirect_n_times', n=n-1))
@app.route('/relative-redirect')
def relative_redirect():
"""302 Redirect once."""
response = app.make_response('')
response.status_code = 302
response.headers['Location'] = flask.url_for('root')
return response
@app.route('/absolute-redirect')
def absolute_redirect():
"""302 Redirect once."""
response = app.make_response('')
response.status_code = 302
response.headers['Location'] = flask.url_for('root', _external=True)
return response
@app.route('/redirect-to')
def redirect_to():
"""302/3XX Redirects to the given URL."""
# We need to build the response manually and convert to UTF-8 to prevent
# werkzeug from "fixing" the URL. This endpoint should set the Location
# header to the exact string supplied.
response = app.make_response('')
response.status_code = 302
response.headers['Location'] = flask.request.args['url'].encode('utf-8')
return response
@app.route('/content-size')
def content_size():
"""Send two bytes of data without a content-size."""
def generate_bytes():
@ -102,7 +153,7 @@ def content_size():
return response
@app.route('/custom/twenty-mb')
@app.route('/twenty-mb')
def twenty_mb():
"""Send 20MB of data."""
def generate_bytes():
@ -116,13 +167,7 @@ def twenty_mb():
return response
@app.route('/custom/redirect-self')
def redirect_self():
"""302 Redirects to itself."""
return app.make_response(flask.redirect(flask.url_for('redirect_self')))
@app.route('/custom/500-inline')
@app.route('/500-inline')
def internal_error_attachment():
"""A 500 error with Content-Disposition: inline."""
response = flask.Response(b"", headers={
@ -133,6 +178,85 @@ def internal_error_attachment():
return response
@app.route('/cookies')
def view_cookies():
"""Show cookies."""
return flask.jsonify(cookies=flask.request.cookies)
@app.route('/cookies/set')
def set_cookies():
"""Set cookie(s) as provided by the query string."""
r = app.make_response(flask.redirect(flask.url_for('view_cookies')))
for key, value in flask.request.args.items():
r.set_cookie(key=key, value=value)
return r
@app.route('/basic-auth/<user>/<passwd>')
def basic_auth(user='user', passwd='passwd'):
"""Prompt the user for authorization using HTTP Basic Auth."""
auth = flask.request.authorization
if not auth or auth.username != user or auth.password != passwd:
r = flask.make_response()
r.status_code = 401
r.headers = {'WWW-Authenticate': 'Basic realm="Fake Realm"'}
return r
return flask.jsonify(authenticated=True, user=user)
@app.route('/drip')
def drip():
"""Drip data over a duration."""
duration = float(flask.request.args.get('duration'))
numbytes = int(flask.request.args.get('numbytes'))
pause = duration / numbytes
def generate_bytes():
for _ in range(numbytes):
yield u"*".encode('utf-8')
time.sleep(pause)
response = flask.Response(generate_bytes(), headers={
"Content-Type": "application/octet-stream",
"Content-Length": str(numbytes),
})
response.status_code = 200
return response
@app.route('/404')
def status_404():
r = flask.make_response()
r.status_code = 404
return r
@app.route('/headers')
def view_headers():
"""Return HTTP headers."""
return flask.jsonify(headers=dict(flask.request.headers))
@app.route('/response-headers')
def response_headers():
"""Return a set of response headers from the query string."""
headers = flask.request.args
response = flask.jsonify(headers)
response.headers.extend(headers)
response = flask.jsonify(dict(response.headers))
response.headers.extend(headers)
return response
@app.route('/user-agent')
def view_user_agent():
"""Return User-Agent."""
return flask.jsonify({'user-agent': flask.request.headers['user-agent']})
@app.after_request
def log_request(response):
"""Log a webserver request."""

View File

@ -70,7 +70,7 @@ def temp_basedir_env(tmpdir, short_tmpdir):
@pytest.mark.linux
def test_ascii_locale(request, httpbin, tmpdir, quteproc_new):
def test_ascii_locale(request, server, tmpdir, quteproc_new):
"""Test downloads with LC_ALL=C set.
https://github.com/qutebrowser/qutebrowser/issues/908
@ -83,7 +83,7 @@ def test_ascii_locale(request, httpbin, tmpdir, quteproc_new):
# Test a normal download
quteproc_new.set_setting('downloads.location.prompt', 'false')
url = 'http://localhost:{port}/data/downloads/ä-issue908.bin'.format(
port=httpbin.port)
port=server.port)
quteproc_new.send_cmd(':download {}'.format(url))
quteproc_new.wait_for(category='downloads',
message='Download ?-issue908.bin finished')
@ -103,7 +103,7 @@ def test_ascii_locale(request, httpbin, tmpdir, quteproc_new):
@pytest.mark.linux
def test_misconfigured_user_dirs(request, httpbin, temp_basedir_env,
def test_misconfigured_user_dirs(request, server, temp_basedir_env,
tmpdir, quteproc_new):
"""Test downloads with a misconfigured XDG_DOWNLOAD_DIR.
@ -122,7 +122,7 @@ def test_misconfigured_user_dirs(request, httpbin, temp_basedir_env,
quteproc_new.set_setting('downloads.location.prompt', 'false')
url = 'http://localhost:{port}/data/downloads/download.bin'.format(
port=httpbin.port)
port=server.port)
quteproc_new.send_cmd(':download {}'.format(url))
line = quteproc_new.wait_for(
loglevel=logging.ERROR, category='message',

View File

@ -85,25 +85,25 @@ def download_dir(tmpdir):
return DownloadDir(tmpdir)
def _test_mhtml_requests(test_dir, test_path, httpbin):
def _test_mhtml_requests(test_dir, test_path, server):
with open(os.path.join(test_dir, 'requests'), encoding='utf-8') as f:
expected_requests = []
for line in f:
if line.startswith('#'):
continue
path = '/{}/{}'.format(test_path, line.strip())
expected_requests.append(httpbin.ExpectedRequest('GET', path))
expected_requests.append(server.ExpectedRequest('GET', path))
actual_requests = httpbin.get_requests()
actual_requests = server.get_requests()
# Requests are not hashable, we need to convert to ExpectedRequests
actual_requests = [httpbin.ExpectedRequest.from_request(req)
actual_requests = [server.ExpectedRequest.from_request(req)
for req in actual_requests]
assert (collections.Counter(actual_requests) ==
collections.Counter(expected_requests))
@pytest.mark.parametrize('test_name', collect_tests())
def test_mhtml(request, test_name, download_dir, quteproc, httpbin):
def test_mhtml(request, test_name, download_dir, quteproc, server):
quteproc.set_setting('downloads.location.directory', download_dir.location)
quteproc.set_setting('downloads.location.prompt', 'false')
@ -119,10 +119,10 @@ def test_mhtml(request, test_name, download_dir, quteproc, httpbin):
# Wait for favicon.ico to be loaded if there is one
if os.path.exists(os.path.join(test_dir, 'favicon.png')):
httpbin.wait_for(path='/{}/favicon.png'.format(test_path))
server.wait_for(path='/{}/favicon.png'.format(test_path))
# Discard all requests that were necessary to display the page
httpbin.clear_data()
server.clear_data()
quteproc.send_cmd(':download --mhtml --dest "{}"'.format(download_dest))
quteproc.wait_for(category='downloads',
message='File successfully written.')
@ -136,4 +136,4 @@ def test_mhtml(request, test_name, download_dir, quteproc, httpbin):
download_dir.sanity_check_mhtml()
if not request.config.webengine:
_test_mhtml_requests(test_dir, test_path, httpbin)
_test_mhtml_requests(test_dir, test_path, server)