summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.github/workflows/bleeding.yml2
-rw-r--r--.github/workflows/ci.yml6
-rw-r--r--.pylintrc1
-rw-r--r--README.asciidoc2
-rw-r--r--doc/changelog.asciidoc14
-rw-r--r--doc/contributing.asciidoc2
-rw-r--r--doc/help/index.asciidoc6
-rw-r--r--doc/install.asciidoc4
-rw-r--r--doc/quickstart.asciidoc6
-rw-r--r--doc/qutebrowser.1.asciidoc6
-rw-r--r--misc/requirements/requirements-dev.txt6
-rw-r--r--misc/requirements/requirements-flake8.txt2
-rw-r--r--misc/requirements/requirements-mypy.txt12
-rw-r--r--misc/requirements/requirements-pyinstaller.txt2
-rw-r--r--misc/requirements/requirements-pylint.txt15
-rw-r--r--misc/requirements/requirements-pylint.txt-raw3
-rw-r--r--misc/requirements/requirements-pyroma.txt4
-rw-r--r--misc/requirements/requirements-qutebrowser.txt-raw6
-rw-r--r--misc/requirements/requirements-sphinx.txt10
-rw-r--r--misc/requirements/requirements-tests.txt25
-rw-r--r--misc/requirements/requirements-tests.txt-raw10
-rw-r--r--misc/requirements/requirements-tox.txt4
-rw-r--r--qutebrowser/browser/webkit/webkithistory.py2
-rw-r--r--qutebrowser/completion/completer.py2
-rw-r--r--qutebrowser/html/settings.html93
-rw-r--r--qutebrowser/mainwindow/prompt.py17
-rw-r--r--qutebrowser/mainwindow/tabwidget.py20
-rw-r--r--qutebrowser/misc/debugcachestats.py26
-rw-r--r--qutebrowser/misc/guiprocess.py34
-rw-r--r--qutebrowser/misc/utilcmds.py5
-rw-r--r--qutebrowser/utils/resources.py21
-rw-r--r--qutebrowser/utils/version.py5
-rw-r--r--requirements.txt8
-rwxr-xr-xscripts/dev/build_release.py6
-rw-r--r--scripts/dev/changelog_urls.json3
-rw-r--r--scripts/dev/recompile_requirements.py58
-rw-r--r--scripts/dev/run_pylint_on_tests.py3
-rw-r--r--tests/end2end/conftest.py33
-rw-r--r--tests/end2end/features/utilcmds.feature1
-rw-r--r--tests/end2end/fixtures/webserver_sub.py2
-rw-r--r--tests/unit/misc/test_editor.py9
-rw-r--r--tests/unit/misc/test_guiprocess.py4
-rw-r--r--tests/unit/utils/test_log.py2
-rw-r--r--tox.ini4
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"
diff --git a/.pylintrc b/.pylintrc
index a3ac3d82a..4ff4f2080 100644
--- a/.pylintrc
+++ b/.pylintrc
@@ -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
diff --git a/tox.ini b/tox.ini
index f52d7b158..370adbc9e 100644
--- a/tox.ini
+++ b/tox.ini
@@ -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 =