diff options
44 files changed, 343 insertions, 163 deletions
diff --git a/.github/workflows/bleeding.yml b/.github/workflows/bleeding.yml index 435141e56..b2370357f 100644 --- a/.github/workflows/bleeding.yml +++ b/.github/workflows/bleeding.yml @@ -15,7 +15,7 @@ jobs: container: image: "qutebrowser/ci:archlinux-webengine-unstable" env: - PY_COLORS: "1" + FORCE_COLOR: "1" DOCKER: "archlinux-webengine-unstable" CI: true PYTEST_ADDOPTS: "--color=yes" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e50ba2c60..afcf720e4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -6,7 +6,7 @@ on: - 'dependabot/*' pull_request: env: - PY_COLORS: "1" + FORCE_COLOR: "1" MYPY_FORCE_TERMINAL_WIDTH: "180" jobs: @@ -43,7 +43,7 @@ jobs: key: "${{ matrix.testenv }}-${{ hashFiles('misc/requirements/requirements-*.txt') }}-${{ hashFiles('requirements.txt') }}-${{ hashFiles('scripts/dev/pylint_checkers/qute_pylint/*.py') }}" - uses: actions/setup-python@v2 with: - python-version: '3.8' + python-version: '3.10' - uses: actions/setup-node@v2-beta with: node-version: '12.x' @@ -53,7 +53,7 @@ jobs: - name: Install dependencies run: | [[ ${{ matrix.testenv }} == eslint ]] && npm install -g eslint - [[ ${{ matrix.testenv }} == docs ]] && sudo apt-get install --no-install-recommends asciidoc + [[ ${{ matrix.testenv }} == docs ]] && sudo apt-get update && sudo apt-get install --no-install-recommends asciidoc if [[ ${{ matrix.testenv }} == shellcheck ]]; then scversion="stable" bindir="$HOME/.local/bin" @@ -15,6 +15,7 @@ load-plugins=qute_pylint.config, pylint.extensions.check_elif, pylint.extensions.typing, pylint.extensions.docparams, + pylint.extensions.private_import, persistent=n py-version=3.6 diff --git a/README.asciidoc b/README.asciidoc index a5d3af9ff..bb1f2562c 100644 --- a/README.asciidoc +++ b/README.asciidoc @@ -63,7 +63,7 @@ ways: * Use the built-in `:report` command or the automatic crash dialog. * Open an issue in the Github issue tracker. * Write a mail to the -https://lists.schokokeks.org/mailman/listinfo.cgi/qutebrowser[mailinglist] at +https://listi.jpberlin.de/mailman/listinfo/qutebrowser[mailinglist] at mailto:qutebrowser@lists.qutebrowser.org[]. For security bugs, please contact me directly at mail@qutebrowser.org, GPG ID diff --git a/doc/changelog.asciidoc b/doc/changelog.asciidoc index 9df2ce00c..252ea088a 100644 --- a/doc/changelog.asciidoc +++ b/doc/changelog.asciidoc @@ -60,7 +60,8 @@ Added which shows relative tab numbers. - New `input.mode_override` option which allows overriding the current mode based on the new URL when navigating or switching tabs. -- New `qt.chromium.sandboxing` setting which allows to disable Chromium's sandboxing (mainly intended for development and testing) +- New `qt.chromium.sandboxing` setting which allows to disable Chromium's + sandboxing (mainly intended for development and testing) Fixed ~~~~~ @@ -83,6 +84,15 @@ Fixed Chromium's would have worked fine. The workaround was now dropped. - Crash when using `<Ctrl-D>` (`:completion-item-del`) in the `:tab-focus` list, rather than `:tab-select`. +- Work around a Qt issue causing `:spawn` to run executables from the current + directory if no system-wide executable was found. The underlying Qt bug is + tracked as [CVE-2022-25255](https://lists.qt-project.org/pipermail/announce/2022-February/000333.html), + though the impact with typical qutebrowser usage is low: Normally, + qutebrowser is run from a fixed location (usually the users home directory), + and `:spawn` is not typically used with executables that don't exist. The main + security impact of this bug is in tools like text editors, which are often + executed in untrusted directories and might attempt to run auxiliary tools + automatically. [[v2.4.1]] v2.4.1 (unreleased) @@ -4259,7 +4269,7 @@ v0.1.4 (2015-03-19) Changed ~~~~~~~ -* The Windows builds come with Qt 5.4.1 which has some https://lists.schokokeks.org/pipermail/qutebrowser/2015-March/000054.html[related bugfixes]. +* The Windows builds come with Qt 5.4.1 which has some https://listi.jpberlin.de/pipermail/qutebrowser/2015-March/000054.html[related bugfixes]. * Improvements to CPU usage when idle. * Ensure there's no size for `font-family` settings. * Handle URLs with double-colon as search strings. diff --git a/doc/contributing.asciidoc b/doc/contributing.asciidoc index 1f87e9163..75c19045e 100644 --- a/doc/contributing.asciidoc +++ b/doc/contributing.asciidoc @@ -24,7 +24,7 @@ several ways: * Send a mail to the mailing list at mailto:qutebrowser@lists.qutebrowser.org[] (optionally -https://lists.schokokeks.org/mailman/listinfo.cgi/qutebrowser[subscribe] +https://listi.jpberlin.de/mailman/listinfo/qutebrowser[subscribe] first). * Join the IRC channel link:ircs://irc.libera.chat:6697/#qutebrowser[`#qutebrowser`] on https://libera.chat/[Libera Chat] (https://web.libera.chat/#qutebrowser[webchat], diff --git a/doc/help/index.asciidoc b/doc/help/index.asciidoc index c7fb88c8d..127cc5d86 100644 --- a/doc/help/index.asciidoc +++ b/doc/help/index.asciidoc @@ -26,10 +26,10 @@ link:ircs://irc.libera.chat:6697/#qutebrowser[`#qutebrowser`] on https://libera.chat/[Libera Chat] (https://web.libera.chat/#qutebrowser[webchat], https://matrix.to/#qutebrowser:libera.chat[via Matrix]), or by writing a message to the -https://lists.schokokeks.org/mailman/listinfo.cgi/qutebrowser[mailinglist] at +https://listi.jpberlin.de/mailman/listinfo/qutebrowser[mailinglist] at mailto:qutebrowser@lists.qutebrowser.org[]. -There's also an https://lists.schokokeks.org/mailman/listinfo.cgi/qutebrowser-announce[announce-only mailinglist] +There's also an https://listi.jpberlin.de/mailman/listinfo/qutebrowser-announce[announce-only mailinglist] at mailto:qutebrowser-announce@lists.qutebrowser.org[] (the announcements also get sent to the general qutebrowser@ list). @@ -49,7 +49,7 @@ ways: * Use the built-in `:report` command or the automatic crash dialog. * Open an issue in the Github issue tracker. * Write a mail to the -https://lists.schokokeks.org/mailman/listinfo.cgi/qutebrowser[mailinglist] at +https://listi.jpberlin.de/mailman/listinfo/qutebrowser[mailinglist] at mailto:qutebrowser@lists.qutebrowser.org[]. Other resources diff --git a/doc/install.asciidoc b/doc/install.asciidoc index 83c332b4d..dd284fb9a 100644 --- a/doc/install.asciidoc +++ b/doc/install.asciidoc @@ -278,7 +278,7 @@ https://github.com/qutebrowser/qutebrowser/releases[are built] for every release. Note that you'll need to upgrade to new versions manually (subscribe to the -https://lists.schokokeks.org/mailman/listinfo.cgi/qutebrowser-announce[qutebrowser-announce +https://listi.jpberlin.de/mailman/listinfo/qutebrowser-announce[qutebrowser-announce mailinglist] to get notified on new releases). You can install a newer version without uninstalling the older one. @@ -335,7 +335,7 @@ files from the https://github.com/qutebrowser/qutebrowser/releases[release page]. Note that you'll need to upgrade to new versions manually (subscribe to the -https://lists.schokokeks.org/mailman/listinfo.cgi/qutebrowser-announce[qutebrowser-announce +https://listi.jpberlin.de/mailman/listinfo/qutebrowser-announce[qutebrowser-announce mailinglist] to get notified on new releases). The binary release ships with a QtWebEngine built without proprietary codec diff --git a/doc/quickstart.asciidoc b/doc/quickstart.asciidoc index 2e61e442d..0c42880ab 100644 --- a/doc/quickstart.asciidoc +++ b/doc/quickstart.asciidoc @@ -33,8 +33,8 @@ image:https://raw.githubusercontent.com/qutebrowser/qutebrowser/master/doc/img/c `scripts/asciidoc2html.py` to generate the documentation. * Go to the link:qute://settings[settings page] to set up qutebrowser the way you want it. * Subscribe to -https://lists.schokokeks.org/mailman/listinfo.cgi/qutebrowser[the mailinglist] or -https://lists.schokokeks.org/mailman/listinfo.cgi/qutebrowser-announce[the announce-only mailinglist]. +https://listi.jpberlin.de/mailman/listinfo/qutebrowser[the mailinglist] or +https://listi.jpberlin.de/mailman/listinfo/qutebrowser-announce[the announce-only mailinglist]. * Let me know what features you are missing or things that need (even small!) improvements. @@ -52,7 +52,7 @@ or https://matrix.to/#qutebrowser:libera.chat[via Matrix]) * On Reddit: https://www.reddit.com/r/qutebrowser/[/r/qutebrowser] * Via https://github.com/qutebrowser/qutebrowser/discussions[GitHub Discussions] * Using the mailinglist: mailto:qutebrowser@lists.qutebrowser.org[] -(https://lists.schokokeks.org/mailman/listinfo.cgi/qutebrowser[subscribe]) +(https://listi.jpberlin.de/mailman/listinfo/qutebrowser[subscribe]) Donating -------- diff --git a/doc/qutebrowser.1.asciidoc b/doc/qutebrowser.1.asciidoc index f22be1ddc..a2b2bcec9 100644 --- a/doc/qutebrowser.1.asciidoc +++ b/doc/qutebrowser.1.asciidoc @@ -130,7 +130,7 @@ If you found a bug, use the built-in ':report' command to create a bug report with all information needed. If you prefer, you can also write to the -https://lists.schokokeks.org/mailman/listinfo.cgi/qutebrowser[mailinglist] at +https://listi.jpberlin.de/mailman/listinfo/qutebrowser[mailinglist] at mailto:qutebrowser@lists.qutebrowser.org[] instead. For security bugs, please contact me directly at me@the-compiler.org, GPG ID @@ -152,9 +152,9 @@ this program. If not, see <https://www.gnu.org/licenses/>. == RESOURCES * Website: https://www.qutebrowser.org/ * Mailinglist: mailto:qutebrowser@lists.qutebrowser.org[] / -https://lists.schokokeks.org/mailman/listinfo.cgi/qutebrowser +https://listi.jpberlin.de/mailman/listinfo/qutebrowser * Announce-only mailinglist: mailto:qutebrowser-announce@lists.qutebrowser.org[] / -https://lists.schokokeks.org/mailman/listinfo.cgi/qutebrowser-announce +https://listi.jpberlin.de/mailman/listinfo/qutebrowser-announce * IRC: link:ircs://irc.libera.chat:6697/#qutebrowser[`#qutebrowser`] on https://libera.chat/[Libera Chat] (https://web.libera.chat/#qutebrowser[webchat], https://matrix.to/#qutebrowser:libera.chat[via Matrix]) diff --git a/misc/requirements/requirements-dev.txt b/misc/requirements/requirements-dev.txt index a299f1ce8..9f7f5074b 100644 --- a/misc/requirements/requirements-dev.txt +++ b/misc/requirements/requirements-dev.txt @@ -7,7 +7,7 @@ certifi==2021.10.8 cffi==1.15.0 charset-normalizer==2.0.12 colorama==0.4.4 -cryptography==36.0.1 +cryptography==36.0.2 docutils==0.18.1 github3.py==3.2.0 hunter==3.4.3 @@ -35,9 +35,9 @@ sip==6.5.1 six==1.16.0 toml==0.10.2 tomli==2.0.1 -tqdm==4.63.0 +tqdm==4.63.1 twine==3.8.0 uritemplate==4.1.1 -# urllib3==1.26.8 +# urllib3==1.26.9 webencodings==0.5.1 zipp==3.7.0 diff --git a/misc/requirements/requirements-flake8.txt b/misc/requirements/requirements-flake8.txt index fb84bb7db..8a7428933 100644 --- a/misc/requirements/requirements-flake8.txt +++ b/misc/requirements/requirements-flake8.txt @@ -2,7 +2,7 @@ attrs==21.4.0 flake8==4.0.1 -flake8-bugbear==22.1.11 +flake8-bugbear==22.3.23 flake8-builtins==1.5.3 flake8-comprehensions==3.8.0 flake8-copyright==0.2.2 diff --git a/misc/requirements/requirements-mypy.txt b/misc/requirements/requirements-mypy.txt index 1b8d97163..d8fbba5ee 100644 --- a/misc/requirements/requirements-mypy.txt +++ b/misc/requirements/requirements-mypy.txt @@ -1,19 +1,19 @@ # This file is automatically generated by scripts/dev/recompile_requirements.py chardet==4.0.0 -diff-cover==6.4.4 +diff-cover==6.4.5 importlib-metadata==4.11.3 -importlib-resources==5.4.0 -Jinja2==3.0.3 +importlib-resources==5.6.0 +Jinja2==3.1.1 lxml==4.8.0 -MarkupSafe==2.1.0 -mypy==0.940 +MarkupSafe==2.1.1 +mypy==0.942 mypy-extensions==0.4.3 pluggy==1.0.0 Pygments==2.11.2 PyQt5-stubs==5.15.2.0 tomli==2.0.1 types-dataclasses==0.6.4 -types-PyYAML==6.0.4 +types-PyYAML==6.0.5 typing_extensions==4.1.1 zipp==3.7.0 diff --git a/misc/requirements/requirements-pyinstaller.txt b/misc/requirements/requirements-pyinstaller.txt index c6a088800..d7e46505c 100644 --- a/misc/requirements/requirements-pyinstaller.txt +++ b/misc/requirements/requirements-pyinstaller.txt @@ -2,4 +2,4 @@ altgraph==0.17.2 pyinstaller==4.10 -pyinstaller-hooks-contrib==2022.2 +pyinstaller-hooks-contrib==2022.3 diff --git a/misc/requirements/requirements-pylint.txt b/misc/requirements/requirements-pylint.txt index a251ffa40..3e5ebea9e 100644 --- a/misc/requirements/requirements-pylint.txt +++ b/misc/requirements/requirements-pylint.txt @@ -1,29 +1,30 @@ # This file is automatically generated by scripts/dev/recompile_requirements.py -astroid==2.9.3 +astroid==2.11.2 certifi==2021.10.8 cffi==1.15.0 charset-normalizer==2.0.12 -cryptography==36.0.1 +cryptography==36.0.2 +dill==0.3.4 future==0.18.2 github3.py==3.2.0 idna==3.3 isort==5.10.1 lazy-object-proxy==1.7.1 -mccabe==0.6.1 +mccabe==0.7.0 pefile==2021.9.3 platformdirs==2.5.1 ; python_version>="3.7" pycparser==2.21 PyJWT==2.3.0 -pylint==2.12.2 +pylint==2.13.3 python-dateutil==2.8.2 ./scripts/dev/pylint_checkers requests==2.27.1 six==1.16.0 -toml==0.10.2 +tomli==2.0.1 typed-ast==1.5.2 ; python_version<"3.8" typing_extensions==4.1.1 uritemplate==4.1.1 -# urllib3==1.26.8 -wrapt==1.13.3 +# urllib3==1.26.9 +wrapt==1.14.0 platformdirs==2.4.0 ; python_version=="3.6.*" diff --git a/misc/requirements/requirements-pylint.txt-raw b/misc/requirements/requirements-pylint.txt-raw index 273d07cf4..52633ec1a 100644 --- a/misc/requirements/requirements-pylint.txt-raw +++ b/misc/requirements/requirements-pylint.txt-raw @@ -11,9 +11,6 @@ pefile # Already included via test requirements #@ ignore: urllib3 -# For pylint_checkers -#@ pip_args: --use-feature=in-tree-build - # Python 3.6 #@ markers: platformdirs python_version>="3.7" #@ add: platformdirs==2.4.0 ; python_version=="3.6.*" diff --git a/misc/requirements/requirements-pyroma.txt b/misc/requirements/requirements-pyroma.txt index 4847b111b..a78ba8560 100644 --- a/misc/requirements/requirements-pyroma.txt +++ b/misc/requirements/requirements-pyroma.txt @@ -5,6 +5,6 @@ charset-normalizer==2.0.12 docutils==0.18.1 idna==3.3 Pygments==2.11.2 -pyroma==3.2 +pyroma==3.3 requests==2.27.1 -urllib3==1.26.8 +urllib3==1.26.9 diff --git a/misc/requirements/requirements-qutebrowser.txt-raw b/misc/requirements/requirements-qutebrowser.txt-raw index 63ac32f86..b260fa16c 100644 --- a/misc/requirements/requirements-qutebrowser.txt-raw +++ b/misc/requirements/requirements-qutebrowser.txt-raw @@ -14,14 +14,18 @@ adblock # Improved adblocking importlib-metadata # Determining PyQt version typing_extensions # from importlib-metadata -#@ markers: importlib-resources python_version<"3.9" +#@ markers: importlib-resources python_version=="3.7.*" or python_version=="3.8.*" #@ markers: importlib-metadata python_version=="3.7.*" #@ markers: typing_extensions python_version<"3.8" #@ markers: dataclasses python_version<"3.7" # Python 3.6 +#@ add: importlib-resources<5.6.0 ; python_version=="3.6.*" #@ add: importlib-metadata<4.9 ; python_version=="3.6.*" +# #@ markers: zipp python_version>="3.7" #@ add: zipp<3.7 ; python_version=="3.6.*" #@ markers: MarkupSafe python_version>="3.7" #@ add: MarkupSafe<2.1.0 ; python_version=="3.6.*" +#@ markers: Jinja2 python_version>="3.7" +#@ add: Jinja2<3.1.0 ; python_version=="3.6.*" diff --git a/misc/requirements/requirements-sphinx.txt b/misc/requirements/requirements-sphinx.txt index 96e303a05..bec429e04 100644 --- a/misc/requirements/requirements-sphinx.txt +++ b/misc/requirements/requirements-sphinx.txt @@ -8,20 +8,20 @@ docutils==0.17.1 idna==3.3 imagesize==1.3.0 importlib-metadata==4.11.3 -Jinja2==3.0.3 -MarkupSafe==2.1.0 +Jinja2==3.1.1 +MarkupSafe==2.1.1 packaging==21.3 Pygments==2.11.2 pyparsing==3.0.7 -pytz==2021.3 +pytz==2022.1 requests==2.27.1 snowballstemmer==2.2.0 -Sphinx==4.4.0 +Sphinx==4.5.0 sphinxcontrib-applehelp==1.0.2 sphinxcontrib-devhelp==1.0.2 sphinxcontrib-htmlhelp==2.0.0 sphinxcontrib-jsmath==1.0.1 sphinxcontrib-qthelp==1.0.3 sphinxcontrib-serializinghtml==1.1.5 -urllib3==1.26.8 +urllib3==1.26.9 zipp==3.7.0 diff --git a/misc/requirements/requirements-tests.txt b/misc/requirements/requirements-tests.txt index 66934702e..787d2791b 100644 --- a/misc/requirements/requirements-tests.txt +++ b/misc/requirements/requirements-tests.txt @@ -5,23 +5,24 @@ beautifulsoup4==4.10.0 certifi==2021.10.8 charset-normalizer==2.0.12 cheroot==8.6.0 -click==8.0.4 +click==8.1.0 ; python_version>="3.7" coverage==6.3.2 ; python_version>="3.7" execnet==1.9.0 filelock==3.6.0 ; python_version>="3.7" -Flask==2.0.3 +Flask==2.1.0 ; python_version>="3.7" glob2==0.7 hunter==3.4.3 -hypothesis==6.39.3 ; python_version>="3.7" +hypothesis==6.40.0 ; python_version>="3.7" icdiff==2.0.4 idna==3.3 +importlib-metadata==4.11.3 ; python_version=="3.7.*" iniconfig==1.1.1 -itsdangerous==2.1.1 ; python_version>="3.7" +itsdangerous==2.1.2 ; python_version>="3.7" jaraco.functools==3.5.0 ; python_version>="3.7" -# Jinja2==3.0.3 +# Jinja2==3.1.1 Mako==1.2.0 ; python_version>="3.7" manhole==1.8.0 -# MarkupSafe==2.1.0 +# MarkupSafe==2.1.1 more-itertools==8.12.0 packaging==21.3 parse==1.19.0 @@ -32,7 +33,7 @@ py==1.11.0 py-cpuinfo==8.0.0 Pygments==2.11.2 pyparsing==3.0.7 -pytest==7.1.0 ; python_version>="3.7" +pytest==7.1.1 ; python_version>="3.7" pytest-bdd==4.1.0 pytest-benchmark==3.4.1 pytest-cov==3.0.0 @@ -54,9 +55,10 @@ soupsieve==2.3.1 tldextract==3.2.0 ; python_version>="3.7" toml==0.10.2 tomli==2.0.1 ; python_version>="3.7" -urllib3==1.26.8 +urllib3==1.26.9 vulture==2.3 -Werkzeug==2.0.3 +Werkzeug==2.1.0 ; python_version>="3.7" +zipp==3.7.0 ; python_version>="3.7" jaraco.functools<3.5 ; python_version=="3.6.*" tomli<2 ; python_version=="3.6.*" filelock==3.4.1 ; python_version=="3.6.*" @@ -67,3 +69,8 @@ itsdangerous<2.1.0 ; python_version=="3.6.*" tldextract<3.2.0 ; python_version=="3.6.*" Mako<1.2.0 ; python_version=="3.6.*" pytest<7.1.0 ; python_version=="3.6.*" +click<8.1.0 ; python_version=="3.6.*" +Flask<2.1.0 ; python_version=="3.6.*" +Werkzeug<2.1.0 ; python_version=="3.6.*" +zipp<3.7 ; python_version=="3.6.*" +importlib-metadata<4.9 ; python_version=="3.6.*" diff --git a/misc/requirements/requirements-tests.txt-raw b/misc/requirements/requirements-tests.txt-raw index e7dc58e0c..6338a1a97 100644 --- a/misc/requirements/requirements-tests.txt-raw +++ b/misc/requirements/requirements-tests.txt-raw @@ -57,3 +57,13 @@ tldextract #@ add: Mako<1.2.0 ; python_version=="3.6.*" #@ markers: pytest python_version>="3.7" #@ add: pytest<7.1.0 ; python_version=="3.6.*" +#@ markers: click python_version>="3.7" +#@ add: click<8.1.0 ; python_version=="3.6.*" +#@ markers: Flask python_version>="3.7" +#@ add: Flask<2.1.0 ; python_version=="3.6.*" +#@ markers: Werkzeug python_version>="3.7" +#@ add: Werkzeug<2.1.0 ; python_version=="3.6.*" +#@ markers: zipp python_version>="3.7" +#@ add: zipp<3.7 ; python_version=="3.6.*" +#@ markers: importlib-metadata python_version=="3.7.*" +#@ add: importlib-metadata<4.9 ; python_version=="3.6.*" diff --git a/misc/requirements/requirements-tox.txt b/misc/requirements/requirements-tox.txt index 506d818b3..a87519740 100644 --- a/misc/requirements/requirements-tox.txt +++ b/misc/requirements/requirements-tox.txt @@ -8,11 +8,11 @@ platformdirs==2.5.1 ; python_version>="3.7" pluggy==1.0.0 py==1.11.0 pyparsing==3.0.7 -setuptools==60.9.3 ; python_version>="3.7" +setuptools==61.2.0 ; python_version>="3.7" six==1.16.0 toml==0.10.2 tox==3.24.5 -virtualenv==20.13.3 +virtualenv==20.14.0 wheel==0.37.1 setuptools<60 ; python_version=="3.6.*" filelock==3.4.1 ; python_version=="3.6.*" diff --git a/qutebrowser/browser/webkit/webkithistory.py b/qutebrowser/browser/webkit/webkithistory.py index 4149bda88..6ffe65193 100644 --- a/qutebrowser/browser/webkit/webkithistory.py +++ b/qutebrowser/browser/webkit/webkithistory.py @@ -44,7 +44,7 @@ class WebHistoryInterface(QWebHistoryInterface): """Required for a QWebHistoryInterface impl, obsoleted by add_url.""" @debugcachestats.register(name='history') - @functools.lru_cache(maxsize=32768) + @functools.lru_cache(maxsize=32768) # noqa: B019 def historyContains(self, url_string): """Called by WebKit to determine if a URL is contained in the history. diff --git a/qutebrowser/completion/completer.py b/qutebrowser/completion/completer.py index fb61e48fb..8b0de9d8a 100644 --- a/qutebrowser/completion/completer.py +++ b/qutebrowser/completion/completer.py @@ -39,7 +39,7 @@ class CompletionInfo: """Context passed into all completion functions.""" config: config.Config - keyconf: config.KeyConfig # pylint: disable=undefined-variable + keyconf: config.KeyConfig # pylint: disable=used-before-assignment win_id: int cur_tab: 'browsertab.AbstractTab' diff --git a/qutebrowser/html/settings.html b/qutebrowser/html/settings.html index dfbc5c168..b06917fd5 100644 --- a/qutebrowser/html/settings.html +++ b/qutebrowser/html/settings.html @@ -65,6 +65,7 @@ input[type="radio"] { width: min-content; margin: 0; border: none; + cursor: pointer; } label { @@ -81,6 +82,12 @@ input[type="radio"]:checked + label { color: #084c88; } +.radio-button { + position: relative; /* The absolutely positioned element inside this tag (the radio button) gets positioned relative to it. */ + display: inline-flex; + margin: 3px 1px; +} + .setting { width: 60%; } @@ -107,13 +114,39 @@ input[type="radio"]:checked + label { color: #635d5dcf; font-size: 80%; font-style: italic; +} + +.option-description p { + margin: 0; +} + +.long-description { white-space: pre-line; } -.radio-button { - position: relative; /* The absolutely positioned element inside this tag (the radio button) gets positioned relative to it. */ - display: inline-flex; - margin: 3px 1px; +details summary > * { + display: inline; +} + +details[open] .details { + display: none; +} + +summary { + margin: .5ex 0; + width: fit-content; + color: #1887c5; + outline: none; + font-size: 105%; + cursor: pointer; +} + +summary .short-description { + color: #635d5dcf; +} + +summary::selection { + background-color: inherit; } {% endblock %} @@ -126,35 +159,49 @@ input[type="radio"]:checked + label { </tr> {% for option in configdata.DATA.values()|sort(attribute='name') if not option.no_autoconfig %} <tr> + {% set loopIndex = loop.index0 %} <!-- FIXME: convert to string properly --> <td class="setting">{{ option.name }} {% if option.description %} - <p class="option-description">{{ option.description|e }}</p> + {% set description = option.description.split('\n', 1) %} + <div class="option-description"> + {% if description|length > 1 %} + <details> + <summary> + <p class="short-description">{{ description[0]|e }}</p> + <span class="details">Details</span> + </summary> + <p class="long-description">{{ description[1]|e }}</p> + </details> + {% else %} + <p>{{ description[0]|e }}</p> + {% endif %} + </div> {% endif %} </td> {% if option.typ.valid_values is not none %} <td class="valid-value"> {% for value in option.typ.valid_values.values %} - <div class="radio-button"> - <input type="radio" id="input-{{ option.name }}-{{ loop.index0 }}" - name="{{ option.name }}" value="{{ value }}" - onclick="cset('{{ option.name }}', this.value)" - {% if confget(option.name) == value %} - checked - {% endif %}> - <label for="input-{{ option.name }}-{{ loop.index0 }}"> - {{ value }} - </label> - </div> - {% endfor %} - </td> + <div class="radio-button"> + <input type="radio" id="input-{{ option.name }}-{{ loop.index0 }}" + name="{{ option.name }}" value="{{ value }}" + onclick="cset('{{ option.name }}', this.value)" + {% if confget(option.name) == value %} + checked + {% endif %}> + <label for="input-{{ option.name }}-{{ loop.index0 }}"> + {{ value }} + </label> + </div> + {% endfor %} + </td> {% else %} <td class="value"> - <input type="text" - id="input-{{ option.name }}" - onblur="cset('{{ option.name }}', this.value)" - value="{{ confget(option.name) }}"> - </input> + <input type="text" + id="input-{{ option.name }}" + onblur="cset('{{ option.name }}', this.value)" + value="{{ confget(option.name) }}"> + </input> </td> {% endif %} </tr> diff --git a/qutebrowser/mainwindow/prompt.py b/qutebrowser/mainwindow/prompt.py index c8cbe572b..29ea9a45f 100644 --- a/qutebrowser/mainwindow/prompt.py +++ b/qutebrowser/mainwindow/prompt.py @@ -536,9 +536,11 @@ class _BasePrompt(QWidget): self.KEY_MODE.name) labels = [] + has_bindings = False for cmd, text in self._allowed_commands(): bindings = all_bindings.get(cmd, []) if bindings: + has_bindings = True binding = None preferred = ['<enter>', '<escape>'] for pref in preferred: @@ -547,8 +549,11 @@ class _BasePrompt(QWidget): if binding is None: binding = bindings[0] key_label = QLabel('<b>{}</b>'.format(html.escape(binding))) - text_label = QLabel(text) - labels.append((key_label, text_label)) + else: + key_label = QLabel(f'<b>unbound</b> (<tt>{html.escape(cmd)}</tt>)') + + text_label = QLabel(text) + labels.append((key_label, text_label)) for i, (key_label, text_label) in enumerate(labels): self._key_grid.addWidget(key_label, i, 0) @@ -559,6 +564,14 @@ class _BasePrompt(QWidget): self._vbox.addLayout(self._key_grid) + if not has_bindings: + label = QLabel( + "<b>Note:</b> You seem to have unbound all keys for this prompt " + f"(<tt>{self.KEY_MODE.name}</tt> key mode)." + "<br/>Run <tt>qutebrowser :CMD</tt> with a command from above to " + "close this prompt, then fix this in your config.") + self._vbox.addWidget(label) + def _check_save_support(self, save): if save: raise UnsupportedOperationError("Saving answers is only possible " diff --git a/qutebrowser/mainwindow/tabwidget.py b/qutebrowser/mainwindow/tabwidget.py index 0e95b7745..511c2c309 100644 --- a/qutebrowser/mainwindow/tabwidget.py +++ b/qutebrowser/mainwindow/tabwidget.py @@ -410,6 +410,15 @@ class TabBar(QTabBar): config.instance.changed.connect(self._on_config_changed) self._set_icon_size() QTimer.singleShot(0, self.maybe_hide) + self._minimum_tab_size_hint_helper = functools.lru_cache(maxsize=2**9)( + self._minimum_tab_size_hint_helper_uncached + ) + debugcachestats.register(name=f'tab width cache (win_id={win_id})')( + self._minimum_tab_size_hint_helper + ) + self._minimum_tab_height = functools.lru_cache(maxsize=1)( + self._minimum_tab_height_uncached + ) def __repr__(self): return utils.get_repr(self, count=self.count()) @@ -575,11 +584,9 @@ class TabBar(QTabBar): icon_width, ellipsis, pinned) - @debugcachestats.register(name='tab width cache') - @functools.lru_cache(maxsize=2**9) - def _minimum_tab_size_hint_helper(self, tab_text: str, - icon_width: int, - ellipsis: bool, pinned: bool) -> QSize: + def _minimum_tab_size_hint_helper_uncached(self, tab_text: str, + icon_width: int, + ellipsis: bool, pinned: bool) -> QSize: """Helper function to cache tab results. Config values accessed in here should be added to _on_config_changed to @@ -610,8 +617,7 @@ class TabBar(QTabBar): width = max(min_width, width) return QSize(width, height) - @functools.lru_cache(maxsize=1) - def _minimum_tab_height(self): + def _minimum_tab_height_uncached(self): padding = config.cache['tabs.padding'] return self.fontMetrics().height() + padding.top + padding.bottom diff --git a/qutebrowser/misc/debugcachestats.py b/qutebrowser/misc/debugcachestats.py index 2004ad7ab..9090bd0ea 100644 --- a/qutebrowser/misc/debugcachestats.py +++ b/qutebrowser/misc/debugcachestats.py @@ -23,11 +23,15 @@ Because many modules depend on this command, this needs to have as few dependencies as possible to avoid cyclic dependencies. """ -from typing import Any, Callable, List, Optional, Tuple, TypeVar +import weakref +import sys +from typing import Any, Callable, Optional, TypeVar, Mapping +from qutebrowser.utils import log -# The second element of each tuple should be a lru_cache wrapped function -_CACHE_FUNCTIONS: List[Tuple[str, Any]] = [] + +# The callable should be a lru_cache wrapped function +_CACHE_FUNCTIONS: Mapping[str, Any] = weakref.WeakValueDictionary() _T = TypeVar('_T', bound=Callable[..., Any]) @@ -36,13 +40,21 @@ _T = TypeVar('_T', bound=Callable[..., Any]) def register(name: Optional[str] = None) -> Callable[[_T], _T]: """Register a lru_cache wrapped function for debug_cache_stats.""" def wrapper(fn: _T) -> _T: - _CACHE_FUNCTIONS.append((fn.__name__ if name is None else name, fn)) - return fn + fn_name = fn.__name__ if name is None else name + if sys.version_info < (3, 9): + log.misc.vdebug( # type: ignore[attr-defined] + "debugcachestats not supported on python < 3.9, not adding '%s'", + fn_name, + ) + return fn + + else: + _CACHE_FUNCTIONS[fn_name] = fn + return fn return wrapper def debug_cache_stats() -> None: """Print LRU cache stats.""" - from qutebrowser.utils import log - for name, fn in _CACHE_FUNCTIONS: + for name, fn in _CACHE_FUNCTIONS.items(): log.misc.info('{}: {}'.format(name, fn.cache_info())) diff --git a/qutebrowser/misc/guiprocess.py b/qutebrowser/misc/guiprocess.py index 8e9747ad8..e14169f93 100644 --- a/qutebrowser/misc/guiprocess.py +++ b/qutebrowser/misc/guiprocess.py @@ -22,6 +22,7 @@ import dataclasses import locale import shlex +import shutil from typing import Mapping, Sequence, Dict, Optional from PyQt5.QtCore import (pyqtSlot, pyqtSignal, QObject, QProcess, @@ -168,6 +169,7 @@ class GUIProcess(QObject): self._output_messages = output_messages self.outcome = ProcessOutcome(what=what) self.cmd: Optional[str] = None + self.resolved_cmd: Optional[str] = None self.args: Optional[Sequence[str]] = None self.pid: Optional[int] = None @@ -269,10 +271,9 @@ class GUIProcess(QObject): # We can't get some kind of error code from Qt... # https://bugreports.qt.io/browse/QTBUG-44769 - # However, it looks like those strings aren't actually translated? - known_errors = ['No such file or directory', 'Permission denied'] - if (': ' in error_string and # pragma: no branch - error_string.split(': ', maxsplit=1)[1] in known_errors): + # but we pre-resolve the executable in Python, which also checks if it's + # runnable. + if self.resolved_cmd is None: # pragma: no branch msg += f'\nHint: Make sure {self.cmd!r} exists and is executable' if version.is_flatpak(): msg += ' inside the Flatpak container' @@ -334,10 +335,23 @@ class GUIProcess(QObject): self.outcome.running = True def _pre_start(self, cmd: str, args: Sequence[str]) -> None: - """Prepare starting of a QProcess.""" + """Resolve the given command and prepare starting of a QProcess. + + Doing the resolving in Python here instead of letting Qt do it serves + two purposes: + + - Being able to show a nicer error message without having to parse the + string we get from Qt: https://bugreports.qt.io/browse/QTBUG-44769 + - Not running the file from the current directory on Unix with + Qt < 5.15.? and 6.2.4, as a WORKAROUND for CVE-2022-25255: + https://invent.kde.org/qt/qt/qtbase/-/merge_requests/139 + https://www.qt.io/blog/security-advisory-qprocess + https://lists.qt-project.org/pipermail/announce/2022-February/000333.html + """ if self.outcome.running: raise ValueError("Trying to start a running QProcess!") self.cmd = cmd + self.resolved_cmd = shutil.which(cmd) self.args = args log.procs.debug(f"Executing: {self}") if self.verbose: @@ -347,7 +361,10 @@ class GUIProcess(QObject): """Convenience wrapper around QProcess::start.""" log.procs.debug("Starting process.") self._pre_start(cmd, args) - self._proc.start(cmd, args) + self._proc.start( + self.resolved_cmd, # type: ignore[arg-type] + args, + ) self._post_start() self._proc.closeWriteChannel() @@ -356,7 +373,10 @@ class GUIProcess(QObject): log.procs.debug("Starting detached.") self._pre_start(cmd, args) ok, self.pid = self._proc.startDetached( - cmd, args, None) # type: ignore[call-arg] + self.resolved_cmd, + args, + None, # workingDirectory + ) # type: ignore[call-arg] if not ok: message.error("Error while spawning {}".format(self.what)) diff --git a/qutebrowser/misc/utilcmds.py b/qutebrowser/misc/utilcmds.py index 7ba45bdc3..14c02864e 100644 --- a/qutebrowser/misc/utilcmds.py +++ b/qutebrowser/misc/utilcmds.py @@ -23,6 +23,7 @@ import functools import os +import sys import traceback from typing import Optional @@ -125,7 +126,9 @@ def debug_all_objects() -> None: @cmdutils.register(debug=True) def debug_cache_stats() -> None: """Print LRU cache stats.""" - debugcachestats.debug_cache_stats() + if sys.version_info < (3, 9): + raise cmdutils.CommandError('debugcachestats not supported on python < 3.9') + debugcachestats.debug_cache_stats() # type: ignore[unreachable] @cmdutils.register(debug=True) diff --git a/qutebrowser/utils/resources.py b/qutebrowser/utils/resources.py index f561d6747..b14b0faf4 100644 --- a/qutebrowser/utils/resources.py +++ b/qutebrowser/utils/resources.py @@ -24,19 +24,25 @@ import sys import contextlib import posixpath import pathlib -from typing import Iterator, Iterable +from typing import Iterator, Iterable, Union # We cannot use the stdlib version on 3.7-3.8 because we need the files() API. if sys.version_info >= (3, 9): import importlib.resources as importlib_resources + from importlib.abc import Traversable else: # pragma: no cover import importlib_resources + from importlib_resources.abc import Traversable import qutebrowser _cache = {} -def _path(filename: str) -> pathlib.Path: + +_ResourceType = Union[Traversable, pathlib.Path] + + +def _path(filename: str) -> _ResourceType: """Get a pathlib.Path object for a resource.""" assert not posixpath.isabs(filename), filename assert os.path.pardir not in filename.split(posixpath.sep), filename @@ -64,7 +70,7 @@ def _keyerror_workaround() -> Iterator[None]: def _glob( - resource_path: pathlib.Path, + resource_path: _ResourceType, subdir: str, ext: str, ) -> Iterable[str]: @@ -77,14 +83,11 @@ def _glob( glob_path = resource_path / subdir if isinstance(resource_path, pathlib.Path): + assert isinstance(glob_path, pathlib.Path) for full_path in glob_path.glob(f'*{ext}'): # . is contained in ext yield full_path.relative_to(resource_path).as_posix() - else: # zipfile.Path or importlib_resources compat object - # Unfortunately, we can't tell mypy about resource_path being of type - # Union[pathlib.Path, zipfile.Path] because we set "python_version = 3.6" in - # .mypy.ini, but the zipfile stubs (correctly) only declare zipfile.Path with - # Python 3.8... - assert glob_path.is_dir(), glob_path # type: ignore[unreachable] + else: # zipfile.Path or other importlib_resources.abc.Traversable + assert glob_path.is_dir(), glob_path for subpath in glob_path.iterdir(): if subpath.name.endswith(ext): yield posixpath.join(subdir, subpath.name) diff --git a/qutebrowser/utils/version.py b/qutebrowser/utils/version.py index 33acdce57..bf6b49fa6 100644 --- a/qutebrowser/utils/version.py +++ b/qutebrowser/utils/version.py @@ -511,11 +511,10 @@ def _get_pyqt_webengine_qt_version() -> Optional[str]: give us an accurate answer. """ try: - import importlib_metadata + import importlib.metadata as importlib_metadata # type: ignore[import] except ImportError: try: - # pylint: disable=line-too-long - import importlib.metadata as importlib_metadata # type: ignore[import, no-redef] + import importlib_metadata # type: ignore[no-redef] except ImportError: log.misc.debug("Neither importlib.metadata nor backport available") return None diff --git a/requirements.txt b/requirements.txt index 19e3efdda..25c1bd98f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,13 +4,15 @@ adblock==0.5.2 colorama==0.4.4 dataclasses==0.6 ; python_version<"3.7" importlib-metadata==4.11.3 ; python_version=="3.7.*" -importlib-resources==5.4.0 ; python_version<"3.9" -Jinja2==3.0.3 -MarkupSafe==2.1.0 ; python_version>="3.7" +importlib-resources==5.6.0 ; python_version=="3.7.*" or python_version=="3.8.*" +Jinja2==3.1.1 ; python_version>="3.7" +MarkupSafe==2.1.1 ; python_version>="3.7" Pygments==2.11.2 PyYAML==6.0 typing_extensions==4.1.1 ; python_version<"3.8" zipp==3.7.0 ; python_version>="3.7" +importlib-resources<5.6.0 ; python_version=="3.6.*" importlib-metadata<4.9 ; python_version=="3.6.*" zipp<3.7 ; python_version=="3.6.*" MarkupSafe<2.1.0 ; python_version=="3.6.*" +Jinja2<3.1.0 ; python_version=="3.6.*" diff --git a/scripts/dev/build_release.py b/scripts/dev/build_release.py index 5463441be..797b15e52 100755 --- a/scripts/dev/build_release.py +++ b/scripts/dev/build_release.py @@ -596,9 +596,9 @@ def github_upload(artifacts, tag, gh_token): assets = [asset for asset in release.assets() if asset.name == basename] if assets: - asset = assets[0] - print("Deleting stray asset {}".format(asset.name)) - asset.delete() + stray_asset = assets[0] + print("Deleting stray asset {}".format(stray_asset.name)) + stray_asset.delete() else: break diff --git a/scripts/dev/changelog_urls.json b/scripts/dev/changelog_urls.json index 53b62b0df..0de1d68d9 100644 --- a/scripts/dev/changelog_urls.json +++ b/scripts/dev/changelog_urls.json @@ -1,6 +1,7 @@ { "pyparsing": "https://github.com/pyparsing/pyparsing/blob/master/CHANGES", "pylint": "https://pylint.pycqa.org/en/latest/whatsnew/changelog.html", + "dill": "https://github.com/uqfoundation/dill/commits/master", "isort": "https://pycqa.github.io/isort/CHANGELOG/", "lazy-object-proxy": "https://github.com/ionelmc/python-lazy-object-proxy/blob/master/CHANGELOG.rst", "mccabe": "https://github.com/PyCQA/mccabe#changes", @@ -134,7 +135,7 @@ "platformdirs": "https://github.com/platformdirs/platformdirs/blob/main/CHANGES.rst", "pluggy": "https://github.com/pytest-dev/pluggy/blob/master/CHANGELOG.rst", "mypy-extensions": "https://github.com/python/mypy_extensions/commits/master", - "pyroma": "https://github.com/regebro/pyroma/blob/master/HISTORY.txt", + "pyroma": "https://github.com/regebro/pyroma/blob/master/CHANGES.txt", "adblock": "https://github.com/ArniDagur/python-adblock/blob/master/CHANGELOG.md", "importlib-resources": "https://importlib-resources.readthedocs.io/en/latest/history.html", "importlib-metadata": "https://github.com/python/importlib_metadata/blob/main/CHANGES.rst", diff --git a/scripts/dev/recompile_requirements.py b/scripts/dev/recompile_requirements.py index 97740e126..f8337b21f 100644 --- a/scripts/dev/recompile_requirements.py +++ b/scripts/dev/recompile_requirements.py @@ -188,10 +188,11 @@ class Change: """A single requirements change from a git diff output.""" - def __init__(self, name): + def __init__(self, name: str, base_path: pathlib.Path) -> None: self.name = name self.old = None self.new = None + self.base = extract_requirement_name(base_path) if CHANGELOG_URLS.get(name): self.url = CHANGELOG_URLS[name] self.link = '[{}]({})'.format(self.name, self.url) @@ -199,24 +200,27 @@ class Change: self.url = '(no changelog)' self.link = self.name + def __lt__(self, other): + return (self.base, self.name.lower()) < (other.base, other.name.lower()) + def __str__(self): + prefix = f"- [{self.base}] {self.name}" + suffix = f" {self.url}" if self.old is None: - return '- {} new: {} {}'.format(self.name, self.new, self.url) + return f"{prefix} new: {self.new} {suffix}" elif self.new is None: - return '- {} removed: {} {}'.format(self.name, self.old, - self.url) + return f"{prefix} removed: {self.old} {suffix}" else: - return '- {} {} -> {} {}'.format(self.name, self.old, self.new, - self.url) + return f"{prefix} {self.old} -> {self.new} {suffix}" def table_str(self): """Generate a markdown table.""" if self.old is None: - return '| {} | -- | {} |'.format(self.link, self.new) + return f'| {self.base} | {self.link} | -- | {self.new} |' elif self.new is None: - return '| {} | {} | -- |'.format(self.link, self.old) + return f'| {self.base} | {self.link} | {self.old} | -- |' else: - return '| {} | {} | {} |'.format(self.link, self.old, self.new) + return f'| {self.base} | {self.link} | {self.old} | {self.new} |' def _get_changed_files(): @@ -224,14 +228,23 @@ def _get_changed_files(): changed_files = set() filenames = git_diff('--name-only') for filename in filenames: - filename = filename.strip() - filename = filename.replace('misc/requirements/requirements-', '') - filename = filename.replace('.txt', '') - changed_files.add(filename) + requirement_name = extract_requirement_name(pathlib.Path(filename)) + changed_files.add(requirement_name) return sorted(changed_files) +def extract_requirement_name(path: pathlib.Path) -> str: + """Get a requirement name from a file path. + + e.g. "pylint" from "misc/requirements/requirements-pylint.txt" + """ + prefix = "requirements-" + assert path.suffix == ".txt", path + assert path.stem.startswith(prefix), path + return path.stem[len(prefix):] + + def parse_versioned_line(line): """Parse a requirements.txt line into name/version.""" if line[0] == '#': # ignored dependency @@ -265,10 +278,19 @@ def parse_versioned_line(line): def _get_changes(diff): """Get a list of changed versions from git.""" changes_dict = {} + current_path = None + for line in diff: if not line.startswith('-') and not line.startswith('+'): continue - elif line.startswith('+++ ') or line.startswith('--- '): + elif line.startswith('--- '): + prefix = '--- a/' + current_path = pathlib.Path(line[len(prefix):]) + continue + elif line.startswith('+++ '): + prefix = '+++ b/' + new_path = pathlib.Path(line[len(prefix):]) + assert current_path == new_path, (current_path, new_path) continue elif not line.strip(): # Could be newline changes on Windows @@ -280,14 +302,14 @@ def _get_changes(diff): name, version = parse_versioned_line(line[1:]) if name not in changes_dict: - changes_dict[name] = Change(name) + changes_dict[name] = Change(name, base_path=current_path) if line.startswith('-'): changes_dict[name].old = version elif line.startswith('+'): changes_dict[name].new = version - return [change for _name, change in sorted(changes_dict.items())] + return sorted(changes_dict.values()) def print_changed_files(): @@ -314,8 +336,8 @@ def print_changed_files(): print('::set-output name=changed::' + files_text.replace('\n', '%0A')) table_header = [ - '| Requirement | old | new |', - '|-------------|-----|-----|', + '| File | Requirement | old | new |', + '|------|-------------|-----|-----|', ] diff_table = '%0A'.join(table_header + [change.table_str() for change in changes]) diff --git a/scripts/dev/run_pylint_on_tests.py b/scripts/dev/run_pylint_on_tests.py index 28c6e32c9..a44828a85 100644 --- a/scripts/dev/run_pylint_on_tests.py +++ b/scripts/dev/run_pylint_on_tests.py @@ -64,6 +64,9 @@ def main(): 'import-error', # tests/helpers imports 'wrong-import-order', + # https://github.com/PyCQA/pylint/issues/6036 + # https://github.com/PyCQA/pylint/issues/6037 + 'unnecessary-ellipsis', ] toxinidir = sys.argv[1] diff --git a/tests/end2end/conftest.py b/tests/end2end/conftest.py index 1d9802049..14ac6f395 100644 --- a/tests/end2end/conftest.py +++ b/tests/end2end/conftest.py @@ -59,7 +59,7 @@ def pytest_unconfigure(config): stats.dump_stats((pathlib.Path('prof') / 'combined.pstats')) -def _check_hex_version(op_str, running_version, version): +def _check_version(op_str, running_version, version_str, as_hex=False): operators = { '==': operator.eq, '!=': operator.ne, @@ -69,9 +69,12 @@ def _check_hex_version(op_str, running_version, version): '<': operator.lt, } op = operators[op_str] - major, minor, patch = [int(e) for e in version.split('.')] - hex_version = (major << 16) | (minor << 8) | patch - return op(running_version, hex_version) + major, minor, patch = [int(e) for e in version_str.split('.')] + if as_hex: + version = (major << 16) | (minor << 8) | patch + else: + version = (major, minor, patch) + return op(running_version, version) def _get_version_tag(tag): @@ -82,7 +85,7 @@ def _get_version_tag(tag): casesinto an appropriate @pytest.mark.skip marker, and falls back to """ version_re = re.compile(r""" - (?P<package>qt|pyqt|pyqtwebengine) + (?P<package>qt|pyqt|pyqtwebengine|python) (?P<operator>==|>=|!=|<) (?P<version>\d+\.\d+(\.\d+)?) """, re.VERBOSE) @@ -106,10 +109,11 @@ def _get_version_tag(tag): return pytest.mark.skipif(do_skip[op], reason='Needs ' + tag) elif package == 'pyqt': return pytest.mark.skipif( - not _check_hex_version( + not _check_version( op_str=match.group('operator'), running_version=PYQT_VERSION, - version=version + version_str=version, + as_hex=True, ), reason='Needs ' + tag, ) @@ -121,10 +125,21 @@ def _get_version_tag(tag): else: running_version = PYQT_WEBENGINE_VERSION return pytest.mark.skipif( - not _check_hex_version( + not _check_version( + op_str=match.group('operator'), + running_version=running_version, + version_str=version, + as_hex=True, + ), + reason='Needs ' + tag, + ) + elif package == 'python': + running_version = sys.version_info + return pytest.mark.skipif( + not _check_version( op_str=match.group('operator'), running_version=running_version, - version=version + version_str=version, ), reason='Needs ' + tag, ) diff --git a/tests/end2end/features/utilcmds.feature b/tests/end2end/features/utilcmds.feature index bb2b3e2fb..e8172ae20 100644 --- a/tests/end2end/features/utilcmds.feature +++ b/tests/end2end/features/utilcmds.feature @@ -93,6 +93,7 @@ Feature: Miscellaneous utility commands exposed to the user. ## :debug-cache-stats + @python>=3.9.0 Scenario: :debug-cache-stats When I run :debug-cache-stats Then "is_valid_prefix: CacheInfo(*)" should be logged diff --git a/tests/end2end/fixtures/webserver_sub.py b/tests/end2end/fixtures/webserver_sub.py index 92f3091d1..b4964d973 100644 --- a/tests/end2end/fixtures/webserver_sub.py +++ b/tests/end2end/fixtures/webserver_sub.py @@ -292,6 +292,8 @@ def view_user_agent(): @app.route('/favicon.ico') def favicon(): + # WORKAROUND for https://github.com/PyCQA/pylint/issues/5783 + # pylint: disable-next=no-member,useless-suppression icon_dir = END2END_DIR.parents[1] / 'icons' return flask.send_from_directory( icon_dir, 'qutebrowser.ico', mimetype='image/vnd.microsoft.icon') diff --git a/tests/unit/misc/test_editor.py b/tests/unit/misc/test_editor.py index 8e5597a0e..ad3f665b4 100644 --- a/tests/unit/misc/test_editor.py +++ b/tests/unit/misc/test_editor.py @@ -19,6 +19,7 @@ """Tests for qutebrowser.misc.editor.""" +import sys import time import pathlib import os @@ -55,17 +56,17 @@ class TestArg: def test_placeholder(self, config_stub, editor): """Test starting editor with placeholder argument.""" - config_stub.val.editor.command = ['bin', 'foo', '{}', 'bar'] + config_stub.val.editor.command = [sys.executable, 'foo', '{}', 'bar'] editor.edit("") editor._proc._proc.start.assert_called_with( - "bin", ["foo", editor._filename, "bar"]) + sys.executable, ["foo", editor._filename, "bar"]) def test_placeholder_inline(self, config_stub, editor): """Test starting editor with placeholder arg inside of another arg.""" - config_stub.val.editor.command = ['bin', 'foo{}', 'bar'] + config_stub.val.editor.command = [sys.executable, 'foo{}', 'bar'] editor.edit("") editor._proc._proc.start.assert_called_with( - "bin", ["foo" + editor._filename, "bar"]) + sys.executable, ["foo" + editor._filename, "bar"]) class TestFileHandling: diff --git a/tests/unit/misc/test_guiprocess.py b/tests/unit/misc/test_guiprocess.py index c664757fd..aaff5154e 100644 --- a/tests/unit/misc/test_guiprocess.py +++ b/tests/unit/misc/test_guiprocess.py @@ -319,8 +319,8 @@ def test_start_env(monkeypatch, qtbot, py_proc): def test_start_detached(fake_proc): """Test starting a detached process.""" - cmd = 'foo' - args = ['bar'] + cmd = sys.executable + args = ['--version'] fake_proc._proc.startDetached.return_value = (True, 0) fake_proc.start_detached(cmd, args) fake_proc._proc.startDetached.assert_called_with(cmd, args, None) diff --git a/tests/unit/utils/test_log.py b/tests/unit/utils/test_log.py index 9f5a15065..bbc6b02db 100644 --- a/tests/unit/utils/test_log.py +++ b/tests/unit/utils/test_log.py @@ -27,7 +27,7 @@ import warnings import dataclasses import pytest -import _pytest.logging +import _pytest.logging # pylint: disable=import-private-name from PyQt5 import QtCore from qutebrowser import qutebrowser @@ -14,7 +14,7 @@ setenv = PYTEST_QT_API=pyqt5 pyqt{,512,513,514,515,5150}: LINK_PYQT_SKIP=true cov: PYTEST_ADDOPTS=--cov --cov-report xml --cov-report=html --cov-report= -passenv = PYTHON DISPLAY XAUTHORITY HOME USERNAME USER CI XDG_* QUTE_* DOCKER QT_QUICK_BACKEND PY_COLORS DBUS_SESSION_BUS_ADDRESS +passenv = PYTHON DISPLAY XAUTHORITY HOME USERNAME USER CI XDG_* QUTE_* DOCKER QT_QUICK_BACKEND FORCE_COLOR DBUS_SESSION_BUS_ADDRESS basepython = py: {env:PYTHON:python3} py3: {env:PYTHON:python3} @@ -78,7 +78,7 @@ commands = {[testenv:vulture]commands} [testenv:pylint] -basepython = {env:PYTHON:python3.8} +basepython = {env:PYTHON:python3} ignore_errors = true passenv = deps = |