diff --git a/misc/requirements/requirements-tests-git.txt b/misc/requirements/requirements-tests-git.txt index 5a4450293..771cf58c8 100644 --- a/misc/requirements/requirements-tests-git.txt +++ b/misc/requirements/requirements-tests-git.txt @@ -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 diff --git a/misc/requirements/requirements-tests.txt-raw b/misc/requirements/requirements-tests.txt-raw index 3ca6de6bd..bc44bc8e1 100644 --- a/misc/requirements/requirements-tests.txt-raw +++ b/misc/requirements/requirements-tests.txt-raw @@ -3,7 +3,6 @@ cheroot coverage Flask hunter -httpbin hypothesis pytest pytest-bdd diff --git a/tests/end2end/conftest.py b/tests/end2end/conftest.py index e9d2a8e95..1fd6eb874 100644 --- a/tests/end2end/conftest.py +++ b/tests/end2end/conftest.py @@ -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 diff --git a/tests/end2end/data/downloads/issue889.html b/tests/end2end/data/downloads/issue889.html index f6dbcfcbd..eccbefb33 100644 --- a/tests/end2end/data/downloads/issue889.html +++ b/tests/end2end/data/downloads/issue889.html @@ -5,7 +5,7 @@ Failing download when redirecting/aborting - redirect after 1s + redirect after 1s 404 diff --git a/tests/end2end/features/conftest.py b/tests/end2end/features/conftest.py index 602657541..52dfe616c 100644 --- a/tests/end2end/features/conftest.py +++ b/tests/end2end/features/conftest.py @@ -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 == '': 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 == '': 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 (?Perror|message|warning) ' r'"(?P.*)"')) -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.*)" into the ' r'(?Pprimary 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'(?Pprimary selection|clipboard):\n' r'(?P.+)$', 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 (?Perror|message|warning) ' r'"(?P.*)" 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'(?Pregex )?"(?P[^"]+)" 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 (?Pprimary selection|clipboard) should ' r'contain "(?P.*)"')) -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))) diff --git a/tests/end2end/features/downloads.feature b/tests/end2end/features/downloads.feature index 29d9fc7e3..103ad0123 100644 --- a/tests/end2end/features/downloads.feature +++ b/tests/end2end/features/downloads.feature @@ -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 diff --git a/tests/end2end/features/history.feature b/tests/end2end/features/history.feature index be4035f37..b86e5743b 100644 --- a/tests/end2end/features/history.feature +++ b/tests/end2end/features/history.feature @@ -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 diff --git a/tests/end2end/features/misc.feature b/tests/end2end/features/misc.feature index e44e84571..3d23033f5 100644 --- a/tests/end2end/features/misc.feature +++ b/tests/end2end/features/misc.feature @@ -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 diff --git a/tests/end2end/features/test_adblock_bdd.py b/tests/end2end/features/test_adblock_bdd.py index 4f9d72f9c..780e55a59 100644 --- a/tests/end2end/features/test_adblock_bdd.py +++ b/tests/end2end/features/test_adblock_bdd.py @@ -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)) diff --git a/tests/end2end/features/test_editor_bdd.py b/tests/end2end/features/test_editor_bdd.py index 8e0537e69..eb937e0f2 100644 --- a/tests/end2end/features/test_editor_bdd.py +++ b/tests/end2end/features/test_editor_bdd.py @@ -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(""" diff --git a/tests/end2end/features/test_history_bdd.py b/tests/end2end/features/test_history_bdd.py index debf72d09..8bdd8c396 100644 --- a/tests/end2end/features/test_history_bdd.py +++ b/tests/end2end/features/test_history_bdd.py @@ -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, '') diff --git a/tests/end2end/features/test_private_bdd.py b/tests/end2end/features/test_private_bdd.py index a9ed92e6e..6e591a2e0 100644 --- a/tests/end2end/features/test_private_bdd.py +++ b/tests/end2end/features/test_private_bdd.py @@ -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) diff --git a/tests/end2end/features/test_sessions_bdd.py b/tests/end2end/features/test_sessions_bdd.py index 052748f90..b4f5dcf2c 100644 --- a/tests/end2end/features/test_sessions_bdd.py +++ b/tests/end2end/features/test_sessions_bdd.py @@ -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: diff --git a/tests/end2end/fixtures/quteprocess.py b/tests/end2end/fixtures/quteprocess.py index eb166f5b6..73f885cfe 100644 --- a/tests/end2end/fixtures/quteprocess.py +++ b/tests/end2end/fixtures/quteprocess.py @@ -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 diff --git a/tests/end2end/fixtures/test_quteprocess.py b/tests/end2end/fixtures/test_quteprocess.py index 0b9680ff4..39cbe598c 100644 --- a/tests/end2end/fixtures/test_quteprocess.py +++ b/tests/end2end/fixtures/test_quteprocess.py @@ -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 diff --git a/tests/end2end/fixtures/test_webserver.py b/tests/end2end/fixtures/test_webserver.py index c885ea5b6..331c0906d 100644 --- a/tests/end2end/fixtures/test_webserver.py +++ b/tests/end2end/fixtures/test_webserver.py @@ -17,7 +17,7 @@ # You should have received a copy of the GNU General Public License # along with qutebrowser. If not, see . -"""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', [ - ('/', 'httpbin(1): HTTP Client Testing Service', 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 diff --git a/tests/end2end/fixtures/testprocess.py b/tests/end2end/fixtures/testprocess.py index 1a2c51baf..68b30a105 100644 --- a/tests/end2end/fixtures/testprocess.py +++ b/tests/end2end/fixtures/testprocess.py @@ -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): diff --git a/tests/end2end/fixtures/webserver.py b/tests/end2end/fixtures/webserver.py index acb99acfe..01887d9b9 100644 --- a/tests/end2end/fixtures/webserver.py +++ b/tests/end2end/fixtures/webserver.py @@ -17,7 +17,7 @@ # You should have received a copy of the GNU General Public License # along with qutebrowser. If not, see . -"""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() diff --git a/tests/end2end/fixtures/webserver_sub.py b/tests/end2end/fixtures/webserver_sub.py index 53c033f5b..1059ca520 100644 --- a/tests/end2end/fixtures/webserver_sub.py +++ b/tests/end2end/fixtures/webserver_sub.py @@ -17,9 +17,13 @@ # You should have received a copy of the GNU General Public License # along with qutebrowser. If not, see . -"""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'user agent') + + @app.route('/data/') 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/') +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//') +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.""" diff --git a/tests/end2end/test_invocations.py b/tests/end2end/test_invocations.py index a59ea5db7..03626dc14 100644 --- a/tests/end2end/test_invocations.py +++ b/tests/end2end/test_invocations.py @@ -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', diff --git a/tests/end2end/test_mhtml_e2e.py b/tests/end2end/test_mhtml_e2e.py index a623dd391..4317271f5 100644 --- a/tests/end2end/test_mhtml_e2e.py +++ b/tests/end2end/test_mhtml_e2e.py @@ -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)