From 519c47841787ed7a29140df2dfb9c48b47cc414e Mon Sep 17 00:00:00 2001 From: Christophe Besson Date: Mon, 5 Oct 2026 17:40:34 +0200 Subject: test: read UTF-8 explicitly in the licensing and keyring-vector tests On Windows a bare `read_text()` or `subprocess.run(text=True)` decodes with the locale's code page (cp1252), not UTF-8. `test_licensing.py` then failed on a byte of the vendored LICENSES.txt, and `test_keyring_vectors.py` decoded the generator's output, which carries CJK test strings, into something that no longer matched keyring.json. Both files are UTF-8; say so. Co-Authored-By: Claude Opus 5.5 --- packages/meshbay-hub/tests/test_keyring_vectors.py | 2 +- packages/meshbay-node/tests/test_licensing.py | 66 ++++++++++++++-------- 2 files changed, 42 insertions(+), 26 deletions(-) diff --git a/packages/meshbay-hub/tests/test_keyring_vectors.py b/packages/meshbay-hub/tests/test_keyring_vectors.py index 3928965..ee4994d 100644 --- a/packages/meshbay-hub/tests/test_keyring_vectors.py +++ b/packages/meshbay-hub/tests/test_keyring_vectors.py @@ -45,7 +45,7 @@ def test_the_file_is_what_the_shipped_keyring_writes(): to either that alters a bundle or a transcript fails here first — which is the moment to decide whether it is a format change every client must make.""" out = subprocess.run(["node", str(GENERATOR)], capture_output=True, text=True, - timeout=120, check=True).stdout + encoding="utf-8", timeout=120, check=True).stdout assert json.loads(out) == _vectors(), ( "keyring.js or transcripts.js no longer produce tests/vectors/keyring.json; " "if the format change is deliberate, regenerate it and update every client") diff --git a/packages/meshbay-node/tests/test_licensing.py b/packages/meshbay-node/tests/test_licensing.py index 6e50c80..68eb6bb 100644 --- a/packages/meshbay-node/tests/test_licensing.py +++ b/packages/meshbay-node/tests/test_licensing.py @@ -50,7 +50,9 @@ def _notices(): def test_each_python_package_declares_its_licence_and_ships_the_text(): for pkg, (expr, files) in EXPECTED.items(): - project = tomllib.loads((PACKAGES / pkg / "pyproject.toml").read_text())["project"] + project = tomllib.loads((PACKAGES / pkg / "pyproject.toml").read_text(encoding="utf-8"))[ + "project" + ] assert project["license"] == expr, pkg assert project["license-files"] == files, pkg for f in files: @@ -58,35 +60,41 @@ def test_each_python_package_declares_its_licence_and_ships_the_text(): def test_licence_texts_are_the_right_ones(): - agpl = (ROOT / "LICENSE").read_text() + agpl = (ROOT / "LICENSE").read_text(encoding="utf-8") assert "GNU AFFERO GENERAL PUBLIC LICENSE" in agpl and "Version 3" in agpl # Copies, because a wheel's license-files cannot reach outside its package. for pkg in ("meshbay-hub", "meshbay-node"): - assert (PACKAGES / pkg / "LICENSE").read_text() == agpl, pkg + assert (PACKAGES / pkg / "LICENSE").read_text(encoding="utf-8") == agpl, pkg common = PACKAGES / "meshbay-common" - assert "GNU LESSER GENERAL PUBLIC LICENSE" in (common / "COPYING.LESSER").read_text() - assert "GNU GENERAL PUBLIC LICENSE" in (common / "COPYING").read_text() + assert "GNU LESSER GENERAL PUBLIC LICENSE" in (common / "COPYING.LESSER").read_text( + encoding="utf-8" + ) + assert "GNU GENERAL PUBLIC LICENSE" in (common / "COPYING").read_text(encoding="utf-8") def test_rpm_specs_and_the_client_agree_with_the_packages(): for pkg in ("meshbay-common", "meshbay-hub", "meshbay-node", "meshbay-client"): - spec = (ROOT / "packaging" / "rpm" / f"{pkg}.spec").read_text() + spec = (ROOT / "packaging" / "rpm" / f"{pkg}.spec").read_text(encoding="utf-8") want = EXPECTED.get(pkg, ("AGPL-3.0-or-later",))[0] assert re.search(rf"^License:\s+{re.escape(want)}\s*$", spec, re.M), pkg assert "%license %{_licensedir}/%{name}" in spec, pkg - pkg_json = json.loads((PACKAGES / "meshbay-client" / "package.json").read_text()) + pkg_json = json.loads( + (PACKAGES / "meshbay-client" / "package.json").read_text(encoding="utf-8") + ) assert pkg_json["license"] == "AGPL-3.0-or-later" def test_every_windows_target_ships_the_licence(): - pkg_json = json.loads((PACKAGES / "meshbay-client" / "package.json").read_text()) + pkg_json = json.loads( + (PACKAGES / "meshbay-client" / "package.json").read_text(encoding="utf-8") + ) assert {"from": "../../LICENSE", "to": "LICENSE.txt"} in pkg_json["build"]["win"][ "extraResources" ] for yml in ("electron-builder.light.yml", "electron-builder.msix.yml"): - text = (ROOT / "packaging" / "win" / yml).read_text() + text = (ROOT / "packaging" / "win" / yml).read_text(encoding="utf-8") assert "- from: ../../LICENSE\n to: LICENSE.txt" in text, yml - ps1 = (ROOT / "packaging" / "win" / "build-node-runtime.ps1").read_text() + ps1 = (ROOT / "packaging" / "win" / "build-node-runtime.ps1").read_text(encoding="utf-8") assert "third_party_notices.py" in ps1 and "THIRD-PARTY-NOTICES.txt" in ps1 @@ -118,8 +126,8 @@ def test_notices_follow_what_the_node_actually_ships(): def test_every_vendored_file_has_its_provenance_and_licence(): - provenance = (VENDOR / "PROVENANCE.md").read_text() - licences = (VENDOR / "LICENSES.txt").read_text() + provenance = (VENDOR / "PROVENANCE.md").read_text(encoding="utf-8") + licences = (VENDOR / "LICENSES.txt").read_text(encoding="utf-8") for f in VENDOR.iterdir(): if f.name in ("PROVENANCE.md", "LICENSES.txt"): continue @@ -135,17 +143,23 @@ def _sources(): def test_the_lgpl_files_are_exactly_the_ones_that_say_so(): - marked = sorted(f for f in _sources() if f.read_text().startswith(SPDX_LGPL)) + marked = sorted(f for f in _sources() if f.read_text(encoding="utf-8").startswith(SPDX_LGPL)) assert marked == LGPL_FILES def test_every_client_carries_the_licence_texts(): """static/ is the interface of the web, the desktop and Android alike.""" texts = STATIC / "licenses" - assert (texts / "AGPL-3.0.txt").read_text() == (ROOT / "LICENSE").read_text() + assert (texts / "AGPL-3.0.txt").read_text(encoding="utf-8") == (ROOT / "LICENSE").read_text( + encoding="utf-8" + ) common = PACKAGES / "meshbay-common" - assert (texts / "LGPL-3.0.txt").read_text() == (common / "COPYING.LESSER").read_text() - assert (texts / "GPL-3.0.txt").read_text() == (common / "COPYING").read_text() + assert (texts / "LGPL-3.0.txt").read_text(encoding="utf-8") == ( + common / "COPYING.LESSER" + ).read_text(encoding="utf-8") + assert (texts / "GPL-3.0.txt").read_text(encoding="utf-8") == (common / "COPYING").read_text( + encoding="utf-8" + ) def _strip_js(src: str) -> str: @@ -165,27 +179,29 @@ def test_the_lgpl_layer_depends_on_nothing_under_the_agpl(): r"([A-Za-z_$][\w$]*)", re.M, ) - defined_in_lgpl = {n for f in lgpl if f.suffix == ".js" for n in top.findall(f.read_text())} + defined_in_lgpl = { + n for f in lgpl if f.suffix == ".js" for n in top.findall(f.read_text(encoding="utf-8")) + } agpl_globals = { n: f.name for f in STATIC.glob("*.js") if f not in lgpl - for n in top.findall(f.read_text()) + for n in top.findall(f.read_text(encoding="utf-8")) if n not in defined_in_lgpl } kt_decl = re.compile(r"^\s*(?:\w+\s+)*(?:class|object|interface)\s+(\w+)", re.M) defined_in_lgpl_kt = { - n for f in lgpl if f.suffix == ".kt" for n in kt_decl.findall(f.read_text()) + n for f in lgpl if f.suffix == ".kt" for n in kt_decl.findall(f.read_text(encoding="utf-8")) } agpl_kotlin = { n: f.name for f in ANDROID.glob("**/*.kt") if f not in lgpl - for n in kt_decl.findall(f.read_text()) + for n in kt_decl.findall(f.read_text(encoding="utf-8")) if n not in defined_in_lgpl_kt } for f in LGPL_FILES: - src = f.read_text() + src = f.read_text(encoding="utf-8") if f.suffix == ".kt": for imp in re.findall(r"^import (org\.meshbay\.[\w.]+)", src, re.M): owner = ( @@ -216,7 +232,7 @@ REFERENCE_APP = [STATIC / "helloworld-app.js", STATIC / "helloworld-app-settings def _interface_modules() -> set[str]: """The modules the permission names — read from it, the one place they are listed.""" - text = APP_EXCEPTION.read_text() + text = APP_EXCEPTION.read_text(encoding="utf-8") block = text.split("2. the names exported by these modules", 1)[1].split("3.", 1)[0] return set(re.findall(r"^\s+([\w-]+\.js)\s*$", block, re.M)) @@ -226,7 +242,7 @@ def test_the_application_interface_names_modules_that_exist(): assert modules == {"i18n.js", "icon.js", "file-utils.js", "settings-ui.js", "folder-tree.js"} for m in modules: assert (STATIC / m).is_file(), m - assert not (STATIC / m).read_text().startswith(SPDX_LGPL), ( + assert not (STATIC / m).read_text(encoding="utf-8").startswith(SPDX_LGPL), ( f"{m} is LGPL already; the permission is for the AGPL part" ) @@ -239,7 +255,7 @@ def test_the_reference_application_is_free_to_copy_and_stays_inside_the_interfac """ allowed = _interface_modules() | {f.name for f in LGPL_FILES if f.parent == STATIC} for f in REFERENCE_APP: - src = f.read_text() + src = f.read_text(encoding="utf-8") assert src.startswith("// SPDX-License-Identifier: 0BSD\n"), f.name for spec in re.findall(r"""^import .* from ['"]\./([^'"]+)['"]""", src, re.M): assert spec.startswith("vendor/") or spec in allowed, f"{f.name} imports {spec}" @@ -248,7 +264,7 @@ def test_the_reference_application_is_free_to_copy_and_stays_inside_the_interfac def test_every_spdx_line_is_one_of_the_known_licences(): for f in _sources(): - first = f.read_text().split("\n", 1)[0] + first = f.read_text(encoding="utf-8").split("\n", 1)[0] if "SPDX-License-Identifier" not in first: continue if f in REFERENCE_APP: -- cgit v1.2.3