From a6ac2793a39fd83e174e5b957f01e4e2888474c7 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Wed, 3 Feb 2021 18:32:07 +0100 Subject: Adopt tests from master branch --- tests/end2end/fixtures/webserver.py | 1 + tests/end2end/fixtures/webserver_sub.py | 10 ++++ tests/end2end/test_invocations.py | 33 ++++++++++++ tests/helpers/utils.py | 2 + tests/unit/browser/test_downloadview.py | 94 +++++++++++++++++++++++++++++++++ tests/unit/utils/test_utils.py | 65 ++++++++++++++++++++--- 6 files changed, 199 insertions(+), 6 deletions(-) create mode 100644 tests/unit/browser/test_downloadview.py diff --git a/tests/end2end/fixtures/webserver.py b/tests/end2end/fixtures/webserver.py index 4677415f1..81a864c8e 100644 --- a/tests/end2end/fixtures/webserver.py +++ b/tests/end2end/fixtures/webserver.py @@ -75,6 +75,7 @@ class Request(testprocess.Line): '/absolute-redirect': [HTTPStatus.FOUND], '/cookies/set': [HTTPStatus.FOUND], + '/cookies/set-custom': [HTTPStatus.FOUND], '/500-inline': [HTTPStatus.INTERNAL_SERVER_ERROR], '/500': [HTTPStatus.INTERNAL_SERVER_ERROR], diff --git a/tests/end2end/fixtures/webserver_sub.py b/tests/end2end/fixtures/webserver_sub.py index b7999148f..a4f54e19c 100644 --- a/tests/end2end/fixtures/webserver_sub.py +++ b/tests/end2end/fixtures/webserver_sub.py @@ -199,6 +199,16 @@ def set_cookies(): return r +@app.route('/cookies/set-custom') +def set_custom_cookie(): + """Set a cookie with a custom max_age/expires.""" + r = app.make_response(flask.redirect(flask.url_for('view_cookies'))) + max_age = flask.request.args.get('max_age') + r.set_cookie(key='cookie', value='value', + max_age=int(max_age) if max_age else None) + return r + + @app.route('/basic-auth//') def basic_auth(user='user', passwd='passwd'): """Prompt the user for authorization using HTTP Basic Auth.""" diff --git a/tests/end2end/test_invocations.py b/tests/end2end/test_invocations.py index b509e355b..4dfe137fc 100644 --- a/tests/end2end/test_invocations.py +++ b/tests/end2end/test_invocations.py @@ -490,3 +490,36 @@ def test_service_worker_workaround( quteproc_new.ensure_not_logged(message='Removing service workers at *') else: assert not service_worker_dir.exists() + + +@utils.qt513 # Qt 5.12 doesn't store cookies immediately +@pytest.mark.parametrize('store', [True, False]) +def test_cookies_store(quteproc_new, request, short_tmpdir, store): + # Start test process + args = _base_args(request.config) + [ + '--basedir', str(short_tmpdir), + '-s', 'content.cookies.store', str(store), + ] + quteproc_new.start(args) + + # Set cookie and ensure it's set + quteproc_new.open_path('cookies/set-custom?max_age=30', wait=False) + quteproc_new.wait_for_load_finished('cookies') + content = quteproc_new.get_content() + data = json.loads(content) + assert data == {'cookies': {'cookie': 'value'}} + + # Restart + quteproc_new.send_cmd(':quit') + quteproc_new.wait_for_quit() + quteproc_new.start(args) + + # Check cookies + quteproc_new.open_path('cookies') + content = quteproc_new.get_content() + data = json.loads(content) + expected_cookies = {'cookie': 'value'} if store else {} + assert data == {'cookies': expected_cookies} + + quteproc_new.send_cmd(':quit') + quteproc_new.wait_for_quit() diff --git a/tests/helpers/utils.py b/tests/helpers/utils.py index 41da08331..a6fd3c0e4 100644 --- a/tests/helpers/utils.py +++ b/tests/helpers/utils.py @@ -41,6 +41,8 @@ from qutebrowser.utils import qtutils, log ON_CI = 'CI' in os.environ +qt513 = pytest.mark.skipif( + not qtutils.version_check('5.13'), reason="Needs Qt 5.13 or newer") qt514 = pytest.mark.skipif( not qtutils.version_check('5.14'), reason="Needs Qt 5.14 or newer") diff --git a/tests/unit/browser/test_downloadview.py b/tests/unit/browser/test_downloadview.py new file mode 100644 index 000000000..84b50fad2 --- /dev/null +++ b/tests/unit/browser/test_downloadview.py @@ -0,0 +1,94 @@ +# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: + +# Copyright 2021 Florian Bruhin (The Compiler) +# +# This file is part of qutebrowser. +# +# qutebrowser is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# qutebrowser is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with qutebrowser. If not, see . + + +import pytest + +from PyQt5.QtCore import QUrl + +from qutebrowser.browser import downloads, qtnetworkdownloads, downloadview + + +class FakeDownload(downloads.AbstractDownloadItem): + + # As this is used for tests, we only override what's actually needed. + # pylint: disable=abstract-method + + def __init__(self, done: bool, successful: bool = False) -> None: + super().__init__(manager=None) + self.done = done + self.successful = successful + + def url(self) -> QUrl: + return QUrl('https://example.org/') + + +@pytest.fixture +def qtnetwork_manager(config_stub, cookiejar_and_cache): + """A QtNetwork-based download manager.""" + return qtnetworkdownloads.DownloadManager() + + +@pytest.fixture +def model(qtnetwork_manager, qtmodeltester, qapp): + """A simple DownloadModel.""" + model = downloads.DownloadModel(qtnetwork_manager) + qtmodeltester.check(model) + return model + + +@pytest.fixture +def view(model, qtbot): + """A DownloadView.""" + dv = downloadview.DownloadView(model) + qtbot.add_widget(dv) + return dv + + +@pytest.mark.parametrize('can_clear', [True, False]) +@pytest.mark.parametrize('item, expected', [ + # Clicking outside of a download item + (None, []), + + # Clicking a in-progress item + (FakeDownload(done=False), ["Cancel", "Copy URL"]), + + # Clicking a successful item + ( + FakeDownload(done=True, successful=True), + ["Open", "Open directory", "Remove", "Copy URL"], + ), + + # Clicking an unsuccessful item + ( + FakeDownload(done=True, successful=False), + ["Retry", "Remove", "Copy URL"], + ), +]) +def test_get_menu_actions(can_clear, item, expected, view, qtnetwork_manager): + if can_clear: + qtnetwork_manager.downloads.append(FakeDownload(done=True)) + expected = expected.copy() + [None, "Remove all finished"] + else: + assert not qtnetwork_manager.downloads + assert "Remove all finished" not in expected + + actions = view._get_menu_actions(item) + texts = [action[0] for action in actions] + assert texts == expected diff --git a/tests/unit/utils/test_utils.py b/tests/unit/utils/test_utils.py index 415d04bd0..f6ac8039d 100644 --- a/tests/unit/utils/test_utils.py +++ b/tests/unit/utils/test_utils.py @@ -131,6 +131,10 @@ class TestReadFile: for filename in ['test1.html', 'test2.html', 'README', 'unrelatedhtml']: (path / filename).touch() + subdir = path / 'subdir' + subdir.mkdir() + (subdir / 'subdir-file.html').touch() + return path @pytest.fixture @@ -140,18 +144,37 @@ class TestReadFile: zip_path = tmp_path / 'qutebrowser.zip' with zipfile.ZipFile(zip_path, 'w') as zf: - for path in html_path.iterdir(): + for path in html_path.rglob('*'): zf.write(path, path.relative_to(tmp_path)) + assert sorted(zf.namelist()) == [ + 'qutebrowser/html/README', + 'qutebrowser/html/subdir/', + 'qutebrowser/html/subdir/subdir-file.html', + 'qutebrowser/html/test1.html', + 'qutebrowser/html/test2.html', + 'qutebrowser/html/unrelatedhtml', + ] + yield zipfile.Path(zip_path) / 'qutebrowser' - def test_glob_resources_pathlib(self, html_path, package_path): - files = sorted(utils._glob_resources(package_path, 'html', '.html')) + @pytest.fixture(params=['pathlib', 'zipfile']) + def resource_root(self, request): + """Resource files packaged either directly or via a zip.""" + if request.param == 'pathlib': + request.getfixturevalue('html_path') + return request.getfixturevalue('package_path') + elif request.param == 'zipfile': + return request.getfixturevalue('html_zip') + raise utils.Unreachable(request.param) + + def test_glob_resources(self, resource_root): + files = sorted(utils._glob_resources(resource_root, 'html', '.html')) assert files == ['html/test1.html', 'html/test2.html'] - def test_glob_resources_zipfile(self, html_zip): - files = sorted(utils._glob_resources(html_zip, 'html', '.html')) - assert files == ['html/test1.html', 'html/test2.html'] + def test_glob_resources_subdir(self, resource_root): + files = sorted(utils._glob_resources(resource_root, 'html/subdir', '.html')) + assert files == ['html/subdir/subdir-file.html'] def test_readfile(self): """Read a test file.""" @@ -171,6 +194,36 @@ class TestReadFile: content = utils.read_file_binary(os.path.join('utils', 'testfile')) assert content.splitlines()[0] == b"Hello World!" + @pytest.mark.parametrize('name', ['read_file', 'read_file_binary']) + @pytest.mark.parametrize('fake_exception', [KeyError, FileNotFoundError, None]) + def test_not_found(self, name, fake_exception, monkeypatch): + """Test behavior when a resources file wasn't found. + + With fake_exception, we emulate the rather odd error handling of certain Python + versions: https://bugs.python.org/issue43063 + """ + class BrokenFileFake: + + def __init__(self, exc): + self.exc = exc + + def read_bytes(self): + raise self.exc("File does not exist") + + def read_text(self, encoding): + raise self.exc("File does not exist") + + def __truediv__(self, _other): + return self + + if fake_exception is not None: + monkeypatch.setattr(utils.importlib_resources, 'files', + lambda _pkg: BrokenFileFake(fake_exception)) + + meth = getattr(utils, name) + with pytest.raises(FileNotFoundError): + meth('doesnotexist') + @pytest.mark.parametrize('seconds, out', [ (-1, '-0:01'), -- cgit v1.2.3-54-g00ecf