From 8a53e9b282749f5ff17d40366077bc8a00a1ef16 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Wed, 22 Nov 2023 20:11:41 +0100 Subject: pakjoy: Use proper logic to discover resources dir --- qutebrowser/misc/pakjoy.py | 42 ++++++++++++++++---- tests/unit/misc/test_pakjoy.py | 90 ++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 120 insertions(+), 12 deletions(-) diff --git a/qutebrowser/misc/pakjoy.py b/qutebrowser/misc/pakjoy.py index ec9906731..6765c8687 100644 --- a/qutebrowser/misc/pakjoy.py +++ b/qutebrowser/misc/pakjoy.py @@ -31,7 +31,7 @@ import pathlib import dataclasses from typing import ClassVar, IO, Optional, Dict, Tuple -from qutebrowser.misc import binparsing +from qutebrowser.misc import binparsing, objects from qutebrowser.utils import qtutils, standarddir, version, utils, log HANGOUTS_MARKER = b"// Extension ID: nkeimhogjdpnpccoofpliimaahmaaome" @@ -146,17 +146,43 @@ class PakParser: raise binparsing.ParseError("Couldn't find hangouts manifest") -def copy_webengine_resources() -> Optional[pathlib.Path]: - """Copy qtwebengine resources to local dir for patching.""" - resources_dir = qtutils.library_path(qtutils.LibraryPath.data) +def _find_webengine_resources() -> pathlib.Path: + """Find the QtWebEngine resources dir. + + Mirrors logic from QtWebEngine: + https://github.com/qt/qtwebengine/blob/v6.6.0/src/core/web_engine_library_info.cpp#L293-L341 + """ + if RESOURCES_ENV_VAR in os.environ: + return pathlib.Path(os.environ[RESOURCES_ENV_VAR]) + + candidates = [] + qt_data_path = qtutils.library_path(qtutils.LibraryPath.data) if utils.is_mac: # pragma: no cover # I'm not sure how to arrive at this path without hardcoding it # ourselves. importlib_resources("PyQt6.Qt6") can serve as a # replacement for the qtutils bit but it doesn't seem to help find the - # actually Resources folder. - resources_dir /= pathlib.Path("lib", "QtWebEngineCore.framework", "Resources") - else: - resources_dir /= "resources" + # actuall Resources folder. + candidates.append( + qt_data_path / "lib" / "QtWebEngineCore.framework" / "Resources" + ) + + candidates += [ + qt_data_path / "resources", + qt_data_path, + pathlib.Path(objects.qapp.applicationDirPath()), + pathlib.Path.home() / f".{objects.qapp.applicationName()}", + ] + + for candidate in candidates: + if (candidate / PAK_FILENAME).exists(): + return candidate + + raise binparsing.ParseError("Couldn't find webengine resources dir") + + +def copy_webengine_resources() -> Optional[pathlib.Path]: + """Copy qtwebengine resources to local dir for patching.""" + resources_dir = _find_webengine_resources() work_dir = pathlib.Path(standarddir.cache()) / CACHE_DIR_NAME if work_dir.exists(): diff --git a/tests/unit/misc/test_pakjoy.py b/tests/unit/misc/test_pakjoy.py index e8463a8b2..55a147269 100644 --- a/tests/unit/misc/test_pakjoy.py +++ b/tests/unit/misc/test_pakjoy.py @@ -36,10 +36,9 @@ skip_if_unsupported = pytest.mark.skipif( @pytest.fixture(autouse=True) -def clean_env(): - yield - if pakjoy.RESOURCES_ENV_VAR in os.environ: - del os.environ[pakjoy.RESOURCES_ENV_VAR] +def prepare_env(qapp, monkeypatch): + monkeypatch.setattr(pakjoy.objects, "qapp", qapp) + monkeypatch.delenv(pakjoy.RESOURCES_ENV_VAR, raising=False) def patch_version(monkeypatch, *args): @@ -78,6 +77,86 @@ def test_version_gate(cache_tmpdir, unaffected_version, mocker, workdir_exists): assert not workdir.exists() +class TestFindWebengineResources: + @pytest.fixture + def qt_data_path(self, monkeypatch: pytest.MonkeyPatch, tmp_path: pathlib.Path): + """Patch qtutils.library_path() to return a temp dir.""" + qt_data_path = tmp_path / "qt_data" + qt_data_path.mkdir() + monkeypatch.setattr(pakjoy.qtutils, "library_path", lambda _which: qt_data_path) + return qt_data_path + + @pytest.fixture + def application_dir_path( + self, + monkeypatch: pytest.MonkeyPatch, + tmp_path: pathlib.Path, + qt_data_path: pathlib.Path, # needs patching + ): + """Patch QApplication.applicationDirPath() to return a temp dir.""" + app_dir_path = tmp_path / "app_dir" + app_dir_path.mkdir() + monkeypatch.setattr( + pakjoy.objects.qapp, "applicationDirPath", lambda: app_dir_path + ) + return app_dir_path + + @pytest.fixture + def fallback_path( + self, + monkeypatch: pytest.MonkeyPatch, + tmp_path: pathlib.Path, + qt_data_path: pathlib.Path, # needs patching + application_dir_path: pathlib.Path, # needs patching + ): + """Patch the fallback path to return a temp dir.""" + home_path = tmp_path / "home" + monkeypatch.setattr(pakjoy.pathlib.Path, "home", lambda: home_path) + + app_path = home_path / f".{pakjoy.objects.qapp.applicationName()}" + app_path.mkdir(parents=True) + return app_path + + @pytest.mark.parametrize("create_file", [True, False]) + def test_overridden( + self, monkeypatch: pytest.MonkeyPatch, tmp_path: pathlib.Path, create_file: bool + ): + """Test the overridden path is used.""" + override_path = tmp_path / "override" + override_path.mkdir() + monkeypatch.setenv(pakjoy.RESOURCES_ENV_VAR, str(override_path)) + if create_file: # should get this no matter if file exists or not + (override_path / pakjoy.PAK_FILENAME).touch() + assert pakjoy._find_webengine_resources() == override_path + + @pytest.mark.parametrize("with_subfolder", [True, False]) + def test_qt_data_path(self, qt_data_path: pathlib.Path, with_subfolder: bool): + """Test qtutils.library_path() is used.""" + resources_path = qt_data_path + if with_subfolder: + resources_path /= "resources" + resources_path.mkdir() + (resources_path / pakjoy.PAK_FILENAME).touch() + assert pakjoy._find_webengine_resources() == resources_path + + def test_application_dir_path(self, application_dir_path: pathlib.Path): + """Test QApplication.applicationDirPath() is used.""" + (application_dir_path / pakjoy.PAK_FILENAME).touch() + assert pakjoy._find_webengine_resources() == application_dir_path + + def test_fallback_path(self, fallback_path: pathlib.Path): + """Test fallback path is used.""" + (fallback_path / pakjoy.PAK_FILENAME).touch() + assert pakjoy._find_webengine_resources() == fallback_path + + def test_nowhere(self, fallback_path: pathlib.Path): + """Test we raise if we can't find the resources.""" + with pytest.raises( + binparsing.ParseError, match="Couldn't find webengine resources dir" + ): + pakjoy._find_webengine_resources() + + def json_without_comments(bytestring): str_without_comments = "\n".join( [ @@ -131,6 +210,9 @@ class TestWithRealResourcesFile: tmpfile = work_dir / "tmp.txt" tmpfile.touch() + # Set by first call to copy_webengine_resources() + del os.environ[pakjoy.RESOURCES_ENV_VAR] + pakjoy.copy_webengine_resources() assert not tmpfile.exists() -- cgit v1.2.3-54-g00ecf